@astrosheep/keiyaku 4.0.0 → 4.0.2

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 (308) hide show
  1. package/README.md +133 -8
  2. package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +2 -2
  3. package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +4 -4
  4. package/build/integrations/marketplace/plugins/keiyaku/opencode.js +2 -2
  5. package/build/integrations/marketplace/plugins/keiyaku/package.json +2 -2
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +13 -5
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +91 -32
  8. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +134 -0
  9. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/agents/openai.yaml +4 -0
  10. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +41 -6
  11. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +262 -46
  12. package/build/src/akuma/abort.d.ts +2 -0
  13. package/build/src/akuma/abort.js +50 -0
  14. package/build/src/akuma/akuma.d.ts +50 -33
  15. package/build/src/akuma/akuma.js +175 -127
  16. package/build/src/akuma/archetype.d.ts +14 -5
  17. package/build/src/akuma/archetype.js +85 -39
  18. package/build/src/akuma/body.d.ts +3 -5
  19. package/build/src/akuma/body.js +490 -183
  20. package/build/src/akuma/coordinate.d.ts +9 -0
  21. package/build/src/akuma/coordinate.js +22 -0
  22. package/build/src/akuma/heart/facts.d.ts +88 -45
  23. package/build/src/akuma/heart/facts.js +19 -8
  24. package/build/src/akuma/heart/index.d.ts +64 -55
  25. package/build/src/akuma/heart/index.js +172 -252
  26. package/build/src/akuma/heart/rows.d.ts +38 -83
  27. package/build/src/akuma/heart/rows.js +117 -155
  28. package/build/src/akuma/heart/schema.d.ts +1 -1
  29. package/build/src/akuma/heart/schema.js +61 -22
  30. package/build/src/akuma/heart/soul.d.ts +8 -0
  31. package/build/src/akuma/heart/soul.js +136 -0
  32. package/build/src/akuma/heart/storage.d.ts +47 -0
  33. package/build/src/akuma/heart/storage.js +224 -0
  34. package/build/src/akuma/heart/tells.d.ts +12 -0
  35. package/build/src/akuma/heart/tells.js +79 -0
  36. package/build/src/akuma/heart/timeline.d.ts +15 -0
  37. package/build/src/akuma/heart/timeline.js +86 -0
  38. package/build/src/akuma/identity.d.ts +2 -2
  39. package/build/src/akuma/identity.js +9 -9
  40. package/build/src/akuma/index.d.ts +10 -3
  41. package/build/src/akuma/index.js +7 -1
  42. package/build/src/akuma/projection.d.ts +176 -0
  43. package/build/src/akuma/projection.js +256 -0
  44. package/build/src/akuma/provider-recipe.d.ts +24 -0
  45. package/build/src/akuma/provider-recipe.js +100 -0
  46. package/build/src/akuma/provider.d.ts +67 -13
  47. package/build/src/akuma/provider.js +172 -105
  48. package/build/src/akuma/providers/acp/core.d.ts +19 -0
  49. package/build/src/akuma/providers/acp/core.js +159 -0
  50. package/build/src/akuma/providers/acp/events.d.ts +20 -0
  51. package/build/src/akuma/providers/acp/events.js +73 -0
  52. package/build/src/akuma/providers/acp/index.d.ts +12 -0
  53. package/build/src/akuma/providers/acp/index.js +94 -0
  54. package/build/src/akuma/providers/{claude.d.ts → claude/events.d.ts} +9 -41
  55. package/build/src/akuma/providers/claude/events.js +238 -0
  56. package/build/src/akuma/providers/claude/index.d.ts +47 -0
  57. package/build/src/akuma/providers/claude/index.js +279 -0
  58. package/build/src/akuma/providers/claude/input.d.ts +13 -0
  59. package/build/src/akuma/providers/claude/input.js +109 -0
  60. package/build/src/akuma/providers/codex-app-server/events.d.ts +1 -1
  61. package/build/src/akuma/providers/codex-app-server/events.js +15 -12
  62. package/build/src/akuma/providers/codex-app-server/index.d.ts +15 -13
  63. package/build/src/akuma/providers/codex-app-server/index.js +77 -33
  64. package/build/src/akuma/providers/grok-build/index.d.ts +4 -0
  65. package/build/src/akuma/providers/grok-build/index.js +70 -0
  66. package/build/src/akuma/providers/index.d.ts +5 -3
  67. package/build/src/akuma/providers/index.js +35 -56
  68. package/build/src/akuma/providers/opencode-sdk/events.d.ts +54 -0
  69. package/build/src/akuma/providers/opencode-sdk/events.js +235 -0
  70. package/build/src/akuma/providers/opencode-sdk/index.d.ts +33 -0
  71. package/build/src/akuma/providers/opencode-sdk/index.js +308 -0
  72. package/build/src/akuma/providers/opencode-sdk/session.d.ts +26 -0
  73. package/build/src/akuma/providers/opencode-sdk/session.js +63 -0
  74. package/build/src/akuma/providers/pi/events.d.ts +33 -0
  75. package/build/src/akuma/providers/pi/events.js +166 -0
  76. package/build/src/akuma/providers/pi/index.d.ts +11 -0
  77. package/build/src/akuma/providers/pi/index.js +191 -0
  78. package/build/src/akuma/publication.d.ts +3 -3
  79. package/build/src/akuma/publication.js +28 -40
  80. package/build/src/akuma/requests.d.ts +9 -6
  81. package/build/src/akuma/requests.js +95 -65
  82. package/build/src/alias/index.d.ts +4 -3
  83. package/build/src/alias/index.js +12 -13
  84. package/build/src/body/decode.js +7 -1
  85. package/build/src/body/region.d.ts +2 -0
  86. package/build/src/body/region.js +77 -7
  87. package/build/src/body/render.js +2 -1
  88. package/build/src/body/shape.js +7 -1
  89. package/build/src/body/verification.js +19 -4
  90. package/build/src/cli/accepted.d.ts +18 -12
  91. package/build/src/cli/accepted.js +73 -34
  92. package/build/src/cli/actor.js +3 -3
  93. package/build/src/cli/commands/akuma-invoke.d.ts +44 -19
  94. package/build/src/cli/commands/akuma-invoke.js +101 -52
  95. package/build/src/cli/commands/akuma.d.ts +27 -9
  96. package/build/src/cli/commands/akuma.js +151 -80
  97. package/build/src/cli/commands/contract.d.ts +155 -0
  98. package/build/src/cli/commands/contract.js +20 -0
  99. package/build/src/cli/commands/install.js +2 -0
  100. package/build/src/cli/commands/task-invoke.d.ts +19 -4
  101. package/build/src/cli/commands/task-invoke.js +65 -17
  102. package/build/src/cli/commands/task-query.d.ts +5 -0
  103. package/build/src/cli/commands/task-query.js +247 -0
  104. package/build/src/cli/commands/task.d.ts +3 -1
  105. package/build/src/cli/commands/task.js +52 -14
  106. package/build/src/cli/coordinates.d.ts +18 -0
  107. package/build/src/cli/coordinates.js +120 -0
  108. package/build/src/cli/draft.d.ts +8 -0
  109. package/build/src/cli/draft.js +96 -0
  110. package/build/src/cli/invoke.d.ts +9 -3
  111. package/build/src/cli/invoke.js +287 -108
  112. package/build/src/cli/main.js +54 -7
  113. package/build/src/cli/parse.d.ts +21 -117
  114. package/build/src/cli/parse.js +79 -84
  115. package/build/src/cli/render/akuma-tool-command.d.ts +6 -0
  116. package/build/src/cli/render/akuma-tool-command.js +144 -0
  117. package/build/src/cli/render/akuma-tool.d.ts +4 -2
  118. package/build/src/cli/render/akuma-tool.js +35 -5
  119. package/build/src/cli/render/akuma.d.ts +3 -1
  120. package/build/src/cli/render/akuma.js +247 -212
  121. package/build/src/cli/render/audit.d.ts +3 -0
  122. package/build/src/cli/render/audit.js +104 -0
  123. package/build/src/cli/render/catalog.d.ts +2 -0
  124. package/build/src/cli/render/catalog.js +16 -0
  125. package/build/src/cli/render/contract.d.ts +3 -3
  126. package/build/src/cli/render/contract.js +200 -54
  127. package/build/src/cli/render/kanshi.js +272 -129
  128. package/build/src/cli/render/receipt.d.ts +19 -0
  129. package/build/src/cli/render/receipt.js +106 -0
  130. package/build/src/cli/render/refusal.d.ts +16 -2
  131. package/build/src/cli/render/refusal.js +88 -3
  132. package/build/src/cli/render/region.d.ts +2 -0
  133. package/build/src/cli/render/region.js +17 -0
  134. package/build/src/cli/render/task.d.ts +2 -1
  135. package/build/src/cli/render/task.js +223 -60
  136. package/build/src/cli/render/terminal.d.ts +20 -0
  137. package/build/src/cli/render/terminal.js +121 -0
  138. package/build/src/cli/render/text.js +11 -3
  139. package/build/src/cli/result.d.ts +126 -19
  140. package/build/src/cli/usage.d.ts +1 -0
  141. package/build/src/cli/usage.js +3 -0
  142. package/build/src/contract-worktree.d.ts +44 -0
  143. package/build/src/contract-worktree.js +251 -0
  144. package/build/src/coordination/durable-file.d.ts +5 -1
  145. package/build/src/coordination/durable-file.js +57 -10
  146. package/build/src/coordination/sqlite-transaction-lock.d.ts +4 -0
  147. package/build/src/coordination/sqlite-transaction-lock.js +22 -5
  148. package/build/src/core/facts/codec.js +23 -4
  149. package/build/src/core/facts/eligibility.d.ts +1 -10
  150. package/build/src/core/facts/eligibility.js +0 -49
  151. package/build/src/core/facts/fold.js +1 -6
  152. package/build/src/core/facts/gate.d.ts +1 -0
  153. package/build/src/core/facts/gate.js +1 -0
  154. package/build/src/core/facts/observation.d.ts +3 -0
  155. package/build/src/core/facts/observation.js +12 -0
  156. package/build/src/core/facts/types.d.ts +10 -3
  157. package/build/src/core/subject.js +2 -2
  158. package/build/src/core/verbs/amend.d.ts +1 -1
  159. package/build/src/core/verbs/amend.js +3 -18
  160. package/build/src/core/verbs/bind.js +2 -16
  161. package/build/src/core/verbs/deliver.d.ts +1 -1
  162. package/build/src/core/verbs/deliver.js +14 -4
  163. package/build/src/core/verbs/placement.d.ts +1 -1
  164. package/build/src/core/verbs/placement.js +7 -9
  165. package/build/src/dispatch/index.d.ts +5 -3
  166. package/build/src/dispatch/index.js +30 -21
  167. package/build/src/duration.d.ts +10 -0
  168. package/build/src/duration.js +22 -0
  169. package/build/src/git/admission.d.ts +2 -2
  170. package/build/src/git/admission.js +33 -23
  171. package/build/src/git/hooks.d.ts +15 -0
  172. package/build/src/git/hooks.js +70 -18
  173. package/build/src/git/identity.d.ts +5 -1
  174. package/build/src/git/identity.js +23 -4
  175. package/build/src/git/integration.d.ts +48 -0
  176. package/build/src/git/integration.js +250 -0
  177. package/build/src/git/observe.d.ts +29 -15
  178. package/build/src/git/observe.js +185 -76
  179. package/build/src/git/read-observation.d.ts +41 -0
  180. package/build/src/git/read-observation.js +361 -0
  181. package/build/src/git/reconcile.d.ts +17 -5
  182. package/build/src/git/reconcile.js +305 -109
  183. package/build/src/git/repository.d.ts +41 -18
  184. package/build/src/git/repository.js +220 -114
  185. package/build/src/git/scratch.d.ts +17 -0
  186. package/build/src/git/scratch.js +73 -0
  187. package/build/src/git/target-placement.d.ts +92 -0
  188. package/build/src/git/target-placement.js +375 -0
  189. package/build/src/git/tender.d.ts +49 -0
  190. package/build/src/git/tender.js +63 -0
  191. package/build/src/git/terminal-seal.d.ts +24 -0
  192. package/build/src/git/terminal-seal.js +89 -0
  193. package/build/src/git/tree.d.ts +2 -2
  194. package/build/src/git/tree.js +4 -0
  195. package/build/src/git/workspace.d.ts +53 -0
  196. package/build/src/git/workspace.js +139 -0
  197. package/build/src/identity/selector.d.ts +6 -0
  198. package/build/src/identity/selector.js +19 -0
  199. package/build/src/index.d.ts +4 -2
  200. package/build/src/index.js +2 -1
  201. package/build/src/kanshi/index.d.ts +2 -2
  202. package/build/src/kanshi/index.js +1 -1
  203. package/build/src/kanshi/read.d.ts +7 -3
  204. package/build/src/kanshi/read.js +245 -45
  205. package/build/src/kanshi/report.d.ts +81 -6
  206. package/build/src/kanshi/select.d.ts +5 -0
  207. package/build/src/kanshi/select.js +33 -1
  208. package/build/src/library/address.d.ts +57 -0
  209. package/build/src/library/address.js +165 -0
  210. package/build/src/library/akuma-creation.d.ts +16 -7
  211. package/build/src/library/akuma-creation.js +96 -22
  212. package/build/src/library/audit.d.ts +18 -0
  213. package/build/src/library/audit.js +44 -0
  214. package/build/src/library/bind.d.ts +21 -0
  215. package/build/src/library/bind.js +56 -0
  216. package/build/src/library/catalog.d.ts +58 -0
  217. package/build/src/library/catalog.js +69 -0
  218. package/build/src/library/configuration.d.ts +6 -15
  219. package/build/src/library/configuration.js +14 -17
  220. package/build/src/library/contract.d.ts +23 -49
  221. package/build/src/library/contract.js +171 -185
  222. package/build/src/library/delivery.d.ts +24 -0
  223. package/build/src/library/delivery.js +21 -0
  224. package/build/src/library/fleet.d.ts +65 -0
  225. package/build/src/library/fleet.js +153 -0
  226. package/build/src/library/input.d.ts +3 -0
  227. package/build/src/library/input.js +19 -0
  228. package/build/src/library/keiyaku.d.ts +18 -3
  229. package/build/src/library/keiyaku.js +12 -1
  230. package/build/src/library/mutation.d.ts +39 -0
  231. package/build/src/library/mutation.js +37 -0
  232. package/build/src/library/reconcile.d.ts +29 -0
  233. package/build/src/library/reconcile.js +179 -0
  234. package/build/src/library/refusal.d.ts +21 -0
  235. package/build/src/library/refusal.js +29 -0
  236. package/build/src/library/region.d.ts +14 -1
  237. package/build/src/library/region.js +18 -4
  238. package/build/src/library/repo.d.ts +4 -13
  239. package/build/src/library/repo.js +14 -24
  240. package/build/src/protocol/attempt.d.ts +16 -3
  241. package/build/src/protocol/attempt.js +41 -10
  242. package/build/src/protocol/bind.d.ts +9 -2
  243. package/build/src/protocol/bind.js +48 -40
  244. package/build/src/protocol/intent.d.ts +57 -20
  245. package/build/src/protocol/intent.js +55 -79
  246. package/build/src/protocol/operations.d.ts +152 -43
  247. package/build/src/protocol/operations.js +482 -166
  248. package/build/src/protocol/outcome.d.ts +14 -3
  249. package/build/src/protocol/outcome.js +11 -3
  250. package/build/src/protocol/placement.d.ts +41 -0
  251. package/build/src/protocol/placement.js +147 -0
  252. package/build/src/protocol/read/documents.d.ts +2 -2
  253. package/build/src/protocol/read/documents.js +4 -4
  254. package/build/src/protocol/read/status.d.ts +18 -5
  255. package/build/src/protocol/read/status.js +73 -13
  256. package/build/src/protocol/run.d.ts +49 -7
  257. package/build/src/protocol/run.js +53 -25
  258. package/build/src/runtime/proc/line-rpc.d.ts +4 -8
  259. package/build/src/runtime/proc/line-rpc.js +22 -28
  260. package/build/src/runtime/proc/run.d.ts +23 -21
  261. package/build/src/runtime/proc/run.js +123 -89
  262. package/build/src/runtime/proc/stdio.d.ts +18 -0
  263. package/build/src/runtime/proc/stdio.js +58 -0
  264. package/build/src/settings.d.ts +7 -1
  265. package/build/src/settings.js +32 -17
  266. package/build/src/settlement/fence.d.ts +5 -0
  267. package/build/src/settlement/fence.js +14 -0
  268. package/build/src/settlement/holder.d.ts +35 -4
  269. package/build/src/settlement/holder.js +127 -28
  270. package/build/src/settlement/settle.d.ts +9 -1
  271. package/build/src/settlement/settle.js +74 -37
  272. package/build/src/task/board.d.ts +23 -5
  273. package/build/src/task/board.js +17 -10
  274. package/build/src/task/compose.d.ts +2 -2
  275. package/build/src/task/compose.js +18 -10
  276. package/build/src/task/context.d.ts +3 -3
  277. package/build/src/task/context.js +15 -20
  278. package/build/src/task/document.d.ts +2 -1
  279. package/build/src/task/document.js +15 -5
  280. package/build/src/task/index.d.ts +27 -15
  281. package/build/src/task/index.js +57 -35
  282. package/build/src/task/operations.d.ts +38 -13
  283. package/build/src/task/operations.js +80 -46
  284. package/build/src/task/query.d.ts +67 -0
  285. package/build/src/task/query.js +279 -0
  286. package/build/src/task/store.d.ts +5 -7
  287. package/build/src/task/store.js +43 -28
  288. package/build/src/verification/declaration.d.ts +1 -0
  289. package/build/src/verification/execution.d.ts +51 -0
  290. package/build/src/verification/execution.js +108 -0
  291. package/build/src/workspace-place.d.ts +41 -0
  292. package/build/src/workspace-place.js +386 -0
  293. package/build/src/world.d.ts +24 -0
  294. package/build/src/world.js +136 -0
  295. package/package.json +8 -4
  296. package/build/src/akuma/activity.d.ts +0 -66
  297. package/build/src/akuma/activity.js +0 -139
  298. package/build/src/akuma/providers/claude.js +0 -305
  299. package/build/src/context-root.d.ts +0 -4
  300. package/build/src/context-root.js +0 -15
  301. package/build/src/git/delivery.d.ts +0 -20
  302. package/build/src/git/delivery.js +0 -109
  303. package/build/src/git/verification.d.ts +0 -14
  304. package/build/src/git/verification.js +0 -56
  305. package/build/src/protocol/read/audit.d.ts +0 -25
  306. package/build/src/protocol/read/audit.js +0 -39
  307. package/build/src/verification/producer.d.ts +0 -23
  308. package/build/src/verification/producer.js +0 -54
