@astrosheep/keiyaku 4.5.19 → 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 (197) hide show
  1. package/README.md +4 -4
  2. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +14 -7
  3. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +53 -20
  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 -4
  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 +23 -0
  14. package/build/src/akuma/akuma-product.d.ts +2 -0
  15. package/build/src/akuma/akuma-product.js +6 -0
  16. package/build/src/akuma/akuma.d.ts +20 -1
  17. package/build/src/akuma/akuma.js +2 -0
  18. package/build/src/akuma/allowed.d.ts +20 -0
  19. package/build/src/akuma/allowed.js +11 -0
  20. package/build/src/akuma/archetype.js +3 -0
  21. package/build/src/akuma/body.d.ts +25 -5
  22. package/build/src/akuma/body.js +70 -31
  23. package/build/src/akuma/call-request.d.ts +5 -0
  24. package/build/src/akuma/call-request.js +14 -4
  25. package/build/src/akuma/fleet-execution.d.ts +28 -0
  26. package/build/src/akuma/fleet-execution.js +36 -4
  27. package/build/src/akuma/fleet-observation.d.ts +54 -0
  28. package/build/src/akuma/fleet-request.d.ts +10 -0
  29. package/build/src/akuma/fleet-request.js +13 -3
  30. package/build/src/akuma/heart/facts.d.ts +2 -0
  31. package/build/src/akuma/heart/index.d.ts +1 -0
  32. package/build/src/akuma/heart/index.js +4 -1
  33. package/build/src/akuma/heart/rows.d.ts +2 -0
  34. package/build/src/akuma/heart/rows.js +7 -5
  35. package/build/src/akuma/heart/schema.d.ts +1 -1
  36. package/build/src/akuma/heart/schema.js +3 -1
  37. package/build/src/akuma/heart/tells.js +6 -5
  38. package/build/src/akuma/heart/timeline.js +1 -1
  39. package/build/src/akuma/provider-recipe.d.ts +1 -0
  40. package/build/src/akuma/provider-recipe.js +5 -0
  41. package/build/src/akuma/provider.d.ts +1 -1
  42. package/build/src/akuma/provider.js +1 -1
  43. package/build/src/akuma/providers/acp/core.js +3 -6
  44. package/build/src/akuma/providers/acp/index.js +3 -0
  45. package/build/src/akuma/providers/claude/index.js +10 -10
  46. package/build/src/akuma/providers/codex-app-server/index.js +12 -11
  47. package/build/src/akuma/providers/execution-environment.d.ts +6 -0
  48. package/build/src/akuma/providers/execution-environment.js +24 -0
  49. package/build/src/akuma/providers/grok-build/index.js +3 -0
  50. package/build/src/akuma/providers/opencode-sdk/index.js +32 -23
  51. package/build/src/akuma/providers/opencode-sdk/session.d.ts +8 -1
  52. package/build/src/akuma/providers/opencode-sdk/session.js +6 -5
  53. package/build/src/akuma/providers/pi/events.js +7 -2
  54. package/build/src/akuma/providers/pi/index.js +5 -2
  55. package/build/src/akuma/request-observation.d.ts +45 -0
  56. package/build/src/akuma/request-observation.js +162 -0
  57. package/build/src/akuma/request-rendezvous.d.ts +6 -3
  58. package/build/src/akuma/request-rendezvous.js +80 -14
  59. package/build/src/akuma/request-serve.js +27 -4
  60. package/build/src/akuma/request-wire.d.ts +5 -0
  61. package/build/src/akuma/request-wire.js +2 -0
  62. package/build/src/akuma/turn-drive.d.ts +1 -0
  63. package/build/src/akuma/turn-drive.js +2 -0
  64. package/build/src/akuma-body.js +5 -0
  65. package/build/src/body/amend.js +6 -4
  66. package/build/src/body/decode.d.ts +3 -1
  67. package/build/src/body/decode.js +53 -17
  68. package/build/src/body/region.d.ts +9 -1
  69. package/build/src/body/region.js +87 -12
  70. package/build/src/body/render.js +1 -1
  71. package/build/src/body/verification.d.ts +3 -1
  72. package/build/src/body/verification.js +4 -1
  73. package/build/src/cli/accepted.js +19 -2
  74. package/build/src/cli/commands/akuma-invoke.d.ts +3 -1
  75. package/build/src/cli/commands/akuma-invoke.js +134 -14
  76. package/build/src/cli/commands/akuma.js +24 -12
  77. package/build/src/cli/commands/contract-help.d.ts +15 -15
  78. package/build/src/cli/commands/contract-help.js +45 -19
  79. package/build/src/cli/commands/contract-invoke.d.ts +3 -0
  80. package/build/src/cli/commands/contract-invoke.js +25 -11
  81. package/build/src/cli/commands/contract.js +6 -4
  82. package/build/src/cli/commands/install.d.ts +1 -1
  83. package/build/src/cli/commands/install.js +1 -1
  84. package/build/src/cli/commands/status-set.js +15 -24
  85. package/build/src/cli/commands/task.js +18 -18
  86. package/build/src/cli/invoke.d.ts +3 -0
  87. package/build/src/cli/invoke.js +58 -23
  88. package/build/src/cli/main.js +5 -0
  89. package/build/src/cli/parse.d.ts +3 -0
  90. package/build/src/cli/parse.js +26 -4
  91. package/build/src/cli/render/akuma-activity.d.ts +100 -2
  92. package/build/src/cli/render/akuma-activity.js +485 -40
  93. package/build/src/cli/render/akuma.d.ts +10 -0
  94. package/build/src/cli/render/akuma.js +31 -21
  95. package/build/src/cli/render/audit.js +1 -1
  96. package/build/src/cli/render/board.js +1 -1
  97. package/build/src/cli/render/catalog.js +16 -13
  98. package/build/src/cli/render/contract-history.js +39 -10
  99. package/build/src/cli/render/contract-observation.js +10 -6
  100. package/build/src/cli/render/contract.js +109 -106
  101. package/build/src/cli/render/execution-progress.d.ts +26 -0
  102. package/build/src/cli/render/execution-progress.js +178 -0
  103. package/build/src/cli/render/kanshi-akuma.js +27 -39
  104. package/build/src/cli/render/kanshi.js +33 -26
  105. package/build/src/cli/render/nuke.js +1 -1
  106. package/build/src/cli/render/receipt.d.ts +1 -1
  107. package/build/src/cli/render/receipt.js +6 -6
  108. package/build/src/cli/render/refusal.js +10 -7
  109. package/build/src/cli/render/region.js +1 -1
  110. package/build/src/cli/render/settings.js +12 -2
  111. package/build/src/cli/render/status-line.d.ts +30 -0
  112. package/build/src/cli/render/status-line.js +58 -0
  113. package/build/src/cli/render/status-set.js +1 -1
  114. package/build/src/cli/render/task.d.ts +2 -0
  115. package/build/src/cli/render/task.js +11 -7
  116. package/build/src/cli/render/terminal.d.ts +0 -1
  117. package/build/src/cli/render/terminal.js +47 -15
  118. package/build/src/cli/result.d.ts +1 -0
  119. package/build/src/cli/runtime.d.ts +2 -0
  120. package/build/src/cli/runtime.js +36 -4
  121. package/build/src/cli/usage.js +2 -2
  122. package/build/src/cli/version.d.ts +1 -0
  123. package/build/src/cli/version.js +25 -0
  124. package/build/src/git/hooks.d.ts +13 -1
  125. package/build/src/git/hooks.js +2 -1
  126. package/build/src/git/workspace.d.ts +1 -1
  127. package/build/src/git/workspace.js +1 -1
  128. package/build/src/identity/mint.d.ts +13 -0
  129. package/build/src/identity/mint.js +26 -0
  130. package/build/src/identity/normalize.d.ts +4 -0
  131. package/build/src/identity/normalize.js +30 -0
  132. package/build/src/index.d.ts +2 -0
  133. package/build/src/index.js +1 -0
  134. package/build/src/kanshi/read.js +17 -4
  135. package/build/src/kanshi/report.d.ts +1 -0
  136. package/build/src/library/akuma-creation.d.ts +2 -0
  137. package/build/src/library/akuma-creation.js +34 -18
  138. package/build/src/library/audit.d.ts +2 -0
  139. package/build/src/library/audit.js +1 -0
  140. package/build/src/library/bind.js +18 -16
  141. package/build/src/library/composition.d.ts +1 -0
  142. package/build/src/library/contract-bind.d.ts +3 -1
  143. package/build/src/library/contract-bind.js +5 -3
  144. package/build/src/library/contract-execution.d.ts +2 -0
  145. package/build/src/library/contract-execution.js +15 -1
  146. package/build/src/library/contract-forwarding.d.ts +4 -0
  147. package/build/src/library/contract-forwarding.js +3 -0
  148. package/build/src/library/contract-handle.d.ts +7 -0
  149. package/build/src/library/contract-handle.js +25 -0
  150. package/build/src/library/contract-operations.d.ts +7 -2
  151. package/build/src/library/contract-operations.js +21 -1
  152. package/build/src/library/contract-types.d.ts +1 -0
  153. package/build/src/library/execution-result.d.ts +2 -2
  154. package/build/src/library/execution.d.ts +8 -0
  155. package/build/src/library/execution.js +102 -0
  156. package/build/src/library/fleet.d.ts +4 -1
  157. package/build/src/library/fleet.js +32 -8
  158. package/build/src/library/keiyaku.d.ts +2 -0
  159. package/build/src/library/mutation.js +15 -7
  160. package/build/src/plugin/akuma-signals.d.ts +6 -0
  161. package/build/src/plugin/akuma-signals.js +9 -1
  162. package/build/src/plugin/public.d.ts +6 -2
  163. package/build/src/plugin/runtime.d.ts +1 -3
  164. package/build/src/plugin/runtime.js +95 -103
  165. package/build/src/protocol/amend.js +4 -3
  166. package/build/src/protocol/attempt.d.ts +2 -0
  167. package/build/src/protocol/bind.js +41 -14
  168. package/build/src/protocol/completion.d.ts +3 -0
  169. package/build/src/protocol/completion.js +35 -11
  170. package/build/src/protocol/deliver.js +165 -92
  171. package/build/src/protocol/execution-observation.d.ts +181 -0
  172. package/build/src/protocol/execution-observation.js +45 -0
  173. package/build/src/protocol/intent.d.ts +18 -22
  174. package/build/src/protocol/intent.js +63 -41
  175. package/build/src/protocol/operations.d.ts +2 -0
  176. package/build/src/protocol/placement.js +5 -1
  177. package/build/src/protocol/progress.d.ts +5 -1
  178. package/build/src/protocol/progress.js +10 -1
  179. package/build/src/protocol/read/status.d.ts +1 -0
  180. package/build/src/protocol/read/status.js +10 -2
  181. package/build/src/protocol/reintegrate.d.ts +1 -1
  182. package/build/src/protocol/reintegrate.js +70 -43
  183. package/build/src/protocol/result-codec.js +27 -11
  184. package/build/src/protocol/review.js +53 -30
  185. package/build/src/protocol/run.d.ts +83 -36
  186. package/build/src/protocol/run.js +90 -75
  187. package/build/src/runtime/proc/run.d.ts +16 -4
  188. package/build/src/runtime/proc/run.js +64 -22
  189. package/build/src/runtime/proc/windows-launch.exe +0 -0
  190. package/build/src/settings.js +5 -14
  191. package/build/src/task/document.d.ts +5 -1
  192. package/build/src/task/document.js +6 -1
  193. package/build/src/verification/execution.d.ts +10 -2
  194. package/build/src/verification/execution.js +143 -42
  195. package/build/src/verification/observation.d.ts +41 -0
  196. package/build/src/verification/observation.js +29 -0
  197. package/package.json +3 -1
