@astrosheep/keiyaku 4.0.2 → 4.0.4

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 (145) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +1 -1
  2. package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
  3. package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
  4. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +1 -1
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +1 -1
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +5 -0
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +3 -1
  8. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +9 -8
  9. package/build/src/akuma/akuma.d.ts +34 -11
  10. package/build/src/akuma/akuma.js +121 -61
  11. package/build/src/akuma/allowed.d.ts +7 -0
  12. package/build/src/akuma/allowed.js +40 -0
  13. package/build/src/akuma/archetype.d.ts +7 -4
  14. package/build/src/akuma/archetype.js +15 -16
  15. package/build/src/akuma/body.d.ts +3 -1
  16. package/build/src/akuma/body.js +19 -8
  17. package/build/src/akuma/heart/facts.d.ts +49 -7
  18. package/build/src/akuma/heart/index.d.ts +4 -8
  19. package/build/src/akuma/heart/index.js +76 -15
  20. package/build/src/akuma/heart/rows.d.ts +7 -5
  21. package/build/src/akuma/heart/rows.js +98 -11
  22. package/build/src/akuma/heart/schema.d.ts +1 -1
  23. package/build/src/akuma/heart/schema.js +15 -10
  24. package/build/src/akuma/heart/soul.js +3 -1
  25. package/build/src/akuma/heart/storage.d.ts +2 -0
  26. package/build/src/akuma/heart/storage.js +20 -0
  27. package/build/src/akuma/heart/timeline.d.ts +1 -5
  28. package/build/src/akuma/heart/timeline.js +8 -17
  29. package/build/src/akuma/index.d.ts +4 -1
  30. package/build/src/akuma/index.js +1 -0
  31. package/build/src/akuma/projection.d.ts +17 -3
  32. package/build/src/akuma/projection.js +40 -3
  33. package/build/src/akuma/provider.d.ts +1 -1
  34. package/build/src/akuma/provider.js +2 -1
  35. package/build/src/akuma/providers/acp/core.d.ts +3 -1
  36. package/build/src/akuma/providers/acp/core.js +4 -4
  37. package/build/src/akuma/providers/acp/events.d.ts +11 -3
  38. package/build/src/akuma/providers/acp/events.js +78 -4
  39. package/build/src/akuma/providers/claude/events.js +62 -3
  40. package/build/src/akuma/providers/codex-app-server/events.js +1 -13
  41. package/build/src/akuma/providers/grok-build/index.d.ts +2 -1
  42. package/build/src/akuma/providers/grok-build/index.js +72 -2
  43. package/build/src/akuma/providers/opencode-sdk/events.js +71 -6
  44. package/build/src/akuma/providers/pi/events.js +17 -3
  45. package/build/src/akuma/providers/unified-patch.d.ts +4 -0
  46. package/build/src/akuma/providers/unified-patch.js +14 -0
  47. package/build/src/akuma/publication.d.ts +1 -1
  48. package/build/src/akuma/publication.js +36 -7
  49. package/build/src/akuma/requests.d.ts +70 -4
  50. package/build/src/akuma/requests.js +288 -77
  51. package/build/src/akuma-body.d.ts +1 -0
  52. package/build/src/akuma-body.js +31 -0
  53. package/build/src/cli/accepted.d.ts +4 -1
  54. package/build/src/cli/accepted.js +26 -4
  55. package/build/src/cli/commands/akuma-invoke.d.ts +8 -7
  56. package/build/src/cli/commands/akuma-invoke.js +18 -10
  57. package/build/src/cli/commands/akuma.d.ts +11 -2
  58. package/build/src/cli/commands/akuma.js +67 -28
  59. package/build/src/cli/commands/amend.d.ts +1 -1
  60. package/build/src/cli/commands/amend.js +1 -1
  61. package/build/src/cli/commands/contract.d.ts +3 -3
  62. package/build/src/cli/commands/contract.js +1 -1
  63. package/build/src/cli/commands/install.js +4 -4
  64. package/build/src/cli/commands/task-query.d.ts +1 -1
  65. package/build/src/cli/commands/task-query.js +11 -5
  66. package/build/src/cli/commands/task.js +4 -4
  67. package/build/src/cli/coordinates.d.ts +1 -0
  68. package/build/src/cli/coordinates.js +5 -1
  69. package/build/src/cli/invoke.d.ts +1 -0
  70. package/build/src/cli/invoke.js +90 -73
  71. package/build/src/cli/main.d.ts +2 -0
  72. package/build/src/cli/main.js +29 -4
  73. package/build/src/cli/parse.d.ts +1 -0
  74. package/build/src/cli/parse.js +8 -4
  75. package/build/src/cli/render/akuma.d.ts +2 -2
  76. package/build/src/cli/render/akuma.js +84 -13
  77. package/build/src/cli/render/audit.js +16 -2
  78. package/build/src/cli/render/contract.d.ts +2 -0
  79. package/build/src/cli/render/contract.js +203 -36
  80. package/build/src/cli/render/kanshi.js +64 -29
  81. package/build/src/cli/render/receipt.d.ts +2 -1
  82. package/build/src/cli/render/receipt.js +9 -1
  83. package/build/src/cli/render/text.js +3 -1
  84. package/build/src/cli/result.d.ts +38 -3
  85. package/build/src/cli/selectors.d.ts +1 -0
  86. package/build/src/cli/selectors.js +9 -6
  87. package/build/src/cli/usage.d.ts +2 -0
  88. package/build/src/cli/usage.js +4 -0
  89. package/build/src/contract-worktree.js +3 -0
  90. package/build/src/dispatch/index.js +50 -39
  91. package/build/src/git/hooks.d.ts +0 -4
  92. package/build/src/git/hooks.js +0 -36
  93. package/build/src/git/integration.d.ts +2 -6
  94. package/build/src/git/integration.js +8 -8
  95. package/build/src/git/read-observation.js +1 -0
  96. package/build/src/git/reconcile.d.ts +6 -1
  97. package/build/src/git/reconcile.js +55 -9
  98. package/build/src/git/tender.d.ts +19 -4
  99. package/build/src/git/tender.js +44 -13
  100. package/build/src/git/terminal-seal.d.ts +14 -0
  101. package/build/src/git/terminal-seal.js +15 -2
  102. package/build/src/git/workspace.d.ts +1 -0
  103. package/build/src/git/workspace.js +12 -4
  104. package/build/src/index.d.ts +1 -1
  105. package/build/src/kanshi/index.d.ts +1 -1
  106. package/build/src/kanshi/index.js +1 -1
  107. package/build/src/kanshi/read.d.ts +9 -1
  108. package/build/src/kanshi/read.js +91 -37
  109. package/build/src/kanshi/report.d.ts +1 -0
  110. package/build/src/library/address.d.ts +5 -14
  111. package/build/src/library/address.js +20 -22
  112. package/build/src/library/akuma-creation.d.ts +4 -2
  113. package/build/src/library/akuma-creation.js +43 -26
  114. package/build/src/library/bind.js +3 -3
  115. package/build/src/library/catalog.d.ts +1 -2
  116. package/build/src/library/catalog.js +11 -6
  117. package/build/src/library/configuration.d.ts +4 -0
  118. package/build/src/library/configuration.js +19 -0
  119. package/build/src/library/contract.d.ts +15 -1
  120. package/build/src/library/contract.js +51 -4
  121. package/build/src/library/fleet.d.ts +47 -9
  122. package/build/src/library/fleet.js +184 -44
  123. package/build/src/library/input.js +1 -0
  124. package/build/src/library/keiyaku.d.ts +4 -4
  125. package/build/src/library/keiyaku.js +1 -2
  126. package/build/src/protocol/bind.d.ts +1 -2
  127. package/build/src/protocol/bind.js +2 -16
  128. package/build/src/protocol/operations.d.ts +4 -1
  129. package/build/src/protocol/operations.js +33 -27
  130. package/build/src/protocol/read/status.d.ts +3 -1
  131. package/build/src/protocol/read/status.js +24 -14
  132. package/build/src/runtime/proc/run.d.ts +1 -0
  133. package/build/src/runtime/proc/run.js +9 -2
  134. package/build/src/settlement/settle.d.ts +1 -0
  135. package/build/src/settlement/settle.js +4 -1
  136. package/build/src/task/board.d.ts +18 -3
  137. package/build/src/task/board.js +73 -26
  138. package/build/src/task/index.d.ts +3 -3
  139. package/build/src/task/index.js +7 -5
  140. package/build/src/task/operations.d.ts +2 -19
  141. package/build/src/task/operations.js +29 -7
  142. package/build/src/task/query.d.ts +9 -2
  143. package/build/src/task/query.js +32 -64
  144. package/build/src/verification/execution.js +2 -1
  145. package/package.json +3 -1