@@ -1,4 +1,89 @@
1
- export function renderRefusal(result) {
2
- const contract = result.contract === undefined ? "" : ` ${result.contract}`;
3
- return `refused ${result.verb}${contract} ${JSON.stringify(result.refusal)}`;
1
+ import { displayColumns, gitShortStat, renderOpaqueBlock, safeText } from "./terminal.js";
2
+ function isRecord(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
5
+ function wrap(lines, text, indent, columns) {
6
+ lines.push(...renderOpaqueBlock(text, indent, columns));
7
+ }
8
+ function collectionLines(name, members, indent, columns) {
9
+ if (members.length === 0)
10
+ return renderOpaqueBlock(`${name} 0`, indent, columns);
11
+ return [
12
+ ...renderOpaqueBlock(name, indent, columns),
13
+ ...members.flatMap((member) => renderOpaqueBlock(member, `${indent}│ `, columns)),
14
+ ];
15
+ }
16
+ function skipAddressedContract(addressed, contractId) {
17
+ return addressed !== undefined && contractId === addressed;
18
+ }
19
+ function refusalIdentity(refusal, addressed) {
20
+ const contractId = "contractId" in refusal ? refusal.contractId : undefined;
21
+ return skipAddressedContract(addressed, contractId) ? undefined : contractId;
22
+ }
23
+ function refusalHead(kind, identity, details) {
24
+ return [kind, identity === undefined ? undefined : `contractId=${identity}`, ...details]
25
+ .filter((part) => part !== undefined).join(" ");
26
+ }
27
+ function renderDirtyRefusal(refusal, indent, columns, identity) {
28
+ const lines = [];
29
+ wrap(lines, refusalHead(refusal.kind, identity, []), indent, columns);
30
+ for (const name of ["staged", "unstaged", "untracked", "submodules"]) {
31
+ lines.push(...collectionLines(name, refusal[name], indent, columns));
32
+ }
33
+ wrap(lines, gitShortStat(refusal.shortStat), indent, columns);
34
+ if (refusal.option !== undefined) {
35
+ wrap(lines, `option ${refusal.option.flag} ${refusal.option.available ? "available" : "unavailable"}`, indent, columns);
36
+ }
37
+ return lines;
38
+ }
39
+ export function renderRefusalFacts(refusal, indent, columns, addressed) {
40
+ const identity = refusalIdentity(refusal, addressed);
41
+ if (refusal.kind === "dirty-workspace")
42
+ return renderDirtyRefusal(refusal, indent, columns, identity);
43
+ if (refusal.kind === "integration-failed") {
44
+ const lines = [
45
+ ...renderOpaqueBlock(refusalHead(refusal.kind, identity, [`reason=${refusal.reason}`, `targetHead=${refusal.targetHead}`]), indent, columns),
46
+ ];
47
+ if (refusal.conflictPaths !== undefined)
48
+ lines.push(...collectionLines("conflictPaths", refusal.conflictPaths, indent, columns));
49
+ return lines;
50
+ }
51
+ if (refusal.kind === "integration-unsupported") {
52
+ return renderOpaqueBlock(refusalHead(refusal.kind, identity, [`requiredGit=${refusal.requiredGit}`]), indent, columns);
53
+ }
54
+ if (refusal.kind === "checkout-not-followable") {
55
+ const lines = [...renderOpaqueBlock(refusalHead(refusal.kind, identity, [`target=${refusal.target}`, `path=${refusal.path}`, `reason=${refusal.reason}`]), indent, columns)];
56
+ lines.push(...collectionLines("paths", refusal.paths, indent, columns));
57
+ return lines;
58
+ }
59
+ if (refusal.kind === "workspace-not-on-target") {
60
+ return renderOpaqueBlock(refusalHead(refusal.kind, identity, [`target=${refusal.target}`, `branch=${refusal.branch}`]), indent, columns);
61
+ }
62
+ if (refusal.kind === "here-target-mismatch") {
63
+ return renderOpaqueBlock(`here-target-mismatch target=${refusal.target} branch=${refusal.branch}`, indent, columns);
64
+ }
65
+ if (refusal.kind === "here-worktree-appointed") {
66
+ return renderOpaqueBlock(refusalHead(refusal.kind, undefined, [refusal.contract === undefined ? "" : `contract=${refusal.contract}`, `path=${refusal.path}`]), indent, columns);
67
+ }
68
+ return renderOpaqueBlock(refusalHead(refusal.kind, identity, []), indent, columns);
69
+ }
70
+ export function renderRefusal(result, context) {
71
+ const columns = context?.columns ?? 80;
72
+ const base = `! ${result.verb} refused`;
73
+ const lines = result.contract === undefined
74
+ ? [base]
75
+ : displayColumns(`${base} — ${result.contract}`) <= columns
76
+ ? [`${base} — ${result.contract}`]
77
+ : [`${base} —`, ` ${safeText(result.contract)}`];
78
+ if (isRecord(result.refusal) && typeof result.refusal.kind === "string") {
79
+ lines.push(...renderRefusalFacts(result.refusal, " ", columns, result.contract));
80
+ }
81
+ const output = lines.join("\n");
82
+ return result.draft === undefined ? output : `${output}\n${renderBindDraftReceipt(result.draft)}`;
83
+ }
84
+ export function renderBindDraftReceipt(receipt) {
85
+ return [
86
+ ...(receipt.path === undefined ? [] : [`draft preserved: ${receipt.path}`]),
87
+ ...(receipt.warning === undefined ? [] : [`warning: ${receipt.warning}`]),
88
+ ].join("\n");
4
89
  }
@@ -0,0 +1,2 @@
1
+ import type { RegionRead, Section } from "../../kanshi/index.js";
2
+ export declare function renderRegionText(section: Section<RegionRead>): string;
@@ -0,0 +1,17 @@
1
+ function rows(section) {
2
+ if (section.kind === "absent")
3
+ return [];
4
+ if (section.kind === "failed")
5
+ return [`region failed ${section.failure.message}`];
6
+ const value = section.value;
7
+ if (value.kind === "declarations")
8
+ return value.declarations.map((declaration) => `region ${declaration.contract} ${declaration.patterns.join(" ")}`);
9
+ if (value.kind === "contract")
10
+ return [`region ${value.declaration.contract} ${value.declaration.patterns.join(" ")}`];
11
+ if (value.kind === "path")
12
+ return value.matches.map((match) => `path ${value.path} ${match.contract} ${match.pattern}`);
13
+ return value.intersections.flatMap((intersection) => intersection.patterns.map((pattern) => `overlap ${intersection.left} ${pattern.left} ~ ${intersection.right} ${pattern.right}`));
14
+ }
15
+ export function renderRegionText(section) {
16
+ return rows(section).join("\n");
17
+ }
@@ -1,6 +1,7 @@
1
1
  import type { TaskCompositionResult } from "../../task/index.js";
2
2
  import type { TaskInvocationResult } from "../commands/task-invoke.js";
3
3
  import type { ParsedTaskCommand } from "../commands/task.js";
4
- export declare function renderTaskText(command: ParsedTaskCommand, result: TaskInvocationResult): string;
4
+ import { type TextRenderContext } from "./terminal.js";
5
+ export declare function renderTaskText(command: ParsedTaskCommand, result: TaskInvocationResult, context?: TextRenderContext): string;
5
6
  export declare function renderTaskIncompleteDiagnostic(result: TaskCompositionResult): string;
6
7
  export declare function taskExitCode(result: TaskInvocationResult): number;
@@ -1,93 +1,256 @@
1
- function row(value) {
2
- const disposition = "disposition" in value ? value.disposition : value.state;
3
- return `${value.id} - P${value.priority} - ${disposition} - ${value.title}`;
1
+ import { outcomeLines, receiptPayload } from "./receipt.js";
2
+ import { displayColumns, renderTextBlock, safeText } from "./terminal.js";
3
+ const DEFAULT_CONTEXT = { columns: 80, color: false };
4
+ function isWorldObservation(result) {
5
+ return typeof result === "object" && result !== null
6
+ && "kind" in result
7
+ && (result.kind === "present" || result.kind === "absent" || result.kind === "failed");
4
8
  }
5
- function changes(values) {
6
- return values.map((change) => change.documentDiff).filter((diff) => diff.length > 0).join("\n");
9
+ function markFor(word) {
10
+ if (word === "in_progress")
11
+ return "●";
12
+ if (word === "ready" || word === "open")
13
+ return "○";
14
+ if (word === "blocked" || word === "missing")
15
+ return "!";
16
+ if (word === "on_hold")
17
+ return "·";
18
+ if (word === "done")
19
+ return "✓";
20
+ if (word === "drop")
21
+ return "×";
22
+ return "?";
7
23
  }
8
- function failure(result) {
9
- return result.kind === "retry" ? `retry ${JSON.stringify(result.reason)}` : `refused ${JSON.stringify(result.refusal)}`;
24
+ function priorityText(priority) {
25
+ return priority === null ? "P?" : `P${priority}`;
10
26
  }
11
- function renderRows(result) {
27
+ function scanUnit(id, priority, word) {
28
+ return `${markFor(word)} ${id} · ${priorityText(priority)} ${word}`;
29
+ }
30
+ function entityLines(entity, columns, indent = "") {
31
+ const scan = `${indent}${scanUnit(entity.id, entity.priority, entity.word)}`;
32
+ if (entity.title === null || entity.title.length === 0)
33
+ return [scan];
34
+ const title = safeText(entity.title);
35
+ const inline = `${scan} — ${title}`;
36
+ if (displayColumns(inline) <= columns)
37
+ return [inline];
38
+ return [`${scan} —`, ...renderTextBlock(title, `${indent} `, columns)];
39
+ }
40
+ function listEntity(item) {
41
+ return { id: item.id, priority: item.priority, word: item.disposition, title: item.title };
42
+ }
43
+ function stateEntity(task) {
44
+ return { id: task.id, priority: "priority" in task && task.priority !== undefined ? task.priority : null, word: task.state, title: task.title };
45
+ }
46
+ function projectRefusal(refusal) {
47
+ if (refusal.kind === "task-missing")
48
+ return { line: `task-missing ${refusal.taskId}` };
49
+ if (refusal.kind === "invalid-lifecycle-transition") {
50
+ return { line: `invalid-lifecycle-transition ${refusal.taskId} ${refusal.state} ${refusal.verb}` };
51
+ }
52
+ if (refusal.kind === "invalid-namespace-context")
53
+ return { line: `invalid-namespace-context ${refusal.path}` };
54
+ if (refusal.kind === "relation-owned-by-other") {
55
+ return { line: `relation-owned-by-other ${refusal.taskId} ${refusal.related} ${refusal.declaringTask}` };
56
+ }
57
+ return { line: refusal.kind, diagnostic: refusal.diagnostic };
58
+ }
59
+ function appendDiagnostic(lines, diagnostic) {
60
+ if (diagnostic !== undefined)
61
+ receiptPayload(lines, "diagnostic", diagnostic);
62
+ }
63
+ function renderFailure(verb, result, columns) {
64
+ if (result.kind === "retry") {
65
+ return [...outcomeLines("?", verb, "retry", undefined, columns), result.reason].join("\n");
66
+ }
67
+ const lines = [...outcomeLines("!", verb, "refused", undefined, columns)];
68
+ const facts = projectRefusal(result.refusal);
69
+ lines.push(facts.line);
70
+ appendDiagnostic(lines, facts.diagnostic);
71
+ return lines.join("\n");
72
+ }
73
+ function edge(label, ref, mark) {
74
+ const prefix = mark === undefined ? ` ${label}` : ` ${mark} ${label}`;
75
+ return `${prefix} ${ref.id} · ${ref.state}`;
76
+ }
77
+ function pageHeading(view, page) {
78
+ if (page.truncated)
79
+ return `${view} ${page.returned} of ${page.total} · limit ${page.returned}`;
80
+ return `${view} ${page.returned}`;
81
+ }
82
+ function renderListRow(item, columns) {
83
+ const lines = [...entityLines(listEntity(item), columns)];
84
+ if ("blockers" in item) {
85
+ for (const blocker of item.blockers)
86
+ lines.push(` needs ${blocker.id} · ${blocker.state}`);
87
+ }
88
+ return lines;
89
+ }
90
+ function renderRows(command, result, columns) {
12
91
  if (result.kind !== "accepted")
13
- return failure(result);
14
- return result.value.map((item) => {
15
- const blockers = "blockers" in item ? item.blockers.map((blocker) => blocker.id).join(", ") : undefined;
16
- return `${row(item)}${blockers === undefined ? "" : ` <- ${blockers}`}`;
17
- }).join("\n");
92
+ return renderFailure(command.action, result, columns);
93
+ const view = command.action === "ls" ? "tasks" : command.action;
94
+ return [pageHeading(view, result.value), ...result.value.rows.flatMap((item) => renderListRow(item, columns))].join("\n");
18
95
  }
19
- function renderShow(result) {
96
+ function renderShow(result, columns) {
20
97
  if ("kind" in result)
21
- return result.kind === "accepted" ? row(result.value) : failure(result);
22
- const task = result.task, lines = [
23
- row(task), `namespace: ${task.namespace.join("/") || "root"}`,
24
- `createdAt: ${task.createdAt}`, `updatedAt: ${task.updatedAt}`, `note: ${task.note}`,
98
+ return result.kind === "accepted" ? entityLines(stateEntity(result.value), columns).join("\n") : renderFailure("show", result, columns);
99
+ const task = result.task;
100
+ const lines = [
101
+ ...entityLines(stateEntity(task), columns),
102
+ ...renderTextBlock(`created ${task.createdAt} · updated ${task.updatedAt}`, "", columns),
103
+ ...(task.createdBy === undefined ? [] : [`created-by ${task.createdBy}`]),
25
104
  ];
26
- for (const [label, values] of [
27
- ["needs", task.needs], ["blockers", result.blockers.map((item) => item.id)], ["blocks", result.blocks.map((item) => item.id)],
28
- ["children", result.children.map((item) => item.id)], ["supersedes", task.supersedes],
29
- ["superseded by", result.supersededBy.map((item) => item.id)], ["related", result.related.map((item) => item.id)],
30
- ])
31
- if (values.length > 0)
32
- lines.push(`${label}: ${values.join(", ")}`);
33
- if (task.parent !== null)
34
- lines.push(`parent: ${task.parent}`);
105
+ for (const need of result.needs.filter((item) => !item.released))
106
+ lines.push(edge("needs", need, "!"));
107
+ for (const need of result.needs.filter((item) => item.released))
108
+ lines.push(edge("needs", need, "✓"));
109
+ for (const item of result.blocks)
110
+ lines.push(edge("blocks", item));
111
+ if (result.parent !== null)
112
+ lines.push(edge("parent", result.parent));
113
+ for (const item of result.children)
114
+ lines.push(edge("child", item));
115
+ for (const item of result.supersedes)
116
+ lines.push(edge("supersedes", item));
117
+ for (const item of result.supersededBy)
118
+ lines.push(edge("superseded-by", item));
119
+ for (const item of result.related)
120
+ lines.push(edge("related", item));
121
+ if (task.note.length > 0)
122
+ receiptPayload(lines, "note", task.note);
35
123
  if (task.body.length > 0)
36
- lines.push("", task.body);
124
+ receiptPayload(lines, "body", task.body);
37
125
  return lines.join("\n");
38
126
  }
39
- function treeLines(node, depth = 0) {
40
- const marker = node.cycle ? "cycle" : node.reference ? "reference" : node.task.state;
41
- const line = `${" ".repeat(depth)}${node.task.id} - ${node.task.priority === null ? "P?" : `P${node.task.priority}`} - ${marker}${node.task.title === null ? "" : ` - ${node.task.title}`}`;
42
- return [line, ...node.needs.flatMap((child) => treeLines(child, depth + 1))];
127
+ function treeLines(node, columns, depth = 0) {
128
+ const indent = " ".repeat(depth);
129
+ if (node.cycle === true)
130
+ return [`${indent}! ${node.task.id} · cycle`];
131
+ return [
132
+ ...entityLines(stateEntity(node.task), columns, indent),
133
+ ...node.children.flatMap((child) => treeLines(child, columns, depth + 1)),
134
+ ];
43
135
  }
44
136
  function doctorIssue(issue) {
45
137
  if (issue.kind === "missing-target")
46
- return `${issue.taskId}: ${issue.relation} target missing: ${issue.target}`;
138
+ return `! missing-target ${issue.taskId} ${issue.relation} ${issue.target}`;
47
139
  if (issue.kind === "self-relation")
48
- return `${issue.taskId}: self ${issue.relation}`;
49
- return `${issue.relation} cycle: ${issue.tasks.join(" -> ")}`;
140
+ return `! self-relation ${issue.taskId} ${issue.relation}`;
141
+ return `! cycle ${issue.relation} ${issue.tasks.join(" ")}`;
50
142
  }
51
- export function renderTaskText(command, result) {
143
+ function renderDoctor(report) {
144
+ if (report.issues.length === 0)
145
+ return "healthy";
146
+ const noun = report.issues.length === 1 ? "issue" : "issues";
147
+ return [`${report.issues.length} ${noun}`, ...report.issues.map(doctorIssue)].join("\n");
148
+ }
149
+ function renderAcceptedMutation(verb, task, columns, documentDiff) {
150
+ const lines = [
151
+ ...outcomeLines("✓", verb, "accepted", task.id, columns),
152
+ ...entityLines(stateEntity(task), columns),
153
+ ];
154
+ if (documentDiff !== undefined)
155
+ receiptPayload(lines, "diff", documentDiff);
156
+ return lines.join("\n");
157
+ }
158
+ function renderMutation(command, result, columns) {
159
+ if (result.kind !== "accepted")
160
+ return renderFailure(command.action, result, columns);
161
+ if (command.action !== "update") {
162
+ return renderAcceptedMutation(command.action, result.value, columns);
163
+ }
164
+ const value = result.value;
165
+ return renderAcceptedMutation(command.action, value.task, columns, value.documentDiff);
166
+ }
167
+ function renderBatchItem(verb, item) {
168
+ if (item.outcome.kind === "accepted")
169
+ return `✓ ${verb} ${item.id}`;
170
+ if (item.outcome.kind === "retry")
171
+ return `? ${verb} ${item.id} · ${item.outcome.reason}`;
172
+ const facts = projectRefusal(item.outcome.refusal);
173
+ const lines = [`! ${verb} ${item.id} · ${facts.line}`];
174
+ appendDiagnostic(lines, facts.diagnostic);
175
+ return lines.join("\n");
176
+ }
177
+ function renderBatch(verb, batch) {
178
+ return batch.items.map((item) => renderBatchItem(verb, item)).join("\n");
179
+ }
180
+ function composeDiffs(lines, changes) {
181
+ for (const change of changes)
182
+ receiptPayload(lines, `diff ${change.taskId}`, change.documentDiff);
183
+ }
184
+ function stoppedLines(stopped) {
185
+ if (stopped.kind === "retry")
186
+ return [`? stopped ${stopped.reason}`];
187
+ const facts = projectRefusal(stopped);
188
+ const lines = [`! stopped ${facts.line}`];
189
+ appendDiagnostic(lines, facts.diagnostic);
190
+ return lines;
191
+ }
192
+ function renderCompose(result, columns) {
193
+ if (result.kind === "incomplete")
194
+ return "";
195
+ if (result.kind !== "accepted")
196
+ return renderFailure("compose", result, columns);
197
+ const lines = [`✓ compose accepted · ${result.documentChanges.length} changed`];
198
+ composeDiffs(lines, result.documentChanges);
199
+ return lines.join("\n");
200
+ }
201
+ export function renderTaskText(command, result, context = DEFAULT_CONTEXT) {
202
+ if (isWorldObservation(result)) {
203
+ if (result.kind === "absent")
204
+ return "task world absent";
205
+ if (result.kind === "failed") {
206
+ const lines = ["task world failed"];
207
+ receiptPayload(lines, "diagnostic", result.failure.message);
208
+ return lines.join("\n");
209
+ }
210
+ result = result.value;
211
+ }
212
+ return renderTaskValue(command, result, context.columns);
213
+ }
214
+ function renderTaskValue(command, result, columns) {
52
215
  if (command.action === "show")
53
- return renderShow(result);
54
- if (command.action === "ls" || command.action === "ready" || command.action === "blocked")
55
- return renderRows(result);
216
+ return renderShow(result, columns);
217
+ if (command.action === "ls" || command.action === "ready" || command.action === "blocked" || command.action === "query") {
218
+ return renderRows(command, result, columns);
219
+ }
56
220
  if (command.action === "tree") {
57
221
  const tree = result;
58
- return tree.kind === "accepted" ? treeLines(tree.value).join("\n") : failure(tree);
59
- }
60
- if (command.action === "doctor") {
61
- const report = result;
62
- return report.issues.length === 0 ? "healthy" : report.issues.map(doctorIssue).join("\n");
222
+ return tree.kind === "accepted" ? treeLines(tree.value, columns).join("\n") : renderFailure(command.action, tree, columns);
63
223
  }
224
+ if (command.action === "doctor")
225
+ return renderDoctor(result);
64
226
  if (command.action === "namespace") {
65
227
  const namespace = result;
66
- return namespace.kind === "accepted" ? namespace.value.join("/") || "root" : failure(namespace);
67
- }
68
- if (command.action === "compose") {
69
- const composition = result;
70
- return composition.kind === "accepted" ? changes(composition.documentChanges) : composition.kind === "incomplete" ? "" : failure(composition);
228
+ if (namespace.kind !== "accepted")
229
+ return renderFailure(command.action, namespace, columns);
230
+ return `namespace ${namespace.value.length === 0 ? "root" : namespace.value.join("/")}`;
71
231
  }
232
+ if (command.action === "compose")
233
+ return renderCompose(result, columns);
72
234
  if (command.action === "hold" || command.action === "done" || command.action === "drop") {
73
- const batch = result;
74
- return batch.items.map((item) => item.outcome.kind === "accepted" ? `accepted ${item.id}` : `${item.id}: ${failure(item.outcome)}`).join("\n");
75
- }
76
- const mutation = result;
77
- if (mutation.kind !== "accepted")
78
- return failure(mutation);
79
- if (command.action === "update") {
80
- const value = mutation.value;
81
- return [row(value.task), value.documentDiff].filter((line) => line.length > 0).join("\n");
235
+ return renderBatch(command.action, result);
82
236
  }
83
- return row(mutation.value);
237
+ return renderMutation(command, result, columns);
84
238
  }
85
239
  export function renderTaskIncompleteDiagnostic(result) {
86
240
  if (result.kind !== "incomplete")
87
241
  return "";
88
- return [`incomplete ${JSON.stringify(result.stopped)}`, changes(result.documentChanges)].filter((line) => line.length > 0).join("\n");
242
+ const lines = [`! compose incomplete · ${result.documentChanges.length} admitted`, ...stoppedLines(result.stopped)];
243
+ composeDiffs(lines, result.documentChanges);
244
+ return lines.join("\n");
89
245
  }
90
246
  export function taskExitCode(result) {
247
+ if (isWorldObservation(result)) {
248
+ if (result.kind === "absent")
249
+ return 1;
250
+ if (result.kind === "failed")
251
+ return 3;
252
+ result = result.value;
253
+ }
91
254
  if (typeof result === "object" && result !== null && "issues" in result)
92
255
  return result.issues.length === 0 ? 0 : 1;
93
256
  if (typeof result === "object" && result !== null && "items" in result) {
@@ -2,9 +2,29 @@ export type TextRenderContext = Readonly<{
2
2
  columns: number;
3
3
  color: boolean;
4
4
  }>;
5
+ export type GitShortStat = Readonly<{
6
+ filesChanged: number;
7
+ insertions: number;
8
+ deletions: number;
9
+ }>;
10
+ export declare function gitShortStat(stat: GitShortStat): string;
5
11
  export declare function displayColumns(value: string): number;
12
+ export declare function takeDisplayColumns(value: string, maximum: number): Readonly<{
13
+ text: string;
14
+ rest: string;
15
+ }>;
16
+ export declare function truncateDisplayText(value: string, maximum: number): string;
17
+ export declare function truncateMiddleDisplayText(value: string, maximum: number): string;
18
+ export declare function renderBoundedTextBlock(value: string, input: Readonly<{
19
+ first: string;
20
+ continuation: string;
21
+ columns: number;
22
+ lines?: number;
23
+ truncated?: boolean;
24
+ }>): readonly string[];
6
25
  export declare function safeText(value: string): string;
7
26
  export declare function renderFacts(primary: string, facts: readonly string[], columns: number): readonly string[];
8
27
  export declare function renderTextBlock(value: string, indent: string, columns: number): readonly string[];
28
+ export declare function renderOpaqueBlock(value: string, indent: string, columns: number): readonly string[];
9
29
  export declare function renderVoiceRuler(left: string, right: string, columns: number): string;
10
30
  export declare function tone(value: string, kind: "dim" | "alert", color: boolean): string;
@@ -1,3 +1,17 @@
1
+ export function gitShortStat(stat) {
2
+ const files = stat.filesChanged === 1 ? "1 file changed" : `${stat.filesChanged} files changed`;
3
+ const parts = [files];
4
+ if (stat.insertions === 1)
5
+ parts.push("1 insertion(+)");
6
+ else if (stat.insertions !== 0)
7
+ parts.push(`${stat.insertions} insertions(+)`);
8
+ if (stat.deletions === 1)
9
+ parts.push("1 deletion(-)");
10
+ else if (stat.deletions !== 0)
11
+ parts.push(`${stat.deletions} deletions(-)`);
12
+ return parts.join(", ");
13
+ }
14
+ const GRAPHEMES = new Intl.Segmenter("und", { granularity: "grapheme" });
1
15
  const WIDE_RANGES = [
2
16
  [0x1100, 0x115f], [0x2329, 0x232a], [0x2e80, 0xa4cf], [0xac00, 0xd7a3],
3
17
  [0xf900, 0xfaff], [0xfe10, 0xfe19], [0xfe30, 0xfe6f], [0xff00, 0xff60],
@@ -19,6 +33,80 @@ export function displayColumns(value) {
19
33
  columns += characterColumns(character);
20
34
  return columns;
21
35
  }
36
+ export function takeDisplayColumns(value, maximum) {
37
+ const characters = [...GRAPHEMES.segment(value)].map(({ segment }) => segment);
38
+ let columns = 0;
39
+ let index = 0;
40
+ for (; index < characters.length; index += 1) {
41
+ const width = displayColumns(characters[index]);
42
+ if (columns + width > maximum)
43
+ break;
44
+ columns += width;
45
+ }
46
+ return { text: characters.slice(0, index).join(""), rest: characters.slice(index).join("") };
47
+ }
48
+ export function truncateDisplayText(value, maximum) {
49
+ const clean = safeText(value);
50
+ if (displayColumns(clean) <= maximum)
51
+ return clean;
52
+ if (maximum <= 0)
53
+ return "";
54
+ if (maximum === 1)
55
+ return "…";
56
+ return `${takeDisplayColumns(clean, maximum - 1).text.replace(/…+$/u, "")}…`;
57
+ }
58
+ export function truncateMiddleDisplayText(value, maximum) {
59
+ const clean = safeText(value);
60
+ if (displayColumns(clean) <= maximum)
61
+ return clean;
62
+ if (maximum <= 0)
63
+ return "";
64
+ if (maximum === 1)
65
+ return "…";
66
+ const available = maximum - 1;
67
+ const headColumns = Math.ceil(available / 2);
68
+ const tailColumns = available - headColumns;
69
+ const head = takeDisplayColumns(clean, headColumns).text.replace(/…+$/u, "");
70
+ const characters = [...GRAPHEMES.segment(clean)].map(({ segment }) => segment);
71
+ let tailStart = characters.length;
72
+ let used = 0;
73
+ while (tailStart > 0) {
74
+ const candidate = characters[tailStart - 1];
75
+ const width = displayColumns(candidate);
76
+ if (used + width > tailColumns)
77
+ break;
78
+ used += width;
79
+ tailStart -= 1;
80
+ }
81
+ return `${head}…${characters.slice(tailStart).join("").replace(/^…+/u, "")}`;
82
+ }
83
+ export function renderBoundedTextBlock(value, input) {
84
+ const maximumLines = input.lines ?? 3;
85
+ let rest = safeText(value).replace(/\s+/gu, " ").trim();
86
+ const lines = [];
87
+ for (let index = 0; index < maximumLines && rest.length > 0; index += 1) {
88
+ const prefix = index === 0 ? input.first : input.continuation;
89
+ const budget = Math.max(1, input.columns - displayColumns(prefix));
90
+ if (index === maximumLines - 1) {
91
+ const force = input.truncated === true || displayColumns(rest) > budget;
92
+ lines.push(`${prefix}${force ? truncateDisplayText(`${rest}…`, budget) : rest}`);
93
+ rest = "";
94
+ break;
95
+ }
96
+ if (displayColumns(rest) <= budget) {
97
+ lines.push(`${prefix}${input.truncated === true ? truncateDisplayText(`${rest}…`, budget) : rest}`);
98
+ rest = "";
99
+ break;
100
+ }
101
+ const taken = takeDisplayColumns(rest, budget);
102
+ let split = taken.text.lastIndexOf(" ");
103
+ if (split < Math.floor(taken.text.length / 2))
104
+ split = taken.text.length;
105
+ lines.push(`${prefix}${taken.text.slice(0, split).trimEnd()}`);
106
+ rest = `${taken.text.slice(split)}${taken.rest}`.trimStart();
107
+ }
108
+ return lines.length === 0 ? [input.first.trimEnd()] : lines;
109
+ }
22
110
  export function safeText(value) {
23
111
  return value.replaceAll(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu, (character) => /\s/u.test(character) ? " " : "�");
24
112
  }
@@ -62,6 +150,39 @@ export function renderTextBlock(value, indent, columns) {
62
150
  lines.push(current);
63
151
  return lines;
64
152
  }
153
+ export function renderOpaqueBlock(value, indent, columns) {
154
+ let rest = safeText(value);
155
+ if (rest.length === 0)
156
+ return [indent.trimEnd()];
157
+ const continuation = `${indent} `;
158
+ const lines = [];
159
+ let prefix = indent;
160
+ while (rest.length > 0) {
161
+ const budget = columns - displayColumns(prefix);
162
+ if (budget <= 0) {
163
+ if (prefix === continuation) {
164
+ lines.push(`${prefix}${rest}`);
165
+ break;
166
+ }
167
+ lines.push(prefix.trimEnd());
168
+ prefix = continuation;
169
+ continue;
170
+ }
171
+ if (displayColumns(rest) <= budget) {
172
+ lines.push(`${prefix}${rest}`);
173
+ break;
174
+ }
175
+ const taken = takeDisplayColumns(rest, budget);
176
+ if (taken.text.length === 0) {
177
+ lines.push(`${prefix}${rest}`);
178
+ break;
179
+ }
180
+ lines.push(`${prefix}${taken.text}`);
181
+ rest = taken.rest;
182
+ prefix = continuation;
183
+ }
184
+ return lines;
185
+ }
65
186
  export function renderVoiceRuler(left, right, columns) {
66
187
  const width = Math.max(20, Math.min(80, columns));
67
188
  const occupied = displayColumns(left) + displayColumns(right) + 2;
@@ -2,14 +2,22 @@ import { renderObservation } from "./board.js";
2
2
  import { renderAccepted, renderRetry } from "./contract.js";
3
3
  import { renderKanshiText } from "./kanshi.js";
4
4
  import { renderRefusal } from "./refusal.js";
5
+ import { renderCatalogText } from "./catalog.js";
6
+ import { renderRegionText } from "./region.js";
5
7
  export function renderText(result, context) {
8
+ if (result.kind === "guidance")
9
+ return result.guidance;
6
10
  if (result.kind === "status")
7
11
  return renderKanshiText(result.report, context, result.selection);
12
+ if (result.kind === "catalog")
13
+ return renderCatalogText(result.catalog);
14
+ if (result.kind === "region")
15
+ return renderRegionText(result.region);
8
16
  if (result.kind === "accepted")
9
- return renderAccepted(result);
17
+ return renderAccepted(result, context);
10
18
  if (result.kind === "refused")
11
- return renderRefusal(result);
19
+ return renderRefusal(result, context);
12
20
  if (result.kind === "retry")
13
- return renderRetry(result);
21
+ return renderRetry(result, context);
14
22
  return renderObservation(result);
15
23
  }