@astrosheep/keiyaku 4.6.3 → 4.6.6

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 (193) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +47 -11
  2. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/references/automation.md +2 -2
  3. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-babysit/SKILL.md +1 -1
  4. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +8 -9
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +37 -25
  6. package/build/src/akuma/akuma-observe.d.ts +18 -15
  7. package/build/src/akuma/akuma-observe.js +75 -16
  8. package/build/src/akuma/akuma-wait.d.ts +4 -3
  9. package/build/src/akuma/akuma-wait.js +75 -53
  10. package/build/src/akuma/akuma.d.ts +6 -4
  11. package/build/src/akuma/akuma.js +4 -0
  12. package/build/src/akuma/call-initial-tell.d.ts +2 -2
  13. package/build/src/akuma/call-initial-tell.js +1 -1
  14. package/build/src/akuma/call-request.d.ts +2 -2
  15. package/build/src/akuma/heart/index.d.ts +7 -2
  16. package/build/src/akuma/heart/index.js +50 -5
  17. package/build/src/akuma/heart/tells.d.ts +33 -2
  18. package/build/src/akuma/heart/tells.js +24 -18
  19. package/build/src/akuma/heart/timeline.d.ts +23 -1
  20. package/build/src/akuma/heart/timeline.js +28 -21
  21. package/build/src/akuma/projection.d.ts +10 -10
  22. package/build/src/akuma/providers/pi/index.js +72 -39
  23. package/build/src/akuma/selection-observation.d.ts +20 -20
  24. package/build/src/akuma-body.js +1 -0
  25. package/build/src/alias/index.d.ts +0 -1
  26. package/build/src/alias/index.js +0 -4
  27. package/build/src/body/amend.d.ts +0 -4
  28. package/build/src/body/amend.js +0 -3
  29. package/build/src/body/region.d.ts +0 -1
  30. package/build/src/body/region.js +0 -3
  31. package/build/src/body/verification.d.ts +0 -1
  32. package/build/src/cli/commands/akuma-grammar.js +23 -66
  33. package/build/src/cli/commands/akuma.js +17 -30
  34. package/build/src/cli/commands/contract-grammar.d.ts +14 -1
  35. package/build/src/cli/commands/contract-grammar.js +54 -21
  36. package/build/src/cli/commands/contract-help.d.ts +9 -8
  37. package/build/src/cli/commands/contract-help.js +14 -7
  38. package/build/src/cli/commands/contract.js +57 -117
  39. package/build/src/cli/commands/install.d.ts +5 -5
  40. package/build/src/cli/commands/install.js +1 -4
  41. package/build/src/cli/commands/task-grammar.js +10 -66
  42. package/build/src/cli/commands/task.d.ts +0 -1
  43. package/build/src/cli/commands/task.js +34 -53
  44. package/build/src/cli/coordinates.d.ts +1 -2
  45. package/build/src/cli/coordinates.js +1 -4
  46. package/build/src/cli/parse.d.ts +2 -3
  47. package/build/src/cli/parse.js +24 -66
  48. package/build/src/cli/render/akuma.d.ts +3 -16
  49. package/build/src/cli/render/akuma.js +1 -13
  50. package/build/src/cli/render/audit.js +1 -1
  51. package/build/src/cli/render/contract.js +34 -32
  52. package/build/src/cli/render/kanshi-akuma.d.ts +8 -2
  53. package/build/src/cli/render/kanshi-akuma.js +14 -7
  54. package/build/src/cli/render/kanshi.d.ts +3 -1
  55. package/build/src/cli/render/kanshi.js +44 -32
  56. package/build/src/cli/render/receipt.js +6 -1
  57. package/build/src/cli/render/task.d.ts +1 -1
  58. package/build/src/cli/render/task.js +3 -27
  59. package/build/src/cli/render/terminal.d.ts +6 -2
  60. package/build/src/cli/render/terminal.js +8 -2
  61. package/build/src/cli/runtime.js +3 -16
  62. package/build/src/cli/scan.d.ts +26 -0
  63. package/build/src/cli/scan.js +82 -0
  64. package/build/src/cli/streams.d.ts +5 -0
  65. package/build/src/cli/streams.js +11 -0
  66. package/build/src/contract-worktree.js +38 -21
  67. package/build/src/core/facts/codec.d.ts +1 -2
  68. package/build/src/core/facts/codec.js +5 -11
  69. package/build/src/core/facts/types.d.ts +16 -2
  70. package/build/src/core/facts/types.js +15 -0
  71. package/build/src/core/verbs/abandon.js +3 -9
  72. package/build/src/core/verbs/amend.js +2 -9
  73. package/build/src/core/verbs/arc.js +6 -13
  74. package/build/src/core/verbs/attestation.js +2 -9
  75. package/build/src/core/verbs/bind.js +2 -9
  76. package/build/src/core/verbs/deliver.js +2 -9
  77. package/build/src/core/verbs/placement.d.ts +1 -1
  78. package/build/src/core/verbs/placement.js +2 -9
  79. package/build/src/core/verbs/reintegrate.d.ts +1 -1
  80. package/build/src/core/verbs/reintegrate.js +2 -9
  81. package/build/src/dispatch/index.d.ts +2 -2
  82. package/build/src/dispatch/index.js +33 -11
  83. package/build/src/git/hooks.d.ts +2 -4
  84. package/build/src/git/hooks.js +0 -12
  85. package/build/src/git/integration.d.ts +5 -3
  86. package/build/src/git/integration.js +36 -25
  87. package/build/src/git/nuke.js +4 -4
  88. package/build/src/git/observe.d.ts +4 -4
  89. package/build/src/git/observe.js +5 -5
  90. package/build/src/git/process.d.ts +1 -4
  91. package/build/src/git/process.js +1 -11
  92. package/build/src/git/read-observation.js +1 -1
  93. package/build/src/git/reconcile.d.ts +1 -1
  94. package/build/src/git/reconcile.js +15 -13
  95. package/build/src/git/repository.d.ts +2 -6
  96. package/build/src/git/repository.js +18 -31
  97. package/build/src/git/scratch.d.ts +6 -6
  98. package/build/src/git/scratch.js +110 -46
  99. package/build/src/git/target-placement.d.ts +5 -5
  100. package/build/src/git/target-placement.js +18 -33
  101. package/build/src/git/tender.d.ts +6 -2
  102. package/build/src/git/tender.js +19 -12
  103. package/build/src/git/terminal-reconcile.js +24 -13
  104. package/build/src/git/terminal-seal.d.ts +2 -5
  105. package/build/src/git/terminal-seal.js +5 -3
  106. package/build/src/git/workspace.d.ts +17 -13
  107. package/build/src/git/workspace.js +58 -41
  108. package/build/src/kanshi/index.d.ts +2 -2
  109. package/build/src/kanshi/index.js +1 -1
  110. package/build/src/kanshi/read.d.ts +8 -1
  111. package/build/src/kanshi/read.js +72 -7
  112. package/build/src/kanshi/report.d.ts +12 -0
  113. package/build/src/library/akuma-creation.d.ts +4 -4
  114. package/build/src/library/bind.d.ts +1 -63
  115. package/build/src/library/bind.js +3 -9
  116. package/build/src/library/contract-creation.js +2 -30
  117. package/build/src/library/contract-execution.d.ts +0 -1
  118. package/build/src/library/contract-execution.js +9 -34
  119. package/build/src/library/contract-operations.d.ts +5 -1
  120. package/build/src/library/contract-operations.js +7 -3
  121. package/build/src/library/contract-types.d.ts +1 -1
  122. package/build/src/library/delivery.d.ts +91 -93
  123. package/build/src/library/delivery.js +2 -1
  124. package/build/src/library/input.d.ts +0 -2
  125. package/build/src/library/input.js +0 -4
  126. package/build/src/library/invocation.d.ts +0 -1
  127. package/build/src/library/invocation.js +3 -25
  128. package/build/src/library/outcome.d.ts +651 -602
  129. package/build/src/library/outcome.js +4 -4
  130. package/build/src/library/reconcile.d.ts +6 -0
  131. package/build/src/library/reconcile.js +31 -1
  132. package/build/src/library/repo.d.ts +0 -1
  133. package/build/src/library/selection.d.ts +1 -10
  134. package/build/src/library/selection.js +14 -15
  135. package/build/src/protocol/abandon.d.ts +1 -1
  136. package/build/src/protocol/abandon.js +3 -10
  137. package/build/src/protocol/amend.js +2 -5
  138. package/build/src/protocol/arc.d.ts +1 -1
  139. package/build/src/protocol/arc.js +2 -9
  140. package/build/src/protocol/attempt.d.ts +3 -20
  141. package/build/src/protocol/attempt.js +6 -5
  142. package/build/src/protocol/audit.d.ts +1 -0
  143. package/build/src/protocol/audit.js +10 -11
  144. package/build/src/protocol/completion.d.ts +129 -55
  145. package/build/src/protocol/completion.js +62 -73
  146. package/build/src/protocol/deliver.d.ts +7 -5
  147. package/build/src/protocol/deliver.js +74 -44
  148. package/build/src/protocol/execution-observation.d.ts +2 -4
  149. package/build/src/protocol/execution-observation.js +3 -7
  150. package/build/src/protocol/intent.d.ts +1 -1
  151. package/build/src/protocol/intent.js +1 -0
  152. package/build/src/protocol/operations.d.ts +24 -12
  153. package/build/src/protocol/operations.js +36 -15
  154. package/build/src/protocol/placement.d.ts +2 -1
  155. package/build/src/protocol/placement.js +21 -5
  156. package/build/src/protocol/progress.d.ts +1 -1
  157. package/build/src/protocol/read/status.js +58 -41
  158. package/build/src/protocol/reintegrate.d.ts +1 -1
  159. package/build/src/protocol/reintegrate.js +10 -19
  160. package/build/src/protocol/review.d.ts +3 -28
  161. package/build/src/protocol/review.js +10 -37
  162. package/build/src/protocol/run.d.ts +19 -7
  163. package/build/src/protocol/run.js +19 -9
  164. package/build/src/runtime/proc/windows-launch.exe +0 -0
  165. package/build/src/settlement/holder.d.ts +2 -4
  166. package/build/src/settlement/holder.js +3 -6
  167. package/build/src/task/board.d.ts +6 -0
  168. package/build/src/task/board.js +30 -1
  169. package/build/src/task/catalog.js +1 -4
  170. package/build/src/task/compose.d.ts +0 -1
  171. package/build/src/task/compose.js +0 -1
  172. package/build/src/task/context.d.ts +0 -1
  173. package/build/src/task/context.js +0 -3
  174. package/build/src/task/created-observation.d.ts +5 -1
  175. package/build/src/task/created-observation.js +12 -5
  176. package/build/src/task/index.d.ts +1 -1
  177. package/build/src/task/index.js +7 -6
  178. package/build/src/task/input.d.ts +0 -6
  179. package/build/src/task/input.js +2 -14
  180. package/build/src/task/mutation.d.ts +3 -4
  181. package/build/src/task/mutation.js +11 -39
  182. package/build/src/task/operations.d.ts +2 -7
  183. package/build/src/task/operations.js +37 -49
  184. package/build/src/task/query.d.ts +0 -1
  185. package/build/src/task/query.js +1 -4
  186. package/build/src/task/store.d.ts +0 -2
  187. package/build/src/task/store.js +27 -11
  188. package/build/src/verification/declaration.js +3 -2
  189. package/build/src/verification/execution.d.ts +3 -2
  190. package/build/src/verification/execution.js +10 -6
  191. package/build/src/workspace-place.d.ts +0 -1
  192. package/build/src/workspace-place.js +0 -11
  193. package/package.json +7 -3
