@astrosheep/keiyaku 4.0.5 → 4.1.0

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 (166) 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 +15 -7
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +5 -3
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +10 -4
  8. package/build/src/akuma/akuma.d.ts +2 -0
  9. package/build/src/akuma/akuma.js +26 -3
  10. package/build/src/akuma/archetype.d.ts +3 -1
  11. package/build/src/akuma/archetype.js +19 -7
  12. package/build/src/akuma/body-turn.d.ts +64 -0
  13. package/build/src/akuma/body-turn.js +449 -0
  14. package/build/src/akuma/body.d.ts +3 -7
  15. package/build/src/akuma/body.js +9 -420
  16. package/build/src/akuma/heart/facts.d.ts +2 -0
  17. package/build/src/akuma/heart/facts.js +3 -1
  18. package/build/src/akuma/heart/index.js +12 -2
  19. package/build/src/akuma/heart/rows.js +4 -1
  20. package/build/src/akuma/heart/schema.d.ts +1 -1
  21. package/build/src/akuma/heart/schema.js +3 -2
  22. package/build/src/akuma/heart/timeline.d.ts +1 -0
  23. package/build/src/akuma/heart/timeline.js +19 -0
  24. package/build/src/akuma/nuke.d.ts +2 -0
  25. package/build/src/akuma/nuke.js +152 -0
  26. package/build/src/akuma/provider-recipe.d.ts +2 -0
  27. package/build/src/akuma/provider-recipe.js +10 -0
  28. package/build/src/akuma/provider.d.ts +3 -1
  29. package/build/src/akuma/providers/acp/config.d.ts +10 -0
  30. package/build/src/akuma/providers/acp/config.js +45 -0
  31. package/build/src/akuma/providers/acp/core.d.ts +5 -2
  32. package/build/src/akuma/providers/acp/core.js +35 -16
  33. package/build/src/akuma/providers/acp/index.d.ts +1 -8
  34. package/build/src/akuma/providers/acp/index.js +10 -39
  35. package/build/src/akuma/providers/claude/index.d.ts +1 -1
  36. package/build/src/akuma/providers/claude/index.js +14 -5
  37. package/build/src/akuma/providers/codex-app-server/index.js +9 -1
  38. package/build/src/akuma/providers/grok-build/index.js +16 -2
  39. package/build/src/akuma/providers/index.d.ts +3 -2
  40. package/build/src/akuma/providers/index.js +37 -37
  41. package/build/src/akuma/providers/opencode-sdk/index.js +9 -0
  42. package/build/src/akuma/providers/opencode-sdk/session.d.ts +1 -1
  43. package/build/src/akuma/providers/opencode-sdk/session.js +1 -1
  44. package/build/src/akuma/providers/pi/index.d.ts +1 -1
  45. package/build/src/akuma/providers/pi/index.js +30 -12
  46. package/build/src/akuma/request-serve.d.ts +1 -0
  47. package/build/src/akuma/request-serve.js +31 -31
  48. package/build/src/akuma/request-wire.d.ts +3 -2
  49. package/build/src/akuma/request-wire.js +5 -2
  50. package/build/src/akuma-body.js +15 -1
  51. package/build/src/alias/index.d.ts +1 -0
  52. package/build/src/alias/index.js +34 -1
  53. package/build/src/body/amend.js +0 -9
  54. package/build/src/body/region.js +2 -2
  55. package/build/src/cli/accepted.js +6 -0
  56. package/build/src/cli/commands/akuma-invoke.js +1 -0
  57. package/build/src/cli/commands/akuma.d.ts +1 -0
  58. package/build/src/cli/commands/akuma.js +11 -8
  59. package/build/src/cli/commands/contract.d.ts +28 -15
  60. package/build/src/cli/commands/contract.js +34 -18
  61. package/build/src/cli/commands/install.d.ts +2 -2
  62. package/build/src/cli/commands/install.js +6 -6
  63. package/build/src/cli/commands/nuke.d.ts +10 -0
  64. package/build/src/cli/commands/nuke.js +20 -0
  65. package/build/src/cli/commands/task-invoke.d.ts +19 -1
  66. package/build/src/cli/commands/task-invoke.js +63 -29
  67. package/build/src/cli/commands/task-query.d.ts +2 -2
  68. package/build/src/cli/commands/task-query.js +1 -2
  69. package/build/src/cli/commands/task.js +30 -22
  70. package/build/src/cli/coordinates.d.ts +5 -2
  71. package/build/src/cli/coordinates.js +5 -52
  72. package/build/src/cli/invoke.d.ts +5 -4
  73. package/build/src/cli/invoke.js +89 -59
  74. package/build/src/cli/main.d.ts +0 -2
  75. package/build/src/cli/main.js +4 -140
  76. package/build/src/cli/parse.d.ts +15 -2
  77. package/build/src/cli/parse.js +73 -8
  78. package/build/src/cli/render/akuma.js +95 -49
  79. package/build/src/cli/render/catalog.js +5 -1
  80. package/build/src/cli/render/contract.js +72 -49
  81. package/build/src/cli/render/kanshi.js +157 -99
  82. package/build/src/cli/render/nuke.d.ts +3 -0
  83. package/build/src/cli/render/nuke.js +8 -0
  84. package/build/src/cli/render/receipt.d.ts +1 -0
  85. package/build/src/cli/render/receipt.js +18 -14
  86. package/build/src/cli/render/refusal.d.ts +2 -1
  87. package/build/src/cli/render/refusal.js +37 -0
  88. package/build/src/cli/render/task.js +63 -11
  89. package/build/src/cli/render/text.js +6 -1
  90. package/build/src/cli/result.d.ts +28 -2
  91. package/build/src/cli/runtime.d.ts +2 -0
  92. package/build/src/cli/runtime.js +140 -0
  93. package/build/src/cli/usage.d.ts +0 -2
  94. package/build/src/cli/usage.js +0 -4
  95. package/build/src/contract-worktree.d.ts +26 -12
  96. package/build/src/contract-worktree.js +119 -64
  97. package/build/src/git/hooks.d.ts +2 -0
  98. package/build/src/git/hooks.js +34 -1
  99. package/build/src/git/integration.d.ts +4 -0
  100. package/build/src/git/integration.js +38 -0
  101. package/build/src/git/nuke.d.ts +2 -0
  102. package/build/src/git/nuke.js +92 -0
  103. package/build/src/git/process.d.ts +2 -0
  104. package/build/src/git/repository.js +4 -1
  105. package/build/src/git/target-placement.d.ts +2 -1
  106. package/build/src/git/target-placement.js +6 -7
  107. package/build/src/git/tender.d.ts +14 -2
  108. package/build/src/git/tender.js +45 -6
  109. package/build/src/git/workspace.d.ts +3 -0
  110. package/build/src/kanshi/read.js +37 -13
  111. package/build/src/kanshi/report.d.ts +2 -1
  112. package/build/src/library/akuma-creation.d.ts +1 -0
  113. package/build/src/library/akuma-creation.js +8 -1
  114. package/build/src/library/audit.d.ts +1 -0
  115. package/build/src/library/audit.js +1 -0
  116. package/build/src/library/continuation.d.ts +26 -0
  117. package/build/src/library/continuation.js +78 -0
  118. package/build/src/library/contract.d.ts +14 -8
  119. package/build/src/library/contract.js +82 -35
  120. package/build/src/library/delivery.d.ts +18 -11
  121. package/build/src/library/keiyaku.d.ts +5 -1
  122. package/build/src/library/keiyaku.js +2 -0
  123. package/build/src/library/mutation.d.ts +1 -0
  124. package/build/src/library/mutation.js +14 -4
  125. package/build/src/library/nuke.d.ts +14 -0
  126. package/build/src/library/nuke.js +54 -0
  127. package/build/src/library/reconcile.js +5 -2
  128. package/build/src/library/refusal.d.ts +10 -1
  129. package/build/src/library/repo.d.ts +1 -0
  130. package/build/src/library/repo.js +2 -0
  131. package/build/src/protocol/audit.js +12 -5
  132. package/build/src/protocol/completion.d.ts +11 -1
  133. package/build/src/protocol/completion.js +68 -15
  134. package/build/src/protocol/deliver.d.ts +25 -3
  135. package/build/src/protocol/deliver.js +148 -14
  136. package/build/src/protocol/intent.d.ts +1 -0
  137. package/build/src/protocol/intent.js +7 -1
  138. package/build/src/protocol/operations.d.ts +36 -4
  139. package/build/src/protocol/operations.js +2 -2
  140. package/build/src/protocol/placement.d.ts +9 -1
  141. package/build/src/protocol/placement.js +5 -4
  142. package/build/src/protocol/read/status.d.ts +16 -4
  143. package/build/src/protocol/read/status.js +54 -12
  144. package/build/src/protocol/review.d.ts +1 -0
  145. package/build/src/protocol/review.js +8 -0
  146. package/build/src/runtime/proc/run.js +9 -5
  147. package/build/src/settlement/settle.js +1 -2
  148. package/build/src/task/board.d.ts +21 -2
  149. package/build/src/task/board.js +25 -12
  150. package/build/src/task/compose.d.ts +1 -1
  151. package/build/src/task/compose.js +2 -7
  152. package/build/src/task/context.d.ts +14 -2
  153. package/build/src/task/context.js +30 -7
  154. package/build/src/task/identity.js +14 -4
  155. package/build/src/task/index.d.ts +7 -5
  156. package/build/src/task/index.js +21 -18
  157. package/build/src/task/mutation.d.ts +7 -2
  158. package/build/src/task/mutation.js +14 -6
  159. package/build/src/task/operations.d.ts +14 -7
  160. package/build/src/task/operations.js +37 -50
  161. package/build/src/task/query.js +2 -2
  162. package/build/src/task/store.d.ts +3 -0
  163. package/build/src/task/store.js +21 -2
  164. package/build/src/workspace-place.d.ts +2 -0
  165. package/build/src/workspace-place.js +27 -1
  166. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import { type ParsedTaskCommand, type TaskAction } from "./commands/task.js";
