@astrosheep/keiyaku 4.0.1 → 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 (245) hide show
  1. package/README.md +137 -5
  2. package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +1 -1
  3. package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
  4. package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -3
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +91 -35
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +98 -58
  8. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +152 -53
  9. package/build/src/akuma/abort.d.ts +2 -0
  10. package/build/src/akuma/abort.js +50 -0
  11. package/build/src/akuma/akuma.d.ts +23 -14
  12. package/build/src/akuma/akuma.js +120 -104
  13. package/build/src/akuma/archetype.d.ts +7 -6
  14. package/build/src/akuma/archetype.js +68 -41
  15. package/build/src/akuma/body.d.ts +3 -5
  16. package/build/src/akuma/body.js +408 -213
  17. package/build/src/akuma/heart/facts.d.ts +25 -34
  18. package/build/src/akuma/heart/facts.js +18 -7
  19. package/build/src/akuma/heart/index.d.ts +39 -42
  20. package/build/src/akuma/heart/index.js +134 -93
  21. package/build/src/akuma/heart/rows.d.ts +9 -31
  22. package/build/src/akuma/heart/rows.js +26 -44
  23. package/build/src/akuma/heart/schema.d.ts +1 -1
  24. package/build/src/akuma/heart/schema.js +7 -15
  25. package/build/src/akuma/heart/soul.d.ts +8 -0
  26. package/build/src/akuma/heart/soul.js +136 -0
  27. package/build/src/akuma/heart/storage.d.ts +28 -14
  28. package/build/src/akuma/heart/storage.js +86 -26
  29. package/build/src/akuma/identity.d.ts +2 -2
  30. package/build/src/akuma/identity.js +9 -9
  31. package/build/src/akuma/index.d.ts +6 -1
  32. package/build/src/akuma/index.js +6 -0
  33. package/build/src/akuma/projection.d.ts +117 -38
  34. package/build/src/akuma/projection.js +208 -97
  35. package/build/src/akuma/provider-recipe.d.ts +24 -0
  36. package/build/src/akuma/provider-recipe.js +100 -0
  37. package/build/src/akuma/provider.d.ts +12 -4
  38. package/build/src/akuma/provider.js +117 -70
  39. package/build/src/akuma/providers/acp/core.d.ts +19 -0
  40. package/build/src/akuma/providers/acp/core.js +159 -0
  41. package/build/src/akuma/providers/acp/events.d.ts +20 -0
  42. package/build/src/akuma/providers/acp/events.js +73 -0
  43. package/build/src/akuma/providers/acp/index.d.ts +12 -0
  44. package/build/src/akuma/providers/acp/index.js +94 -0
  45. package/build/src/akuma/providers/claude/events.js +66 -13
  46. package/build/src/akuma/providers/claude/index.d.ts +2 -1
  47. package/build/src/akuma/providers/claude/index.js +35 -21
  48. package/build/src/akuma/providers/codex-app-server/events.js +4 -1
  49. package/build/src/akuma/providers/codex-app-server/index.d.ts +3 -3
  50. package/build/src/akuma/providers/codex-app-server/index.js +24 -31
  51. package/build/src/akuma/providers/grok-build/index.d.ts +4 -0
  52. package/build/src/akuma/providers/grok-build/index.js +70 -0
  53. package/build/src/akuma/providers/index.d.ts +5 -3
  54. package/build/src/akuma/providers/index.js +24 -59
  55. package/build/src/akuma/providers/opencode-sdk/events.d.ts +40 -34
  56. package/build/src/akuma/providers/opencode-sdk/events.js +220 -101
  57. package/build/src/akuma/providers/opencode-sdk/index.d.ts +3 -2
  58. package/build/src/akuma/providers/opencode-sdk/index.js +233 -128
  59. package/build/src/akuma/providers/opencode-sdk/session.d.ts +11 -26
  60. package/build/src/akuma/providers/opencode-sdk/session.js +10 -9
  61. package/build/src/akuma/providers/pi/events.js +49 -11
  62. package/build/src/akuma/providers/pi/index.d.ts +1 -1
  63. package/build/src/akuma/providers/pi/index.js +50 -21
  64. package/build/src/akuma/publication.d.ts +3 -3
  65. package/build/src/akuma/publication.js +28 -40
  66. package/build/src/akuma/requests.d.ts +9 -7
  67. package/build/src/akuma/requests.js +92 -65
  68. package/build/src/alias/index.d.ts +2 -2
  69. package/build/src/alias/index.js +10 -10
  70. package/build/src/body/decode.js +7 -1
  71. package/build/src/body/region.d.ts +2 -0
  72. package/build/src/body/region.js +77 -7
  73. package/build/src/cli/accepted.d.ts +17 -13
  74. package/build/src/cli/accepted.js +72 -34
  75. package/build/src/cli/actor.js +3 -3
  76. package/build/src/cli/commands/akuma-invoke.d.ts +4 -3
  77. package/build/src/cli/commands/akuma-invoke.js +14 -9
  78. package/build/src/cli/commands/akuma.d.ts +13 -3
  79. package/build/src/cli/commands/akuma.js +67 -34
  80. package/build/src/cli/commands/contract.d.ts +18 -5
  81. package/build/src/cli/commands/contract.js +9 -3
  82. package/build/src/cli/commands/task-invoke.d.ts +17 -4
  83. package/build/src/cli/commands/task-invoke.js +43 -20
  84. package/build/src/cli/commands/task-query.d.ts +5 -0
  85. package/build/src/cli/commands/task-query.js +247 -0
  86. package/build/src/cli/commands/task.d.ts +3 -1
  87. package/build/src/cli/commands/task.js +51 -13
  88. package/build/src/cli/coordinates.d.ts +18 -0
  89. package/build/src/cli/coordinates.js +120 -0
  90. package/build/src/cli/draft.d.ts +8 -0
  91. package/build/src/cli/draft.js +96 -0
  92. package/build/src/cli/invoke.d.ts +4 -3
  93. package/build/src/cli/invoke.js +160 -179
  94. package/build/src/cli/main.js +34 -6
  95. package/build/src/cli/parse.d.ts +9 -2
  96. package/build/src/cli/parse.js +29 -7
  97. package/build/src/cli/render/akuma-tool-command.d.ts +6 -0
  98. package/build/src/cli/render/akuma-tool-command.js +144 -0
  99. package/build/src/cli/render/akuma-tool.d.ts +2 -2
  100. package/build/src/cli/render/akuma-tool.js +30 -4
  101. package/build/src/cli/render/akuma.d.ts +1 -0
  102. package/build/src/cli/render/akuma.js +211 -116
  103. package/build/src/cli/render/audit.d.ts +3 -0
  104. package/build/src/cli/render/audit.js +104 -0
  105. package/build/src/cli/render/contract.d.ts +3 -3
  106. package/build/src/cli/render/contract.js +192 -79
  107. package/build/src/cli/render/kanshi.js +263 -168
  108. package/build/src/cli/render/receipt.d.ts +19 -0
  109. package/build/src/cli/render/receipt.js +106 -0
  110. package/build/src/cli/render/refusal.d.ts +16 -2
  111. package/build/src/cli/render/refusal.js +88 -20
  112. package/build/src/cli/render/region.d.ts +2 -0
  113. package/build/src/cli/render/region.js +17 -0
  114. package/build/src/cli/render/task.d.ts +2 -1
  115. package/build/src/cli/render/task.js +223 -60
  116. package/build/src/cli/render/terminal.d.ts +7 -0
  117. package/build/src/cli/render/terminal.js +46 -0
  118. package/build/src/cli/render/text.js +6 -3
  119. package/build/src/cli/result.d.ts +115 -20
  120. package/build/src/cli/usage.d.ts +1 -0
  121. package/build/src/cli/usage.js +3 -0
  122. package/build/src/contract-worktree.d.ts +6 -5
  123. package/build/src/contract-worktree.js +95 -57
  124. package/build/src/coordination/durable-file.d.ts +4 -3
  125. package/build/src/coordination/durable-file.js +29 -25
  126. package/build/src/coordination/sqlite-transaction-lock.d.ts +4 -0
  127. package/build/src/coordination/sqlite-transaction-lock.js +22 -5
  128. package/build/src/core/facts/fold.js +1 -6
  129. package/build/src/core/facts/gate.d.ts +1 -0
  130. package/build/src/core/facts/gate.js +1 -0
  131. package/build/src/core/verbs/amend.d.ts +1 -1
  132. package/build/src/core/verbs/amend.js +0 -11
  133. package/build/src/core/verbs/deliver.d.ts +1 -1
  134. package/build/src/core/verbs/deliver.js +1 -7
  135. package/build/src/core/verbs/placement.d.ts +1 -1
  136. package/build/src/core/verbs/placement.js +4 -1
  137. package/build/src/dispatch/index.d.ts +2 -2
  138. package/build/src/dispatch/index.js +17 -17
  139. package/build/src/git/admission.d.ts +1 -1
  140. package/build/src/git/admission.js +11 -11
  141. package/build/src/git/hooks.js +13 -13
  142. package/build/src/git/integration.d.ts +14 -15
  143. package/build/src/git/integration.js +80 -44
  144. package/build/src/git/observe.d.ts +5 -3
  145. package/build/src/git/observe.js +18 -12
  146. package/build/src/git/read-observation.js +9 -9
  147. package/build/src/git/reconcile.d.ts +2 -0
  148. package/build/src/git/reconcile.js +138 -95
  149. package/build/src/git/repository.d.ts +27 -19
  150. package/build/src/git/repository.js +155 -83
  151. package/build/src/git/scratch.d.ts +7 -3
  152. package/build/src/git/scratch.js +44 -37
  153. package/build/src/git/target-placement.d.ts +40 -4
  154. package/build/src/git/target-placement.js +221 -101
  155. package/build/src/git/tender.d.ts +6 -5
  156. package/build/src/git/tender.js +24 -43
  157. package/build/src/git/terminal-seal.d.ts +1 -1
  158. package/build/src/git/terminal-seal.js +7 -5
  159. package/build/src/git/workspace.d.ts +36 -4
  160. package/build/src/git/workspace.js +53 -18
  161. package/build/src/identity/selector.js +1 -1
  162. package/build/src/index.d.ts +3 -3
  163. package/build/src/index.js +1 -1
  164. package/build/src/kanshi/index.d.ts +2 -2
  165. package/build/src/kanshi/index.js +1 -1
  166. package/build/src/kanshi/read.d.ts +2 -1
  167. package/build/src/kanshi/read.js +118 -27
  168. package/build/src/kanshi/report.d.ts +50 -0
  169. package/build/src/kanshi/select.d.ts +5 -0
  170. package/build/src/kanshi/select.js +32 -0
  171. package/build/src/library/address.d.ts +12 -3
  172. package/build/src/library/address.js +77 -46
  173. package/build/src/library/akuma-creation.d.ts +6 -1
  174. package/build/src/library/akuma-creation.js +86 -13
  175. package/build/src/library/audit.d.ts +18 -0
  176. package/build/src/library/audit.js +44 -0
  177. package/build/src/library/bind.js +4 -4
  178. package/build/src/library/catalog.d.ts +1 -1
  179. package/build/src/library/catalog.js +5 -8
  180. package/build/src/library/contract.d.ts +5 -6
  181. package/build/src/library/contract.js +11 -35
  182. package/build/src/library/delivery.d.ts +2 -1
  183. package/build/src/library/fleet.d.ts +2 -2
  184. package/build/src/library/fleet.js +35 -71
  185. package/build/src/library/keiyaku.d.ts +3 -2
  186. package/build/src/library/keiyaku.js +1 -0
  187. package/build/src/library/mutation.d.ts +7 -6
  188. package/build/src/library/mutation.js +23 -25
  189. package/build/src/library/reconcile.d.ts +29 -0
  190. package/build/src/library/reconcile.js +179 -0
  191. package/build/src/library/region.d.ts +12 -0
  192. package/build/src/library/region.js +15 -1
  193. package/build/src/library/repo.d.ts +4 -16
  194. package/build/src/library/repo.js +13 -38
  195. package/build/src/protocol/attempt.js +6 -6
  196. package/build/src/protocol/bind.js +4 -4
  197. package/build/src/protocol/intent.d.ts +15 -2
  198. package/build/src/protocol/intent.js +21 -6
  199. package/build/src/protocol/operations.d.ts +73 -22
  200. package/build/src/protocol/operations.js +292 -88
  201. package/build/src/protocol/outcome.d.ts +9 -3
  202. package/build/src/protocol/outcome.js +3 -1
  203. package/build/src/protocol/placement.d.ts +15 -4
  204. package/build/src/protocol/placement.js +58 -25
  205. package/build/src/protocol/read/status.d.ts +8 -1
  206. package/build/src/protocol/read/status.js +61 -14
  207. package/build/src/runtime/proc/line-rpc.d.ts +3 -8
  208. package/build/src/runtime/proc/line-rpc.js +16 -43
  209. package/build/src/runtime/proc/run.d.ts +15 -29
  210. package/build/src/runtime/proc/run.js +113 -105
  211. package/build/src/runtime/proc/stdio.d.ts +18 -0
  212. package/build/src/runtime/proc/stdio.js +58 -0
  213. package/build/src/settings.d.ts +2 -2
  214. package/build/src/settings.js +8 -8
  215. package/build/src/settlement/fence.d.ts +0 -4
  216. package/build/src/settlement/fence.js +0 -9
  217. package/build/src/settlement/holder.d.ts +11 -1
  218. package/build/src/settlement/holder.js +25 -0
  219. package/build/src/settlement/settle.d.ts +1 -1
  220. package/build/src/settlement/settle.js +52 -89
  221. package/build/src/task/board.d.ts +3 -3
  222. package/build/src/task/board.js +6 -9
  223. package/build/src/task/compose.d.ts +1 -1
  224. package/build/src/task/compose.js +18 -10
  225. package/build/src/task/context.d.ts +3 -3
  226. package/build/src/task/context.js +14 -14
  227. package/build/src/task/document.d.ts +2 -1
  228. package/build/src/task/document.js +15 -5
  229. package/build/src/task/index.d.ts +19 -7
  230. package/build/src/task/index.js +42 -23
  231. package/build/src/task/operations.d.ts +15 -9
  232. package/build/src/task/operations.js +78 -48
  233. package/build/src/task/query.d.ts +67 -0
  234. package/build/src/task/query.js +279 -0
  235. package/build/src/task/store.d.ts +2 -2
  236. package/build/src/task/store.js +40 -25
  237. package/build/src/verification/execution.d.ts +4 -2
  238. package/build/src/verification/execution.js +9 -5
  239. package/build/src/workspace-place.d.ts +41 -0
  240. package/build/src/workspace-place.js +386 -0
  241. package/build/src/world.d.ts +13 -2
  242. package/build/src/world.js +81 -48
  243. package/package.json +3 -2
  244. package/build/src/protocol/read/audit.d.ts +0 -31
  245. package/build/src/protocol/read/audit.js +0 -55
