@astrosheep/keiyaku 4.5.20 → 4.5.21

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 (154) hide show
  1. package/README.md +4 -4
  2. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -3
  3. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +22 -8
  4. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/references/automation.md +237 -0
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +6 -7
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +2 -3
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +3 -3
  8. package/build/src/akuma/akuma-handle.d.ts +5 -1
  9. package/build/src/akuma/akuma-handle.js +4 -1
  10. package/build/src/akuma/akuma-instance.d.ts +7 -2
  11. package/build/src/akuma/akuma-instance.js +14 -7
  12. package/build/src/akuma/akuma-observe.d.ts +11 -0
  13. package/build/src/akuma/akuma-observe.js +22 -0
  14. package/build/src/akuma/akuma-product.d.ts +1 -0
  15. package/build/src/akuma/akuma-product.js +3 -0
  16. package/build/src/akuma/akuma.d.ts +1 -0
  17. package/build/src/akuma/body.d.ts +25 -5
  18. package/build/src/akuma/body.js +65 -8
  19. package/build/src/akuma/call-request.d.ts +2 -0
  20. package/build/src/akuma/call-request.js +2 -0
  21. package/build/src/akuma/fleet-execution.d.ts +28 -0
  22. package/build/src/akuma/fleet-execution.js +36 -4
  23. package/build/src/akuma/fleet-request.d.ts +10 -0
  24. package/build/src/akuma/fleet-request.js +11 -2
  25. package/build/src/akuma/heart/facts.d.ts +2 -0
  26. package/build/src/akuma/heart/index.d.ts +1 -0
  27. package/build/src/akuma/heart/index.js +4 -1
  28. package/build/src/akuma/heart/rows.d.ts +2 -0
  29. package/build/src/akuma/heart/rows.js +7 -5
  30. package/build/src/akuma/heart/schema.d.ts +1 -1
  31. package/build/src/akuma/heart/schema.js +3 -1
  32. package/build/src/akuma/heart/tells.js +6 -5
  33. package/build/src/akuma/heart/timeline.js +1 -1
  34. package/build/src/akuma/provider.d.ts +1 -1
  35. package/build/src/akuma/provider.js +1 -1
  36. package/build/src/akuma/providers/acp/core.js +3 -6
  37. package/build/src/akuma/providers/claude/index.js +9 -12
  38. package/build/src/akuma/providers/codex-app-server/index.js +4 -11
  39. package/build/src/akuma/providers/execution-environment.d.ts +6 -0
  40. package/build/src/akuma/providers/execution-environment.js +24 -0
  41. package/build/src/akuma/providers/opencode-sdk/index.js +30 -23
  42. package/build/src/akuma/providers/opencode-sdk/session.d.ts +8 -1
  43. package/build/src/akuma/providers/opencode-sdk/session.js +6 -5
  44. package/build/src/akuma/providers/pi/events.js +7 -2
  45. package/build/src/akuma/providers/pi/index.js +3 -2
  46. package/build/src/akuma/request-rendezvous.js +8 -3
  47. package/build/src/akuma/turn-drive.d.ts +1 -0
  48. package/build/src/akuma/turn-drive.js +2 -0
  49. package/build/src/akuma-body.js +2 -0
  50. package/build/src/body/amend.js +6 -4
  51. package/build/src/body/decode.d.ts +3 -1
  52. package/build/src/body/decode.js +53 -17
  53. package/build/src/body/region.d.ts +9 -1
  54. package/build/src/body/region.js +87 -12
  55. package/build/src/body/render.js +1 -1
  56. package/build/src/body/verification.d.ts +3 -1
  57. package/build/src/body/verification.js +4 -1
  58. package/build/src/cli/accepted.js +19 -2
  59. package/build/src/cli/commands/akuma-invoke.d.ts +2 -0
  60. package/build/src/cli/commands/akuma-invoke.js +133 -13
  61. package/build/src/cli/commands/akuma.js +11 -4
  62. package/build/src/cli/commands/contract-help.js +17 -4
  63. package/build/src/cli/commands/contract.js +6 -4
  64. package/build/src/cli/commands/status-set.js +15 -24
  65. package/build/src/cli/main.js +5 -0
  66. package/build/src/cli/parse.d.ts +2 -0
  67. package/build/src/cli/parse.js +5 -0
  68. package/build/src/cli/render/akuma-activity.d.ts +99 -2
  69. package/build/src/cli/render/akuma-activity.js +484 -40
  70. package/build/src/cli/render/akuma.d.ts +10 -0
  71. package/build/src/cli/render/akuma.js +28 -21
  72. package/build/src/cli/render/audit.js +1 -1
  73. package/build/src/cli/render/board.js +1 -1
  74. package/build/src/cli/render/catalog.js +16 -13
  75. package/build/src/cli/render/contract-history.js +39 -10
  76. package/build/src/cli/render/contract-observation.js +10 -6
  77. package/build/src/cli/render/contract.js +109 -106
  78. package/build/src/cli/render/execution-progress.d.ts +23 -1
  79. package/build/src/cli/render/execution-progress.js +151 -28
  80. package/build/src/cli/render/kanshi-akuma.js +27 -39
  81. package/build/src/cli/render/kanshi.js +33 -26
  82. package/build/src/cli/render/nuke.js +1 -1
  83. package/build/src/cli/render/receipt.d.ts +1 -1
  84. package/build/src/cli/render/receipt.js +5 -5
  85. package/build/src/cli/render/refusal.js +10 -7
  86. package/build/src/cli/render/region.js +1 -1
  87. package/build/src/cli/render/settings.js +12 -2
  88. package/build/src/cli/render/status-line.d.ts +30 -0
  89. package/build/src/cli/render/status-line.js +58 -0
  90. package/build/src/cli/render/status-set.js +1 -1
  91. package/build/src/cli/render/task.d.ts +2 -0
  92. package/build/src/cli/render/task.js +11 -7
  93. package/build/src/cli/render/terminal.d.ts +0 -1
  94. package/build/src/cli/render/terminal.js +47 -15
  95. package/build/src/cli/result.d.ts +1 -0
  96. package/build/src/cli/runtime.d.ts +2 -0
  97. package/build/src/cli/runtime.js +13 -7
  98. package/build/src/cli/usage.js +2 -2
  99. package/build/src/cli/version.d.ts +1 -0
  100. package/build/src/cli/version.js +25 -0
  101. package/build/src/git/workspace.d.ts +1 -1
  102. package/build/src/git/workspace.js +1 -1
  103. package/build/src/identity/mint.d.ts +13 -0
  104. package/build/src/identity/mint.js +26 -0
  105. package/build/src/identity/normalize.d.ts +4 -0
  106. package/build/src/identity/normalize.js +30 -0
  107. package/build/src/index.d.ts +2 -0
  108. package/build/src/index.js +1 -0
  109. package/build/src/kanshi/read.js +17 -4
  110. package/build/src/kanshi/report.d.ts +1 -0
  111. package/build/src/library/akuma-creation.d.ts +2 -0
  112. package/build/src/library/akuma-creation.js +33 -17
  113. package/build/src/library/bind.js +18 -16
  114. package/build/src/library/contract-bind.d.ts +3 -1
  115. package/build/src/library/contract-bind.js +5 -3
  116. package/build/src/library/contract-operations.d.ts +2 -2
  117. package/build/src/library/contract-operations.js +1 -1
  118. package/build/src/library/contract-types.d.ts +1 -0
  119. package/build/src/library/fleet.d.ts +4 -1
  120. package/build/src/library/fleet.js +32 -8
  121. package/build/src/plugin/akuma-signals.d.ts +6 -0
  122. package/build/src/plugin/akuma-signals.js +8 -0
  123. package/build/src/plugin/public.d.ts +4 -0
  124. package/build/src/plugin/runtime.js +4 -1
  125. package/build/src/protocol/amend.js +4 -3
  126. package/build/src/protocol/attempt.d.ts +2 -0
  127. package/build/src/protocol/bind.js +41 -14
  128. package/build/src/protocol/completion.d.ts +3 -0
  129. package/build/src/protocol/completion.js +7 -1
  130. package/build/src/protocol/deliver.js +165 -92
  131. package/build/src/protocol/execution-observation.d.ts +0 -4
  132. package/build/src/protocol/intent.d.ts +15 -20
  133. package/build/src/protocol/intent.js +29 -17
  134. package/build/src/protocol/operations.d.ts +2 -0
  135. package/build/src/protocol/placement.js +5 -1
  136. package/build/src/protocol/read/status.d.ts +1 -0
  137. package/build/src/protocol/read/status.js +10 -2
  138. package/build/src/protocol/reintegrate.d.ts +1 -1
  139. package/build/src/protocol/reintegrate.js +70 -43
  140. package/build/src/protocol/result-codec.js +9 -7
  141. package/build/src/protocol/review.js +53 -30
  142. package/build/src/protocol/run.d.ts +83 -36
  143. package/build/src/protocol/run.js +90 -75
  144. package/build/src/runtime/proc/windows-launch.exe +0 -0
  145. package/build/src/settings.js +5 -14
  146. package/build/src/task/document.d.ts +5 -1
  147. package/build/src/task/document.js +6 -1
  148. package/build/src/verification/execution.d.ts +0 -1
  149. package/build/src/verification/execution.js +0 -24
  150. package/build/src/verification/observation.d.ts +0 -4
  151. package/build/src/verification/observation.js +1 -2
  152. package/package.json +3 -1
  153. package/build/src/git/verification-environment.d.ts +0 -10
  154. package/build/src/git/verification-environment.js +0 -281