2
2
  import { type AkumaAction, type ParsedAkumaCommand } from "./commands/akuma.js";
3
- import type { CatalogQuery } from "../index.js";
3
+ import type { CatalogQuery } from "../library/catalog.js";
4
4
  import { type ParsedInstallCommand } from "./commands/install.js";
5
5
  import { type ContractCommand as Command } from "./commands/contract.js";
6
6
  export { CliUsageError } from "./usage.js";
@@ -8,6 +8,7 @@ export type { Command };
8
8
  export declare function renderRootHelp(): string;
9
9
  export declare function renderContractHelp(command: Command): string;
10
10
  export declare function renderCommandUsage(command: ParsedCommand): string;
11
+ export declare function renderHelp(coordinate: CliHelpCoordinate): string;
11
12
  type Output = Readonly<{
12
13
  output: "text" | "json";
13
14
  }>;
@@ -35,6 +36,7 @@ export type ParsedDeliver = Output & Actor & Readonly<{
35
36
  contract?: string;
36
37
  message?: string;
37
38
  includeDirty: boolean;
39
+ materializeConflict: boolean;
38
40
  }>;
39
41
  export type ParsedReview = Output & Actor & Readonly<{
40
42
  command: "review";
@@ -77,6 +79,10 @@ type ParsedReconcile = Output & Readonly<{
77
79
  contract?: string;
78
80
  retryHooks: boolean;
79
81
  }>;
82
+ export type ParsedNuke = Output & Readonly<{
83
+ command: "nuke";
84
+ confirm?: string;
85
+ }>;
80
86
  export type ParsedSettings = Output & Readonly<{
81
87
  command: "settings";
82
88
  }>;
@@ -89,7 +95,7 @@ export type ParsedRegion = Output & Readonly<{
89
95
  export type ParsedCommand = ParsedBind | ParsedAmend | ParsedDeliver | ParsedReview | ParsedArc | ParsedAbandon | ParsedStatus | (Output & Readonly<{
90
96
  command: "show";
91
97
  contract?: string;
92
- }>) | ParsedLs | ParsedAudit | ParsedReconcile | ParsedSettings | ParsedRegion | ParsedInstallCommand | ParsedAkumaCommand | ParsedTaskCommand;
98
+ }>) | ParsedLs | ParsedAudit | ParsedReconcile | ParsedNuke | ParsedSettings | ParsedRegion | ParsedInstallCommand | ParsedAkumaCommand | ParsedTaskCommand;
93
99
  export type CliHelpCoordinate = Readonly<{
94
100
  kind: "root";
95
101
  }> | Readonly<{
@@ -112,4 +118,11 @@ export type ParsedExecution = Readonly<{
112
118
  export type ParsedInvocation = ParsedExecution | Readonly<{
113
119
  help: CliHelpCoordinate;
114
120
  }>;
121
+ type RepoUse = "none" | "optional" | "required";
122
+ export type CommandRepoPolicy = Readonly<{
123
+ use: RepoUse;
124
+ acceptsExplicit: boolean;
125
+ }>;
126
+ export declare function commandRepoPolicy(command: ParsedCommand): CommandRepoPolicy;
127
+ export declare function assertExplicitRepoUse(command: ParsedCommand, repo: string | undefined): void;
115
128
  export declare function parseArgv(argv: readonly string[]): ParsedInvocation;
@@ -1,12 +1,12 @@
1
- import { isTaskAction, parseTaskCommand, renderTaskUsage, } from "./commands/task.js";
2
- import { isAkumaAction, parseAkumaCatalogPath, parseAkumaCommand, renderAkumaRootRows, renderAkumaUsage, } from "./commands/akuma.js";
1
+ import { isTaskAction, parseTaskCommand, renderTaskHelp, renderTaskUsage, } from "./commands/task.js";
2
+ import { isAkumaAction, parseAkumaCatalogPath, parseAkumaCommand, renderAkumaHelp, renderAkumaRootRows, renderAkumaUsage, } from "./commands/akuma.js";
3
3
  import { INSTALL_USAGE, parseInstallCommand, renderInstallHelp } from "./commands/install.js";
4
- import { AMEND_MINIMAL_STDIN_HELP, CONTRACT_COMMAND_SPECS, } from "./commands/contract.js";
5
- import { CliUsageError, isBlankInput, usageLine, withJsonAutomationHelp } from "./usage.js";
4
+ import { CONTRACT_COMMAND_SPECS, } from "./commands/contract.js";
5
+ import { CliUsageError, isBlankInput, usageLine } 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 withJsonAutomationHelp([
9
+ return [
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 withJsonAutomationHelp(command === "amend" ? `${help}\n\n${AMEND_MINIMAL_STDIN_HELP}` : help);
28
+ return spec.help === undefined ? help : `${help}\n\n${spec.help}`;
29
29
  }
30
30
  function contractUsage(command) {
31
31
  return usageLine(CONTRACT_COMMAND_SPECS[command].usage);
@@ -39,6 +39,65 @@ export function renderCommandUsage(command) {
39
39
  return renderAkumaUsage(command.command);
40
40
  return contractUsage(command.command);
41
41
  }
42
+ export function renderHelp(coordinate) {
43
+ switch (coordinate.kind) {
44
+ case "root": return renderRootHelp();
45
+ case "contract": return renderContractHelp(coordinate.command);
46
+ case "task": return renderTaskHelp(coordinate.action);
47
+ case "install": return renderInstallHelp();
48
+ case "akuma": return renderAkumaHelp(coordinate.action);
49
+ }
50
+ }
51
+ export function commandRepoPolicy(command) {
52
+ switch (command.command) {
53
+ case "bind":
54
+ case "amend":
55
+ case "deliver":
56
+ case "review":
57
+ case "arc":
58
+ case "abandon":
59
+ case "audit":
60
+ case "reconcile":
61
+ case "show":
62
+ case "region":
63
+ return { use: "required", acceptsExplicit: true };
64
+ case "ls":
65
+ return { use: command.query.kind === "contracts" ? "required" : "none", acceptsExplicit: false };
66
+ case "status":
67
+ case "tell":
68
+ return { use: "optional", acceptsExplicit: false };
69
+ case "history":
70
+ return "contract" in command
71
+ ? { use: "required", acceptsExplicit: true }
72
+ : { use: "optional", acceptsExplicit: false };
73
+ case "fork":
74
+ return { use: "optional", acceptsExplicit: true };
75
+ case "wait":
76
+ case "kill": {
77
+ const contractSelector = command.akuma.some((selector) => selector.startsWith("kei/"));
78
+ return { use: contractSelector ? "required" : "optional", acceptsExplicit: contractSelector };
79
+ }
80
+ case "call":
81
+ return {
82
+ use: command.contract === undefined ? "none" : "required",
83
+ acceptsExplicit: command.contract !== undefined,
84
+ };
85
+ case "settings":
86
+ case "nuke":
87
+ case "task":
88
+ case "install":
89
+ return { use: "none", acceptsExplicit: false };
90
+ }
91
+ }
92
+ function refuseUnusedRepo(command) {
93
+ if (command.command === "call")
94
+ throw new CliUsageError("--repo has no consumer without --contract");
95
+ throw new CliUsageError(`--repo has no consumer for ${command.command}`);
96
+ }
97
+ export function assertExplicitRepoUse(command, repo) {
98
+ if (repo !== undefined && !commandRepoPolicy(command).acceptsExplicit)
99
+ refuseUnusedRepo(command);
100
+ }
42
101
  function optionalFlag(flags, name) {
43
102
  const value = flags[name];
44
103
  return typeof value === "string" ? value : undefined;
@@ -175,6 +234,7 @@ function parseDeliver(parts) {
175
234
  ...(parts.actor === undefined ? {} : { actor: parts.actor }),
176
235
  ...(message === undefined ? {} : { message }),
177
236
  includeDirty: parts.flags["include-dirty"] === true,
237
+ materializeConflict: parts.flags["materialize-conflict"] === true,
178
238
  output: parts.output,
179
239
  };
180
240
  }
@@ -356,6 +416,10 @@ function parseCommand(parts) {
356
416
  output: parts.output,
357
417
  };
358
418
  }
419
+ case "nuke": {
420
+ const confirm = optionalFlag(parts.flags, "confirm");
421
+ return { command: "nuke", ...(confirm === undefined ? {} : { confirm }), output: parts.output };
422
+ }
359
423
  case "settings": return { command: "settings", output: parts.output };
360
424
  case "region": return parseRegion(parts);
361
425
  }
@@ -379,9 +443,10 @@ export function parseArgv(argv) {
379
443
  const akuma = isAkumaAction(invocation.commandArgv[0])
380
444
  ? parseAkumaCommand(invocation.commandArgv)
381
445
  : undefined;
382
- return {
446
+ const parsed = {
383
447
  ...(invocation.cwd === undefined ? {} : { cwd: invocation.cwd }),
384
448
  ...(invocation.repo === undefined ? {} : { repo: invocation.repo }),
385
449
  command: task ?? akuma ?? install ?? parseCommand(scanArgv(invocation.commandArgv)),
386
450
  };
451
+ return parsed;
387
452
  }
@@ -1,5 +1,5 @@
1
1
  import { toolRepr } from "./akuma-tool.js";
2
- import { displayColumns, renderBoundedTextBlock, renderTextBlock, safeText, truncateMiddleDisplayText, } from "./terminal.js";
2
+ import { displayColumns, renderBoundedTextBlock, 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;
@@ -14,14 +14,20 @@ function associatedIdentity(id, alias, contract) {
14
14
  const contractId = contract === undefined ? undefined : associatedContractId(contract);
15
15
  return `${identity(id, alias)}${contractId === undefined ? "" : ` [${contractId}]`}`;
16
16
  }
17
- function snapshotHeading(id, alias, contract) {
17
+ function ruler(columns) {
18
+ return "─".repeat(Math.max(OPENING_STROKE.length, columns));
19
+ }
20
+ function snapshotHeading(id, alias, contract, columns) {
18
21
  const contractId = contract === undefined ? undefined : associatedContractId(contract);
19
22
  return [
20
- OPENING_STROKE,
21
23
  identity(id, alias),
24
+ ruler(columns),
22
25
  ...(contractId === undefined ? [] : [`└─ ${contractId}`]),
23
26
  ];
24
27
  }
28
+ function answeredHeading(id, alias, columns) {
29
+ return [`✓ came back ${identity(id, alias)}`, ruler(columns)].join("\n");
30
+ }
25
31
  function contractFacts(contract) {
26
32
  return contract.kind === "failed" ? [`! contract failed ${safeText(contract.diagnostic)}`] : [];
27
33
  }
@@ -30,9 +36,9 @@ function unobservedText(id, diagnostic) {
30
36
  }
31
37
  function lifeLabel(life) {
32
38
  if (life === "running")
33
- return "● running";
39
+ return "● STILL RUNNING";
34
40
  if (life === "asleep")
35
- return "○ asleep";
41
+ return "✓ came back";
36
42
  if (life === "killed")
37
43
  return "× killed";
38
44
  return `? ${life}`;
@@ -70,7 +76,7 @@ function mark(row) {
70
76
  return "?";
71
77
  return row.state.status === "ok" ? "✓" : "!";
72
78
  }
73
- return "·";
79
+ return "│";
74
80
  }
75
81
  function rowText(row) {
76
82
  if (row.kind === "said" || row.kind === "thought" || row.kind === "note" || row.kind === "call" || row.kind === "tell") {
@@ -160,47 +166,72 @@ function taskDispositionMark(disposition) {
160
166
  return "●";
161
167
  return disposition === "blocked" ? "‖" : "○";
162
168
  }
163
- function taskScanLine(row, indent = "") {
164
- return `${indent}${taskDispositionMark(row.disposition)} ${row.id} · P${row.priority} ${row.disposition}`;
165
- }
166
- function changeOp(op) {
167
- return op === "add" ? "write" : op === "delete" ? "delete" : "edit";
168
- }
169
- function renderReportedChangeLines(snapshot, columns) {
170
- const lines = [` changes ${snapshot.reportedChanges.length + snapshot.reportedChangesOmitted}`];
171
- for (const change of snapshot.reportedChanges) {
172
- const scan = ` ${changeOp(change.op)}`;
173
- const path = safeText(change.path);
174
- const stat = change.diffstat === undefined
175
- ? ""
176
- : ` — +${change.diffstat.added} -${change.diffstat.removed}`;
177
- const inline = `${scan} ${path}${stat}`;
178
- if (displayColumns(inline) <= columns)
179
- lines.push(inline);
180
- else
181
- lines.push(`${scan}`, ...renderTextBlock(`${path}${stat}`, " ", columns));
169
+ function changeStat(group) {
170
+ let added = 0;
171
+ let removed = 0;
172
+ for (const change of group) {
173
+ if (change.diffstat === undefined)
174
+ return "+? -?";
175
+ added += change.diffstat.added;
176
+ removed += change.diffstat.removed;
182
177
  }
183
- if (snapshot.reportedChangesOmitted > 0) {
184
- lines.push(` ⋮ ${snapshot.reportedChangesOmitted} earlier changes`);
178
+ return `+${added} -${removed}`;
179
+ }
180
+ function groupedChangeRows(changes) {
181
+ const groups = [];
182
+ const seen = new Map();
183
+ for (const change of changes) {
184
+ const index = seen.get(change.path);
185
+ if (index === undefined) {
186
+ seen.set(change.path, groups.length);
187
+ groups.push([change]);
188
+ }
189
+ else
190
+ groups[index].push(change);
185
191
  }
186
- return lines;
192
+ return groups.map((group) => ({ path: group[0].path, stat: changeStat(group) }));
193
+ }
194
+ function renderReportedChangeLines(snapshot) {
195
+ const rows = groupedChangeRows(snapshot.reportedChanges);
196
+ const width = rows.reduce((max, row) => Math.max(max, row.stat.length), 0);
197
+ return [
198
+ `changes ${snapshot.reportedChanges.length + snapshot.reportedChangesOmitted}`,
199
+ ...rows.map((row) => ` ${row.stat.padEnd(width)} ${safeText(row.path)}`),
200
+ ...(snapshot.reportedChangesOmitted > 0 ? [` ⋮ ${snapshot.reportedChangesOmitted} earlier changes`] : []),
201
+ ];
202
+ }
203
+ function renderTaskRow(row, columns) {
204
+ const prefix = ` ${taskDispositionMark(row.disposition)} ${row.id} · `;
205
+ const body = `${safeText(row.title)} · ${row.disposition} · P${row.priority}`;
206
+ const inline = `${prefix}${body}`;
207
+ if (displayColumns(inline) <= columns)
208
+ return [inline];
209
+ return renderBoundedTextBlock(body, {
210
+ first: prefix,
211
+ continuation: " ",
212
+ columns,
213
+ lines: Number.MAX_SAFE_INTEGER,
214
+ });
187
215
  }
188
216
  function renderTaskContextLines(created, columns) {
189
217
  if (created === undefined)
190
218
  return [];
191
219
  if (created.kind === "failed")
192
220
  return [`! tasks failed ${safeText(created.diagnostic)}`];
193
- const lines = [` tasks ${created.rows.length}`];
194
- for (const row of created.rows) {
195
- const scan = ` ${taskScanLine(row)}`;
196
- const title = safeText(row.title);
197
- const inline = `${scan} — ${title}`;
198
- if (displayColumns(inline) <= columns)
199
- lines.push(inline);
200
- else
201
- lines.push(`${scan} —`, ...renderTextBlock(title, " ", columns));
202
- }
203
- return lines;
221
+ return [`tasks ${created.rows.length}`, ...created.rows.flatMap((row) => renderTaskRow(row, columns))];
222
+ }
223
+ function answerContextLines(observation, columns) {
224
+ return [
225
+ ...renderTaskContextLines(observation.createdTasks, columns),
226
+ ...renderReportedChangeLines(observation.status.timeline),
227
+ ];
228
+ }
229
+ function answeredBlock(observation, answer, alias, columns) {
230
+ const context = answerContextLines(observation, columns).join("\n");
231
+ if (context.length === 0)
232
+ return `${answeredHeading(observation.status.id, alias, columns)}\n${answer}`;
233
+ const separator = answer.endsWith("\n") ? "\n" : "\n\n";
234
+ return `${answeredHeading(observation.status.id, alias, columns)}\n${answer}${separator}${context}`;
204
235
  }
205
236
  function snapshotText(view, context, options = {}) {
206
237
  const snapshot = view.status.timeline;
@@ -215,14 +246,17 @@ function snapshotText(view, context, options = {}) {
215
246
  ...contractFacts(view.contract),
216
247
  ...(options.facts ?? []),
217
248
  ];
218
- const footer = options.showLife === false ? [] : [` ${lifeLabel(view.status.life)}`];
249
+ const taskContext = renderTaskContextLines(view.createdTasks, context.columns);
250
+ const changes = renderReportedChangeLines(snapshot);
251
+ const footer = options.showLife === false ? [] : ["", lifeLabel(view.status.life)];
219
252
  return [
220
- ...snapshotHeading(view.status.id, options.alias, view.contract),
253
+ ...snapshotHeading(view.status.id, options.alias, view.contract, context.columns),
221
254
  ...facts,
222
255
  ...activity,
256
+ ...(activity.length > 0 && taskContext.length > 0 ? [""] : []),
257
+ ...taskContext,
258
+ ...changes,
223
259
  ...footer,
224
- ...renderReportedChangeLines(snapshot, context.columns),
225
- ...renderTaskContextLines(view.createdTasks, context.columns),
226
260
  ].join("\n");
227
261
  }
228
262
  function observationStageText(id, observation, context, options = {}) {
@@ -232,18 +266,27 @@ function observationStageText(id, observation, context, options = {}) {
232
266
  }
233
267
  function waitText(result, context) {
234
268
  const alias = result.alias;
269
+ const total = result.result.observations.length + result.result.unobserved.length;
270
+ const done = result.result.observations.filter((observation) => observation.status.life !== "running").length;
235
271
  const blocks = [
236
- ...result.result.observations.map((observation) => snapshotText(observation, context, { ...(alias === undefined ? {} : { alias }) })),
272
+ ...result.result.observations.map((observation) => {
273
+ const answer = statusAnswer(observation);
274
+ if (answer !== undefined)
275
+ return answeredBlock(observation, answer, alias, context.columns);
276
+ return snapshotText(observation, context, { ...(alias === undefined ? {} : { alias }) });
277
+ }),
237
278
  ...result.result.unobserved.map((member) => unobservedText(member.id, member.diagnostic)),
238
279
  ];
239
- return blocks.join("\n\n");
280
+ if (total <= 1)
281
+ return blocks.join("\n\n");
282
+ return [...blocks, `${done} of ${total} done`].join("\n\n");
240
283
  }
241
284
  function historyText(command, result, context) {
242
285
  if (command.last)
243
286
  return result.mode === "last" ? result.answer : "no answer retained";
244
287
  if (result.mode !== "page")
245
288
  throw new Error("history result lacks page");
246
- return [...snapshotHeading(result.akuma, result.alias, result.historyResult.contract), ...groupedRows(result.history.rows, context, true)].join("\n");
289
+ return [...snapshotHeading(result.akuma, result.alias, result.historyResult.contract, context.columns), ...groupedRows(result.history.rows, context, true)].join("\n");
247
290
  }
248
291
  function tellText(result, context) {
249
292
  const facts = typeof result.result.tell.wake === "string" ? [] : [`! error ${safeText(result.result.tell.wake.diagnostic)}`];
@@ -291,6 +334,9 @@ export function akumaRawAnswer(result) {
291
334
  function executionCwdLine(result) {
292
335
  return result.execution.source === "contract-worktree" ? [`cwd ${result.execution.cwd}`] : [];
293
336
  }
337
+ function posixShellArgument(value) {
338
+ return `'${value.replace(/'/g, `'\"'\"'`)}'`;
339
+ }
294
340
  function callText(result, context) {
295
341
  const alias = result.result.alias.kind === "aliased" ? result.result.alias.alias.alias : undefined;
296
342
  const contractId = result.result.dispatch.kind === "dispatched" ? result.result.dispatch.dispatch.contractId : undefined;
@@ -302,17 +348,17 @@ function callText(result, context) {
302
348
  if (result.result.observation.kind === "detached") {
303
349
  const lines = [...snapshotHeading(result.result.akuma, alias, contractId === undefined
304
350
  ? { kind: "none" }
305
- : { kind: "associated", contractId }), ...cwd, ...restraint, ...facts];
351
+ : { kind: "associated", contractId }, context.columns), ...cwd, ...restraint, ...facts];
306
352
  if (!callFailed(result.result)) {
307
353
  const selector = result.result.alias.kind === "aliased" ? result.result.alias.alias.alias : result.result.akuma;
308
- lines.push(`$ keiyaku -C ${result.world} wait ${selector} --timeout 5m`);
354
+ lines.push(`$ keiyaku -C ${posixShellArgument(result.world)} wait ${selector} --timeout 5m`);
309
355
  }
310
356
  return lines.join("\n");
311
357
  }
312
358
  if (result.result.observation.kind === "failed") {
313
359
  return [...snapshotHeading(result.result.akuma, alias, contractId === undefined
314
360
  ? { kind: "none" }
315
- : { kind: "associated", contractId }), ...cwd, ...restraint, ...facts, `! error ${safeText(result.result.observation.failure.diagnostic)}`].join("\n");
361
+ : { kind: "associated", contractId }, context.columns), ...cwd, ...restraint, ...facts, `! error ${safeText(result.result.observation.failure.diagnostic)}`].join("\n");
316
362
  }
317
363
  return snapshotText({
318
364
  status: result.result.observation.status,
@@ -12,5 +12,9 @@ export function renderCatalogText(catalog) {
12
12
  ...(row.description === undefined ? [] : [` ${safeText(row.description)}`]),
13
13
  ]).join("\n");
14
14
  }
15
- return catalog.rows.map((row) => `${safeText(row.id)} - ${row.life}`).join("\n");
15
+ return catalog.rows.map((row) => {
16
+ if (!("lifeAt" in row))
17
+ return `${safeText(row.id)} - ${row.life}`;
18
+ return `${safeText(row.id)} - ${row.life} - runtime ${row.lifeAt ?? "-"} - last activity ${row.lastActivityAt ?? "-"}`;
19
+ }).join("\n");
16
20
  }
@@ -1,5 +1,5 @@
1
1
  import { renderAcceptedAudit } from "./audit.js";
2
- import { appendHookPayload, cleanupLines, hookFailureSummary, leakLines, outcomeLines, receiptPayload, receiptRow, reuseLines, stopLines, titleLines, } from "./receipt.js";
2
+ import { appendHookPayload, cleanupLines, hookFailureSummary, leakLines, outcomeLines, receiptPayload, receiptRow, reuseLines, stopLines, stopSummary, titleLines, } from "./receipt.js";
3
3
  import { gitShortStat, renderOpaqueBlock } from "./terminal.js";
4
4
  const HANG = " ";
5
5
  function isRecord(value) {
@@ -113,11 +113,20 @@ function acceptedRecord(result, columns) {
113
113
  const record = [];
114
114
  for (const fact of result.facts) {
115
115
  const contract = fact.contract === result.contract ? [] : [{ text: fact.contract, opaque: true }];
116
- receiptRow(record, " ", "journal", [
117
- ...contract,
118
- { text: fact.entry, opaque: true },
119
- { text: `· ${fact.kind}` },
120
- ], columns);
116
+ receiptRow(record, " ", "journal", fact.kind === "reintegrated"
117
+ ? [
118
+ ...contract,
119
+ { text: fact.entry, opaque: true },
120
+ { text: "· reintegrated" },
121
+ { text: fact.data.predecessor, opaque: true },
122
+ { text: "->" },
123
+ { text: fact.data.snapshot, opaque: true },
124
+ ]
125
+ : [
126
+ ...contract,
127
+ { text: fact.entry, opaque: true },
128
+ { text: `· ${fact.kind}` },
129
+ ], columns);
121
130
  }
122
131
  receiptRow(record, " ", "head", [{ text: result.head, opaque: true }], columns);
123
132
  if (result.verb === "deliver") {
@@ -176,11 +185,58 @@ function recordBlock(result, columns) {
176
185
  const rows = [...acceptedRecord(result, columns), ...acceptedLagRows(result, columns)];
177
186
  return [" record", ...indentRecord(rows)];
178
187
  }
179
- function verificationVerdictLine(verdict, columns) {
188
+ function completionLines(result, columns) {
180
189
  const lines = [];
181
- receiptRow(lines, verdict === "satisfied" ? "✓" : "!", "verification", [
182
- { text: verdict === "satisfied" ? "passed" : "failed" },
190
+ const completion = result.completion;
191
+ if (completion === undefined)
192
+ return lines;
193
+ const verification = completion.verification;
194
+ receiptRow(lines, " ", "target", [
195
+ { text: "->" },
196
+ { text: completion.integration, opaque: true },
197
+ ...(verification?.verdict === "satisfied"
198
+ ? [{ text: `· verified (${verification.mode})` }]
199
+ : []),
183
200
  ], columns);
201
+ if (verification?.verdict === "unsatisfied") {
202
+ receiptRow(lines, "!", "verification", [
203
+ { text: "unsatisfied" },
204
+ { text: `(${verification.mode})` },
205
+ ], columns);
206
+ if (result.verificationSummary !== undefined) {
207
+ receiptPayload(lines, " summary", result.verificationSummary);
208
+ }
209
+ }
210
+ return lines;
211
+ }
212
+ function movementLines(result, columns) {
213
+ const count = result.facts.filter((fact) => fact.kind === "reintegrated").length;
214
+ if (count === 0)
215
+ return [];
216
+ const lines = [];
217
+ receiptRow(lines, "~", "target", [{ text: `moved · re-integrated x${count}` }], columns);
218
+ return lines;
219
+ }
220
+ function continuationLines(result, columns) {
221
+ const report = result.continuation;
222
+ if (report === undefined)
223
+ return [];
224
+ const lines = [];
225
+ for (const contractId of report.claimed) {
226
+ receiptRow(lines, "✓", "continuation", [
227
+ { text: "complete" },
228
+ { text: contractId, opaque: true },
229
+ ], columns);
230
+ }
231
+ for (const { contractId, stop } of report.stopped) {
232
+ const detail = "kind" in stop ? [{ text: stop.kind }] : stopSummary(stop, contractId);
233
+ receiptRow(lines, "!", "continuation", [
234
+ { text: "blocked" },
235
+ { text: contractId, opaque: true },
236
+ { text: "·" },
237
+ ...detail,
238
+ ], columns);
239
+ }
184
240
  return lines;
185
241
  }
186
242
  function renderAcceptedBind(result, columns) {
@@ -201,51 +257,19 @@ function renderAcceptedAmend(result, columns) {
201
257
  lines.push(...acceptedDeviations(result, columns), ...recordBlock(result, columns));
202
258
  return lines.join("\n");
203
259
  }
204
- function reintegrationLines(result, complete, columns) {
205
- const reintegrated = result.facts.filter((fact) => fact.kind === "reintegrated");
206
- if (reintegrated.length === 0)
207
- return [];
208
- const lines = [];
209
- for (const fact of reintegrated) {
210
- receiptRow(lines, " ", "re-integrated", [
211
- { text: fact.data.predecessor, opaque: true },
212
- { text: "->" },
213
- { text: fact.data.snapshot, opaque: true },
214
- ], columns);
215
- }
216
- if (result.verificationVerdict !== undefined) {
217
- receiptRow(lines, " ", "verification re-run", [
218
- { text: "·" },
219
- { text: result.verificationVerdict },
220
- ], columns);
221
- }
222
- if (complete) {
223
- receiptRow(lines, " ", "target", [
224
- { text: "->" },
225
- { text: reintegrated.at(-1).data.snapshot, opaque: true },
226
- ], columns);
227
- }
228
- return lines;
229
- }
230
260
  function renderAcceptedDeliver(result, columns) {
231
- const complete = result.facts.some((fact) => fact.kind === "claimed");
261
+ const complete = result.completion !== undefined;
232
262
  const title = complete ? "delivered" : "deliver — not complete";
233
263
  const lines = titleLines("✓", title, result.contract, columns);
234
- lines.push(...reintegrationLines(result, complete, columns));
264
+ lines.push(...movementLines(result, columns), ...completionLines(result, columns), ...continuationLines(result, columns));
235
265
  if (!complete)
236
266
  receiptRow(lines, " ", "candidate", [{ text: "kept" }], columns);
237
267
  if (result.verification !== undefined) {
238
268
  lines.push(...stopLines("verification", result.verification, columns, result.contract));
239
269
  }
240
- else if (result.verificationVerdict !== undefined) {
241
- lines.push(...verificationVerdictLine(result.verificationVerdict, columns));
242
- }
243
270
  if (!complete && result.placement !== undefined) {
244
271
  lines.push(...stopLines("completion", result.placement, columns, result.contract));
245
272
  }
246
- else if (!complete && (result.verification !== undefined || result.verificationVerdict === "unsatisfied")) {
247
- receiptRow(lines, "·", "completion", [{ text: "not attempted" }], columns);
248
- }
249
273
  if (result.cleanup !== undefined)
250
274
  pushBlock(lines, cleanupLines(result.cleanup, columns));
251
275
  if (result.leak !== undefined)
@@ -254,12 +278,11 @@ function renderAcceptedDeliver(result, columns) {
254
278
  return lines.join("\n");
255
279
  }
256
280
  function renderAcceptedReview(result, columns) {
257
- const complete = result.facts.some((fact) => fact.kind === "claimed");
258
- const lines = titleLines("✓", `review ${result.verdict}`, result.contract, columns);
259
- lines.push(...reintegrationLines(result, complete, columns));
260
- receiptRow(lines, complete ? "✓" : "!", "contract", [
261
- { text: complete ? "complete" : "not complete" },
262
- ], columns);
281
+ const complete = result.completion !== undefined;
282
+ const lines = titleLines("✓", `review ${result.verdict} — ${complete ? "complete" : "not complete"}`, result.contract, columns);
283
+ lines.push(...movementLines(result, columns), ...completionLines(result, columns), ...continuationLines(result, columns));
284
+ if (!complete)
285
+ receiptRow(lines, " ", "candidate", [{ text: "kept" }], columns);
263
286
  if (result.placement !== undefined) {
264
287
  lines.push(...stopLines("completion", result.placement, columns, result.contract));
265
288
  }