@@ -1,11 +1,22 @@
1
1
  import { displayColumns, renderTextBlock, safeText, tone } from "./terminal.js";
2
- function contractMark(phase) {
3
- if (phase === "claimed")
2
+ const RULE = "─";
3
+ const HORIZON = "───";
4
+ const PLUMB = " │ ";
5
+ const PATH_PREFIX = `${PLUMB}↳ `;
6
+ function contractMark(row) {
7
+ if (row.phase === "claimed")
4
8
  return "✓";
5
- if (phase === "abandoned")
9
+ if (row.phase === "abandoned")
6
10
  return "×";
7
- if (phase === "waiting" || phase === "pending-delivery")
8
- return "⧗";
11
+ if (row.title === null)
12
+ return "?";
13
+ if (row.gates.reports.some((report) => report.current.kind === "attested" && report.current.verdict === "unsatisfied")) {
14
+ return "!";
15
+ }
16
+ if (row.targetLag.kind === "unknown")
17
+ return "?";
18
+ if (row.phase === "waiting")
19
+ return "○";
9
20
  return "●";
10
21
  }
11
22
  function taskMark(row) {
@@ -14,10 +25,10 @@ function taskMark(row) {
14
25
  if (row.disposition === "drop")
15
26
  return "×";
16
27
  if (row.disposition === "on_hold")
17
- return "‖";
28
+ return "⧗";
18
29
  if (row.disposition === "in_progress")
19
30
  return "●";
20
- return row.disposition === "blocked" ? "⧗" : "○";
31
+ return row.disposition === "blocked" ? "‖" : "○";
21
32
  }
22
33
  function akumaMark(life) {
23
34
  if (life === "running")
@@ -35,39 +46,111 @@ function gateMark(report) {
35
46
  return "?";
36
47
  return report.current.verdict === "satisfied" ? "✓" : "!";
37
48
  }
38
- function hotFirst(rows, hot) {
39
- return [...rows.filter(hot), ...rows.filter((row) => !hot(row))];
49
+ function formatAge(source, observedAt) {
50
+ if (source === null || source === undefined)
51
+ return "—";
52
+ const sourceMs = Date.parse(source);
53
+ const observedMs = Date.parse(observedAt);
54
+ if (sourceMs > observedMs)
55
+ return "future";
56
+ const seconds = Math.floor((observedMs - sourceMs) / 1_000);
57
+ if (seconds < 60)
58
+ return `${seconds}s`;
59
+ const minutes = Math.floor(seconds / 60);
60
+ if (minutes < 60)
61
+ return `${minutes}m`;
62
+ const hours = Math.floor(minutes / 60);
63
+ return hours < 24 ? `${hours}h` : `${Math.floor(hours / 24)}d`;
40
64
  }
41
- function stablePriority(rows, priority) {
42
- const priorities = [...new Set(rows.map(priority))].sort((left, right) => left - right);
43
- return priorities.flatMap((value) => rows.filter((row) => priority(row) === value));
65
+ function isLost(life) {
66
+ return life === "stranded" || life === "hung" || life === "untidy";
44
67
  }
45
- function wrapChain(parts, columns) {
46
- if (parts.length === 0)
47
- return [];
48
- const lines = [];
49
- let current = " ";
50
- for (const part of parts.map(safeText)) {
51
- const candidate = current === " " ? `${current}${part}` : `${current} · ${part}`;
52
- if (current !== " " && displayColumns(candidate) > columns) {
53
- lines.push(current);
54
- current = ` ${part}`;
55
- }
56
- else
57
- current = candidate;
68
+ function branchName(target) {
69
+ if (target === null)
70
+ return null;
71
+ return target.startsWith("refs/heads/") ? target.slice("refs/heads/".length) : target;
72
+ }
73
+ function targetFacts(row) {
74
+ const name = branchName(row.target);
75
+ if (name === null)
76
+ return ["no target"];
77
+ const facts = [`target ${name}`];
78
+ if (row.targetLag.kind === "unknown")
79
+ facts.push("behind unknown");
80
+ if (row.targetLag.kind === "counted")
81
+ facts.push(`behind ${row.targetLag.behind}`);
82
+ if (row.targetObservation?.drift === true)
83
+ facts.push("drift");
84
+ return facts;
85
+ }
86
+ function workspaceState(row) {
87
+ const observation = row.workspaceObservation;
88
+ if (row.workspace === "here")
89
+ return `workspace here · ${observation.kind}`;
90
+ if (observation.kind === "unappointed")
91
+ return "worktree unappointed";
92
+ if (observation.kind === "unavailable")
93
+ return "worktree unavailable";
94
+ if (observation.kind === "clean")
95
+ return "worktree clean";
96
+ const counts = [
97
+ ...(observation.counts.staged > 0 ? [`staged ${observation.counts.staged}`] : []),
98
+ ...(observation.counts.unstaged > 0 ? [`unstaged ${observation.counts.unstaged}`] : []),
99
+ ...(observation.counts.untracked > 0 ? [`untracked ${observation.counts.untracked}`] : []),
100
+ ...(observation.counts.submodules > 0 ? [`submodules ${observation.counts.submodules}`] : []),
101
+ ];
102
+ return counts.length === 0 ? "worktree dirty" : `worktree dirty · ${counts.join(" · ")}`;
103
+ }
104
+ function showWorkspacePath(row, hot) {
105
+ if (row.workspace !== "worktree")
106
+ return false;
107
+ if (row.workspaceObservation.kind === "unappointed")
108
+ return false;
109
+ if (row.workspaceObservation.kind !== "clean")
110
+ return true;
111
+ return hot;
112
+ }
113
+ function contractHot(row) {
114
+ if (row.title === null)
115
+ return true;
116
+ if (row.phase === "pending-delivery")
117
+ return true;
118
+ if (row.holder.kind === "unavailable" || row.holder.kind === "held")
119
+ return true;
120
+ if (row.fleet.length > 0)
121
+ return true;
122
+ if (row.targetLag.kind === "counted" && row.targetLag.behind > 0)
123
+ return true;
124
+ if (row.workspaceObservation.kind !== "clean" && row.workspaceObservation.kind !== "unappointed") {
125
+ return true;
58
126
  }
59
- lines.push(current);
60
- return lines;
127
+ return row.gates.reports.some((report) => report.current.kind === "stale"
128
+ || (report.current.kind === "attested" && report.current.verdict === "unsatisfied"));
61
129
  }
62
- function wrapHeading(primary, facts, columns) {
63
- const head = safeText(primary);
130
+ function taskHot(row) {
131
+ return row.disposition === "blocked" || row.disposition === "in_progress";
132
+ }
133
+ function akumaHot(row) {
134
+ if (row.life === "running" || row.life === "stillborn" || isLost(row.life))
135
+ return true;
136
+ return row.contract?.observed === "missing" || row.contract?.observed === "unavailable";
137
+ }
138
+ function aperture(label, columns) {
139
+ const prefix = `──[ ${label} ]`;
140
+ const fill = Math.max(1, columns - displayColumns(prefix));
141
+ return `${prefix}${RULE.repeat(fill)}`;
142
+ }
143
+ function plumbFacts(facts, columns) {
144
+ const clean = facts.map(safeText).filter((fact) => fact.length > 0);
145
+ if (clean.length === 0)
146
+ return [];
64
147
  const lines = [];
65
- let current = head;
66
- for (const fact of facts.map(safeText)) {
67
- const candidate = `${current} · ${fact}`;
68
- if (displayColumns(candidate) > columns) {
148
+ let current = PLUMB;
149
+ for (const fact of clean) {
150
+ const candidate = current === PLUMB ? `${PLUMB}${fact}` : `${current} · ${fact}`;
151
+ if (current !== PLUMB && displayColumns(candidate) > columns) {
69
152
  lines.push(current);
70
- current = ` ${fact}`;
153
+ current = `${PLUMB}${fact}`;
71
154
  }
72
155
  else
73
156
  current = candidate;
@@ -75,178 +158,190 @@ function wrapHeading(primary, facts, columns) {
75
158
  lines.push(current);
76
159
  return lines;
77
160
  }
78
- function renderGates(reports, context, includeSummaries) {
79
- const lines = [...wrapChain(reports.map((report) => `${gateMark(report)} ${safeText(report.gate)}`), context.columns)];
161
+ function plumbBlock(value, columns) {
162
+ return renderTextBlock(value, PLUMB, columns);
163
+ }
164
+ function plumbPath(path) {
165
+ return `${PATH_PREFIX}${safeText(path)}`;
166
+ }
167
+ function identityLine(mark, identity, extra = "") {
168
+ return extra.length === 0 ? `${mark} ${safeText(identity)}` : `${mark} ${safeText(identity)} ${safeText(extra)}`;
169
+ }
170
+ function renderGates(reports, observedAt, columns, includeSummaries) {
171
+ if (reports.length === 0)
172
+ return [];
173
+ const lines = [...plumbFacts([`gates: ${reports.map((report) => `${gateMark(report)} ${report.gate} ${formatAge(report.current.kind === "attested" ? report.current.at : null, observedAt)}`).join(" · ")}`], columns)];
80
174
  if (!includeSummaries)
81
175
  return lines;
82
176
  for (const report of reports) {
83
177
  if (report.current.kind !== "attested" || report.current.summary === undefined)
84
178
  continue;
85
- lines.push(...renderTextBlock(`${safeText(report.gate)}: ${report.current.summary}`, " ", context.columns)
86
- .map((line) => tone(line, "dim", context.color)));
179
+ lines.push(...plumbBlock(`${report.gate}: ${report.current.summary}`, columns));
87
180
  }
88
181
  return lines;
89
182
  }
90
183
  function failure(name, section, context) {
91
- return `${name} failed\n${tone(`! ${safeText(section.failure.message)}`, "alert", context.color)}`;
184
+ return [aperture(name, context.columns), `${tone(`! ${safeText(section.failure.message)}`, "alert", context.color)}`];
185
+ }
186
+ function sectionAbsent(name, columns) {
187
+ return [aperture(name, columns), `${PLUMB}${name.toLowerCase()} absent`];
188
+ }
189
+ function endpointFact(id, observed) {
190
+ if (observed === "missing")
191
+ return `-> ${id} (missing)`;
192
+ if (observed === "unavailable")
193
+ return `-> ${id} (unavailable)`;
194
+ return `-> ${id}`;
92
195
  }
93
196
  function renderContracts(report, context, selection) {
94
197
  const section = report.contracts;
95
198
  if (section.kind === "absent")
96
- return "keiyaku absent";
199
+ return sectionAbsent("KEIYAKU", context.columns);
97
200
  if (section.kind === "failed")
98
- return failure("keiyaku", section, context);
99
- const rows = stablePriority(section.value.rows, (row) => {
100
- if (row.holder.kind === "unavailable")
101
- return 0;
102
- if (row.phase === "waiting" || row.phase === "pending-delivery")
103
- return 1;
104
- return 2;
105
- });
106
- const lines = [`keiyaku ${rows.length}`];
201
+ return failure("KEIYAKU", section, context);
202
+ const rows = [
203
+ ...section.value.rows.filter(contractHot),
204
+ ...section.value.rows.filter((row) => !contractHot(row)),
205
+ ];
206
+ const lines = [aperture("KEIYAKU", context.columns)];
107
207
  for (const row of rows) {
108
- const deliveryFacts = row.delivery === null
109
- ? []
110
- : selection === "world"
111
- ? [`integration ${safeText(row.delivery.integration.snapshot).slice(0, 8)}`]
112
- : [
113
- `tender ${row.delivery.tenderSnapshot}`,
114
- `predecessor ${row.delivery.integration.predecessor}`,
115
- `integration ${row.delivery.integration.snapshot}`,
116
- `change ${row.delivery.integration.changeId}`,
117
- ];
118
- const targetFacts = selection !== "contract" || row.targetObservation === null
119
- ? []
120
- : [
121
- `target head ${row.targetObservation.head ?? "missing"}`,
122
- `target ${row.targetObservation.drift ? "drifted" : "current"}`,
123
- ];
124
- const facts = [
125
- row.workspace,
126
- ...deliveryFacts,
127
- ...(row.target === null ? [] : [`-> ${row.target}`]),
128
- ...targetFacts,
129
- ];
130
- lines.push(`${contractMark(row.phase)} ${safeText(row.id)} ${row.phase}`);
131
- lines.push(...wrapChain(facts, context.columns));
132
- lines.push(...renderGates(row.gates.reports, context, selection === "contract"));
208
+ const hot = contractHot(row);
209
+ const compact = !hot && selection !== "contract";
210
+ const title = row.title ?? "title unavailable";
211
+ const phase = `${row.phase} · ${formatAge(row.phaseAt, report.observedAt)}`;
212
+ lines.push(identityLine(contractMark(row), row.id));
213
+ lines.push(...plumbFacts(compact
214
+ ? [title, phase, ...targetFacts(row)]
215
+ : [title], context.columns));
216
+ if (!compact)
217
+ lines.push(...plumbFacts([phase, ...targetFacts(row)], context.columns));
218
+ lines.push(...plumbFacts([workspaceState(row)], context.columns));
219
+ if (showWorkspacePath(row, hot || selection === "contract")
220
+ && row.workspaceObservation.kind !== "unappointed"
221
+ && row.workspaceObservation.location.kind === "worktree") {
222
+ lines.push(plumbPath(row.workspaceObservation.location.path));
223
+ }
224
+ lines.push(...renderGates(row.gates.reports, report.observedAt, context.columns, selection === "contract"));
133
225
  if (row.holder.kind === "held")
134
- lines.push(...wrapChain([`held by ${row.holder.taskId}`], context.columns));
226
+ lines.push(...plumbFacts([`task ${row.holder.taskId}`], context.columns));
135
227
  if (row.holder.kind === "unavailable")
136
- lines.push(...wrapChain(["holder unavailable"], context.columns));
228
+ lines.push(...plumbFacts(["holder unavailable"], context.columns));
229
+ for (const attached of row.fleet) {
230
+ const aliases = attached.aliases.length === 0 ? "" : ` (${attached.aliases.join(" ")})`;
231
+ lines.push(...plumbFacts([`akuma ${attached.id}${aliases}`], context.columns));
232
+ }
137
233
  }
138
- return lines.join("\n");
234
+ const attention = rows.filter(contractHot).length;
235
+ lines.push(aperture(`${rows.length} keiyaku · ${attention} attention`, context.columns));
236
+ return lines;
139
237
  }
140
238
  function renderTasks(report, context) {
141
239
  const section = report.tasks;
142
240
  if (section.kind === "absent")
143
- return "task absent";
241
+ return sectionAbsent("TASK", context.columns);
144
242
  if (section.kind === "failed")
145
- return failure("task", section, context);
146
- const active = section.value.rows.filter((row) => row.disposition !== "done" && row.disposition !== "drop");
147
- const ready = active.filter((row) => row.disposition === "ready").length;
148
- const onHold = active.filter((row) => row.disposition === "on_hold").length;
149
- const visible = hotFirst(active.filter((row) => row.disposition === "in_progress" || row.disposition === "blocked"), (row) => row.disposition === "blocked");
150
- const lines = [...wrapHeading(`task ${active.length}`, [`${ready} ready`, `${onHold} held`], context.columns)];
151
- for (const row of visible) {
152
- const contract = row.contract === undefined ? [] : [`keiyaku ${row.contract.id} (${row.contract.observed})`];
153
- lines.push(`${taskMark(row)} ${safeText(row.id)} ${row.disposition}`);
154
- lines.push(...renderTextBlock(row.title, " ", context.columns));
155
- lines.push(...wrapChain([`P${row.priority}`, ...contract], context.columns));
156
- for (const blocker of row.blockers ?? []) {
157
- lines.push(...wrapChain([`blocked by ${blocker.id} (${blocker.state})`], context.columns));
243
+ return failure("TASK", section, context);
244
+ const rows = [
245
+ ...section.value.rows.filter(taskHot),
246
+ ...section.value.rows.filter((row) => !taskHot(row)),
247
+ ];
248
+ const lines = [aperture("TASK", context.columns)];
249
+ for (const row of rows) {
250
+ const compact = !taskHot(row);
251
+ lines.push(identityLine(taskMark(row), row.id));
252
+ const relation = row.contract === undefined
253
+ ? ["unbound"]
254
+ : [endpointFact(row.contract.id, row.contract.observed)];
255
+ const facts = [row.title, row.disposition, `P${row.priority}`, ...relation];
256
+ if (compact)
257
+ lines.push(...plumbFacts(facts, context.columns));
258
+ else {
259
+ lines.push(...plumbBlock(row.title, context.columns));
260
+ lines.push(...plumbFacts([row.disposition, `P${row.priority}`], context.columns));
261
+ for (const blocker of row.blockers ?? []) {
262
+ lines.push(...plumbFacts([`blocked by ${blocker.id}`], context.columns));
263
+ }
264
+ lines.push(...plumbFacts(relation, context.columns));
158
265
  }
159
266
  }
160
- return lines.join("\n");
161
- }
162
- function isLost(life) {
163
- return life === "stranded" || life === "headless";
164
- }
165
- function akumaPriority(row) {
166
- if (isLost(row.life) || row.life === "stillborn")
167
- return 0;
168
- return row.life === "running" ? 1 : 2;
169
- }
170
- function firstLine(value) {
171
- return value.split(/[\r\n\u2028\u2029]/u, 1)[0] ?? "";
172
- }
173
- function truncateDisplay(value, columns) {
174
- if (displayColumns(value) <= columns)
175
- return value;
176
- if (columns <= 0)
177
- return "";
178
- const ellipsis = "…";
179
- if (columns <= displayColumns(ellipsis))
180
- return ellipsis;
181
- let result = "";
182
- let used = 0;
183
- for (const character of value) {
184
- const width = displayColumns(character);
185
- if (used + width + displayColumns(ellipsis) > columns)
186
- break;
187
- result += character;
188
- used += width;
189
- }
190
- return `${result}${ellipsis}`;
191
- }
192
- function bornFacts(row) {
193
- const association = [
194
- ...(row.aliases ?? []).map((alias) => `alias ${alias}`),
195
- ...(row.contract === undefined ? [] : [`keiyaku ${row.contract.id} (${row.contract.observed})`]),
196
- ];
197
- if (!("pending" in row) || (row.life !== "running" && !isLost(row.life)))
198
- return association;
199
- const confinement = row.confinement.kind === "unconfined"
200
- ? ["unconfined"]
201
- : row.confinement.writableRoots.map((root, index) => index === 0 ? `writes ${root}` : root);
202
- return [
203
- ...association,
204
- ...(row.pending.length === 0 ? [] : [`pending ${row.pending.length}`]),
205
- ...confinement,
206
- ];
267
+ const attention = rows.filter(taskHot).length;
268
+ lines.push(aperture(`${rows.length} task · ${attention} attention`, context.columns));
269
+ return lines;
207
270
  }
208
- function stillbornEvidence(row, columns) {
209
- if (row.life !== "stillborn" || !("seal" in row) || row.seal === undefined)
210
- return null;
211
- const prefix = " seal ";
212
- const evidence = safeText(firstLine(row.seal.evidence)).trim();
213
- if (evidence.length === 0)
214
- return null;
215
- return `${prefix}${truncateDisplay(evidence, Math.max(0, columns - displayColumns(prefix)))}`;
271
+ function akumaLabel(row) {
272
+ const aliases = row.aliases ?? [];
273
+ return aliases.length === 0 ? "" : `(${aliases.join(" ")})`;
216
274
  }
217
275
  function renderAkuma(report, context) {
218
276
  const section = report.akuma;
219
277
  if (section.kind === "absent")
220
- return "akuma absent";
278
+ return sectionAbsent("FLEET", context.columns);
221
279
  if (section.kind === "failed")
222
- return failure("akuma", section, context);
280
+ return failure("FLEET", section, context);
223
281
  const rows = [
224
- ...section.value.rows.filter((row) => akumaPriority(row) === 0),
225
- ...section.value.rows.filter((row) => akumaPriority(row) === 1),
226
- ...section.value.rows.filter((row) => akumaPriority(row) === 2),
282
+ ...section.value.rows.filter(akumaHot),
283
+ ...section.value.rows.filter((row) => !akumaHot(row)),
227
284
  ];
228
- const lines = [`akuma ${rows.length}`];
285
+ const lines = [aperture("FLEET", context.columns)];
229
286
  for (const row of rows) {
230
- lines.push(`${akumaMark(row.life)} ${safeText(row.id)} ${row.life}`);
231
- lines.push(...wrapChain(bornFacts(row), context.columns));
232
- const evidence = stillbornEvidence(row, context.columns);
233
- if (evidence !== null)
234
- lines.push(evidence);
287
+ lines.push(identityLine(akumaMark(row.life), row.id, akumaLabel(row)));
288
+ const lifeAt = "lifeAt" in row ? row.lifeAt : null;
289
+ const life = `${row.life} · ${formatAge(lifeAt, report.observedAt)}`;
290
+ const key = row.life === "stranded" && "strandedReason" in row && row.strandedReason === "resume-unsupported"
291
+ ? [life, "resume unsupported"]
292
+ : [life];
293
+ const relation = row.contract === undefined
294
+ ? ["unbound"]
295
+ : [endpointFact(row.contract.id, row.contract.observed)];
296
+ if (!akumaHot(row))
297
+ lines.push(...plumbFacts([...key, ...relation], context.columns));
298
+ else {
299
+ lines.push(...plumbFacts(key, context.columns));
300
+ lines.push(...plumbFacts(relation, context.columns));
301
+ }
235
302
  }
236
- return lines.join("\n");
303
+ const attention = rows.filter(akumaHot).length;
304
+ lines.push(aperture(`${rows.length} akuma · ${attention} attention`, context.columns));
305
+ return lines;
237
306
  }
238
- export function renderKanshiText(report, context = { columns: 80, color: false }, selection = "world") {
307
+ function sectionCount(name, section) {
308
+ if (section.kind === "present")
309
+ return `${section.value.rows.length} ${name}`;
310
+ return `${section.kind} ${name}`;
311
+ }
312
+ function worldCoordinate(report) {
313
+ const project = report.root;
239
314
  const branch = report.branch?.startsWith("refs/heads/") === true
240
315
  ? report.branch.slice("refs/heads/".length)
241
316
  : report.branch;
242
- const state = report.contracts.kind === "present" ? report.contracts.value.state : null;
317
+ const state = report.contracts.kind === "present" ? report.contracts.value.state ?? null : null;
318
+ return [project, branch, state].filter((part) => part !== null && part.length > 0).map(safeText).join(" ");
319
+ }
320
+ function splitHorizon(report, columns) {
321
+ if (report.root === null)
322
+ return ["✗ keiyaku world absent"];
323
+ const aggregate = [
324
+ sectionCount("keiyaku", report.contracts),
325
+ sectionCount("akuma", report.akuma),
326
+ sectionCount("task", report.tasks),
327
+ ].join(" · ");
328
+ const world = worldCoordinate(report);
329
+ const minimum = `kanshi ${HORIZON} ${aggregate} ${HORIZON} ${world}`;
330
+ if (displayColumns(minimum) > columns)
331
+ return [minimum];
332
+ const fill = columns - displayColumns(minimum);
333
+ const left = Math.floor(fill / 2);
334
+ const right = Math.ceil(fill / 2);
335
+ return [`kanshi ${HORIZON}${RULE.repeat(left)} ${aggregate} ${HORIZON}${RULE.repeat(right)} ${world}`];
336
+ }
337
+ export function renderKanshiText(report, context = { columns: 80, color: false }, selection = "world") {
243
338
  return [
244
- [
245
- `kanshi ${report.root === null ? "none" : safeText(report.root)}`,
246
- `state ${state?.slice(0, 8) ?? "none"} · ${branch === null ? "none" : safeText(branch)} · observed ${safeText(report.observedAt)}`,
247
- ].join("\n"),
248
- renderContracts(report, context, selection),
249
- renderTasks(report, context),
250
- renderAkuma(report, context),
251
- ].join("\n\n");
339
+ ...splitHorizon(report, context.columns),
340
+ "",
341
+ ...renderContracts(report, context, selection),
342
+ "",
343
+ ...renderTasks(report, context),
344
+ "",
345
+ ...renderAkuma(report, context),
346
+ ].join("\n");
252
347
  }
@@ -0,0 +1,19 @@
1
+ import type { PlacementStop, VerificationReuse, VerificationStop } from "../../index.js";
2
+ import type { AcceptedDeliverResult, Lag } from "../result.js";
3
+ export type ReceiptSegment = Readonly<{
4
+ text: string;
5
+ opaque?: boolean;
6
+ }>;
7
+ type HookFailure = Extract<Lag, {
8
+ kind: "worktree-hook-failed";
9
+ }>["failure"];
10
+ export declare function receiptRow(lines: string[], mark: string, label: string, segments: readonly ReceiptSegment[], columns: number): void;
11
+ export declare function receiptPayload(lines: string[], label: string, payload: string): void;
12
+ export declare function outcomeLines(mark: "✓" | "!" | "?", verb: string, word: "accepted" | "refused" | "retry", contract: string | undefined, columns?: number): string[];
13
+ export declare function hookFailureSummary(failure: HookFailure): string;
14
+ export declare function appendHookPayload(lines: string[], failure: HookFailure): void;
15
+ export declare function reuseLines(reuse: VerificationReuse | undefined, columns: number): readonly string[];
16
+ export declare function stopLines(label: "verification" | "claim", stop: VerificationStop | PlacementStop, columns: number, addressed: string): readonly string[];
17
+ export declare function cleanupLines(cleanup: NonNullable<AcceptedDeliverResult["cleanup"]>, columns: number): readonly string[];
18
+ export declare function leakLines(leak: NonNullable<AcceptedDeliverResult["leak"]>, columns: number): readonly string[];
19
+ export {};
@@ -0,0 +1,106 @@
1
+ import { displayColumns, renderOpaqueBlock, safeText } from "./terminal.js";
2
+ export function receiptRow(lines, mark, label, segments, columns) {
3
+ let current = `${mark} ${label}`;
4
+ for (const segment of segments) {
5
+ const text = segment.opaque === true ? safeText(segment.text) : segment.text;
6
+ const candidate = `${current} ${text}`;
7
+ if (displayColumns(candidate) <= columns) {
8
+ current = candidate;
9
+ continue;
10
+ }
11
+ if (current === `${mark} ${label}` && segment.opaque === true) {
12
+ lines.push(current);
13
+ current = ` ${text}`;
14
+ continue;
15
+ }
16
+ lines.push(current);
17
+ current = ` ${text}`;
18
+ }
19
+ lines.push(current);
20
+ }
21
+ export function receiptPayload(lines, label, payload) {
22
+ lines.push(label, "", payload, "");
23
+ }
24
+ export function outcomeLines(mark, verb, word, contract, columns = 80) {
25
+ const base = `${mark} ${verb} ${word}`;
26
+ if (contract === undefined)
27
+ return [base];
28
+ const inline = `${base} — ${contract}`;
29
+ if (displayColumns(inline) <= columns)
30
+ return [inline];
31
+ return [`${base} —`, ` ${safeText(contract)}`];
32
+ }
33
+ export function hookFailureSummary(failure) {
34
+ if (failure.kind === "timeout" || failure.kind === "unknown-exit")
35
+ return failure.kind;
36
+ if (failure.kind === "spawn-error")
37
+ return failure.kind;
38
+ return `exit=${failure.code} · truncated=${failure.truncated}`;
39
+ }
40
+ export function appendHookPayload(lines, failure) {
41
+ if (failure.kind === "spawn-error")
42
+ receiptPayload(lines, "diagnostic", failure.diagnostic);
43
+ if (!("stdout" in failure))
44
+ return;
45
+ if (failure.stdout.length > 0)
46
+ receiptPayload(lines, "stdout", failure.stdout);
47
+ if (failure.stderr.length > 0)
48
+ receiptPayload(lines, "stderr", failure.stderr);
49
+ }
50
+ export function reuseLines(reuse, columns) {
51
+ if (reuse === undefined)
52
+ return [];
53
+ return renderOpaqueBlock(`reuse verification ${reuse.entry} ${reuse.verdict}`, " ", columns);
54
+ }
55
+ export function stopLines(label, stop, columns, addressed) {
56
+ const detail = [];
57
+ if ("refusal" in stop && stop.refusal !== undefined) {
58
+ detail.push({ text: stop.refusal.kind });
59
+ if ("contractId" in stop.refusal && stop.refusal.contractId !== addressed) {
60
+ detail.push({ text: `contract=${stop.refusal.contractId}`, opaque: true });
61
+ }
62
+ }
63
+ else if ("retry" in stop && stop.retry !== undefined) {
64
+ detail.push({ text: stop.retry.kind });
65
+ }
66
+ else if ("failure" in stop) {
67
+ detail.push({ text: stop.failure });
68
+ if (stop.failure === "environment-failure" && "command" in stop) {
69
+ detail.push({ text: `command=${stop.command}` }, { text: hookFailureSummary(stop.detail), opaque: true });
70
+ }
71
+ else if (stop.failure === "target-moved") {
72
+ detail.push({ text: stop.target, opaque: true }, { text: `${stop.expected} -> ${stop.observed}`, opaque: true });
73
+ }
74
+ }
75
+ const lines = [];
76
+ receiptRow(lines, "!", label, detail, columns);
77
+ if ("failure" in stop && stop.failure === "environment-failure" && "command" in stop) {
78
+ appendHookPayload(lines, stop.detail);
79
+ }
80
+ if ("retry" in stop && stop.retry?.kind === "publication-failed") {
81
+ receiptPayload(lines, "diagnostic", stop.retry.diagnostic);
82
+ }
83
+ else if ("failure" in stop && "diagnostic" in stop) {
84
+ receiptPayload(lines, "diagnostic", stop.diagnostic);
85
+ }
86
+ return lines;
87
+ }
88
+ export function cleanupLines(cleanup, columns) {
89
+ const lines = [];
90
+ receiptRow(lines, "!", "cleanup", [
91
+ { text: cleanup.phase },
92
+ { text: `command=${cleanup.command}` },
93
+ { text: hookFailureSummary(cleanup.detail), opaque: true },
94
+ ], columns);
95
+ appendHookPayload(lines, cleanup.detail);
96
+ return lines;
97
+ }
98
+ export function leakLines(leak, columns) {
99
+ const lines = [];
100
+ receiptRow(lines, "!", "leak", [
101
+ { text: "worktree" },
102
+ { text: leak.path, opaque: true },
103
+ ], columns);
104
+ receiptPayload(lines, "diagnostic", leak.diagnostic);
105
+ return lines;
106
+ }
@@ -1,2 +1,16 @@
1
- import type { RefusedResult } from "../result.js";
2
- export declare function renderRefusal(result: RefusedResult): string;
1
+ import type { BindDraftReceipt, RefusedResult } from "../result.js";
2
+ import type { KeiyakuRefusal } from "../../index.js";
3
+ import { type TextRenderContext } from "./terminal.js";
4
+ type DirtyWithOption = Extract<KeiyakuRefusal, {
5
+ kind: "dirty-workspace";
6
+ }> & {
7
+ option?: Readonly<{
8
+ flag: string;
9
+ available: boolean;
10
+ }>;
11
+ };
12
+ export type RenderableRefusal = KeiyakuRefusal | DirtyWithOption;
13
+ export declare function renderRefusalFacts(refusal: RenderableRefusal, indent: string, columns: number, addressed?: string): readonly string[];
14
+ export declare function renderRefusal(result: RefusedResult, context?: TextRenderContext): string;
15
+ export declare function renderBindDraftReceipt(receipt: BindDraftReceipt): string;
16
+ export {};