@@ -187,7 +187,7 @@ function acceptedLagRows(result, columns) {
187
187
  receiptRow(obligations, "!", "settlement owner", [{ text: contract, opaque: true }], columns);
188
188
  pushBlock(obligations, settlementLagRows(lag, columns));
189
189
  }
190
- pushBlock(obligations, executionCleanupLines(effectCleanup(result.effects).filter((issue) => issue.kind !== "worktree-leak"), columns, result.contract));
190
+ pushBlock(obligations, executionCleanupLines(effectCleanup(result.effects), columns, result.contract));
191
191
  pushBlock(obligations, executionStopLines(effectExecutionStops(result.effects), columns));
192
192
  return obligations;
193
193
  }
@@ -205,21 +205,6 @@ function recordBlock(result, columns) {
205
205
  const rows = [...acceptedRecord(result, columns), ...acceptedLagRows(result, columns)];
206
206
  return rows;
207
207
  }
208
- function nonGatingVerificationLines(result, columns) {
209
- const lines = [];
210
- const verification = result.value.completion?.verification;
211
- if (verification === undefined || verification.verdict !== "unsatisfied")
212
- return lines;
213
- receiptRow(lines, "!", "verification", [
214
- ...(verification.mode === "reused" ? [{ text: "reused" }] : []),
215
- { text: "unsatisfied" },
216
- { text: "· not required by Contract gates" },
217
- ], columns);
218
- if (result.value.verificationSummary !== undefined) {
219
- receiptPayload(lines, " summary", result.value.verificationSummary);
220
- }
221
- return lines;
222
- }
223
208
  /** A terminal command names the worktree it retired, or the retained worktree it failed to remove. */