@@ -1,6 +1,6 @@
1
1
  import { renderAcceptedAudit } from "./audit.js";
2
2
  import { appendHookPayload, executionCleanupLines, executionStopLines, hookFailureSummary, outcomeLines, receiptPayload, receiptRow, reuseLines, stopLines, titleLines, } from "./receipt.js";
3
- import { gitShortStat, renderOpaqueBlock } from "./terminal.js";
3
+ import { renderOpaqueBlock, safeText, tone } from "./terminal.js";
4
4
  const HANG = " ";
5
5
  function isRecord(value) {
6
6
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -14,23 +14,19 @@ function retryLines(detail, indent, columns) {
14
14
  function lagRows(lag, columns) {
15
15
  const lines = [];
16
16
  if (lag.kind === "worktree-retained") {
17
- receiptRow(lines, "!", "lag", [{ text: `worktree-retained ${lag.path}`, opaque: true }], columns);
17
+ receiptRow(lines, "!", "lag", [{ text: "worktree retained" }, { text: lag.path, opaque: true }], columns);
18
18
  }
19
19
  else if (lag.kind === "worktree-follow-retained") {
20
20
  receiptRow(lines, "!", "lag", [
21
- {
22
- text: `${lag.kind} reason ${lag.reason} tender ${lag.tender} head ${lag.head}${lag.paths === undefined || lag.paths.length === 0 ? "" : ` paths ${lag.paths.join(",")}`} path ${lag.path}`,
23
- opaque: true,
24
- },
21
+ { text: "worktree follow retained" },
22
+ { text: "·" },
23
+ { text: lag.reason.replaceAll("-", " ") },
24
+ { text: "·" },
25
+ { text: lag.path, opaque: true },
25
26
  ], columns);
26
27
  }
27
28
  else if (lag.kind === "unsealed-bytes") {
28
- receiptRow(lines, "!", "lag", [
29
- {
30
- text: `unsealed-bytes ${lag.path}${lag.head === undefined ? "" : ` head ${lag.head}`}${lag.paths.length === 0 ? "" : ` paths ${lag.paths.join(",")}`}`,
31
- opaque: true,
32
- },
33
- ], columns);
29
+ receiptRow(lines, "!", "lag", [{ text: "unsealed bytes" }, { text: lag.path, opaque: true }], columns);
34
30
  }
35
31
  else if (lag.kind === "target-checkout-retained") {
36
32
  receiptRow(lines, "!", "lag", [{ text: `target-checkout-retained ${lag.target} ${lag.path}`, opaque: true }], columns);
@@ -57,39 +53,43 @@ function lagRows(lag, columns) {
57
53
  }
58
54
  function settlementLagRows(lag, columns) {
59
55
  const lines = [];
60
- receiptRow(lines, "!", "settlement", [
61
- {
62
- text: [
63
- `surface ${lag.surface}`,
64
- lag.taskId === undefined ? undefined : `task ${lag.taskId}`,
65
- lag.path === undefined ? undefined : `path ${lag.path}`,
66
- ]
67
- .filter((part) => part !== undefined)
68
- .join(" "),
69
- opaque: true,
70
- },
71
- ], columns);
56
+ receiptRow(lines, "!", "settlement", [{ text: lag.surface.replaceAll("-", " ") }], columns);
57
+ if (lag.taskId !== undefined)
58
+ receiptRow(lines, " ", "task", [{ text: lag.taskId, opaque: true }], columns);
59
+ if (lag.path !== undefined)
60
+ receiptRow(lines, " ", "path", [{ text: lag.path, opaque: true }], columns);
72
61
  receiptPayload(lines, "diagnostic", lag.diagnostic);
73
62
  return lines;
74
63
  }
75
- function workspaceRows(workspace, columns) {
76
- const lines = [];
77
- receiptRow(lines, " ", "workspace", [{ text: gitShortStat(workspace.shortStat) }], columns);
78
- for (const name of ["staged", "unstaged", "untracked"]) {
79
- for (const path of workspace[name]) {
80
- receiptRow(lines, " ", name, [{ text: path, opaque: true }], columns);
64
+ function overlapRows(overlaps, color) {
65
+ const groups = new Map();
66
+ for (const overlap of overlaps) {
67
+ let patterns = groups.get(overlap.contract);
68
+ if (patterns === undefined) {
69
+ patterns = new Map();
70
+ groups.set(overlap.contract, patterns);
81
71
  }
72
+ for (const pattern of overlap.patterns)
73
+ patterns.set(JSON.stringify([pattern.mine, pattern.theirs]), pattern);
82
74
  }
83
- return lines;
84
- }
85
- function overlapRows(overlaps, columns) {
86
- const lines = [];
87
- for (const overlap of overlaps) {
88
- for (const pattern of overlap.patterns) {
89
- receiptRow(lines, " ", "overlap", [
90
- { text: overlap.contract, opaque: true },
91
- { text: `${pattern.mine} ~ ${pattern.theirs}`, opaque: true },
92
- ], columns);
75
+ if (groups.size === 0)
76
+ return [];
77
+ const lines = ["", ` ${tone("overlap", "dim", color)}`];
78
+ for (const [contract, patterns] of groups) {
79
+ if (lines.length > 2)
80
+ lines.push("");
81
+ const identity = safeText(contract);
82
+ lines.push(` ${tone("└─", "dim", color)} ${color ? `\u001b[1m${identity}\u001b[0m` : identity}`);
83
+ let first = true;
84
+ for (const { mine, theirs } of patterns.values()) {
85
+ if (mine === theirs)
86
+ lines.push(` ${safeText(mine)}`);
87
+ else {
88
+ if (!first)
89
+ lines.push("");
90
+ lines.push(` this ${safeText(mine)}`, ` other ${safeText(theirs)}`);
91
+ }
92
+ first = false;
93
93
  }
94
94
  }
95
95
  return lines;
@@ -101,19 +101,6 @@ function pushBlock(lines, block) {
101
101
  }
102
102
  function acceptedRecord(result, columns) {
103
103
  const record = [];
104
- for (const fact of result.facts) {
105
- const contract = fact.contract === result.contract ? [] : [{ text: fact.contract, opaque: true }];
106
- receiptRow(record, " ", "journal", fact.kind === "reintegrated"
107
- ? [
108
- ...contract,
109
- { text: fact.entry, opaque: true },
110
- { text: "· reintegrated" },
111
- { text: fact.data.predecessor, opaque: true },
112
- { text: "->" },
113
- { text: fact.data.snapshot, opaque: true },
114
- ]
115
- : [...contract, { text: fact.entry, opaque: true }, { text: `· ${fact.kind}` }], columns);
116
- }
117
104
  if (result.recoverySnapshot !== undefined)
118
105
  receiptRow(record, " ", "recovery snapshot", [{ text: result.recoverySnapshot, opaque: true }], columns);
119
106
  if (result.verb === "deliver") {
@@ -134,25 +121,10 @@ function acceptedLagRows(result, columns) {
134
121
  pushBlock(obligations, executionStopLines(result.executionStops ?? [], columns));
135
122
  return obligations;
136
123
  }
137
- function indentRecord(lines) {
138
- let payload = false;
139
- return lines.map((line) => {
140
- if (line.length === 0) {
141
- payload = !payload;
142
- return line;
143
- }
144
- return payload ? line : ` ${line}`;
145
- });
146
- }
147
- function acceptedDeviations(result, columns) {
124
+ function acceptedDeviations(result, columns, color) {
148
125
  const deviations = [];
149
- if (result.verb === "review") {
150
- if (result.workspace !== undefined)
151
- pushBlock(deviations, workspaceRows(result.workspace, columns));
152
- return deviations;
153
- }
154
126
  if (result.overlaps !== undefined)
155
- pushBlock(deviations, overlapRows(result.overlaps, columns));
127
+ pushBlock(deviations, overlapRows(result.overlaps, color));
156
128
  if (result.overlapFailure !== undefined) {
157
129
  receiptRow(deviations, "!", "overlap", [{ text: "unavailable" }], columns);
158
130
  receiptPayload(deviations, "diagnostic", result.overlapFailure);
@@ -161,29 +133,57 @@ function acceptedDeviations(result, columns) {
161
133
  }
162
134
  function recordBlock(result, columns) {
163
135
  const rows = [...acceptedRecord(result, columns), ...acceptedLagRows(result, columns)];
164
- return [" record", ...indentRecord(rows)];
136
+ return rows;
165
137
  }
166
- function completionLines(result, columns) {
138
+ function nonGatingVerificationLines(result, columns) {
167
139
  const lines = [];
140
+ const verification = result.completion?.verification;
141
+ if (verification === undefined || verification.verdict !== "unsatisfied")
142
+ return lines;
143
+ receiptRow(lines, "!", "verification", [{ text: "unsatisfied" }, { text: `(${verification.mode})` }, { text: "· not required by Contract gates" }], columns);
144
+ if (result.verificationSummary !== undefined) {
145
+ receiptPayload(lines, " summary", result.verificationSummary);
146
+ }
147
+ return lines;
148
+ }
149
+ function shortGitId(value) {
150
+ return /^[0-9a-f]{40}$/iu.test(value) ? value.slice(0, 7) : value;
151
+ }
152
+ /**
153
+ * Shared presentation of a completed placement, so a deliver and a review that placed the same candidate read
154
+ * identically: one git-shaped target movement row, naming the reference it advanced, then the final lifecycle
155
+ * state. Satisfied Verification adds one fact row naming the commit the verdict ran against; placement made that
156
+ * commit the reference's new head, so the two rows share one sha. Verification mode words stay in JSON and
157
+ * `history`, never in ordinary receipt text. A completion without an advanced reference states no movement.
158
+ * Journal ULIDs stay in JSON and `history`, never in ordinary receipt text.
159
+ */
160
+ function completedPlacementLines(result, columns) {
168
161
  const completion = result.completion;
169
162
  if (completion === undefined)
170
- return lines;
171
- const verification = completion.verification;
172
- receiptRow(lines, " ", "target", [
173
- { text: "->" },
174
- { text: completion.integration, opaque: true },
175
- ...(verification !== undefined && "verdict" in verification && verification.verdict === "satisfied"
176
- ? [{ text: `· verified (${verification.mode})` }]
177
- : []),
178
- ], columns);
179
- if (verification !== undefined && "verdict" in verification && verification.verdict === "unsatisfied") {
180
- receiptRow(lines, "!", "verification", [{ text: "unsatisfied" }, { text: `(${verification.mode})` }, { text: "· not required by Contract gates" }], columns);
181
- if (result.verificationSummary !== undefined) {
182
- receiptPayload(lines, " summary", result.verificationSummary);
183
- }
163
+ return [];
164
+ const lines = [];
165
+ if (completion.predecessor !== undefined && completion.target !== undefined) {
166
+ receiptRow(lines, " ", "target", [
167
+ { text: `${shortGitId(completion.predecessor)}..${shortGitId(completion.integration)}` },
168
+ { text: completion.target, opaque: true },
169
+ ], columns);
184
170
  }
171
+ if (completion.verification?.verdict === "satisfied") {
172
+ receiptRow(lines, " ", "verification", [{ text: "satisfied" }, { text: `· on ${shortGitId(completion.integration)}` }], columns);
173
+ }
174
+ lines.push(...nonGatingVerificationLines(result, columns));
175
+ // Placement admission always admits the claim entry beside the movement, so a completed placement is claimed.
176
+ receiptRow(lines, "●", "claimed", [], columns);
185
177
  return lines;
186
178
  }
179
+ /** Evidence handles stay in JSON and history; ordinary receipt text carries only outstanding obligations. */
180
+ function obligationLines(result, columns) {
181
+ const rows = [];
182
+ if (result.recoverySnapshot !== undefined)
183
+ receiptRow(rows, " ", "recovery snapshot", [{ text: result.recoverySnapshot, opaque: true }], columns);
184
+ rows.push(...acceptedLagRows(result, columns));
185
+ return rows;
186
+ }
187
187
  function movementLines(result, columns) {
188
188
  const count = result.facts.filter((fact) => fact.kind === "reintegrated").length;
189
189
  if (count === 0)
@@ -213,7 +213,7 @@ function continuationLines(result, columns) {
213
213
  }
214
214
  return lines;
215
215
  }
216
- function renderAcceptedBind(result, columns) {
216
+ function renderAcceptedBind(result, columns, color) {
217
217
  const lines = titleLines("✓", "bound", result.contract, columns);
218
218
  if (result.workspace !== undefined)
219
219
  receiptRow(lines, " ", "workspace", [{ text: "worktree" }, { text: result.workspace.path, opaque: true }], columns);
@@ -221,27 +221,34 @@ function renderAcceptedBind(result, columns) {
221
221
  receiptRow(lines, " ", "no target", [], columns);
222
222
  else
223
223
  receiptRow(lines, " ", "target", [{ text: result.target, opaque: true }], columns);
224
- lines.push(...acceptedDeviations(result, columns), ...recordBlock(result, columns));
224
+ for (const warning of result.warnings ?? [])
225
+ receiptRow(lines, "!", "region warning", [{ text: warning }], columns);
226
+ lines.push(...acceptedDeviations(result, columns, color), ...recordBlock(result, columns));
225
227
  return lines.join("\n");
226
228
  }
227
- function renderAcceptedAmend(result, columns) {
228
- const lines = titleLines("✓", "terms replaced", result.contract, columns);
229
- receiptPayload(lines, " terms diff", result.diff);
230
- lines.push(...acceptedDeviations(result, columns), ...recordBlock(result, columns));
229
+ function renderAcceptedAmend(result, columns, color) {
230
+ const changed = result.diff.length > 0;
231
+ const lines = titleLines("✓", changed ? "terms replaced" : "terms unchanged", result.contract, columns);
232
+ if (changed)
233
+ receiptPayload(lines, " terms diff", result.diff);
234
+ lines.push(...acceptedDeviations(result, columns, color), ...recordBlock(result, columns));
231
235
  return lines.join("\n");
232
236
  }
233
237
  function renderAcceptedDeliver(result, columns) {
234
238
  const complete = result.completion !== undefined;
235
- const title = complete ? "delivered" : "deliver — not complete";
239
+ const title = complete ? "delivered" : "deliver incomplete";
236
240
  const lines = titleLines("✓", title, result.contract, columns);
237
241
  if (result.leading !== undefined) {
238
- receiptRow(lines, " ", "leading", [{ text: result.leading.kind }, { text: result.leading.fact, opaque: true }], columns);
242
+ receiptRow(lines, " ", "leading", [{ text: result.leading.kind.replaceAll("-", " ") }], columns);
239
243
  }
240
244
  if (result.tenderSnapshot !== undefined)
241
245
  receiptRow(lines, " ", "tender commit", [{ text: result.tenderSnapshot, opaque: true }], columns);
242
246
  if (result.integration !== undefined)
243
247
  receiptRow(lines, " ", "content identity (not commit)", [{ text: result.integration.changeId, opaque: true }], columns);
244
- lines.push(...movementLines(result, columns), ...completionLines(result, columns));
248
+ if (complete)
249
+ lines.push(...completedPlacementLines(result, columns));
250
+ else
251
+ lines.push(...movementLines(result, columns));
245
252
  if (result.verification !== undefined) {
246
253
  lines.push(...stopLines(result.verification, columns, result.contract));
247
254
  }
@@ -251,16 +258,12 @@ function renderAcceptedDeliver(result, columns) {
251
258
  if (!complete)
252
259
  receiptRow(lines, " ", "candidate", [{ text: "kept" }], columns);
253
260
  lines.push(...continuationLines(result, columns));
254
- lines.push(...recordBlock(result, columns));
261
+ lines.push(...(complete ? obligationLines(result, columns) : recordBlock(result, columns)));
255
262
  return lines.join("\n");
256
263
  }
257
264
  function renderAcceptedReview(result, columns) {
258
- const lines = titleLines("✓", `review ${result.verdict} recorded`, result.contract, columns);
259
- lines.push(...movementLines(result, columns), ...completionLines(result, columns));
260
- if (result.completion !== undefined) {
261
- receiptRow(lines, " ", "placement", [{ text: "complete" }], columns);
262
- receiptRow(lines, " ", "integration commit", [{ text: result.completion.integration, opaque: true }], columns);
263
- }
265
+ const lines = titleLines("✓", `review ${result.verdict}`, result.contract, columns);
266
+ lines.push(...completedPlacementLines(result, columns));
264
267
  if (result.verification !== undefined) {
265
268
  lines.push(...stopLines(result.verification, columns, result.contract));
266
269
  }
@@ -268,11 +271,11 @@ function renderAcceptedReview(result, columns) {
268
271
  lines.push(...stopLines(result.placement, columns, result.contract));
269
272
  }
270
273
  lines.push(...continuationLines(result, columns));
271
- lines.push(...acceptedDeviations(result, columns), ...recordBlock(result, columns));
274
+ lines.push(...obligationLines(result, columns));
272
275
  return lines.join("\n");
273
276
  }
274
277
  function renderAcceptedArc(result, columns) {
275
- const lines = titleLines("✓", "chapter recorded", result.contract, columns);
278
+ const lines = titleLines("✓", `entered chapter ${result.chapter.seq}`, result.contract, columns);
276
279
  receiptRow(lines, " ", "chapter", [{ text: String(result.chapter.seq) }, { text: "·" }, { text: result.chapter.title }], columns);
277
280
  lines.push(...recordBlock(result, columns));
278
281
  return lines.join("\n");
@@ -290,9 +293,9 @@ export function renderAccepted(result, context) {
290
293
  case "audit":
291
294
  return renderAcceptedAudit(result, context);
292
295
  case "bind":
293
- return renderAcceptedBind(result, columns);
296
+ return renderAcceptedBind(result, columns, context?.color === true);
294
297
  case "amend":
295
- return renderAcceptedAmend(result, columns);
298
+ return renderAcceptedAmend(result, columns, context?.color === true);
296
299
  case "review":
297
300
  return renderAcceptedReview(result, columns);
298
301
  case "arc":
@@ -1,4 +1,26 @@
1
1
  import type { ExecutionEvent } from "../../library/execution.js";
2
2
  import { type TextRenderContext } from "./terminal.js";
3
- /** Render one witnessed execution event for the CLI's ephemeral stderr stream. */
3
+ import { type StatusLineOptions, type StatusLineStream } from "./status-line.js";
4
+ /** Sparse rendering for consumers that do not own a live stream. */
4
5
  export declare function executionProgressLines(event: ExecutionEvent, context: TextRenderContext): readonly string[];
6
+ export type ExecutionProgressOptions = StatusLineOptions & Readonly<{
7
+ stream: StatusLineStream;
8
+ context: TextRenderContext;
9
+ }>;
10
+ /** Consumes witnessed events into either a live TTY line or sparse text. */
11
+ export declare class ExecutionProgressRenderer {
12
+ private readonly input;
13
+ private readonly status;
14
+ private verificationStartedAt;
15
+ private declarations;
16
+ private declarationFailed;
17
+ private phaseFailed;
18
+ private phaseUnknown;
19
+ constructor(input: ExecutionProgressOptions);
20
+ consume(event: ExecutionEvent): Promise<void>;
21
+ finish(): void;
22
+ private consumeVerification;
23
+ private write;
24
+ private finalMark;
25
+ }
26
+ export declare function renderExecutionProgress(events: AsyncIterable<ExecutionEvent>, input: ExecutionProgressOptions): Promise<void>;
@@ -1,4 +1,5 @@
1
1
  import { renderOpaqueBlock, safeText } from "./terminal.js";
2
+ import { StatusLine } from "./status-line.js";
2
3
  const LIVE_OUTPUT_BYTES = 4 * 1024;
3
4
  function boundedUtf8Prefix(value) {
4
5
  let bytes = 0;
@@ -12,44 +13,166 @@ function boundedUtf8Prefix(value) {
12
13
  }
13
14
  return { text, truncated: false };
14
15
  }
15
- function phaseCoordinates(observation) {
16
- return [
17
- ...(observation.cwd === undefined ? [] : [`cwd=${safeText(observation.cwd)}`]),
18
- ...(observation.source === undefined ? [] : [`source=${safeText(observation.source)}`]),
19
- ...(observation.name === undefined ? [] : [`hook=${safeText(observation.name)}`]),
20
- ...(observation.index === undefined ? [] : [`declaration=${observation.index}/${observation.total ?? "?"}`]),
21
- ];
16
+ function elapsed(ms) {
17
+ const seconds = Math.max(0, Math.floor(ms / 1_000));
18
+ return seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m${seconds % 60}s`;
19
+ }
20
+ function phaseDetail(observation) {
21
+ if (observation.phase === "declaration")
22
+ return `declaration ${observation.index ?? "?"}/${observation.total ?? "?"}`;
23
+ return observation.name === undefined ? observation.phase : `${observation.phase} · ${safeText(observation.name)}`;
24
+ }
25
+ function phaseStartLine(observation) {
26
+ return `● ${[phaseDetail(observation), ...(observation.cwd === undefined ? [] : [safeText(observation.cwd)])].join(" · ")}`;
27
+ }
28
+ function phaseMark(outcome) {
29
+ if (outcome === undefined)
30
+ return "?";
31
+ return outcome === "ok" || outcome === "exit 0" ? "✓" : "×";
22
32
  }
23
- function renderVerification(event, context) {
24
- const observation = event.observation;
25
- const coordinates = phaseCoordinates(observation);
26
- if (observation.kind === "output") {
27
- const output = boundedUtf8Prefix(observation.text);
28
- return [
29
- `• verification ${observation.phase} ${observation.stream}${coordinates.length === 0 ? "" : ` ${coordinates.join(" ")}`}`,
30
- ...renderOpaqueBlock(output.text, " ", context.columns),
31
- ...(output.truncated ? [` [live output truncated at ${LIVE_OUTPUT_BYTES} bytes]`] : []),
32
- ];
33
- }
34
- const details = [
35
- ...coordinates,
36
- ...(observation.outcome === undefined ? [] : [`outcome=${safeText(observation.outcome)}`]),
37
- ...(observation.elapsedMs === undefined ? [] : [`elapsed=${Math.round(observation.elapsedMs)}ms`]),
33
+ function phaseFinishLine(observation) {
34
+ const facts = [
35
+ phaseDetail(observation),
36
+ ...(observation.elapsedMs === undefined ? [] : [elapsed(observation.elapsedMs)]),
38
37
  ];
38
+ if (observation.outcome !== undefined && observation.outcome !== "ok" && observation.outcome !== "exit 0")
39
+ facts.push(safeText(observation.outcome));
40
+ return `${phaseMark(observation.outcome)} ${facts.join(" · ")}`;
41
+ }
42
+ function outputLines(observation, context) {
43
+ const output = boundedUtf8Prefix(observation.text);
39
44
  return [
40
- `• verification ${observation.phase} ${observation.state}${details.length === 0 ? "" : ` ${details.join(" ")}`}`,
45
+ observation.stream,
46
+ ...renderOpaqueBlock(output.text, " ", context.columns),
47
+ ...(output.truncated ? [" [live output truncated]"] : []),
41
48
  ];
42
49
  }
43
- /** Render one witnessed execution event for the CLI's ephemeral stderr stream. */
50
+ /** Sparse rendering for consumers that do not own a live stream. */
44
51
  export function executionProgressLines(event, context) {
45
52
  switch (event.kind) {
46
53
  case "admitted":
47
- return [`• admitted ${event.fact.kind} ${event.contractId}`];
54
+ return [`✓ admitted ${event.fact.kind} · ${event.contractId}`];
48
55
  case "verification":
49
- return renderVerification(event, context);
56
+ return event.observation.kind === "output"
57
+ ? outputLines(event.observation, context)
58
+ : [
59
+ event.observation.state === "started"
60
+ ? phaseStartLine(event.observation)
61
+ : phaseFinishLine(event.observation),
62
+ ];
50
63
  case "stage":
51
- return [`• ${event.stage} ${event.state} ${event.contractId}`];
64
+ return [`${event.state === "started" ? "●" : "✓"} ${event.stage}`];
52
65
  case "progress-dropped":
53
- return [`• progress dropped ${event.count} event${event.count === 1 ? "" : "s"}`];
66
+ return [`progress dropped ${event.count} event${event.count === 1 ? "" : "s"}`];
67
+ }
68
+ }
69
+ /** Consumes witnessed events into either a live TTY line or sparse text. */
70
+ export class ExecutionProgressRenderer {
71
+ input;
72
+ status;
73
+ verificationStartedAt;
74
+ declarations;
75
+ declarationFailed = false;
76
+ phaseFailed = false;
77
+ phaseUnknown = false;
78
+ constructor(input) {
79
+ this.input = input;
80
+ this.status = new StatusLine(input.stream, input);
81
+ }
82
+ async consume(event) {
83
+ if (event.kind === "verification") {
84
+ await this.consumeVerification(event);
85
+ return;
86
+ }
87
+ await this.write(executionProgressLines(event, this.input.context));
54
88
  }
89
+ finish() {
90
+ if (!this.status.isTTY || this.verificationStartedAt === undefined)
91
+ return;
92
+ const now = this.input.now ?? (() => performance.now());
93
+ const duration = elapsed(now() - this.verificationStartedAt);
94
+ if (this.declarations !== undefined) {
95
+ const { passed, total } = this.declarations;
96
+ this.status.finish(`verify ${this.finalMark()} ${passed}/${total} · ${duration}`);
97
+ return;
98
+ }
99
+ this.status.finish(`verify ${this.finalMark()} · ${duration}`);
100
+ }
101
+ async consumeVerification(event) {
102
+ const observation = event.observation;
103
+ if (observation.kind === "output") {
104
+ await this.write(outputLines(observation, this.input.context));
105
+ return;
106
+ }
107
+ if (observation.state === "started") {
108
+ this.verificationStartedAt ??= (this.input.now ?? (() => performance.now()))();
109
+ if (this.status.isTTY)
110
+ this.status.show((duration) => `verify ● ${phaseDetail(observation)} · ${elapsed(duration)}`);
111
+ else
112
+ await this.write([phaseStartLine(observation)]);
113
+ return;
114
+ }
115
+ const mark = phaseMark(observation.outcome);
116
+ this.phaseFailed ||= mark === "×";
117
+ this.phaseUnknown ||= mark === "?";
118
+ if (observation.phase === "declaration") {
119
+ const total = observation.total ?? this.declarations?.total ?? 0;
120
+ const passed = (this.declarations?.passed ?? 0) + (mark === "✓" ? 1 : 0);
121
+ this.declarations = { passed, total };
122
+ this.declarationFailed ||= mark !== "✓";
123
+ }
124
+ if (this.status.isTTY)
125
+ this.status.show((duration) => `verify ${mark} ${phaseDetail(observation)} · ${elapsed(duration)}`);
126
+ else
127
+ await this.write([phaseFinishLine(observation)]);
128
+ }
129
+ write(lines) {
130
+ if (this.status.isTTY) {
131
+ this.status.writeBlock(lines);
132
+ return Promise.resolve();
133
+ }
134
+ return writeChunk(this.input.stream, `${lines.join("\n")}\n`);
135
+ }
136
+ finalMark() {
137
+ return this.phaseFailed || this.declarationFailed ? "×" : this.phaseUnknown ? "?" : "✓";
138
+ }
139
+ }
140
+ export async function renderExecutionProgress(events, input) {
141
+ const renderer = new ExecutionProgressRenderer(input);
142
+ const destination = watchDestination(input.stream);
143
+ const iterator = events[Symbol.asyncIterator]();
144
+ try {
145
+ for (;;) {
146
+ const next = await Promise.race([iterator.next(), destination.promise]);
147
+ if (next.done === true)
148
+ break;
149
+ await renderer.consume(next.value);
150
+ }
151
+ renderer.finish();
152
+ }
153
+ finally {
154
+ destination.detach();
155
+ }
156
+ }
157
+ /** Reject when the destination dies while the producer is still owned. */
158
+ function watchDestination(stream) {
159
+ let detach = () => undefined;
160
+ const promise = new Promise((_, reject) => {
161
+ const onError = (error) => reject(error);
162
+ const onClose = () => reject(Object.assign(new Error("write on prematurely closed destination"), { code: "ERR_STREAM_PREMATURE_CLOSE" }));
163
+ stream.once("error", onError);
164
+ stream.once("close", onClose);
165
+ detach = () => {
166
+ stream.off("error", onError);
167
+ stream.off("close", onClose);
168
+ };
169
+ });
170
+ promise.catch(() => undefined);
171
+ return { promise, detach };
172
+ }
173
+ /** Await one destination write: backpressure and error propagation in one callback. */
174
+ function writeChunk(stream, text) {
175
+ return new Promise((resolve, reject) => {
176
+ stream.write(text, (error) => (error ? reject(error) : resolve()));
177
+ });
55
178
  }