@@ -1,5 +1,5 @@
1
- import { AkumaWorldScopeError, gatesFrom, Keiyaku, requireBranchesToBeUpToDateFrom, settings, SettingsError, worktreeHooksFrom } from "../index.js";
2
- import { kanshi, selectKanshi, selectRegion } from "../kanshi/index.js";
1
+ import { AkumaWorldScopeError, gatesFrom, Keiyaku, KeiyakuRefused, requireBranchesToBeUpToDateFrom, settings, SettingsError, worktreeHooksFrom, } from "../index.js";
2
+ import { kanshi, observeKanshi, selectKanshi, selectRegion, } from "../kanshi/index.js";
3
3
  import { resolveActor } from "./actor.js";
4
4
  import { acceptedAbandon, acceptedAmend, acceptedArc, acceptedAudit, acceptedBind, acceptedDeliver, acceptedReview, resultFromMutationCall, } from "./accepted.js";
5
5
  import { amendFromCommand } from "./commands/amend.js";
@@ -10,7 +10,7 @@ import { installHarnesses } from "./commands/install.js";
10
10
  import { invokeTask } from "./commands/task-invoke.js";
11
11
  import { CliUsageError, renderCommandUsage } from "./parse.js";
12
12
  import { isBlankInput } from "./usage.js";
13
- import { contractFromInput, resolveContextualContract, resolveKanshiContract } from "./selectors.js";
13
+ import { canonicalContractSelector, contractFromInput, resolveContextualContract, resolveKanshiContract, } from "./selectors.js";
14
14
  import { resolveNamedAddress } from "../library/address.js";
15
15
  import { assertExplicitRepoUse, resolveCliCoordinates } from "./coordinates.js";
16
16
  import { BindDraftError, preserveBindDraft } from "./draft.js";