@@ -1,5 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { open } from "node:fs/promises";
3
+ import { StringDecoder } from "node:string_decoder";
3
4
  import crossSpawn from "cross-spawn";
4
5
  import { spawnOptionsFor } from "./launch.js";
5
6
  import { detachedExitStatus, retainDetachedExitEvidence } from "./process-exit.js";
@@ -73,7 +74,10 @@ function tailCapture(limit) {
73
74
  const size = Math.min(total, limit);
74
75
  const start = total > limit ? cursor : 0;
75
76
  const value = start === 0 ? bytes.subarray(0, size) : Buffer.concat([bytes.subarray(start), bytes.subarray(0, start)]);
76
- return { text: value.toString("utf8"), truncated: total > limit };
77
+ let startAt = 0;
78
+ while (startAt < value.length && (value[startAt] & 0xc0) === 0x80)
79
+ startAt += 1;
80
+ return { text: value.subarray(startAt).toString("utf8"), truncated: total > limit };
77
81
  },
78
82
  };
79
83
  }
@@ -137,21 +141,55 @@ export async function spawnDetachedProcess(input) {
137
141
  await log.close();
138
142
  }
139
143
  }
140
- function terminalOutcome(code, stdout, stderr, consuming) {
144
+ function capturedOutput(stdout, stderr, consuming) {
141
145
  return {
142
- kind: "terminal",
143
- code,
144
146
  stdout: consuming ? "" : stdout.text,
145
147
  stderr: stderr.text,
146
148
  truncated: (consuming ? false : stdout.truncated) || stderr.truncated,
147
149
  };
148
150
  }
151
+ function outputObservation(input, consumingStdout) {
152
+ const decoders = input.onOutput === undefined
153
+ ? undefined
154
+ : {
155
+ ...(consumingStdout ? {} : { stdout: new StringDecoder("utf8") }),
156
+ stderr: new StringDecoder("utf8"),
157
+ };
158
+ const report = (stream, text) => {
159
+ if (text.length === 0)
160
+ return;
161
+ try {
162
+ input.onOutput?.({ stream, text });
163
+ }
164
+ catch {
165
+ // Observation is not process ownership: a consumer cannot strand the owned child.
166
+ }
167
+ };
168
+ return {
169
+ observe(stream, chunk) {
170
+ const decoder = decoders?.[stream];
171
+ if (decoder !== undefined)
172
+ report(stream, decoder.write(chunk));
173
+ },
174
+ finish() {
175
+ if (decoders === undefined)
176
+ return;
177
+ for (const stream of ["stdout", "stderr"]) {
178
+ const decoder = decoders[stream];
179
+ if (decoder !== undefined)
180
+ report(stream, decoder.end());
181
+ }
182
+ },
183
+ };
184
+ }
149
185
  async function executeProcess(input, timeoutMs, consumeStdout, spawnProcess = spawn) {
150
- if (input.signal?.aborted === true)
151
- return { outcome: { kind: "cancelled" }, pid: null };
186
+ if (input.signal?.aborted === true) {
187
+ return { outcome: { kind: "cancelled", stdout: "", stderr: "", truncated: false }, pid: null };
188
+ }
152
189
  const child = spawnProcess(input.argv[0], input.argv.slice(1), spawnOptionsFor(input, ["ignore", "pipe", "pipe"]));
153
190
  const stdout = tailCapture(STREAM_TAIL_BYTES);
154
191
  const stderr = tailCapture(STREAM_TAIL_BYTES);
192
+ const output = outputObservation(input, consumeStdout !== undefined);
155
193
  let stop;
156
194
  let streamError;
157
195
  let termination;
@@ -175,16 +213,21 @@ async function executeProcess(input, timeoutMs, consumeStdout, spawnProcess = sp
175
213
  child.stdout.on("data", (chunk) => {
176
214
  if (consumeStdout === undefined) {
177
215
  stdout.append(chunk);
178
- return;
179
- }
180
- try {
181
- consumeStdout(chunk);
182
216
  }
183
- catch (error) {
184
- failStream(error);
217
+ else {
218
+ try {
219
+ consumeStdout(chunk);
220
+ }
221
+ catch (error) {
222
+ failStream(error);
223
+ }
185
224
  }
225
+ output.observe("stdout", chunk);
226
+ });
227
+ child.stderr.on("data", (chunk) => {
228
+ stderr.append(chunk);
229
+ output.observe("stderr", chunk);
186
230
  });
187
- child.stderr.on("data", (chunk) => stderr.append(chunk));
188
231
  if (consumeStdout !== undefined) {
189
232
  child.stdout.once("error", failStream);
190
233
  child.stderr.once("error", failStream);
@@ -199,29 +242,28 @@ async function executeProcess(input, timeoutMs, consumeStdout, spawnProcess = sp
199
242
  if (terminal.kind === "termination-error")
200
243
  throw terminal.error;
201
244
  await termination;
245
+ output.finish();
202
246
  const pid = child.pid ?? null;
247
+ const capture = capturedOutput(stdout.result(), stderr.result(), consumeStdout !== undefined);
203
248
  if (terminal.kind === "spawn-error") {
204
- return { outcome: { kind: "spawn-error", diagnostic: terminal.error.message }, pid };
249
+ return { outcome: { kind: "spawn-error", diagnostic: terminal.error.message, ...capture }, pid };
205
250
  }
206
251
  if (streamError !== undefined) {
207
252
  return {
208
253
  outcome: {
209
254
  kind: "stream-error",
210
255
  diagnostic: streamError instanceof Error ? streamError.message : String(streamError),
256
+ stderr: capture.stderr,
257
+ truncated: capture.truncated,
211
258
  },
212
259
  pid,
213
260
  };
214
261
  }
215
262
  if (stop !== undefined)
216
- return { outcome: { kind: stop }, pid };
263
+ return { outcome: { kind: stop, ...capture }, pid };
217
264
  if (terminal.code === null)
218
- return { outcome: { kind: "unknown-exit" }, pid };
219
- const capturedStdout = stdout.result();
220
- const capturedStderr = stderr.result();
221
- return {
222
- outcome: terminalOutcome(terminal.code, capturedStdout, capturedStderr, consumeStdout !== undefined),
223
- pid,
224
- };
265
+ return { outcome: { kind: "unknown-exit", ...capture }, pid };
266
+ return { outcome: { kind: "terminal", code: terminal.code, ...capture }, pid };
225
267
  }
226
268
  export async function runProcess(input) {
227
269
  const result = await executeProcess(input, input.timeoutMs);
@@ -152,9 +152,6 @@ function bundleGates(name, value) {
152
152
  if (!gateWord(gate)) {
153
153
  throw new SettingsError(`gate bundle '${name}' contains an invalid gate word`);
154
154
  }
155
- if (gate !== "reviewed" && gate !== "verified") {
156
- throw new SettingsError(`gate bundle '${name}' contains a gate without a producer: ${gate}`);
157
- }
158
155
  return gate;
159
156
  });
160
157
  }
@@ -167,8 +164,10 @@ export function gatesFrom(input) {
167
164
  const names = input.names ?? ["default"];
168
165
  for (const name of names) {
169
166
  if (!gateWord(name))
170
- throw new SettingsError("gate bundle name must match ^[a-z][a-z0-9-]{0,63}$");
167
+ throw new SettingsError("gate or bundle name must match ^[a-z][a-z0-9-]{0,63}$");
171
168
  }
169
+ if (names.length === 0)
170
+ return Object.freeze([]);
172
171
  const view = input.settings.namespace("gates");
173
172
  if (view.kind === "failed")
174
173
  namespaceFailure(view);
@@ -176,16 +175,8 @@ export function gatesFrom(input) {
176
175
  const seen = new Set();
177
176
  for (const name of names) {
178
177
  const selected = view.entries.find((entry) => entry.name === name);
179
- if (selected === undefined) {
180
- if (input.names === undefined) {
181
- if (!seen.has("reviewed"))
182
- expanded.push("reviewed");
183
- seen.add("reviewed");
184
- continue;
185
- }
186
- throw new SettingsError(`unknown gate bundle: ${name}`);
187
- }
188
- for (const gate of bundleGates(name, selected.value)) {
178
+ const gates = selected === undefined ? [input.names === undefined ? "reviewed" : name] : bundleGates(name, selected.value);
179
+ for (const gate of gates) {
189
180
  if (seen.has(gate))
190
181
  continue;
191
182
  seen.add(gate);
@@ -18,7 +18,11 @@ export type TaskDocument = Readonly<{
18
18
  }>;
19
19
  export type TaskCreationDocument = Omit<TaskDocument, "id" | "createdBy" | "createdAt" | "updatedAt">;
20
20
  export declare class TaskAuthorityCorruptionError extends Error {
21
- constructor(message: string, options?: ErrorOptions);
21
+ /** The Task document whose bytes failed decode, when the reader knew its coordinate. */
22
+ readonly coordinate?: TaskId;
23
+ constructor(message: string, options?: ErrorOptions & {
24
+ coordinate?: TaskId;
25
+ });
22
26
  }
23
27
  export declare function parseTaskDocument(bytes: Uint8Array, expected: TaskCoordinate): TaskDocument;
24
28
  export declare function parseTaskCreationDocument(markdown: string): TaskCreationDocument;
@@ -1,9 +1,13 @@
1
1
  import { isMap, parseDocument, stringify } from "yaml";
2
2
  import { formatTaskId, parseTaskId } from "./identity.js";
3
3
  export class TaskAuthorityCorruptionError extends Error {
4
+ /** The Task document whose bytes failed decode, when the reader knew its coordinate. */
5
+ coordinate;
4
6
  constructor(message, options) {
5
7
  super(message, options);
6
8
  this.name = "TaskAuthorityCorruptionError";
9
+ if (options?.coordinate !== undefined)
10
+ this.coordinate = options.coordinate;
7
11
  }
8
12
  }
9
13
  const STATES = new Set(["open", "in_progress", "on_hold", "done", "drop"]);
@@ -107,8 +111,9 @@ function fields(value, body, fail) {
107
111
  };
108
112
  }
109
113
  export function parseTaskDocument(bytes, expected) {
114
+ const coordinate = formatTaskId(expected);
110
115
  const fail = (message, cause) => {
111
- throw new TaskAuthorityCorruptionError(message, cause === undefined ? {} : { cause });
116
+ throw new TaskAuthorityCorruptionError(message, { coordinate, ...(cause === undefined ? {} : { cause }) });
112
117
  };
113
118
  const { value, body } = frontMatter(Buffer.from(bytes).toString("utf8"), fail);
114
119
  closed(value, STORED_KEYS, REQUIRED_STORED_KEYS, fail);
@@ -3,6 +3,7 @@ import { type HookFailure } from "../git/hooks.js";
3
3
  import type { MaterializedScratchCandidate, WorktreeLeak } from "../git/scratch.js";
4
4
  import type { GitRepository } from "../git/process.js";
5
5
  import type { Settings } from "../settings.js";
6
+ import type { VerificationObservation } from "./observation.js";
6
7
  import type { VerificationDeclaration } from "./declaration.js";
7
8
  type VerificationVerdict = "satisfied" | "unsatisfied";
8
9
  export type VerificationTerminalOutcome = Readonly<{
@@ -12,14 +13,20 @@ export type VerificationTerminalOutcome = Readonly<{
12
13
  total: number;
13
14
  summary?: string;
14
15
  }>;
15
- export type VerificationNonterminalOutcome = Readonly<{
16
+ export type CapturedOutput = Readonly<{
17
+ stdout?: string;
18
+ stderr?: string;
19
+ truncated?: boolean;
20
+ }>;
21
+ export type VerificationNonterminalOutcome = CapturedOutput & (Readonly<{
16
22
  kind: "unknown-exit";
17
23
  }> | Readonly<{
18
24
  kind: "cancelled";
19
25
  }> | Readonly<{
20
26
  kind: "spawn-error";
21
27
  diagnostic: string;
22
- }>;
28
+ }>);
29
+ export declare function capturedOutput(value: CapturedOutput): CapturedOutput;
23
30
  export type VerificationExecutionStop = VerificationNonterminalOutcome | Readonly<{
24
31
  kind: "candidate-unavailable" | "environment-failure";
25
32
  diagnostic: string;
@@ -43,6 +50,7 @@ export type ExecuteVerificationInput = Readonly<{
43
50
  declarations: readonly VerificationDeclaration[];
44
51
  materializeScratchCandidate: (repository: GitRepository, candidate: SnapshotId) => Promise<MaterializedScratchCandidate>;
45
52
  projectSettings: (root: string) => Promise<Settings>;
53
+ observe?: (event: VerificationObservation) => void;
46
54
  signal?: AbortSignal;
47
55
  }>;
48
56
  /** Execute one disposable Verification attempt over the exact integration snapshot. */
@@ -1,6 +1,28 @@
1
1
  import { runHookCommands, worktreeHooksFrom } from "../git/hooks.js";
2
2
  import { runProcess } from "../runtime/proc/run.js";
3
3
  const SUMMARY_BYTES = 32 * 1024;
4
+ export function capturedOutput(value) {
5
+ return {
6
+ ...(value.stdout ? { stdout: value.stdout } : {}),
7
+ ...(value.stderr ? { stderr: value.stderr } : {}),
8
+ ...(value.truncated === true ? { truncated: true } : {}),
9
+ };
10
+ }
11
+ function observe(input, event) {
12
+ try {
13
+ input.observe?.(event);
14
+ }
15
+ catch {
16
+ /* Observation does not own execution. */
17
+ }
18
+ }
19
+ function outputSummary(output) {
20
+ return [
21
+ ...(output.stdout ? [`stdout:\n${output.stdout}`] : []),
22
+ ...(output.stderr ? [`stderr:\n${output.stderr}`] : []),
23
+ ...(output.truncated ? ["[output truncated]"] : []),
24
+ ].join("\n");
25
+ }
4
26
  function argvFor(declaration) {
5
27
  const args = declaration.executor === "pwsh" ? ["-Command", declaration.script] : ["-c", declaration.script];
6
28
  return [declaration.executor, ...args];
@@ -27,26 +49,63 @@ function appendSummary(current, diagnostic) {
27
49
  start += 1;
28
50
  return Buffer.concat([marker, combined.subarray(start)]).toString("utf8");
29
51
  }
52
+ async function observedHooks(input) {
53
+ const { execution, cwd, commands, phase, signal, environment } = input;
54
+ for (const command of commands) {
55
+ const coordinate = { phase, cwd, name: command.name };
56
+ const started = performance.now();
57
+ observe(execution, { kind: "phase", ...coordinate, state: "started" });
58
+ const result = await runHookCommands(cwd, [command], signal, environment, (output) => observe(execution, { kind: "output", ...coordinate, ...output }));
59
+ observe(execution, {
60
+ kind: "phase",
61
+ ...coordinate,
62
+ state: "finished",
63
+ outcome: result.kind,
64
+ elapsedMs: performance.now() - started,
65
+ });
66
+ if (result.kind !== "ok")
67
+ return result;
68
+ }
69
+ return { kind: "ok" };
70
+ }
71
+ function nonterminalOutcome(outcome) {
72
+ return outcome.kind === "spawn-error"
73
+ ? { kind: outcome.kind, diagnostic: outcome.diagnostic, ...capturedOutput(outcome) }
74
+ : { kind: outcome.kind, ...capturedOutput(outcome) };
75
+ }
30
76
  async function executeDeclarations(input) {
31
77
  let verdict = "satisfied";
32
78
  let passed = 0;
33
79
  const total = input.declarations.length;
34
80
  let summary;
35
81
  for (const [index, declaration] of input.declarations.entries()) {
82
+ const coordinate = { phase: "declaration", cwd: input.cwd, index: index + 1, total };
83
+ const started = performance.now();
84
+ observe(input, { kind: "phase", ...coordinate, state: "started" });
36
85
  const outcome = await runProcess({
37
86
  argv: argvFor(declaration),
38
87
  cwd: input.cwd,
39
88
  env: input.environment,
89
+ onOutput: (output) => observe(input, { kind: "output", ...coordinate, ...output }),
40
90
  ...(input.signal === undefined ? {} : { signal: input.signal }),
41
91
  ...(declaration.timeoutMs === undefined ? {} : { timeoutMs: declaration.timeoutMs }),
42
92
  });
93
+ observe(input, {
94
+ kind: "phase",
95
+ ...coordinate,
96
+ state: "finished",
97
+ outcome: outcome.kind === "terminal" ? `exit ${outcome.code}` : outcome.kind,
98
+ elapsedMs: performance.now() - started,
99
+ });
43
100
  if (outcome.kind === "timeout") {
44
101
  verdict = "unsatisfied";
45
- summary = appendSummary(summary, `[${index + 1} ${declaration.executor} timeout after ${declaration.timeoutMs}ms]`);
102
+ summary = appendSummary(summary, [`[${index + 1} ${declaration.executor} timeout after ${declaration.timeoutMs}ms]`, outputSummary(outcome)]
103
+ .filter(Boolean)
104
+ .join("\n"));
46
105
  continue;
47
106
  }
48
107
  if (outcome.kind === "spawn-error" || outcome.kind === "unknown-exit" || outcome.kind === "cancelled")
49
- return outcome;
108
+ return nonterminalOutcome(outcome);
50
109
  if (outcome.code === 0)
51
110
  passed += 1;
52
111
  else
@@ -55,58 +114,100 @@ async function executeDeclarations(input) {
55
114
  }
56
115
  return { kind: "terminal", verdict, passed, total, ...(summary === undefined ? {} : { summary }) };
57
116
  }
58
- /** Execute one disposable Verification attempt over the exact integration snapshot. */
59
- export async function executeVerification(input) {
60
- const environment = process.env;
61
- let scratch;
117
+ async function materializeScratch(input) {
118
+ observe(input, { kind: "phase", phase: "materialize", state: "started" });
62
119
  try {
63
- scratch = await input.materializeScratchCandidate(input.repository, input.candidate);
120
+ input.signal?.throwIfAborted();
121
+ const scratch = await input.materializeScratchCandidate(input.repository, input.candidate);
122
+ observe(input, { kind: "phase", phase: "materialize", state: "finished", cwd: scratch.cwd, outcome: "ok" });
123
+ return scratch;
64
124
  }
65
125
  catch (error) {
126
+ observe(input, {
127
+ kind: "phase",
128
+ phase: "materialize",
129
+ state: "finished",
130
+ outcome: input.signal?.aborted ? "cancelled" : "failed",
131
+ });
132
+ if (input.signal?.aborted)
133
+ return { outcome: { kind: "cancelled" } };
66
134
  return {
67
135
  outcome: { kind: "candidate-unavailable", diagnostic: error instanceof Error ? error.message : String(error) },
68
136
  };
69
137
  }
70
- let outcome;
71
- let cleanup;
72
- let leak = null;
73
- let destroy;
138
+ }
139
+ async function runScratch(input, scratch) {
74
140
  try {
75
- try {
76
- const hooks = worktreeHooksFrom({ settings: await input.projectSettings(scratch.cwd) });
77
- destroy = hooks.destroy;
78
- const readiness = await runHookCommands(scratch.cwd, hooks.create, input.signal, environment);
79
- outcome =
80
- readiness.kind === "cancelled"
81
- ? { kind: "cancelled" }
82
- : readiness.kind === "failed"
83
- ? { kind: "environment-failure", name: readiness.name, detail: readiness.failure }
84
- : await executeDeclarations({
85
- declarations: input.declarations,
86
- cwd: scratch.cwd,
87
- environment,
88
- ...(input.signal === undefined ? {} : { signal: input.signal }),
89
- });
90
- }
91
- catch (error) {
92
- outcome = { kind: "environment-failure", diagnostic: error instanceof Error ? error.message : String(error) };
93
- }
141
+ input.signal?.throwIfAborted();
142
+ const hooks = worktreeHooksFrom({ settings: await input.projectSettings(scratch.cwd) });
143
+ const readiness = await observedHooks({
144
+ execution: input,
145
+ cwd: scratch.cwd,
146
+ commands: hooks.create,
147
+ phase: "setup",
148
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
149
+ environment: process.env,
150
+ });
151
+ if (readiness.kind === "cancelled")
152
+ return { outcome: { kind: "cancelled", ...capturedOutput(readiness) }, destroy: hooks.destroy };
153
+ if (readiness.kind === "failed")
154
+ return {
155
+ outcome: { kind: "environment-failure", name: readiness.name, detail: readiness.failure },
156
+ destroy: hooks.destroy,
157
+ };
158
+ return {
159
+ outcome: await executeDeclarations({
160
+ declarations: input.declarations,
161
+ cwd: scratch.cwd,
162
+ environment: process.env,
163
+ ...(input.observe === undefined ? {} : { observe: input.observe }),
164
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
165
+ }),
166
+ destroy: hooks.destroy,
167
+ };
94
168
  }
95
- finally {
96
- if (destroy !== undefined) {
97
- const result = await runHookCommands(scratch.cwd, destroy, undefined, environment);
98
- if (result.kind === "cancelled")
99
- throw new Error("scratch destroy cancelled without a signal");
100
- if (result.kind === "failed")
101
- cleanup = { phase: "destroy", name: result.name, detail: result.failure };
102
- }
103
- leak = await scratch.dispose();
169
+ catch (error) {
170
+ return {
171
+ outcome: input.signal?.aborted
172
+ ? { kind: "cancelled" }
173
+ : { kind: "environment-failure", diagnostic: error instanceof Error ? error.message : String(error) },
174
+ };
104
175
  }
105
- if (outcome === undefined)
106
- throw new Error("Verification ended without an outcome");
176
+ }
177
+ async function retireScratch(input, scratch, destroy) {
178
+ observe(input, { kind: "phase", phase: "cleanup", cwd: scratch.cwd, state: "started" });
179
+ let cleanup;
180
+ if (destroy !== undefined) {
181
+ const result = await observedHooks({
182
+ execution: input,
183
+ cwd: scratch.cwd,
184
+ commands: destroy,
185
+ phase: "cleanup",
186
+ environment: process.env,
187
+ });
188
+ if (result.kind === "cancelled")
189
+ throw new Error("scratch destroy cancelled without a signal");
190
+ if (result.kind === "failed")
191
+ cleanup = { phase: "destroy", name: result.name, detail: result.failure };
192
+ }
193
+ const leak = await scratch.dispose();
194
+ observe(input, {
195
+ kind: "phase",
196
+ phase: "cleanup",
197
+ cwd: scratch.cwd,
198
+ state: "finished",
199
+ outcome: leak !== null || cleanup !== undefined ? "failed" : "ok",
200
+ });
107
201
  return {
108
- outcome,
109
202
  ...(cleanup === undefined ? {} : { cleanup }),
110
203
  ...(leak === null ? {} : { leak }),
111
204
  };
112
205
  }
206
+ /** Execute one disposable Verification attempt over the exact integration snapshot. */
207
+ export async function executeVerification(input) {
208
+ const materialized = await materializeScratch(input);
209
+ if ("outcome" in materialized)
210
+ return materialized;
211
+ const prepared = await runScratch(input, materialized);
212
+ return { outcome: prepared.outcome, ...(await retireScratch(input, materialized, prepared.destroy)) };
213
+ }
@@ -0,0 +1,41 @@
1
+ import { z } from "zod";
2
+ export declare const verificationObservationSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
3
+ state: z.ZodEnum<{
4
+ finished: "finished";
5
+ started: "started";
6
+ }>;
7
+ outcome: z.ZodOptional<z.ZodString>;
8
+ elapsedMs: z.ZodOptional<z.ZodNumber>;
9
+ phase: z.ZodEnum<{
10
+ cleanup: "cleanup";
11
+ materialize: "materialize";
12
+ setup: "setup";
13
+ declaration: "declaration";
14
+ }>;
15
+ cwd: z.ZodOptional<z.ZodString>;
16
+ name: z.ZodOptional<z.ZodString>;
17
+ command: z.ZodOptional<z.ZodString>;
18
+ index: z.ZodOptional<z.ZodNumber>;
19
+ total: z.ZodOptional<z.ZodNumber>;
20
+ kind: z.ZodLiteral<"phase">;
21
+ }, z.core.$strict>, z.ZodObject<{
22
+ stream: z.ZodEnum<{
23
+ stdout: "stdout";
24
+ stderr: "stderr";
25
+ }>;
26
+ text: z.ZodString;
27
+ phase: z.ZodEnum<{
28
+ cleanup: "cleanup";
29
+ materialize: "materialize";
30
+ setup: "setup";
31
+ declaration: "declaration";
32
+ }>;
33
+ cwd: z.ZodOptional<z.ZodString>;
34
+ name: z.ZodOptional<z.ZodString>;
35
+ command: z.ZodOptional<z.ZodString>;
36
+ index: z.ZodOptional<z.ZodNumber>;
37
+ total: z.ZodOptional<z.ZodNumber>;
38
+ kind: z.ZodLiteral<"output">;
39
+ }, z.core.$strict>], "kind">;
40
+ /** Attempt-local observations, never testimony, liveness, or recovery authority. */
41
+ export type VerificationObservation = z.infer<typeof verificationObservationSchema>;
@@ -0,0 +1,29 @@
1
+ import { z } from "zod";
2
+ const phase = z.enum(["materialize", "setup", "declaration", "cleanup"]);
3
+ const coordinate = {
4
+ phase,
5
+ cwd: z.string().optional(),
6
+ name: z.string().optional(),
7
+ command: z.string().optional(),
8
+ index: z.number().int().positive().optional(),
9
+ total: z.number().int().nonnegative().optional(),
10
+ };
11
+ export const verificationObservationSchema = z.discriminatedUnion("kind", [
12
+ z
13
+ .object({
14
+ kind: z.literal("phase"),
15
+ ...coordinate,
16
+ state: z.enum(["started", "finished"]),
17
+ outcome: z.string().optional(),
18
+ elapsedMs: z.number().nonnegative().optional(),
19
+ })
20
+ .strict(),
21
+ z
22
+ .object({
23
+ kind: z.literal("output"),
24
+ ...coordinate,
25
+ stream: z.enum(["stdout", "stderr"]),
26
+ text: z.string(),
27
+ })
28
+ .strict(),
29
+ ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/keiyaku",
3
- "version": "4.5.19",
3
+ "version": "4.5.21",
4
4
  "workspaces": [
5
5
  "plugins/*"
6
6
  ],
@@ -39,6 +39,7 @@
39
39
  "node": ">=22.19.0"
40
40
  },
41
41
  "scripts": {
42
+ "release": "npm run test:release && npm version patch && npm publish",
42
43
  "build": "node --input-type=module -e \"import { rmSync } from 'node:fs'; rmSync('build', { recursive: true, force: true })\" && tsc -p tsconfig.json && npm run build --workspace @astrosheep/keiyaku-plugin-square && node scripts/copy-integrations.js && node scripts/build-windows-launcher.js && node --input-type=module -e \"import { chmodSync } from 'node:fs'; chmodSync('build/src/cli/index.js', 0o755)\"",
43
44
  "prepack": "node scripts/verify-package.js",
44
45
  "postpack": "node scripts/verify-package.js",
@@ -80,6 +81,7 @@
80
81
  "@agentclientprotocol/sdk": "^1.3.0",
81
82
  "@anthropic-ai/claude-agent-sdk": "^0.3.226",
82
83
  "@astrosheep/keiyaku-plugin-square": "0.1.5",
84
+ "@astrosheep/square": "^0.3.50",
83
85
  "@earendil-works/pi-coding-agent": "0.85.1",
84
86
  "@opencode-ai/sdk": "1.18.3",
85
87
  "cross-spawn": "^7.0.6",