224
209
  function worktreeRetirementLines(result, columns) {
225
210
  const lines = [];
@@ -281,15 +266,15 @@ function completedPlacementLines(result, columns) {
281
266
  { text: completion.target, opaque: true },
282
267
  ], columns);
283
268
  }
284
- if (completion.verification?.verdict === "satisfied") {
269
+ const verification = result.value.verification;
270
+ if (verification?.kind !== "stopped" && verification?.verdict === "satisfied") {
285
271
  receiptRow(lines, " ", "integration result", [
286
272
  {
287
- text: `${displayGitId(completion.integration, abbreviations)} · verification ${completion.verification.mode === "reused" ? "reused " : ""}satisfied`,
273
+ text: `${displayGitId(completion.integration, abbreviations)} · verification ${verification.kind === "reused" ? "reused " : ""}satisfied`,
288
274
  },
289
275
  ], columns);
290
276
  }
291
277
  lines.push(...landedChangesLines(completion.scope, columns));
292
- lines.push(...nonGatingVerificationLines(result, columns));
293
278
  // Placement admission always admits the claim entry beside the movement, so a completed placement is accepted.
294
279
  receiptRow(lines, "✓", lifecycleWord("claimed"), [], columns);
295
280
  return lines;
@@ -372,33 +357,50 @@ function deliverIdentityLines(result, abbreviations, columns, kept) {
372
357
  receiptRow(lines, " ", "candidate", [{ text: `${displayGitId(result.value.tenderSnapshot, abbreviations)}${kept ? " · kept" : ""}`, opaque: true }], columns);
373
358
  if (result.value.integration !== undefined && !/^0{40}$/u.test(result.value.integration.changeId))
374
359
  receiptRow(lines, " ", "content identity", [{ text: displayGitId(result.value.integration.changeId, abbreviations), opaque: true }], columns);
360
+ if (result.value.contentChange !== undefined)
361
+ receiptRow(lines, " ", "candidate-content-changed", [
362
+ { text: `previous ${displayGitId(result.value.contentChange.previousChange, abbreviations)}`, opaque: true },
363
+ { text: `current ${displayGitId(result.value.contentChange.currentChange, abbreviations)}`, opaque: true },
364
+ ], columns);
365
+ if (result.value.note !== undefined)
366
+ receiptPayload(lines, "note", result.value.note);
367
+ const reusedIntegration = result.value.leading.reusedIntegration;
368
+ if (reusedIntegration !== undefined)
369
+ receiptRow(lines, " ", "integration", [{ text: `${displayGitId(reusedIntegration, abbreviations)} reused · commit message kept from first delivery` }], columns);
375
370
  return lines;
376
371
  }
372
+ function deliveryIdentityAbbreviations(value, verificationSnapshot) {
373
+ return abbreviateGitIds([
374
+ value.tenderSnapshot ?? "",
375
+ value.integration?.changeId ?? "",
376
+ value.integration.snapshot,
377
+ value.contentChange?.previousChange ?? "",
378
+ value.contentChange?.currentChange ?? "",
379
+ verificationSnapshot ?? "",
380
+ value.completion?.predecessor ?? "",
381
+ value.completion?.integration ?? "",
382
+ ]);
383
+ }
377
384
  function renderAcceptedDeliver(result, columns) {
378
385
  const complete = result.value.completion !== undefined;
386
+ const verification = result.value.verification;
387
+ const subject = verification?.kind === "stopped" ? undefined : verification;
379
388
  const lines = titleLines("✓", "delivered", result.contract, columns);
380
- const abbreviations = abbreviateGitIds([
381
- result.value.tenderSnapshot ?? "",
382
- result.value.integration?.changeId ?? "",
383
- result.value.verificationSubject?.snapshot ?? "",
384
- result.value.completion?.predecessor ?? "",
385
- result.value.completion?.integration ?? "",
386
- ]);
389
+ const abbreviations = deliveryIdentityAbbreviations(result.value, subject?.snapshot);
387
390
  lines.push(...deliverIdentityLines(result, abbreviations, columns, !complete));
388
391
  if (complete)
389
392
  lines.push(...completedPlacementLines(result, columns));
390
393
  else {
391
- const subject = result.value.verificationSubject;
392
394
  if (subject !== undefined)
393
395
  receiptRow(lines, " ", "integration result", [
394
396
  {
395
- text: `${displayGitId(subject.snapshot, abbreviations)} · verification ${subject.mode === "reused" ? "reused " : ""}${subject.verdict}`,
397
+ text: `${displayGitId(subject.snapshot, abbreviations)} · verification ${subject.kind === "reused" ? "reused " : ""}${subject.verdict}`,
396
398
  },
397
399
  ], columns);
398
400
  lines.push(...movementLines(result, columns));
399
401
  }
400
- if (result.value.verification !== undefined)
401
- lines.push(...stopLines(result.value.verification, columns, result.contract));
402
+ if (verification?.kind === "stopped")
403
+ lines.push(...stopLines(verification.stop, columns, result.contract));
402
404
  if (!complete && result.value.placement !== undefined)
403
405
  lines.push(...stopLines(result.value.placement, columns, result.contract));
404
406
  lines.push(...continuationLines(result, columns), ...(complete ? obligationLines(result, columns) : recordBlock(result, columns)), ...worktreeRetirementLines(result, columns), ...gatesAwaitLines(result.value.placement));
@@ -407,8 +409,8 @@ function renderAcceptedDeliver(result, columns) {
407
409
  function renderAcceptedReview(result, columns) {
408
410
  const lines = titleLines("✓", `review ${attestationVerdict(result.facts)}`, result.contract, columns);
409
411
  lines.push(...completedPlacementLines(result, columns));
410
- if (result.value.verification !== undefined) {
411
- lines.push(...stopLines(result.value.verification, columns, result.contract));
412
+ if (result.value.verification?.kind === "stopped") {
413
+ lines.push(...stopLines(result.value.verification.stop, columns, result.contract));
412
414
  }
413
415
  if (result.value.placement !== undefined) {
414
416
  lines.push(...stopLines(result.value.placement, columns, result.contract));
@@ -1,7 +1,13 @@
1
- import type { KanshiReport } from "../../kanshi/index.js";
1
+ import type { AkumaKanshiWorld, KanshiReport, Section } from "../../kanshi/index.js";
2
2
  import { type TextRenderContext } from "./terminal.js";
3
3
  declare const NARROW_COLUMNS = 72;
4
4
  declare function endpointFact(id: string, observed: string | undefined): string;
5
+ /**
6
+ * One Akuma board section. The World board omits an empty section because its other
7
+ * sections already say what the World holds; a category board states that emptiness
8
+ * itself, since it is the whole answer.
9
+ */
10
+ declare function renderAkumaBoard(section: Section<AkumaKanshiWorld>, observedAt: string, context: TextRenderContext, emptyRows: readonly string[]): readonly string[];
5
11
  declare function renderAkuma(report: KanshiReport, context: TextRenderContext): readonly string[];
6
12
  declare function formatAge(source: string | null | undefined, observedAt: string): string;
7
- export { endpointFact, formatAge, NARROW_COLUMNS, renderAkuma };
13
+ export { endpointFact, formatAge, NARROW_COLUMNS, renderAkuma, renderAkumaBoard };
@@ -38,22 +38,26 @@ function endpointFact(id, observed) {
38
38
  // cannot name the reason is absent, while a complete read that established absence says so.
39
39
  return observed === "missing" ? `${id} (missing)` : id;
40
40
  }
41
- function renderAkuma(report, context) {
42
- const section = report.akuma;
41
+ /**
42
+ * One Akuma board section. The World board omits an empty section because its other
43
+ * sections already say what the World holds; a category board states that emptiness
44
+ * itself, since it is the whole answer.
45
+ */
46
+ function renderAkumaBoard(section, observedAt, context, emptyRows) {
43
47
  if (section.kind === "absent")
44
48
  return ["AKUMA // absent"];
45
49
  if (section.kind === "failed")
46
50
  return ["AKUMA // unavailable", "", tone(`! ${safeText(section.failure.message)}`, "alert", context.color)];
47
51
  const rows = section.value.rows;
48
52
  if (rows.length === 0)
49
- return [];
53
+ return emptyRows;
50
54
  const rowLines = rows.map((row) => {
51
- const statusTone = akumaStatusTone(row, report.observedAt);
55
+ const statusTone = akumaStatusTone(row, observedAt);
52
56
  const mark = statusTone === null ? akumaMark(row.life) : tone(akumaMark(row.life), statusTone, context.color);
53
57
  const lifeAt = "lifeAt" in row ? row.lifeAt : null;
54
- const lifeAge = formatAge(lifeAt, report.observedAt);
58
+ const lifeAge = formatAge(lifeAt, observedAt);
55
59
  const life = `${row.life} · ${lifeAge}`;
56
- const activityAge = "lastActivityAt" in row && row.lastActivityAt !== null ? formatAge(row.lastActivityAt, report.observedAt) : null;
60
+ const activityAge = "lastActivityAt" in row && row.lastActivityAt !== null ? formatAge(row.lastActivityAt, observedAt) : null;
57
61
  const activity = activityAge === null || activityAge === lifeAge ? [] : [`activity ${activityAge}`];
58
62
  const key = row.life === "stranded" && "strandedReason" in row && row.strandedReason === "resume-unsupported"
59
63
  ? [life, ...activity, "resume unsupported"]
@@ -85,9 +89,12 @@ function renderAkuma(report, context) {
85
89
  const header = `AKUMA // ${rows.length} recent`;
86
90
  return [header, ...rendered.slice(1)];
87
91
  }
92
+ function renderAkuma(report, context) {
93
+ return renderAkumaBoard(report.akuma, report.observedAt, context, []);
94
+ }
88
95
  function formatAge(source, observedAt) {
89
96
  if (source === null || source === undefined)
90
97
  return "—";
91
98
  return ageText(source, observedAt);
92
99
  }
93
- export { endpointFact, formatAge, NARROW_COLUMNS, renderAkuma };
100
+ export { endpointFact, formatAge, NARROW_COLUMNS, renderAkuma, renderAkumaBoard };
@@ -1,3 +1,5 @@
1
- import type { KanshiReport } from "../../kanshi/index.js";
1
+ import type { KanshiCategoryReport, KanshiReport } from "../../kanshi/index.js";
2
2
  import { type TextRenderContext } from "./terminal.js";
3
3
  export declare function renderKanshiText(report: KanshiReport, context?: TextRenderContext, selection?: "world" | "contract"): string;
4
+ /** One category board: the section the selector asked for, and nothing it did not read. */
5
+ export declare function renderKanshiCategoryText(report: KanshiCategoryReport, context?: TextRenderContext): string;
@@ -1,6 +1,6 @@
1
1
  import { abbreviateGitIds, afterWording, progressStrip, displayGitId, gateFact, gitIdsInRow, lifecycleWord, mergeSummary, targetMovementFacts, verificationFact, } from "./contract-observation.js";
2
- import { DEFAULT_CLI_COLUMNS, entityLines, elapsedMilliseconds, identityLine, plumbFacts, RECENT_TONE_MS, renderBoundedPayload, renderSectionBlock, safeText, tone, } from "./terminal.js";
3
- import { endpointFact, formatAge, NARROW_COLUMNS, renderAkuma } from "./kanshi-akuma.js";
2
+ import { DEFAULT_CLI_COLUMNS, emptyCatalogue, entityLines, elapsedMilliseconds, identityLine, plumbFacts, RECENT_TONE_MS, renderBoundedPayload, renderSectionBlock, safeText, tone, } from "./terminal.js";
3
+ import { endpointFact, formatAge, NARROW_COLUMNS, renderAkuma, renderAkumaBoard } from "./kanshi-akuma.js";
4
4
  import { contractMark, taskDispositionMark } from "./marks.js";
5
5
  import { dispositionText } from "./task.js";
6
6
  const REVIEW_ATTENTION_MS = 15 * 60 * 1_000;
@@ -26,13 +26,11 @@ function contractStatusTone(row, observedAt) {
26
26
  const journalAge = elapsedMilliseconds(row.lastJournalAt, observedAt);
27
27
  return journalAge !== null && journalAge <= RECENT_TONE_MS ? "recent" : null;
28
28
  }
29
- function gitAbbreviations(report) {
29
+ function contractAbbreviations(section) {
30
30
  const ids = [];
31
- if (report.contracts.kind !== "present")
32
- return abbreviateGitIds(ids);
33
- if (report.contracts.value.state !== null)
34
- ids.push(report.contracts.value.state);
35
- report.contracts.value.rows.forEach((row) => ids.push(...gitIdsInRow(row)));
31
+ if (section.state !== null)
32
+ ids.push(section.state);
33
+ section.rows.forEach((row) => ids.push(...gitIdsInRow(row)));
36
34
  return abbreviateGitIds(ids);
37
35
  }
38
36
  function workspaceState(row) {
@@ -54,10 +52,10 @@ function mergeFacts(row) {
54
52
  const summary = mergeSummary(row.workspaceObservation);
55
53
  return summary === undefined ? [] : [summary];
56
54
  }
57
- function linkedTask(report, taskId) {
58
- if (report.tasks.kind !== "present")
55
+ function linkedTask(tasks, taskId) {
56
+ if (tasks.kind !== "present")
59
57
  return `! ${taskId} · unavailable`;
60
- const task = report.tasks.value.rows.find((candidate) => candidate.id === taskId);
58
+ const task = tasks.value.rows.find((candidate) => candidate.id === taskId);
61
59
  return task === undefined
62
60
  ? `! ${taskId} · unavailable`
63
61
  : `${taskDispositionMark(task.disposition)} ${task.id} · ${dispositionText(task.disposition)}`;
@@ -65,12 +63,12 @@ function linkedTask(report, taskId) {
65
63
  function isTerminalAkuma(life) {
66
64
  return life === "killed" || life === "stillborn";
67
65
  }
68
- function linkedAkumaSummary(row, report) {
66
+ function linkedAkumaSummary(row, akuma) {
69
67
  if (row.roster.length === 0)
70
68
  return undefined;
71
- if (report.akuma.kind !== "present")
69
+ if (akuma === undefined || akuma.kind !== "present")
72
70
  return "akuma unavailable";
73
- const byId = new Map(report.akuma.value.rows.map((akuma) => [akuma.id, akuma]));
71
+ const byId = new Map(akuma.value.rows.map((akuma) => [akuma.id, akuma]));
74
72
  const known = row.roster
75
73
  .map((attached) => byId.get(attached.id))
76
74
  .filter((akuma) => akuma !== undefined);
@@ -104,11 +102,11 @@ function payload(label, text, context) {
104
102
  truncated: false,
105
103
  });
106
104
  }
107
- function liveAlarms(row, report) {
105
+ function liveAlarms(row, tasks) {
108
106
  return [
109
107
  ...mergeFacts(row),
110
108
  ...(row.holder.kind === "unavailable" ? ["task unavailable"] : []),
111
- ...(row.holder.kind === "held" && linkedTask(report, row.holder.taskId).startsWith("!")
109
+ ...(row.holder.kind === "held" && tasks !== undefined && linkedTask(tasks, row.holder.taskId).startsWith("!")
112
110
  ? ["task unavailable"]
113
111
  : []),
114
112
  ...(row.issue === undefined ? [] : ["pending reconciliation"]),
@@ -135,14 +133,14 @@ function staleVerificationFacts(row) {
135
133
  const fact = verificationFact(row.verification);
136
134
  return fact === undefined ? [] : [fact];
137
135
  }
138
- function renderSelectedContractRow(row, report, context, abbreviations) {
136
+ function renderSelectedContractRow(row, facts, context, abbreviations) {
139
137
  const title = row.title ?? "title unavailable";
140
- const statusTone = contractStatusTone(row, report.observedAt);
138
+ const statusTone = contractStatusTone(row, facts.observedAt);
141
139
  const lines = [
142
140
  ...entityLines({
143
141
  mark: statusTone === null ? contractMark(row) : tone(contractMark(row), statusTone, context.color),
144
142
  identity: row.id,
145
- state: `${lifecycleWord(row.phase)} · ${formatAge(row.phaseAt, report.observedAt)}`,
143
+ state: `${lifecycleWord(row.phase)} · ${formatAge(row.phaseAt, facts.observedAt)}`,
146
144
  title,
147
145
  facts: [],
148
146
  context,
@@ -170,51 +168,57 @@ function renderSelectedContractRow(row, report, context, abbreviations) {
170
168
  }
171
169
  if (deniedReview?.current.kind === "attested" && deniedReview.current.summary !== undefined)
172
170
  lines.push(...payload("review ×", deniedReview.current.summary, context));
173
- const akuma = linkedAkumaSummary(row, report);
171
+ const akuma = linkedAkumaSummary(row, facts.akuma);
174
172
  if (akuma !== undefined)
175
173
  lines.push(...semanticBlock("akuma", [akuma], context));
176
174
  if (row.worktreePath !== null)
177
175
  lines.push(...semanticBlock("worktree", [`worktree ${row.worktreePath}`], context));
178
- lines.push(...semanticBlock("alarms", liveAlarms(row, report), context));
176
+ lines.push(...semanticBlock("alarms", liveAlarms(row, facts.tasks), context));
179
177
  return lines;
180
178
  }
181
- function renderWorldContractRow(row, report, context) {
179
+ function renderWorldContractRow(row, facts, context, abbreviations) {
182
180
  const title = row.title ?? "title unavailable";
183
- const abbreviations = gitAbbreviations(report);
184
181
  const contractFacts = row.phase === "claimed" || row.phase === "abandoned"
185
182
  ? terminalFacts(row, context, abbreviations)
186
183
  .filter((fact) => !fact.startsWith(" "))
187
184
  .slice(0, 1)
188
185
  : [
189
186
  progressStrip(row),
190
- ...(linkedAkumaSummary(row, report) === undefined ? [] : [linkedAkumaSummary(row, report)]),
191
- ...liveAlarms(row, report),
187
+ ...(linkedAkumaSummary(row, facts.akuma) === undefined ? [] : [linkedAkumaSummary(row, facts.akuma)]),
188
+ ...liveAlarms(row, facts.tasks),
192
189
  ];
193
- const statusTone = contractStatusTone(row, report.observedAt);
190
+ const statusTone = contractStatusTone(row, facts.observedAt);
194
191
  return entityLines({
195
192
  mark: statusTone === null ? contractMark(row) : tone(contractMark(row), statusTone, context.color),
196
193
  identity: row.id,
197
- state: formatAge(row.phaseAt, report.observedAt),
194
+ state: formatAge(row.phaseAt, facts.observedAt),
198
195
  title,
199
196
  facts: contractFacts,
200
197
  context,
201
198
  });
202
199
  }
203
- function renderContracts(report, context) {
204
- const section = report.contracts;
200
+ /**
201
+ * One Contract board section. A category board states its own emptiness; the World
202
+ * board leaves an empty section out because the world is its surrounding answer.
203
+ */
204
+ function renderContractBoard(section, facts, context, emptyRows) {
205
205
  if (section.kind === "absent")
206
206
  return ["CONTRACTS // absent"];
207
207
  if (section.kind === "failed")
208
208
  return ["CONTRACTS // unavailable", "", tone(`! ${safeText(section.failure.message)}`, "alert", context.color)];
209
209
  if (section.value.rows.length === 0)
210
- return [];
210
+ return emptyRows;
211
+ const abbreviations = contractAbbreviations(section.value);
211
212
  const rendered = renderSectionBlock({
212
213
  name: "CONTRACTS",
213
- rows: section.value.rows.map((row) => renderWorldContractRow(row, report, context)),
214
+ rows: section.value.rows.map((row) => renderWorldContractRow(row, facts, context, abbreviations)),
214
215
  });
215
216
  const header = "CONTRACTS // recent";
216
217
  return [header, ...rendered.slice(1), ...(section.value.hasMore === true ? ["…"] : [])];
217
218
  }
219
+ function renderContracts(report, context) {
220
+ return renderContractBoard(report.contracts, { observedAt: report.observedAt, akuma: report.akuma, tasks: report.tasks }, context, []);
221
+ }
218
222
  function renderSelectedContract(report, context) {
219
223
  const section = report.contracts;
220
224
  if (section.kind === "absent")
@@ -224,7 +228,7 @@ function renderSelectedContract(report, context) {
224
228
  const row = section.value.rows[0];
225
229
  return row === undefined
226
230
  ? [" keiyaku absent"]
227
- : renderSelectedContractRow(row, report, context, gitAbbreviations(report));
231
+ : renderSelectedContractRow(row, { observedAt: report.observedAt, akuma: report.akuma, tasks: report.tasks }, context, contractAbbreviations(section.value));
228
232
  }
229
233
  function taskFailureFact(failure) {
230
234
  return failure.coordinate === undefined ? failure.message : `${failure.coordinate} · failed ${failure.message}`;
@@ -277,3 +281,11 @@ export function renderKanshiText(report, context = { columns: DEFAULT_CLI_COLUMN
277
281
  const blocks = [renderContracts(report, context), renderAkuma(report, context), renderTasks(report, context)].filter((block) => block.length > 0);
278
282
  return blocks.length === 0 ? "○ world empty" : blocks.map((block) => block.join("\n")).join("\n\n");
279
283
  }
284
+ /** One category board: the section the selector asked for, and nothing it did not read. */
285
+ export function renderKanshiCategoryText(report, context = { columns: DEFAULT_CLI_COLUMNS, color: false }) {
286
+ if (report.category === "akuma")
287
+ return renderAkumaBoard(report.akuma, report.observedAt, context, [emptyCatalogue("akuma")]).join("\n");
288
+ return renderContractBoard(report.contracts, { observedAt: report.observedAt }, context, [
289
+ emptyCatalogue("contracts"),
290
+ ]).join("\n");
291
+ }
@@ -243,7 +243,12 @@ export function cleanupLines(cleanup, columns) {
243
243
  }
244
244
  export function leakLines(leak, columns) {
245
245
  const lines = [];
246
- receiptRow(lines, " ", "verification scratch kept at", [{ text: leak.path, opaque: true }], columns);
246
+ if (leak.path === undefined) {
247
+ receiptRow(lines, " ", "verification scratch not retained", [], columns);
248
+ }
249
+ else {
250
+ receiptRow(lines, " ", "verification scratch kept at", [{ text: leak.path, opaque: true }], columns);
251
+ }
247
252
  receiptPayload(lines, "reason", leak.diagnostic);
248
253
  return lines;
249
254
  }
@@ -22,12 +22,12 @@ type TaskFailure = Extract<TaskMutationResult, {
22
22
  /** Task dispositions and relation states are snake_case facts; people read them as words. */
23
23
  export declare function dispositionText(word: string): string;
24
24
  export declare function taskFrameHead(view: string, scope: string): string;
25
+ export declare function renderDoctor(report: TaskDoctorReport): string;
25
26
  /** One addressed show selection rendered directly from its native answer. */
26
27
  export declare function renderTaskShow(result: TaskShowResult, context?: TextRenderContext): string;
27
28
  /** One list read rendered from its native answer and the leaf's presentation-only scope. */
28
29
  export declare function renderTaskList(action: "ls" | "ready" | "blocked" | "query", scope: TaskListScope, result: TaskListOutcome, context?: TextRenderContext): string;
29
30
  export declare function renderTaskTree(result: TaskDecompositionTree, context?: TextRenderContext): string;
30
- export declare function renderTaskDoctor(result: TaskDoctorReport): string;
31
31
  export declare function renderTaskContext(result: TaskContextResult, context?: TextRenderContext): string;
32
32
  export declare function renderTaskCompose(result: TaskCompositionResult, context?: TextRenderContext): string;
33
33
  /** One single-Task mutation answer; `verb` is the literal action the leaf invoked. */
@@ -1,6 +1,6 @@
1
1
  import { taskMark } from "./marks.js";
2
2
  import { outcomeLines, receiptPayload, receiptRow, refusalLines } from "./receipt.js";
3
- import { DEFAULT_CLI_COLUMNS, displayColumns, emptyCatalogue, safeText } from "./terminal.js";
3
+ import { DEFAULT_CLI_COLUMNS, displayColumns, emptyCatalogue, renderTextBlock, safeText, } from "./terminal.js";
4
4
  export { taskMark } from "./marks.js";
5
5
  const DEFAULT_CONTEXT = { columns: DEFAULT_CLI_COLUMNS, color: false };
6
6
  /** Task dispositions and relation states are snake_case facts; people read them as words. */
@@ -23,28 +23,7 @@ function entityLines(entity, columns, indent = "") {
23
23
  const inline = `${scan} — ${title}`;
24
24
  if (displayColumns(inline) <= columns)
25
25
  return [inline];
26
- return [scan, ...dashTitleLines(title, `${indent} `, columns)];
27
- }
28
- /** Wrap `— <title>` as one unit: the dash never dangles alone at a line end. */
29
- function dashTitleLines(title, indent, columns) {
30
- const words = title
31
- .trim()
32
- .split(/\s+/u)
33
- .filter((word) => word.length > 0);
34
- const unit = words.length === 0 ? [`—`] : [`— ${words[0]}`, ...words.slice(1)];
35
- const lines = [];
36
- let current = indent;
37
- for (const word of unit) {
38
- const candidate = current === indent ? `${indent}${word}` : `${current} ${word}`;
39
- if (current !== indent && displayColumns(candidate) > columns) {
40
- lines.push(current);
41
- current = `${indent}${word}`;
42
- }
43
- else
44
- current = candidate;
45
- }
46
- lines.push(current);
47
- return lines;
26
+ return [scan, ...renderTextBlock(title, `${indent} `, columns, "—")];
48
27
  }
49
28
  function stateEntity(task) {
50
29
  return {
@@ -206,7 +185,7 @@ function doctorIssue(issue) {
206
185
  return `! self-relation ${issue.taskId} ${issue.relation}`;
207
186
  return `! cycle ${issue.relation} ${issue.tasks.join(" ")}`;
208
187
  }
209
- function renderDoctor(report) {
188
+ export function renderDoctor(report) {
210
189
  if (report.issues.length === 0)
211
190
  return "✓ doctor healthy";
212
191
  const noun = report.issues.length === 1 ? "issue" : "issues";
@@ -306,9 +285,6 @@ export function renderTaskTree(result, context = DEFAULT_CONTEXT) {
306
285
  ? treeLines(result.value, context.columns).join("\n")
307
286
  : renderFailure("tree", result, context.columns);
308
287
  }
309
- export function renderTaskDoctor(result) {
310
- return renderDoctor(result);
311
- }
312
288
  export function renderTaskContext(result, context = DEFAULT_CONTEXT) {
313
289
  if (result.kind !== "accepted")
314
290
  return renderFailure("context", result, context.columns);
@@ -5,7 +5,7 @@ export type TextRenderContext = Readonly<{
5
5
  columns: number;
6
6
  color: boolean;
7
7
  }>;
8
- export type GitShortStat = Readonly<{
8
+ type GitShortStat = Readonly<{
9
9
  filesChanged: number;
10
10
  insertions: number;
11
11
  deletions: number;
@@ -50,7 +50,11 @@ type CheckoutNotFollowable = Readonly<{
50
50
  paths: readonly string[];
51
51
  }>;
52
52
  export declare function checkoutNotFollowableLines(refusal: CheckoutNotFollowable): readonly string[];
53
- export declare function renderTextBlock(value: string, indent: string, columns: number): readonly string[];
53
+ /**
54
+ * Wrap `value` into the column budget under one indent. A `lead` such as an em dash joins the first
55
+ * word as one unit, so it never dangles alone at a line end.
56
+ */
57
+ export declare function renderTextBlock(value: string, indent: string, columns: number, lead?: string): readonly string[];
54
58
  export declare function renderOpaqueBlock(value: string, indent: string, columns: number): readonly string[];
55
59
  export type SemanticTone = "dim" | "recent" | "attention" | "alert";
56
60
  export declare const RECENT_TONE_MS: number;
@@ -234,13 +234,19 @@ export function checkoutNotFollowableLines(refusal) {
234
234
  }
235
235
  return lines;
236
236
  }
237
- export function renderTextBlock(value, indent, columns) {
237
+ /**
238
+ * Wrap `value` into the column budget under one indent. A `lead` such as an em dash joins the first
239
+ * word as one unit, so it never dangles alone at a line end.
240
+ */
241
+ export function renderTextBlock(value, indent, columns, lead) {
238
242
  const words = safeText(value)
239
243
  .trim()
240
244
  .split(/\s+/u)
241
245
  .filter((word) => word.length > 0);
242
246
  if (words.length === 0)
243
- return [indent];
247
+ return [lead === undefined ? indent : `${indent}${lead}`];
248
+ if (lead !== undefined)
249
+ words[0] = `${lead} ${words[0]}`;
244
250
  const lines = [];
245
251
  let current = indent;
246
252
  for (const word of words) {
@@ -15,7 +15,7 @@ import { createExecutionProgressRenderer } from "./render/execution-progress.js"
15
15
  import { executionFailureLines } from "./render/receipt.js";
16
16
  import { renderRefusal, renderStructuredRefusal } from "./render/refusal.js";
17
17
  import { DEFAULT_CLI_COLUMNS, safeText } from "./render/terminal.js";
18
- import { displayContext, writeJson, writeStderr, writeStdout } from "./streams.js";
18
+ import { displayContext, emit, writeJson, writeStderr, writeStdout } from "./streams.js";
19
19
  function processStdin() {
20
20
  return (async () => {
21
21
  const chunks = [];
@@ -78,11 +78,7 @@ async function admitBindInput(command, invocation, runtime, processCwd) {
78
78
  }
79
79
  async function runInstall(command, environment) {
80
80
  const result = await installHarnesses(command.harnesses, environment);
81
- if (command.output === "json")
82
- writeJson(result);
83
- else
84
- writeStdout(renderInstallText(result));
85
- return installExitCode(result);
81
+ return emit(command.output, result, () => renderInstallText(result), installExitCode(result));
86
82
  }
87
83
  /** Projection of one native Akuma addressing/observation failure into its stated refusal body. */
88
84
  export async function akumaFailureProjection(error, command) {
@@ -164,16 +160,7 @@ async function writeFailure(error, command) {
164
160
  return akumaFailure.exitCode;
165
161
  }
166
162
  if (error instanceof AkumaArchetypeError && command.command === "call") {
167
- if (command.output === "json")
168
- writeJson({
169
- kind: "refused",
170
- diagnostic: "Akuma not found",
171
- archetype: error.archetype,
172
- available: "keiyaku ls aku/",
173
- });
174
- else
175
- writeStdout(renderStructuredRefusal("call", `Akuma not found · ${safeText(error.archetype)}`, ["available keiyaku ls aku/"], usageGuideForCommand(command)));
176
- return 1;
163
+ return emit(command.output, { kind: "refused", diagnostic: "Akuma not found", archetype: error.archetype, available: "keiyaku ls aku/" }, () => renderStructuredRefusal("call", `Akuma not found · ${safeText(error.archetype)}`, ["available keiyaku ls aku/"], usageGuideForCommand(command)), 1);
177
164
  }
178
165
  if (command.output === "json")
179
166
  writeJson(encodeFailureWire(error));
@@ -0,0 +1,26 @@
1
+ /** One surface's option table, named the way its refusal text names the surface. */
2
+ export type ScanFlagKind = "boolean" | "value" | "raw-value" | "repeat";
3
+ export type ScanSpec<Reader extends string = string> = Readonly<{
4
+ /** Names this surface in refusal text: `bind`, `call`, `task add`. */
5
+ subject: string;
6
+ flags: Readonly<Record<string, ScanFlagKind>>;
7
+ /** The reader that consumes `-` input; absent makes `-` invalid here. */
8
+ stdin?: Reader;
9
+ /** Refusal text for `-` on a surface that reads no stdin. */
10
+ stdinRefusal: string;
11
+ /** Option whose `-` value is stdin input, named as the reader it selects. */
12
+ stdinValue?: Reader;
13
+ /** Tokens this surface refuses everywhere, each with its own option-position refusal text. */
14
+ refused?: Readonly<Record<string, string>>;
15
+ }>;
16
+ export type ScannedArgs<Reader extends string = string> = Readonly<{
17
+ flags: Record<string, string | true | readonly string[]>;
18
+ positionals: readonly string[];
19
+ stdin?: Reader;
20
+ }>;
21
+ /**
22
+ * One argument scan for every CLI grammar: `--name value` options, `--` positional-only mode,
23
+ * `-` stdin, and the surface's own refusal text. A refused token is refused in option position with
24
+ * its own text, and in value position as the missing-value refusal.
25
+ */
26
+ export declare function scanArgs<Reader extends string = string>(argv: readonly string[], spec: ScanSpec<Reader>, fail: (message: string) => never): ScannedArgs<Reader>;