@@ -23,9 +23,10 @@ async function readStdin() {
23
23
  function selectedStdinDiagnostic(command) {
24
24
  switch (command.command) {
25
25
  case "bind":
26
- case "amend":
27
26
  case "arc":
28
27
  return `${command.command} requires a nonblank stdin document`;
28
+ case "amend":
29
+ return command.stdin === true ? "amend requires a nonblank stdin document" : undefined;
29
30
  case "review":
30
31
  return command.summaryFromStdin === true ? "review requires a nonblank summary" : undefined;
31
32
  case "call":
@@ -53,13 +54,12 @@ async function withAcquiredStdin(command, runtime) {
53
54
  throw new CliUsageError(diagnostic);
54
55
  return { ...runtime, readStdin: async () => bytes };
55
56
  }
56
- function settingsAt(root, environment) {
57
- const home = environment.KEIYAKU_HOME?.trim();
58
- return settings({ ...(root === undefined ? {} : { root }), ...(home === undefined || home.length === 0 ? {} : { home }) });
57
+ function settingsAt(root, home) {
58
+ return settings({ ...(root === undefined ? {} : { root }), ...(home === undefined ? {} : { home }) });
59
59
  }
60
- function selectedGates(value, name) {
60
+ function consumeSettings(run) {
61
61
  try {
62
- return gatesFrom({ settings: value, ...(name === undefined ? {} : { name }) });
62
+ return run();
63
63
  }
64
64
  catch (error) {
65
65
  if (error instanceof SettingsError)
@@ -67,25 +67,11 @@ function selectedGates(value, name) {
67
67
  throw error;
68
68
  }
69
69
  }
70
- function selectedHooks(value) {
71
- try {
72
- return worktreeHooksFrom({ settings: value });
73
- }
74
- catch (error) {
75
- if (error instanceof SettingsError)
76
- throw new CliUsageError(error.message);
77
- throw error;
78
- }
70
+ function selectedGates(value, name) {
71
+ return consumeSettings(() => gatesFrom({ settings: value, ...(name === undefined ? {} : { name }) }));
79
72
  }
80
73
  function selectedGitPolicy(value) {
81
- try {
82
- return requireBranchesToBeUpToDateFrom({ settings: value });
83
- }
84
- catch (error) {
85
- if (error instanceof SettingsError)
86
- throw new CliUsageError(error.message);
87
- throw error;
88
- }
74
+ return consumeSettings(() => requireBranchesToBeUpToDateFrom({ settings: value }));
89
75
  }
90
76
  function actorFromEdge(actor, environment) {
91
77
  let resolved;
@@ -127,7 +113,7 @@ async function invokeBind({ parsed, repo, edge, configuration, hooks, establishW
127
113
  const bound = accepted.facts.find((fact) => fact.kind === "bind");
128
114
  if (bound === undefined || bound.kind !== "bind")
129
115
  throw new Error("accepted bind is missing its bind fact");
130
- return acceptedBind(accepted, bound.data.coordinates.target ?? null);
116
+ return acceptedBind(accepted, bound.data.coordinates);
131
117
  });
132
118
  if (result.kind !== "refused")
133
119
  return result;
@@ -182,11 +168,19 @@ async function invokeExisting({ parsed, repo, edge, scope, configuration, hooks
182
168
  const seat = { contract, id, ...(actor === undefined ? {} : { actor }), hooks };
183
169
  switch (parsed.command) {
184
170
  case "amend": {
185
- const markdown = await edge.readStdin();
171
+ const markdown = parsed.stdin === true ? await edge.readStdin() : undefined;
186
172
  const gates = parsed.gates === undefined
187
173
  ? undefined
188
174
  : selectedGates(configuration, parsed.gates);
189
- return resultFromMutationCall("amend", () => amendFromCommand({ command: parsed, repo, contract, markdown, gates, ...(actor === undefined ? {} : { actor }), hooks }), (result) => acceptedAmend(result, id), { coordinate: id });
175
+ return resultFromMutationCall("amend", () => amendFromCommand({
176
+ command: parsed,
177
+ repo,
178
+ contract,
179
+ ...(markdown === undefined ? {} : { markdown }),
180
+ gates,
181
+ ...(actor === undefined ? {} : { actor }),
182
+ hooks,
183
+ }), (result) => acceptedAmend(result, id), { coordinate: id });
190
184
  }
191
185
  case "deliver":
192
186
  return invokeDeliver(parsed, seat, selectedGitPolicy(configuration));
@@ -212,32 +206,23 @@ async function invokeExisting({ parsed, repo, edge, scope, configuration, hooks
212
206
  return parsed;
213
207
  }
214
208
  }
215
- async function invokeAkumaCommand(parsed, input) {
216
- const { path, executionCwd, repo, configuration, edge } = input;
217
- if (parsed.command === "call" && parsed.contract !== undefined) {
218
- if (repo === undefined)
209
+ async function invokeAkumaFromEdge(parsed, input) {
210
+ try {
211
+ const { path, statedCwd, repo, home, configuration, edge } = input;
212
+ if (parsed.command === "call" && parsed.contract !== undefined && repo === undefined) {
219
213
  throw new Error("call with Contract requires a resolved Repo");
220
- const selected = contractFromInput(repo, parsed.contract);
214
+ }
221
215
  return await invokeAkuma(parsed, {
222
216
  path,
223
- ...(executionCwd === undefined ? {} : { executionCwd }),
224
- settings: configuration,
225
- contract: selected.contract,
217
+ ...(statedCwd === undefined ? {} : { statedCwd }),
218
+ ...(home === undefined ? {} : { home }),
219
+ ...(configuration === undefined ? {} : { settings: configuration }),
220
+ ...(parsed.command === "call" && parsed.contract !== undefined
221
+ ? { contract: contractFromInput(repo, parsed.contract).contract }
222
+ : repo === undefined ? {} : { repo }),
226
223
  readStdin: edge.readStdin,
227
224
  });
228
225
  }
229
- return await invokeAkuma(parsed, {
230
- path,
231
- ...(executionCwd === undefined ? {} : { executionCwd }),
232
- settings: configuration,
233
- ...(repo === undefined ? {} : { repo }),
234
- readStdin: edge.readStdin,
235
- });
236
- }
237
- async function invokeAkumaFromEdge(parsed, input) {
238
- try {
239
- return await invokeAkumaCommand(parsed, input);
240
- }
241
226
  catch (error) {
242
227
  if (error instanceof AkumaWorldScopeError)
243
228
  throw error;
@@ -252,7 +237,7 @@ async function akumaWorldFor(parsed, located, candidate, establish) {
252
237
  throw new CliUsageError("no Keiyaku world contains the invocation cwd");
253
238
  return world;
254
239
  }
255
- async function invokeCatalog(parsed, world, repo, edge) {
240
+ async function invokeCatalog(parsed, world, repo, home) {
256
241
  try {
257
242
  if (parsed.query.kind === "contracts") {
258
243
  if (repo === undefined)
@@ -262,7 +247,7 @@ async function invokeCatalog(parsed, world, repo, edge) {
262
247
  if (parsed.query.kind === "archetypes") {
263
248
  return {
264
249
  kind: "catalog",
265
- catalog: await Keiyaku.ls({ query: parsed.query, settings: await settingsAt(undefined, edge.environment) }),
250
+ catalog: await Keiyaku.ls({ query: parsed.query, ...(home === undefined ? {} : { home }) }),
266
251
  };
267
252
  }
268
253
  if (world === null)
@@ -278,12 +263,11 @@ async function invokeCatalog(parsed, world, repo, edge) {
278
263
  throw error;
279
264
  }
280
265
  }
281
- async function invokeStatus(parsed, world, repo, edge) {
282
- const configuration = await settingsAt(world ?? undefined, edge.environment);
266
+ async function invokeStatus(parsed, world, repo) {
283
267
  if (parsed.akuma === true) {
284
268
  if (world === null)
285
269
  throw new CliUsageError("no Keiyaku world contains the invocation cwd");
286
- return invokeAkumaStatus(world, parsed.contract, configuration, undefined, repo);
270
+ return invokeAkumaStatus(world, parsed.contract, undefined, repo);
287
271
  }
288
272
  if (parsed.contract === undefined) {
289
273
  const report = await kanshi({ world, ...(repo === undefined ? {} : { repo }) });
@@ -291,18 +275,18 @@ async function invokeStatus(parsed, world, repo, edge) {
291
275
  }
292
276
  if (parsed.contract.startsWith("@")) {
293
277
  try {
294
- const address = await resolveNamedAddress({
295
- path: world,
278
+ const observation = await observeKanshi({ world, ...(repo === undefined ? {} : { repo }) });
279
+ const address = resolveNamedAddress({
296
280
  selector: parsed.contract,
297
- contracts: repo === undefined ? [] : (await Keiyaku.list({ repo })).rows,
281
+ report: observation.report,
282
+ aliases: observation.aliases,
298
283
  });
299
284
  if (address.kind === "akuma") {
300
285
  if (world === null)
301
286
  throw new CliUsageError("no Keiyaku world contains the invocation cwd");
302
- return invokeAkumaStatus(world, address.id, configuration, parsed.contract, repo);
287
+ return invokeAkumaStatus(world, address.id, parsed.contract, repo);
303
288
  }
304
- const report = await kanshi({ world, ...(repo === undefined ? {} : { repo }) });
305
- return { kind: "status", report: selectKanshi({ report, contract: address.id }), selection: "contract" };
289
+ return { kind: "status", report: selectKanshi({ report: observation.report, contract: address.id }), selection: "contract" };
306
290
  }
307
291
  catch (error) {
308
292
  if (error instanceof TypeError)
@@ -310,6 +294,16 @@ async function invokeStatus(parsed, world, repo, edge) {
310
294
  throw error;
311
295
  }
312
296
  }
297
+ if (parsed.contract.startsWith("kei/")) {
298
+ if (repo === undefined)
299
+ throw new CliUsageError("cannot select a contract while the Contract world is absent");
300
+ const contract = canonicalContractSelector(parsed.contract);
301
+ return {
302
+ kind: "status",
303
+ report: await kanshi({ world, repo, contract }),
304
+ selection: "contract",
305
+ };
306
+ }
313
307
  const report = await kanshi({ world, ...(repo === undefined ? {} : { repo }) });
314
308
  const contract = resolveKanshiContract(report, parsed.contract);
315
309
  return { kind: "status", report: selectKanshi({ report, contract }), selection: "contract" };
@@ -343,6 +337,22 @@ async function invokeRegion(parsed, world, repo) {
343
337
  const selection = parsed.overlap ? { kind: "overlap", contract } : { kind: "contract", contract };
344
338
  return { kind: "region", region: { kind: "present", value: selectRegion({ declarations: report.region.value.declarations, selection }) } };
345
339
  }
340
+ async function invokeContractHistory(repo, contract) {
341
+ if (repo === undefined)
342
+ throw new Error("history kei/... requires a resolved Repo");
343
+ const selected = contractFromInput(repo, contract);
344
+ try {
345
+ return { kind: "contract-history", history: await selected.contract.history() };
346
+ }
347
+ catch (error) {
348
+ if (error instanceof TypeError)
349
+ throw new CliUsageError(error.message);
350
+ if (error instanceof KeiyakuRefused) {
351
+ return { kind: "refused", verb: "history", contract: selected.id, refusal: error.refusal };
352
+ }
353
+ throw error;
354
+ }
355
+ }
346
356
  // eslint-disable-next-line complexity -- command dispatch keeps the CLI's existing boundary in one place.
347
357
  async function invokeParsed(invocation, runtime) {
348
358
  const coordinates = await resolveCliCoordinates({
@@ -351,14 +361,16 @@ async function invokeParsed(invocation, runtime) {
351
361
  ...(invocation.repo === undefined ? {} : { repo: invocation.repo }),
352
362
  command: invocation.command,
353
363
  });
354
- const { cwd, repo, world, candidateWorld, establishWorld } = coordinates;
364
+ const { cwd, cwdSource, repo, world, candidateWorld, establishWorld } = coordinates;
355
365
  const edge = {
356
366
  environment: runtime.environment ?? process.env,
357
367
  readStdin: runtime.readStdin ?? readStdin,
358
368
  };
359
369
  const parsed = invocation.command;
370
+ const mapped = edge.environment.KEIYAKU_HOME?.trim();
371
+ const home = mapped === undefined || mapped.length === 0 ? undefined : mapped;
360
372
  if (parsed.command === "settings") {
361
- return { kind: "settings", value: await settingsAt(world ?? undefined, edge.environment) };
373
+ return { kind: "settings", value: await settingsAt(world ?? undefined, home) };
362
374
  }
363
375
  if (parsed.command === "task") {
364
376
  try {
@@ -375,30 +387,31 @@ async function invokeParsed(invocation, runtime) {
375
387
  throw error;
376
388
  }
377
389
  }
390
+ if (parsed.command === "history" && "contract" in parsed)
391
+ return await invokeContractHistory(repo, parsed.contract);
378
392
  if (isParsedAkumaCommand(parsed)) {
379
393
  const akumaWorld = await akumaWorldFor(parsed, world, candidateWorld, coordinates.establishWorld);
380
- const configuration = await settingsAt(akumaWorld, edge.environment);
394
+ const configuration = parsed.command === "call" ? await settingsAt(akumaWorld, home) : undefined;
381
395
  return await invokeAkumaFromEdge(parsed, {
382
396
  path: akumaWorld,
383
- ...(parsed.command === "call" && parsed.contract !== undefined && invocation.cwd === undefined
384
- ? {}
385
- : { executionCwd: cwd }),
397
+ ...(cwdSource === "input" ? { statedCwd: cwd } : {}),
386
398
  ...(repo === undefined ? {} : { repo }),
387
- configuration,
399
+ ...(parsed.command === "call" && home !== undefined ? { home } : {}),
400
+ ...(configuration === undefined ? {} : { configuration }),
388
401
  edge,
389
402
  });
390
403
  }
391
404
  if (parsed.command === "ls")
392
- return await invokeCatalog(parsed, world, repo, edge);
405
+ return await invokeCatalog(parsed, world, repo, home);
393
406
  if (parsed.command === "status")
394
- return await invokeStatus(parsed, world, repo, edge);
407
+ return await invokeStatus(parsed, world, repo);
395
408
  if (repo === undefined)
396
409
  throw new Error(`${parsed.command} requires a resolved Repo`);
397
410
  if (parsed.command === "region")
398
411
  return invokeRegion(parsed, world, repo);
399
412
  const scope = cwd;
400
- const configuration = await settingsAt(world ?? undefined, edge.environment);
401
- const hooks = selectedHooks(configuration);
413
+ const configuration = await settingsAt(world ?? undefined, home);
414
+ const hooks = consumeSettings(() => worktreeHooksFrom({ settings: configuration }));
402
415
  if (parsed.command === "show") {
403
416
  const selected = await selectContract(repo, parsed.contract, scope);
404
417
  return { kind: "guidance", contract: selected.id, guidance: await selected.contract.guidance() };
@@ -427,13 +440,17 @@ export async function invoke(invocation, runtime = {}) {
427
440
  try {
428
441
  const command = invocation.command;
429
442
  assertExplicitRepoUse(command, invocation.repo);
430
- if (command.command === "install")
443
+ if (command.command === "install") {
444
+ runtime.onOperationStart?.();
431
445
  return installHarnesses(command.harnesses, runtime.environment ?? process.env);
446
+ }
447
+ const acquiredRuntime = await withAcquiredStdin(command, withResolvedTaskActor(command, runtime));
448
+ runtime.onOperationStart?.();
432
449
  return await invokeParsed({
433
450
  ...(invocation.cwd === undefined ? {} : { cwd: invocation.cwd }),
434
451
  ...(invocation.repo === undefined ? {} : { repo: invocation.repo }),
435
452
  command,
436
- }, await withAcquiredStdin(command, withResolvedTaskActor(command, runtime)));
453
+ }, acquiredRuntime);
437
454
  }
438
455
  catch (error) {
439
456
  if (error instanceof CliUsageError && error.projection === undefined) {
@@ -1 +1,3 @@
1
+ import { type ParsedCommand } from "./parse.js";
2
+ export declare function invocationStart(command: ParsedCommand): string | undefined;
1
3
  export declare function main(argv?: readonly string[]): Promise<number>;
@@ -28,7 +28,7 @@ function writeTask(command, result) {
28
28
  return taskExitCode(result);
29
29
  }
30
30
  function writeAkuma(command, result) {
31
- const output = command.output === "json" ? renderAkumaJson(command, result) : renderAkumaText(command, result, {
31
+ const output = command.output === "json" ? renderAkumaJson(result) : renderAkumaText(command, result, {
32
32
  columns: process.stdout.isTTY === true && Number.isInteger(process.stdout.columns) ? process.stdout.columns : 80,
33
33
  color: process.stdout.isTTY === true && process.env.NO_COLOR === undefined,
34
34
  });
@@ -46,6 +46,18 @@ function renderHelp(coordinate) {
46
46
  case "akuma": return renderAkumaHelp(coordinate.action);
47
47
  }
48
48
  }
49
+ export function invocationStart(command) {
50
+ if (command.output === "json")
51
+ return undefined;
52
+ switch (command.command) {
53
+ case "bind": return "⧖ preparing keiyaku";
54
+ case "deliver": return "⧖ delivering";
55
+ case "audit": return "⧖ auditing";
56
+ case "reconcile": return "⧖ reconciling";
57
+ case "install": return "⧖ installing skills";
58
+ default: return undefined;
59
+ }
60
+ }
49
61
  function writeGuidance(command, result) {
50
62
  if (command.output === "json")
51
63
  process.stdout.write(`${JSON.stringify({ contract: result.contract, guidance: result.guidance })}\n`);
@@ -71,8 +83,17 @@ function writeResult(command, result) {
71
83
  process.stdout.write(`${command.output === "json" ? JSON.stringify(settingsJsonValue(value)) : renderSettingsText(value)}\n`);
72
84
  return 0;
73
85
  }
74
- if (isParsedAkumaCommand(command)
75
- || (typeof result === "object" && result !== null && "kind" in result && result.kind === "akuma")) {
86
+ if (typeof result === "object" && result !== null && "kind" in result && result.kind === "contract-history") {
87
+ const history = result.history;
88
+ const output = command.output === "json" ? JSON.stringify(history) : renderText(result);
89
+ process.stdout.write(`${output}\n`);
90
+ return 0;
91
+ }
92
+ const akumaResult = typeof result === "object" && result !== null && "kind" in result && result.kind === "akuma";
93
+ if (isParsedAkumaCommand(command)) {
94
+ return writeAkuma(command, result);
95
+ }
96
+ if (akumaResult && command.command === "status") {
76
97
  return writeAkuma(command, result);
77
98
  }
78
99
  const contractResult = result;
@@ -111,7 +132,11 @@ export async function main(argv = process.argv.slice(2)) {
111
132
  }
112
133
  output = parsed.command.output;
113
134
  const { invoke } = await import("./invoke.js");
114
- const result = await invoke(parsed, { cwd: process.cwd() });
135
+ const start = invocationStart(parsed.command);
136
+ const result = await invoke(parsed, {
137
+ cwd: process.cwd(),
138
+ ...(start === undefined ? {} : { onOperationStart: () => { process.stderr.write(`${start}\n`); } }),
139
+ });
115
140
  return writeResult(parsed.command, result);
116
141
  }
117
142
  catch (error) {
@@ -28,6 +28,7 @@ export type ParsedAmend = Output & Actor & Readonly<{
28
28
  after?: readonly string[];
29
29
  clearAfter?: true;
30
30
  gates?: string;
31
+ stdin?: true;
31
32
  }>;
32
33
  export type ParsedDeliver = Output & Actor & Readonly<{
33
34
  command: "deliver";
@@ -2,11 +2,11 @@ import { isTaskAction, parseTaskCommand, renderTaskUsage, } from "./commands/tas
2
2
  import { isAkumaAction, parseAkumaCatalogPath, parseAkumaCommand, renderAkumaRootRows, renderAkumaUsage, } from "./commands/akuma.js";
3
3
  import { INSTALL_USAGE, parseInstallCommand, renderInstallHelp } from "./commands/install.js";
4
4
  import { AMEND_MINIMAL_STDIN_HELP, CONTRACT_COMMAND_SPECS } from "./commands/contract.js";
5
- import { CliUsageError, isBlankInput, usageLine } from "./usage.js";
5
+ import { CliUsageError, isBlankInput, usageLine, withJsonAutomationHelp } from "./usage.js";
6
6
  export { CliUsageError } from "./usage.js";
7
7
  const ROOT_USAGE = "usage: keiyaku [-C <path>] [--repo <path>] <command> [<contract>|@<contract>] [--flag ...] [-]";
8
8
  export function renderRootHelp() {
9
- return [
9
+ return withJsonAutomationHelp([
10
10
  ROOT_USAGE,
11
11
  "",
12
12
  "global options:",
@@ -20,12 +20,12 @@ export function renderRootHelp() {
20
20
  " task ...",
21
21
  " Task coordination; see `keiyaku task --help`.",
22
22
  ...renderAkumaRootRows(),
23
- ].join("\n");
23
+ ].join("\n"));
24
24
  }
25
25
  export function renderContractHelp(command) {
26
26
  const spec = CONTRACT_COMMAND_SPECS[command];
27
27
  const help = `${spec.purpose}\n\n${usageLine(spec.usage)}`;
28
- return command === "amend" ? `${help}\n\n${AMEND_MINIMAL_STDIN_HELP}` : help;
28
+ return withJsonAutomationHelp(command === "amend" ? `${help}\n\n${AMEND_MINIMAL_STDIN_HELP}` : help);
29
29
  }
30
30
  function contractUsage(command) {
31
31
  return usageLine(CONTRACT_COMMAND_SPECS[command].usage);
@@ -139,6 +139,9 @@ function parseAmend(parts) {
139
139
  if (parts.flags["clear-after"] === true && after.length > 0)
140
140
  refuse("amend", "--clear-after and --after are mutually exclusive");
141
141
  const gates = optionalFlag(parts.flags, "gates");
142
+ if (!parts.stdin && parts.flags.after === undefined && parts.flags["clear-after"] !== true && gates === undefined) {
143
+ refuse("amend", "amend requires stdin or --after, --clear-after, or --gates");
144
+ }
142
145
  return {
143
146
  command: "amend",
144
147
  ...(contract === undefined ? {} : { contract }),
@@ -146,6 +149,7 @@ function parseAmend(parts) {
146
149
  ...(parts.flags["clear-after"] === true ? { clearAfter: true } : {}),
147
150
  ...(gates === undefined ? {} : { gates }),
148
151
  ...(parts.actor === undefined ? {} : { actor: parts.actor }),
152
+ ...(parts.stdin ? { stdin: true } : {}),
149
153
  output: parts.output,
150
154
  };
151
155
  }
@@ -4,5 +4,5 @@ import { type TextRenderContext } from "./terminal.js";
4
4
  export declare function akumaRawAnswer(result: AkumaInvocationResult): string | undefined;
5
5
  export declare function renderAkumaText(command: ParsedCommand, result: AkumaInvocationResult, context?: TextRenderContext): string;
6
6
  export declare function akumaExitCode(result: AkumaInvocationResult): number;
7
- export declare function akumaJsonValue(command: ParsedCommand, result: AkumaInvocationResult): unknown;
8
- export declare function renderAkumaJson(command: ParsedCommand, result: AkumaInvocationResult): string;
7
+ export declare function akumaJsonValue(result: AkumaInvocationResult): unknown;
8
+ export declare function renderAkumaJson(result: AkumaInvocationResult): string;
@@ -1,5 +1,5 @@
1
1
  import { toolRepr } from "./akuma-tool.js";
2
- import { displayColumns, renderBoundedTextBlock, safeText, truncateMiddleDisplayText } from "./terminal.js";
2
+ import { displayColumns, renderBoundedTextBlock, renderTextBlock, safeText, truncateMiddleDisplayText, } from "./terminal.js";
3
3
  const DEFAULT_CONTEXT = { columns: 80, color: false };
4
4
  const OPENING_STROKE = "─────";
5
5
  const TIME_WIDTH = 5;
@@ -138,6 +138,59 @@ function groupedEntries(entries, context, history = false) {
138
138
  function groupedRows(rows, context, history = false) {
139
139
  return groupedEntries(rows.filter((row) => row.kind !== "turn").map((row) => ({ kind: "row", row })), context, history);
140
140
  }
141
+ function taskDispositionMark(disposition) {
142
+ if (disposition === "done")
143
+ return "✓";
144
+ if (disposition === "drop")
145
+ return "×";
146
+ if (disposition === "on_hold")
147
+ return "⧗";
148
+ if (disposition === "in_progress")
149
+ return "●";
150
+ return disposition === "blocked" ? "‖" : "○";
151
+ }
152
+ function taskScanLine(row, indent = "") {
153
+ return `${indent}${taskDispositionMark(row.disposition)} ${row.id} · P${row.priority} ${row.disposition}`;
154
+ }
155
+ function changeOp(op) {
156
+ return op === "add" ? "write" : op === "delete" ? "delete" : "edit";
157
+ }
158
+ function renderReportedChangeLines(snapshot, columns) {
159
+ const lines = [` changes ${snapshot.reportedChanges.length + snapshot.reportedChangesOmitted}`];
160
+ for (const change of snapshot.reportedChanges) {
161
+ const scan = ` ${changeOp(change.op)}`;
162
+ const path = safeText(change.path);
163
+ const stat = change.diffstat === undefined
164
+ ? ""
165
+ : ` — +${change.diffstat.added} -${change.diffstat.removed}`;
166
+ const inline = `${scan} ${path}${stat}`;
167
+ if (displayColumns(inline) <= columns)
168
+ lines.push(inline);
169
+ else
170
+ lines.push(`${scan}`, ...renderTextBlock(`${path}${stat}`, " ", columns));
171
+ }
172
+ if (snapshot.reportedChangesOmitted > 0) {
173
+ lines.push(` ⋮ ${snapshot.reportedChangesOmitted} earlier changes`);
174
+ }
175
+ return lines;
176
+ }
177
+ function renderTaskContextLines(created, columns) {
178
+ if (created === undefined)
179
+ return [];
180
+ if (created.kind === "failed")
181
+ return [`! tasks failed ${safeText(created.diagnostic)}`];
182
+ const lines = [` tasks ${created.rows.length}`];
183
+ for (const row of created.rows) {
184
+ const scan = ` ${taskScanLine(row)}`;
185
+ const title = safeText(row.title);
186
+ const inline = `${scan} — ${title}`;
187
+ if (displayColumns(inline) <= columns)
188
+ lines.push(inline);
189
+ else
190
+ lines.push(`${scan} —`, ...renderTextBlock(title, " ", columns));
191
+ }
192
+ return lines;
193
+ }
141
194
  function snapshotText(view, context, options = {}) {
142
195
  const snapshot = view.status.timeline;
143
196
  const activity = snapshot.kind === "idle" && snapshot.outcome !== undefined
@@ -151,7 +204,14 @@ function snapshotText(view, context, options = {}) {
151
204
  ...(options.facts ?? []),
152
205
  ];
153
206
  const footer = options.showLife === false ? [] : [` ${lifeLabel(view.status.life)}`];
154
- return [...snapshotHeading(view.status.id, options.alias, view.contractId), ...facts, ...activity, ...footer].join("\n");
207
+ return [
208
+ ...snapshotHeading(view.status.id, options.alias, view.contractId),
209
+ ...facts,
210
+ ...activity,
211
+ ...footer,
212
+ ...renderReportedChangeLines(snapshot, context.columns),
213
+ ...renderTaskContextLines(view.createdTasks, context.columns),
214
+ ].join("\n");
155
215
  }
156
216
  function historyText(command, result, context) {
157
217
  if (command.last)
@@ -195,8 +255,9 @@ export function akumaRawAnswer(result) {
195
255
  return undefined;
196
256
  return statusAnswer({ status: result.result.observation.status });
197
257
  }
198
- if (result.action === "wait" && result.result.statuses.length === 1)
199
- return statusAnswer(result.result.statuses[0]);
258
+ if (result.action === "wait" && result.result.observations.length === 1) {
259
+ return statusAnswer(result.result.observations[0]);
260
+ }
200
261
  return undefined;
201
262
  }
202
263
  function executionCwdLine(result) {
@@ -237,9 +298,11 @@ export function renderAkumaText(command, result, context = DEFAULT_CONTEXT) {
237
298
  switch (result.action) {
238
299
  case "call": return callText(result, context);
239
300
  case "status": return snapshotText(result.status, context, { ...(result.alias === undefined ? {} : { alias: result.alias }) });
240
- case "wait": return result.result.statuses.map((status) => snapshotText(status, context, { ...(result.alias === undefined ? {} : { alias: result.alias }) })).join("\n\n");
301
+ case "wait":
302
+ return result.result.observations.map((observation) => snapshotText(observation, context, { ...(result.alias === undefined ? {} : { alias: result.alias }) })).join("\n\n");
241
303
  case "tell": return result.mode === "ordinary" ? tellText(result, context) : snapshotText(result.result.observation, context, { ...(result.alias === undefined ? {} : { alias: result.alias }), showLife: false });
242
- case "history": return historyText(command, result, context);
304
+ case "history":
305
+ return historyText(command, result, context);
243
306
  case "fork": {
244
307
  if (result.receipt.kind !== "forked")
245
308
  return result.receipt.kind === "unknown-history" ? `${result.receipt.at} has no matching retained answered turn` : result.receipt.kind === "provider-cannot-fork" ? `${result.receipt.provider} cannot fork` : result.receipt.diagnostic;
@@ -277,13 +340,21 @@ export function akumaExitCode(result) {
277
340
  default: return 0;
278
341
  }
279
342
  }
280
- export function akumaJsonValue(command, result) {
281
- if (result.action === "call" || result.action === "fork" || result.action === "status" || result.action === "wait" || result.action === "tell" || result.action === "kill")
282
- return result.action === "status" ? result.status : result.action === "wait" ? result.result : result.action === "tell" ? result.result : result.action === "kill" ? result.result : result.action === "call" ? result.result : result.receipt;
283
- if (command.command !== "history")
284
- throw new Error("history result requires the history command");
343
+ export function akumaJsonValue(result) {
344
+ if (result.action === "call")
345
+ return result.result;
346
+ if (result.action === "fork")
347
+ return result.receipt;
348
+ if (result.action === "status")
349
+ return result.status;
350
+ if (result.action === "wait")
351
+ return result.result;
352
+ if (result.action === "tell")
353
+ return result.result;
354
+ if (result.action === "kill")
355
+ return result.result;
285
356
  return result.historyResult;
286
357
  }
287
- export function renderAkumaJson(command, result) {
288
- return JSON.stringify(akumaJsonValue(command, result));
358
+ export function renderAkumaJson(result) {
359
+ return JSON.stringify(akumaJsonValue(result));
289
360
  }
@@ -1,4 +1,4 @@
1
- import { cleanupLines, leakLines, outcomeLines, receiptPayload, receiptRow, stopLines, } from "./receipt.js";
1
+ import { cleanupLines, leakLines, receiptPayload, receiptRow, stopLines, titleLines, } from "./receipt.js";
2
2
  import { renderRefusalFacts } from "./refusal.js";
3
3
  import { gitShortStat, renderBoundedTextBlock, renderOpaqueBlock, } from "./terminal.js";
4
4
  const CHILD = " ";
@@ -91,14 +91,28 @@ function obligationLines(result, columns) {
91
91
  ...(result.leak === undefined ? [] : leakLines(result.leak, columns)),
92
92
  ];
93
93
  }
94
+ function recordLines(result, columns) {
95
+ if (result.facts.length === 0)
96
+ return [];
97
+ const rows = [];
98
+ for (const fact of result.facts) {
99
+ receiptRow(rows, " ", "journal", [
100
+ { text: fact.entry, opaque: true },
101
+ { text: `· ${fact.kind}` },
102
+ ], columns);
103
+ }
104
+ receiptRow(rows, " ", "head", [{ text: result.head, opaque: true }], columns);
105
+ return [" record", ...rows.map((line) => ` ${line}`)];
106
+ }
94
107
  export function renderAcceptedAudit(result, context) {
95
108
  const report = result.report;
96
109
  const columns = context?.columns ?? 80;
97
110
  return [
98
- ...outcomeLines("✓", result.verb, "accepted", result.contract, columns),
111
+ ...titleLines("✓", "audit", result.contract, columns),
99
112
  ...candidateLines(report, columns, result.contract),
100
113
  ...verificationLines(report.verification, columns, result.contract),
101
114
  ...targetLines(report.target, columns, result.contract),
102
115
  ...obligationLines(result, columns),
116
+ ...recordLines(result, columns),
103
117
  ].join("\n");
104
118
  }
@@ -1,4 +1,6 @@
1
+ import type { ContractHistory } from "../../index.js";
1
2
  import type { AcceptedResult, RetryResult } from "../result.js";
2
3
  import { type TextRenderContext } from "./terminal.js";
3
4
  export declare function renderAccepted(result: AcceptedResult, context?: TextRenderContext): string;
4
5
  export declare function renderRetry(result: RetryResult, context?: TextRenderContext): string;
6
+ export declare function renderContractHistory(history: ContractHistory): string;