@astrosheep/keiyaku 4.5.7 → 4.5.9

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 (244) hide show
  1. package/README.md +10 -0
  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/opencode.js +3 -2
  5. package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +12 -11
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +33 -12
  8. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-babysit/SKILL.md +35 -96
  9. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +28 -49
  10. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +21 -15
  11. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +148 -89
  12. package/build/src/akuma/abort.d.ts +2 -1
  13. package/build/src/akuma/abort.js +6 -23
  14. package/build/src/akuma/akuma-handle.js +4 -2
  15. package/build/src/akuma/akuma-observe.d.ts +2 -0
  16. package/build/src/akuma/akuma-observe.js +1 -0
  17. package/build/src/akuma/akuma-product.js +9 -7
  18. package/build/src/akuma/akuma.d.ts +392 -9
  19. package/build/src/akuma/akuma.js +31 -1
  20. package/build/src/akuma/allowed.d.ts +1 -1
  21. package/build/src/akuma/allowed.js +1 -0
  22. package/build/src/akuma/archetype.js +1 -1
  23. package/build/src/akuma/body.d.ts +8 -5
  24. package/build/src/akuma/body.js +12 -5
  25. package/build/src/akuma/call-request.d.ts +80 -0
  26. package/build/src/akuma/call-request.js +211 -0
  27. package/build/src/akuma/heart/facts.d.ts +16 -78
  28. package/build/src/akuma/heart/facts.js +11 -0
  29. package/build/src/akuma/heart/index.d.ts +3 -2
  30. package/build/src/akuma/heart/index.js +4 -1
  31. package/build/src/akuma/heart/request-authority.d.ts +5 -4
  32. package/build/src/akuma/heart/request-authority.js +20 -34
  33. package/build/src/akuma/heart/request-rows.js +12 -23
  34. package/build/src/akuma/heart/schema.d.ts +1 -1
  35. package/build/src/akuma/heart/schema.js +5 -13
  36. package/build/src/akuma/index.d.ts +1 -1
  37. package/build/src/akuma/index.js +1 -1
  38. package/build/src/akuma/projection-read.d.ts +1 -0
  39. package/build/src/akuma/projection-read.js +8 -1
  40. package/build/src/akuma/projection.d.ts +907 -137
  41. package/build/src/akuma/projection.js +193 -9
  42. package/build/src/akuma/provider.d.ts +30 -3
  43. package/build/src/akuma/provider.js +111 -0
  44. package/build/src/akuma/providers/acp/core.d.ts +2 -2
  45. package/build/src/akuma/providers/acp/core.js +13 -6
  46. package/build/src/akuma/providers/acp/index.js +5 -4
  47. package/build/src/akuma/providers/claude/events.d.ts +2 -2
  48. package/build/src/akuma/providers/claude/events.js +16 -3
  49. package/build/src/akuma/providers/claude/index.d.ts +3 -3
  50. package/build/src/akuma/providers/claude/index.js +16 -5
  51. package/build/src/akuma/providers/codex-app-server/events.d.ts +2 -2
  52. package/build/src/akuma/providers/codex-app-server/events.js +25 -4
  53. package/build/src/akuma/providers/codex-app-server/index.js +18 -6
  54. package/build/src/akuma/providers/grok-build/index.js +5 -4
  55. package/build/src/akuma/providers/opencode-sdk/index.js +66 -25
  56. package/build/src/akuma/providers/opencode-sdk/session.d.ts +3 -2
  57. package/build/src/akuma/providers/opencode-sdk/session.js +28 -11
  58. package/build/src/akuma/providers/pi/index.js +72 -58
  59. package/build/src/akuma/request-lifecycle.d.ts +4 -10
  60. package/build/src/akuma/request-lifecycle.js +7 -5
  61. package/build/src/akuma/request-rendezvous.d.ts +26 -0
  62. package/build/src/akuma/request-rendezvous.js +100 -0
  63. package/build/src/akuma/request-serve.d.ts +6 -66
  64. package/build/src/akuma/request-serve.js +83 -210
  65. package/build/src/akuma/request-wire.d.ts +109 -115
  66. package/build/src/akuma/request-wire.js +63 -318
  67. package/build/src/akuma/requests.d.ts +19 -47
  68. package/build/src/akuma/requests.js +51 -107
  69. package/build/src/akuma/turn-drive.d.ts +5 -3
  70. package/build/src/akuma/turn-drive.js +39 -11
  71. package/build/src/akuma-body.d.ts +7 -2
  72. package/build/src/akuma-body.js +68 -46
  73. package/build/src/body/amend.js +18 -19
  74. package/build/src/cli/accepted.d.ts +1 -1
  75. package/build/src/cli/accepted.js +2 -2
  76. package/build/src/cli/commands/akuma-invoke.d.ts +2 -0
  77. package/build/src/cli/commands/akuma-invoke.js +17 -9
  78. package/build/src/cli/commands/akuma.js +8 -8
  79. package/build/src/cli/commands/contract-invoke.d.ts +3 -2
  80. package/build/src/cli/commands/contract-invoke.js +42 -30
  81. package/build/src/cli/commands/contract.d.ts +16 -20
  82. package/build/src/cli/commands/contract.js +58 -25
  83. package/build/src/cli/commands/install.d.ts +2 -1
  84. package/build/src/cli/commands/install.js +3 -2
  85. package/build/src/cli/commands/task-invoke.d.ts +3 -0
  86. package/build/src/cli/commands/task-invoke.js +23 -140
  87. package/build/src/cli/commands/task.js +54 -20
  88. package/build/src/cli/invoke.js +39 -25
  89. package/build/src/cli/main.js +2 -1
  90. package/build/src/cli/parse.d.ts +2 -2
  91. package/build/src/cli/parse.js +56 -27
  92. package/build/src/cli/render/akuma-activity.d.ts +3 -0
  93. package/build/src/cli/render/akuma-activity.js +45 -29
  94. package/build/src/cli/render/akuma-tool.d.ts +5 -1
  95. package/build/src/cli/render/akuma.js +4 -8
  96. package/build/src/cli/render/audit.js +1 -1
  97. package/build/src/cli/render/catalog.js +80 -21
  98. package/build/src/cli/render/contract-observation.d.ts +2 -0
  99. package/build/src/cli/render/contract-observation.js +11 -0
  100. package/build/src/cli/render/contract.js +35 -62
  101. package/build/src/cli/render/kanshi-akuma.d.ts +8 -0
  102. package/build/src/cli/render/kanshi-akuma.js +132 -0
  103. package/build/src/cli/render/kanshi.js +131 -213
  104. package/build/src/cli/render/receipt.d.ts +1 -2
  105. package/build/src/cli/render/receipt.js +76 -26
  106. package/build/src/cli/render/refusal.js +2 -10
  107. package/build/src/cli/render/region.js +5 -2
  108. package/build/src/cli/render/settings.d.ts +1 -1
  109. package/build/src/cli/render/settings.js +27 -8
  110. package/build/src/cli/render/task.js +22 -5
  111. package/build/src/cli/render/terminal.d.ts +32 -1
  112. package/build/src/cli/render/terminal.js +77 -1
  113. package/build/src/cli/result.d.ts +3 -3
  114. package/build/src/cli/runtime.js +4 -2
  115. package/build/src/cli/selectors.d.ts +2 -1
  116. package/build/src/cli/selectors.js +4 -3
  117. package/build/src/cli/square-edge.js +120 -36
  118. package/build/src/cli/usage.js +2 -1
  119. package/build/src/contract-guidance.d.ts +5 -0
  120. package/build/src/contract-guidance.js +121 -0
  121. package/build/src/contract-worktree.d.ts +0 -1
  122. package/build/src/contract-worktree.js +32 -83
  123. package/build/src/coordination/sqlite-transaction-lock.d.ts +1 -0
  124. package/build/src/coordination/sqlite-transaction-lock.js +5 -0
  125. package/build/src/core/facts/codec.d.ts +2 -0
  126. package/build/src/core/facts/codec.js +4 -0
  127. package/build/src/core/verbs/placement.d.ts +6 -1
  128. package/build/src/core/verbs/placement.js +5 -3
  129. package/build/src/dispatch/index.d.ts +0 -2
  130. package/build/src/dispatch/index.js +54 -55
  131. package/build/src/git/admission.js +2 -2
  132. package/build/src/git/hooks.d.ts +9 -15
  133. package/build/src/git/hooks.js +23 -317
  134. package/build/src/git/nuke.d.ts +2 -1
  135. package/build/src/git/nuke.js +39 -21
  136. package/build/src/git/private-state-seat.d.ts +12 -0
  137. package/build/src/git/private-state-seat.js +39 -0
  138. package/build/src/git/process.d.ts +6 -0
  139. package/build/src/git/process.js +26 -11
  140. package/build/src/git/read-observation.js +29 -20
  141. package/build/src/git/reconcile.d.ts +13 -1
  142. package/build/src/git/reconcile.js +70 -11
  143. package/build/src/git/ref-migration.d.ts +22 -0
  144. package/build/src/git/ref-migration.js +55 -0
  145. package/build/src/git/repository.d.ts +10 -6
  146. package/build/src/git/repository.js +16 -8
  147. package/build/src/git/target-placement.d.ts +1 -0
  148. package/build/src/git/target-placement.js +5 -0
  149. package/build/src/git/tender.d.ts +1 -0
  150. package/build/src/git/terminal-reconcile.d.ts +1 -1
  151. package/build/src/git/terminal-reconcile.js +22 -20
  152. package/build/src/git/workspace.d.ts +14 -0
  153. package/build/src/git/workspace.js +44 -0
  154. package/build/src/kanshi/index.d.ts +1 -1
  155. package/build/src/kanshi/read.js +16 -16
  156. package/build/src/kanshi/report.d.ts +2 -2
  157. package/build/src/library/address.d.ts +9 -3
  158. package/build/src/library/address.js +3 -2
  159. package/build/src/library/akuma-creation.d.ts +4 -3
  160. package/build/src/library/akuma-creation.js +19 -13
  161. package/build/src/library/audit.d.ts +14 -6
  162. package/build/src/library/audit.js +13 -23
  163. package/build/src/library/catalog.d.ts +3 -1
  164. package/build/src/library/catalog.js +20 -6
  165. package/build/src/library/composition.d.ts +385 -0
  166. package/build/src/library/composition.js +28 -0
  167. package/build/src/library/configuration.js +8 -2
  168. package/build/src/library/continuation.js +66 -30
  169. package/build/src/library/contract-bind.js +10 -2
  170. package/build/src/library/contract-forwarding-reconciliation-result.d.ts +102 -0
  171. package/build/src/library/contract-forwarding-reconciliation-result.js +92 -0
  172. package/build/src/library/contract-forwarding-result.d.ts +4464 -0
  173. package/build/src/library/contract-forwarding-result.js +521 -0
  174. package/build/src/library/contract-forwarding.d.ts +71 -0
  175. package/build/src/library/contract-forwarding.js +114 -0
  176. package/build/src/library/contract-handle.d.ts +38 -0
  177. package/build/src/library/contract-handle.js +314 -0
  178. package/build/src/library/contract-operations.d.ts +214 -69
  179. package/build/src/library/contract-operations.js +189 -118
  180. package/build/src/library/contract-types.d.ts +7 -3
  181. package/build/src/library/contract.d.ts +21 -34
  182. package/build/src/library/contract.js +29 -287
  183. package/build/src/library/fleet-result.d.ts +1580 -0
  184. package/build/src/library/fleet-result.js +98 -0
  185. package/build/src/library/fleet.d.ts +112 -54
  186. package/build/src/library/fleet.js +211 -71
  187. package/build/src/library/input.d.ts +3 -1
  188. package/build/src/library/input.js +11 -1
  189. package/build/src/library/keiyaku.d.ts +388 -20
  190. package/build/src/library/keiyaku.js +13 -21
  191. package/build/src/library/mutation.d.ts +3 -3
  192. package/build/src/library/mutation.js +5 -9
  193. package/build/src/library/nuke.d.ts +1 -1
  194. package/build/src/library/nuke.js +4 -3
  195. package/build/src/library/reconcile.d.ts +4 -0
  196. package/build/src/library/reconcile.js +2 -0
  197. package/build/src/library/refusal.d.ts +0 -9
  198. package/build/src/library/refusal.js +0 -7
  199. package/build/src/protocol/amend.js +56 -50
  200. package/build/src/protocol/attempt.d.ts +2 -0
  201. package/build/src/protocol/attempt.js +8 -1
  202. package/build/src/protocol/completion.js +7 -0
  203. package/build/src/protocol/deliver.js +6 -2
  204. package/build/src/protocol/operations.d.ts +4 -1
  205. package/build/src/protocol/operations.js +5 -2
  206. package/build/src/protocol/placement.d.ts +1 -0
  207. package/build/src/protocol/placement.js +52 -38
  208. package/build/src/protocol/read/observation.d.ts +1 -4
  209. package/build/src/protocol/read/observation.js +2 -15
  210. package/build/src/protocol/reintegrate.js +62 -58
  211. package/build/src/protocol/review.js +5 -0
  212. package/build/src/protocol/run.js +17 -13
  213. package/build/src/runtime/proc/run.d.ts +9 -0
  214. package/build/src/runtime/proc/run.js +51 -4
  215. package/build/src/runtime/proc/termination.js +26 -22
  216. package/build/src/runtime/proc/windows-launch.exe +0 -0
  217. package/build/src/settlement/holder.d.ts +2 -1
  218. package/build/src/settlement/holder.js +15 -4
  219. package/build/src/settlement/settle.js +99 -33
  220. package/build/src/task/board.d.ts +68 -27
  221. package/build/src/task/board.js +31 -1
  222. package/build/src/task/catalog.d.ts +4 -0
  223. package/build/src/task/catalog.js +23 -0
  224. package/build/src/task/compose-language.js +33 -7
  225. package/build/src/task/compose-parser.d.ts +1 -1
  226. package/build/src/task/compose-parser.js +1 -1
  227. package/build/src/task/compose.js +2 -0
  228. package/build/src/task/index.d.ts +12 -4
  229. package/build/src/task/index.js +97 -16
  230. package/build/src/task/mutation-result.d.ts +923 -0
  231. package/build/src/task/mutation-result.js +140 -0
  232. package/build/src/task/mutation.d.ts +552 -28
  233. package/build/src/task/mutation.js +304 -97
  234. package/build/src/task/operations.d.ts +1 -4
  235. package/build/src/task/operations.js +0 -6
  236. package/build/src/task/public.d.ts +2 -0
  237. package/build/src/task/public.js +1 -0
  238. package/build/src/workspace-place.d.ts +7 -2
  239. package/build/src/workspace-place.js +34 -24
  240. package/build/src/world.d.ts +1 -0
  241. package/build/src/world.js +11 -0
  242. package/package.json +8 -6
  243. package/build/src/akuma/request-execution.d.ts +0 -8
  244. package/build/src/akuma/request-execution.js +0 -98
@@ -1,8 +1,29 @@
1
- import { visibleFleetRows } from "../../kanshi/fleet.js";
2
- import { abbreviateGitIds, afterWording, candidateFact, dependentWording, displayGitId, gateFact, gitIdsInRow, mergeSummary, } from "./contract-observation.js";
3
- import { displayColumns, renderTextBlock, safeText, tone, truncateDisplayText, } from "./terminal.js";
1
+ import { abbreviateGitIds, afterWording, candidateFact, dependentWording, displayGitId, gateFact, gitIdsInRow, mergeSummary, targetMovementFacts, } from "./contract-observation.js";
2
+ import { entityLines, elapsedMilliseconds, identityLine, plumbFacts, RECENT_TONE_MS, renderTextBlock, renderSectionBlock, safeText, tone, } from "./terminal.js";
3
+ import { akumaMark, endpointFact, formatAge, NARROW_COLUMNS, renderAkuma } from "./kanshi-akuma.js";
4
4
  const PLUMB = " │ ";
5
5
  const MAX_VISIBLE_ROWS = 10;
6
+ const REVIEW_ATTENTION_MS = 15 * 60 * 1_000;
7
+ const PENDING_ATTENTION_MS = 60 * 60 * 1_000;
8
+ function contractHasError(row) {
9
+ return (row.title === null ||
10
+ row.gates.reports.some((report) => report.current.kind === "attested" && report.current.verdict === "unsatisfied") ||
11
+ row.targetLag.kind === "unknown" ||
12
+ row.workspaceObservation.kind === "failed" ||
13
+ row.workspaceObservation.kind === "unavailable" ||
14
+ row.issue !== undefined);
15
+ }
16
+ function contractStatusTone(row, observedAt) {
17
+ if (contractHasError(row))
18
+ return "alert";
19
+ const phaseAge = elapsedMilliseconds(row.phaseAt, observedAt);
20
+ if (row.phase === "tendered" && phaseAge !== null && phaseAge >= REVIEW_ATTENTION_MS)
21
+ return "attention";
22
+ if ((row.phase === "waiting" || row.phase === "bound") && phaseAge !== null && phaseAge >= PENDING_ATTENTION_MS)
23
+ return "attention";
24
+ const journalAge = elapsedMilliseconds(row.lastJournalAt, observedAt);
25
+ return journalAge !== null && journalAge <= RECENT_TONE_MS ? "recent" : null;
26
+ }
6
27
  function contractMark(row) {
7
28
  if (row.phase === "claimed")
8
29
  return "✓";
@@ -10,9 +31,8 @@ function contractMark(row) {
10
31
  return "×";
11
32
  if (row.title === null)
12
33
  return "?";
13
- if (row.gates.reports.some((report) => report.current.kind === "attested" && report.current.verdict === "unsatisfied")) {
34
+ if (row.gates.reports.some((report) => report.current.kind === "attested" && report.current.verdict === "unsatisfied"))
14
35
  return "!";
15
- }
16
36
  if (row.targetLag.kind === "unknown")
17
37
  return "?";
18
38
  if (row.phase === "waiting")
@@ -26,20 +46,7 @@ function taskMark(row) {
26
46
  return "×";
27
47
  if (row.disposition === "on_hold")
28
48
  return "⧗";
29
- if (row.disposition === "in_progress")
30
- return "●";
31
- return row.disposition === "blocked" ? "‖" : "○";
32
- }
33
- function akumaMark(life) {
34
- if (life === "running")
35
- return "●";
36
- if (life === "stillborn")
37
- return "!";
38
- if (life === "asleep")
39
- return "○";
40
- if (life === "killed")
41
- return "×";
42
- return "?";
49
+ return row.disposition === "in_progress" ? "●" : row.disposition === "blocked" ? "‖" : "○";
43
50
  }
44
51
  function gitAbbreviations(report) {
45
52
  const ids = [];
@@ -47,32 +54,13 @@ function gitAbbreviations(report) {
47
54
  return abbreviateGitIds(ids);
48
55
  if (report.contracts.value.state !== null)
49
56
  ids.push(report.contracts.value.state);
50
- for (const row of report.contracts.value.rows)
51
- ids.push(...gitIdsInRow(row));
57
+ report.contracts.value.rows.forEach((row) => ids.push(...gitIdsInRow(row)));
52
58
  return abbreviateGitIds(ids);
53
59
  }
54
- function formatAge(source, observedAt) {
55
- if (source === null || source === undefined)
56
- return "—";
57
- const sourceMs = Date.parse(source);
58
- const observedMs = Date.parse(observedAt);
59
- if (sourceMs > observedMs)
60
- return "future";
61
- const seconds = Math.floor((observedMs - sourceMs) / 1_000);
62
- if (seconds < 60)
63
- return `${seconds}s`;
64
- const minutes = Math.floor(seconds / 60);
65
- if (minutes < 60)
66
- return `${minutes}m`;
67
- const hours = Math.floor(minutes / 60);
68
- return hours < 24 ? `${hours}h` : `${Math.floor(hours / 24)}d`;
69
- }
70
60
  function branchName(target) {
71
- if (target === null)
72
- return null;
73
- return target.startsWith("refs/heads/") ? target.slice("refs/heads/".length) : target;
61
+ return target === null ? null : target.startsWith("refs/heads/") ? target.slice("refs/heads/".length) : target;
74
62
  }
75
- function targetFacts(row) {
63
+ function targetFacts(row, abbreviations) {
76
64
  const name = branchName(row.target);
77
65
  if (name === null)
78
66
  return ["no target"];
@@ -81,16 +69,13 @@ function targetFacts(row) {
81
69
  facts.push(`commits behind ${name} unknown`);
82
70
  if (row.targetLag.kind === "counted")
83
71
  facts.push(`${row.targetLag.behind} commits behind ${name}`);
84
- if (row.targetObservation?.drift === true)
85
- facts.push("target moved");
86
- return facts;
72
+ return [...facts, ...targetMovementFacts(row, abbreviations)];
87
73
  }
88
74
  function selectedTargetFacts(row, abbreviations) {
75
+ const head = row.targetObservation?.head;
89
76
  return [
90
- ...targetFacts(row),
91
- ...(row.target !== null && row.targetObservation?.head !== null && row.targetObservation?.head !== undefined
92
- ? [`target head ${displayGitId(row.targetObservation.head, abbreviations)}`]
93
- : []),
77
+ ...targetFacts(row, abbreviations),
78
+ ...(row.target !== null && head ? [`target head ${displayGitId(head, abbreviations)}`] : []),
94
79
  ];
95
80
  }
96
81
  function workspaceState(row) {
@@ -103,12 +88,9 @@ function workspaceState(row) {
103
88
  return "worktree unavailable";
104
89
  if (observation.kind === "clean")
105
90
  return "worktree clean";
106
- const counts = [
107
- ...(observation.counts.staged > 0 ? [`staged ${observation.counts.staged}`] : []),
108
- ...(observation.counts.unstaged > 0 ? [`unstaged ${observation.counts.unstaged}`] : []),
109
- ...(observation.counts.untracked > 0 ? [`untracked ${observation.counts.untracked}`] : []),
110
- ...(observation.counts.submodules > 0 ? [`submodules ${observation.counts.submodules}`] : []),
111
- ];
91
+ const counts = Object.entries(observation.counts)
92
+ .filter(([, count]) => count > 0)
93
+ .map(([name, count]) => `${name} ${count}`);
112
94
  return counts.length === 0 ? "worktree dirty" : `worktree dirty · ${counts.join(" · ")}`;
113
95
  }
114
96
  function mergeFacts(row, selected, abbreviations) {
@@ -116,9 +98,8 @@ function mergeFacts(row, selected, abbreviations) {
116
98
  const summary = mergeSummary(observation);
117
99
  if (summary === undefined ||
118
100
  (observation.kind !== "clean" && observation.kind !== "dirty") ||
119
- observation.merge === null) {
101
+ observation.merge === null)
120
102
  return [];
121
- }
122
103
  if (!selected)
123
104
  return [summary];
124
105
  const paths = observation.merge.unmergedPaths;
@@ -128,80 +109,6 @@ function mergeFacts(row, selected, abbreviations) {
128
109
  ...(paths.length === 0 ? ["0 paths"] : paths),
129
110
  ];
130
111
  }
131
- function worldAfterFacts(row) {
132
- return row.after.map(afterWording);
133
- }
134
- function activitySnapshotLine(row) {
135
- const snapshot = row.snapshot;
136
- if (snapshot === undefined)
137
- return undefined;
138
- const entries = snapshot.entries.flatMap((entry) => (entry.kind === "row" ? [entry.row] : []));
139
- const latest = entries.at(-1);
140
- if (latest === undefined) {
141
- if (snapshot.kind === "idle" && snapshot.outcome !== undefined) {
142
- return snapshot.outcome.outcome.kind === "answered"
143
- ? snapshot.outcome.outcome.answer
144
- : snapshot.outcome.outcome.diagnostic;
145
- }
146
- return undefined;
147
- }
148
- if (latest.kind === "said" ||
149
- latest.kind === "thought" ||
150
- latest.kind === "note" ||
151
- latest.kind === "call" ||
152
- latest.kind === "tell") {
153
- return latest.text;
154
- }
155
- if (latest.kind === "tool")
156
- return latest.name;
157
- return "activity";
158
- }
159
- function boundedActivity(value, columns, prefix) {
160
- const budget = Math.max(1, columns - displayColumns(prefix));
161
- return `activity "${truncateDisplayText(value, Math.max(1, budget - displayColumns('activity ""')))}"`;
162
- }
163
- function renderColdFleetLine(row, prefix, snapshot, columns) {
164
- const mark = akumaMark(row.life);
165
- const identity = identityLine(mark, row.id, prefix);
166
- if (snapshot === undefined)
167
- return [identity];
168
- const activity = boundedActivity(snapshot, columns, PLUMB);
169
- return [identity, ...plumbFacts([activity], columns)];
170
- }
171
- function plumbFacts(facts, columns) {
172
- const clean = facts.map(safeText).filter((fact) => fact.length > 0);
173
- if (clean.length === 0)
174
- return [];
175
- const lines = [];
176
- let current = PLUMB;
177
- for (const fact of clean) {
178
- const candidate = current === PLUMB ? `${PLUMB}${fact}` : `${current} · ${fact}`;
179
- if (current !== PLUMB && displayColumns(candidate) > columns) {
180
- lines.push(current);
181
- current = `${PLUMB}${fact}`;
182
- }
183
- else
184
- current = candidate;
185
- }
186
- lines.push(current);
187
- return lines;
188
- }
189
- function identityLine(mark, identity, extra = "") {
190
- return extra.length === 0 ? `${mark} ${safeText(identity)}` : `${mark} ${safeText(identity)} ${safeText(extra)}`;
191
- }
192
- function failure(name, section, context) {
193
- return [`[ ${name} ]`, "", `${tone(`! ${safeText(section.failure.message)}`, "alert", context.color)}`];
194
- }
195
- function sectionAbsent(name, _columns) {
196
- return [`[ ${name} ]`, "", `${PLUMB}${name.toLowerCase()} absent`];
197
- }
198
- function endpointFact(id, observed) {
199
- if (observed === "missing")
200
- return `-> ${id} (missing)`;
201
- if (observed === "unavailable")
202
- return `-> ${id} (unavailable)`;
203
- return `-> ${id}`;
204
- }
205
112
  function linkedTask(report, taskId) {
206
113
  if (report.tasks.kind !== "present")
207
114
  return `! ${taskId} · unavailable`;
@@ -219,11 +126,11 @@ function isTerminalAkuma(life) {
219
126
  return life === "killed" || life === "stillborn";
220
127
  }
221
128
  function attachmentTimestamp(akuma) {
222
- if ("lifeAt" in akuma && akuma.lifeAt !== null)
223
- return akuma.lifeAt;
224
- if ("lastActivityAt" in akuma && akuma.lastActivityAt !== null)
225
- return akuma.lastActivityAt;
226
- return null;
129
+ return "lifeAt" in akuma && akuma.lifeAt !== null
130
+ ? akuma.lifeAt
131
+ : "lastActivityAt" in akuma
132
+ ? akuma.lastActivityAt
133
+ : null;
227
134
  }
228
135
  function worldAttachments(row, report) {
229
136
  if (report.akuma.kind !== "present")
@@ -265,16 +172,37 @@ function linkedFacts(row, report, mode) {
265
172
  linked.push(linkedAkuma(report, attached.id, attached.aliases));
266
173
  return linked;
267
174
  }
175
+ function linkedAkumaSummary(row, report) {
176
+ if (row.fleet.length === 0)
177
+ return undefined;
178
+ if (report.akuma.kind !== "present")
179
+ return "akuma unavailable";
180
+ const byId = new Map(report.akuma.value.rows.map((akuma) => [akuma.id, akuma]));
181
+ const known = row.fleet
182
+ .map((attached) => byId.get(attached.id))
183
+ .filter((akuma) => akuma !== undefined);
184
+ if (known.length === 0)
185
+ return `akuma ${row.fleet.length} missing`;
186
+ const live = known.filter((akuma) => !isTerminalAkuma(akuma.life)).length;
187
+ const terminal = known.length - live;
188
+ const facts = [`akuma ${row.fleet.length}`];
189
+ if (live > 0)
190
+ facts.push(`${live} live`);
191
+ if (terminal > 0)
192
+ facts.push(`${terminal} terminal`);
193
+ return facts.join(" · ");
194
+ }
268
195
  function semanticBlock(name, facts, context) {
196
+ if (facts.length === 0)
197
+ return [];
269
198
  const lines = [` ${name}`];
270
- const values = facts.length === 0 ? ["none"] : facts;
271
- for (const fact of values)
199
+ for (const fact of facts)
272
200
  lines.push(...renderTextBlock(safeText(fact), " ", context.columns));
273
201
  return lines;
274
202
  }
275
203
  function namespaceTaskFacts(row) {
276
204
  if (row.namespaceTasks.kind === "absent")
277
- return ["absent"];
205
+ return [];
278
206
  if (row.namespaceTasks.kind === "failed") {
279
207
  return [`failed ${row.namespaceTasks.failure.message}`];
280
208
  }
@@ -282,10 +210,18 @@ function namespaceTaskFacts(row) {
282
210
  }
283
211
  function renderSelectedContractRow(row, report, context, abbreviations) {
284
212
  const title = row.title ?? "title unavailable";
213
+ const statusTone = contractStatusTone(row, report.observedAt);
285
214
  const lines = [
286
- identityLine(contractMark(row), row.id, `· ${row.phase} · ${formatAge(row.phaseAt, report.observedAt)} · ${title}`),
215
+ ...entityLines({
216
+ mark: statusTone === null ? contractMark(row) : tone(contractMark(row), statusTone, context.color),
217
+ identity: row.id,
218
+ state: `${row.phase} · ${formatAge(row.phaseAt, report.observedAt)}`,
219
+ title,
220
+ facts: [],
221
+ context,
222
+ }),
287
223
  ];
288
- lines.push(...semanticBlock("after", worldAfterFacts(row), context));
224
+ lines.push(...semanticBlock("after", row.after.map(afterWording), context));
289
225
  lines.push(...semanticBlock("dependents", row.dependents.map(dependentWording), context));
290
226
  const gateFacts = row.gates.reports.flatMap((gate) => [
291
227
  `${gateFact(gate)} ${formatAge(gate.current.kind === "attested" ? gate.current.at : null, report.observedAt)}`,
@@ -304,10 +240,8 @@ function renderSelectedContractRow(row, report, context, abbreviations) {
304
240
  }
305
241
  lines.push(...semanticBlock("workspace/merge", [...workspaceFacts, ...mergeFacts(row, true, abbreviations)], context));
306
242
  const attachments = [...linkedFacts(row, report, "selected")];
307
- if (row.issue !== undefined) {
308
- const detail = row.issue.kind === "hook-failure" ? row.issue.diagnostic : `target-checkout-retained ${row.issue.target}`;
309
- attachments.push(`lag (observed now): ${detail}`);
310
- }
243
+ if (row.issue !== undefined)
244
+ attachments.push(`lag (observed now): target-checkout-retained ${row.issue.target}`);
311
245
  lines.push(...semanticBlock("attachments", attachments, context));
312
246
  lines.push(...semanticBlock("namespace tasks", namespaceTaskFacts(row), context));
313
247
  return lines;
@@ -331,19 +265,27 @@ function isTerminalTask(row) {
331
265
  }
332
266
  function renderWorldContractRow(row, report, context) {
333
267
  const title = row.title ?? "title unavailable";
334
- const facts = [
268
+ const contractFacts = [
335
269
  candidateFact(row.delivery),
336
- ...targetFacts(row),
337
- ...worldAfterFacts(row),
270
+ ...targetFacts(row, gitAbbreviations(report)),
271
+ ...row.after.map(afterWording),
338
272
  ...(row.dependents.length === 0 ? [] : [`dependents ${row.dependents.map(dependentWording).join(" · ")}`]),
339
273
  ...row.gates.reports.map(gateFact),
340
- ...linkedFacts(row, report, "world"),
341
274
  ];
342
- const lines = [
343
- identityLine(contractMark(row), row.id, `· ${row.phase} · ${formatAge(row.lastJournalAt, report.observedAt)} · ${title}`),
275
+ const linkedFacts = [
276
+ ...(row.holder.kind === "held" ? [linkedTask(report, row.holder.taskId)] : []),
277
+ ...(row.holder.kind === "unavailable" ? ["! task · unavailable"] : []),
278
+ ...(linkedAkumaSummary(row, report) === undefined ? [] : [linkedAkumaSummary(row, report)]),
344
279
  ];
345
- lines.push(...plumbFacts(facts, context.columns));
346
- return lines;
280
+ const statusTone = contractStatusTone(row, report.observedAt);
281
+ return entityLines({
282
+ mark: statusTone === null ? contractMark(row) : tone(contractMark(row), statusTone, context.color),
283
+ identity: row.id,
284
+ state: `${row.phase} · ${formatAge(row.lastJournalAt, report.observedAt)}`,
285
+ title,
286
+ facts: contractFacts,
287
+ context,
288
+ }).concat(plumbFacts(linkedFacts, context.columns));
347
289
  }
348
290
  function selectRecentRows(rows, timestamp) {
349
291
  return [...rows]
@@ -356,35 +298,24 @@ function selectRecentRows(rows, timestamp) {
356
298
  })
357
299
  .slice(0, MAX_VISIBLE_ROWS);
358
300
  }
359
- function sectionFooter({ visible, total, unit, selector, neutral, }) {
360
- if (visible === total)
361
- return [` (all ${total}${neutral ? "" : " live"} ${unit} shown)`];
362
- return [` + ${total - visible} more${neutral ? "" : " live"} ${unit} not shown`, ` keiyaku ls ${selector}/`];
363
- }
364
- function sectionBlock({ name, unit, selector, rows, total, neutral = false, }) {
365
- const lines = [`[ ${name} ] ${total}${neutral ? ` ${unit}` : " live"}`, "", ...rows.flat()];
366
- lines.push("", ...sectionFooter({ visible: rows.length, total, unit, selector, neutral }));
367
- return lines;
368
- }
369
301
  function renderContracts(report, context) {
370
302
  const section = report.contracts;
371
303
  if (section.kind === "absent")
372
- return sectionAbsent("KEIYAKU", context.columns);
304
+ return ["CONTRACTS // absent", "", `${PLUMB}contracts absent`];
373
305
  if (section.kind === "failed")
374
- return failure("KEIYAKU", section, context);
306
+ return ["CONTRACTS // unavailable", "", tone(`! ${safeText(section.failure.message)}`, "alert", context.color)];
375
307
  const live = section.value.rows.filter((row) => !isTerminalContract(row));
376
308
  const rows = selectRecentRows(live, (row) => row.lastJournalAt);
377
- const abbreviations = gitAbbreviations(report);
378
309
  const candidates = live.filter((row) => row.delivery !== null).length;
379
- const rendered = sectionBlock({
380
- name: "KEIYAKU",
310
+ const rendered = renderSectionBlock({
311
+ name: "CONTRACTS",
381
312
  unit: "keiyaku",
382
313
  selector: "kei",
383
314
  rows: rows.map((row) => renderWorldContractRow(row, report, context)),
384
315
  total: live.length,
385
316
  });
386
- const header = `[ KEIYAKU ] contract state ${section.value.state === null ? "-" : displayGitId(section.value.state, abbreviations)} · observedAt ${report.observedAt} · ${live.length} live · candidate ${candidates}`;
387
- return [header, "", ...rendered.slice(1)];
317
+ const header = `CONTRACTS // ${live.length} live · ${candidates} candidates`;
318
+ return [header, ...rendered.slice(1)];
388
319
  }
389
320
  function renderSelectedContract(report, context) {
390
321
  const section = report.contracts;
@@ -400,65 +331,52 @@ function renderSelectedContract(report, context) {
400
331
  function renderTasks(report, context) {
401
332
  const section = report.tasks;
402
333
  if (section.kind === "absent")
403
- return sectionAbsent("TASK", context.columns);
334
+ return ["TASKS // absent", "", `${PLUMB}tasks absent`];
404
335
  if (section.kind === "failed")
405
- return failure("TASK", section, context);
336
+ return ["TASKS // unavailable", "", tone(`! ${safeText(section.failure.message)}`, "alert", context.color)];
406
337
  const live = section.value.rows.filter((row) => !isTerminalTask(row));
407
338
  const rows = selectRecentRows(live, (row) => row.updatedAt);
408
339
  const rowLines = rows.map((row) => {
409
340
  const relation = row.contract === undefined ? ["unbound"] : [endpointFact(row.contract.id, row.contract.observed)];
410
- const lines = [
411
- identityLine(taskMark(row), row.id, `· ${row.disposition} · P${row.priority} · ${row.title} · ${relation.join(" · ")}`),
412
- ];
413
- lines.push(...plumbFacts((row.blockers ?? []).map((blocker) => `blocked ${blocker.id}`), context.columns));
414
- return lines;
415
- });
416
- return sectionBlock({ name: "TASK", unit: "task", selector: "task", rows: rowLines, total: live.length });
417
- }
418
- function akumaLabel(row) {
419
- const aliases = row.aliases ?? [];
420
- return aliases.length === 0 ? "" : `(${aliases.join(" ")})`;
421
- }
422
- function renderAkuma(report, context) {
423
- const section = report.akuma;
424
- if (section.kind === "absent")
425
- return sectionAbsent("FLEET", context.columns);
426
- if (section.kind === "failed")
427
- return failure("FLEET", section, context);
428
- const rows = visibleFleetRows(section.value.rows);
429
- const rowLines = rows.map((row) => {
430
- const lifeAt = "lifeAt" in row ? row.lifeAt : null;
431
- const life = `${row.life} · ${formatAge(lifeAt, report.observedAt)}`;
432
- const activity = "lastActivityAt" in row && row.lastActivityAt !== null
433
- ? [`activity ${formatAge(row.lastActivityAt, report.observedAt)}`]
434
- : [];
435
- const key = row.life === "stranded" && "strandedReason" in row && row.strandedReason === "resume-unsupported"
436
- ? [life, ...activity, "resume unsupported"]
437
- : [life, ...activity];
438
- const relation = row.contract === undefined ? ["unbound"] : [endpointFact(row.contract.id, row.contract.observed)];
439
- const snapshot = activitySnapshotLine(row);
440
- const prefix = `${akumaLabel(row)} · ${[...key, ...relation].join(" · ")}`.trim();
441
- return renderColdFleetLine(row, prefix, snapshot, context.columns);
442
- });
443
- return sectionBlock({
444
- name: "FLEET",
445
- unit: "akuma",
446
- selector: "aku",
447
- rows: rowLines,
448
- total: section.value.rows.length,
449
- neutral: true,
341
+ const childFacts = row.children === undefined ? [] : [`children ${row.children.live} live · ${row.children.total} total`];
342
+ const blockerFacts = (row.blockers ?? []).map((blocker) => `blocked ${blocker.id}`);
343
+ if (context.columns > NARROW_COLUMNS) {
344
+ return [
345
+ identityLine(taskMark(row), row.id, `· ${row.disposition} · P${row.priority} · ${row.title} · ${relation.join(" · ")}`),
346
+ ...plumbFacts([...childFacts, ...blockerFacts], context.columns),
347
+ ];
348
+ }
349
+ return entityLines({
350
+ mark: taskMark(row),
351
+ identity: row.id,
352
+ state: `${row.disposition} · P${row.priority}`,
353
+ title: row.title,
354
+ facts: [...relation, ...childFacts, ...blockerFacts],
355
+ context,
356
+ });
450
357
  });
358
+ return [
359
+ `TASKS // ${live.length} live`,
360
+ ...renderSectionBlock({
361
+ name: "TASKS",
362
+ unit: "task",
363
+ selector: "task",
364
+ continuation: "keiyaku task ls --world",
365
+ rows: rowLines,
366
+ total: live.length,
367
+ }).slice(1),
368
+ ];
451
369
  }
452
370
  export function renderKanshiText(report, context = { columns: 80, color: false }, selection = "world") {
453
371
  if (selection === "contract")
454
372
  return renderSelectedContract(report, context).join("\n");
455
373
  return [
456
- ...renderContracts(report, context),
374
+ `${tone("契", "alert", context.color)} KEIYAKU // WORLD`,
457
375
  "",
376
+ ...renderContracts(report, context),
458
377
  "",
459
378
  ...renderAkuma(report, context),
460
379
  "",
461
- "",
462
380
  ...renderTasks(report, context),
463
381
  ].join("\n");
464
382
  }
@@ -14,8 +14,7 @@ export declare function titleLines(mark: string, title: string, contract: string
14
14
  export declare function hookFailureSummary(failure: HookFailure): string;
15
15
  export declare function appendHookPayload(lines: string[], failure: HookFailure): void;
16
16
  export declare function reuseLines(reuse: VerificationReuse | undefined, columns: number): readonly string[];
17
- export declare function stopLines(label: "verification" | "completion", stop: VerificationStop | PlacementStop, columns: number, addressed: string): readonly string[];
18
- export declare function stopSummary(stop: VerificationStop | PlacementStop, addressed: string): readonly ReceiptSegment[];
17
+ export declare function stopLines(stop: VerificationStop | PlacementStop, columns: number, addressed: string, dependent?: string): readonly string[];
19
18
  export declare function cleanupLines(cleanup: NonNullable<AcceptedDeliverResult["cleanup"]>, columns: number): readonly string[];
20
19
  export declare function leakLines(leak: NonNullable<AcceptedDeliverResult["leak"]>, columns: number): readonly string[];
21
20
  export {};
@@ -1,3 +1,4 @@
1
+ import { renderRefusalFacts } from "./refusal.js";
1
2
  import { displayColumns, renderOpaqueBlock, safeText } from "./terminal.js";
2
3
  export function receiptRow(lines, mark, label, segments, columns) {
3
4
  let current = `${mark} ${label}`;
@@ -68,25 +69,96 @@ function prerequisiteRows(stop, columns) {
68
69
  }
69
70
  return lines;
70
71
  }
72
+ function gateRows(stop, columns) {
73
+ if (!("refusal" in stop) || stop.refusal?.kind !== "gates-unsatisfied")
74
+ return [];
75
+ const lines = [];
76
+ for (const report of stop.refusal.unmet) {
77
+ const { gate, current } = report;
78
+ if (current.kind === "attested") {
79
+ receiptRow(lines, " ", "gate", [{ text: gate, opaque: true }, { text: "·" }, { text: current.verdict }, { text: `· at=${current.at}` }], columns);
80
+ if (current.summary !== undefined)
81
+ receiptPayload(lines, ` summary ${gate}`, current.summary);
82
+ }
83
+ else if (current.kind === "stale") {
84
+ receiptRow(lines, " ", "gate", [{ text: gate, opaque: true }, { text: "· stale" }, { text: `· prior=${current.priorVerdict}` }], columns);
85
+ }
86
+ else {
87
+ receiptRow(lines, " ", "gate", [{ text: gate, opaque: true }, { text: "· missing" }], columns);
88
+ }
89
+ }
90
+ return lines;
91
+ }
71
92
  function targetMovedDetail(stop) {
72
93
  if ("integratedAt" in stop) {
73
94
  return [
74
95
  { text: stop.target, opaque: true },
75
96
  { text: `${stop.integratedAt} -> ${stop.observed}`, opaque: true },
76
97
  { text: `attempts=${stop.attempts}` },
98
+ ...(stop.observedTreeEqualsCandidate ? [{ text: "content=identical" }] : []),
77
99
  ];
78
100
  }
79
101
  return [
80
102
  { text: stop.target, opaque: true },
81
103
  { text: `${stop.expected} -> ${stop.observed}`, opaque: true },
104
+ ...(stop.observedTreeEqualsCandidate ? [{ text: "content=identical" }] : []),
82
105
  ];
83
106
  }
84
- export function stopLines(label, stop, columns, addressed) {
85
- const detail = stopSummary(stop, addressed);
107
+ function directStopName(stop) {
108
+ if ("refusal" in stop && stop.refusal !== undefined)
109
+ return stop.refusal.kind.replaceAll("-", " ");
110
+ if ("retry" in stop && stop.retry !== undefined)
111
+ return stop.retry.kind.replaceAll("-", " ");
112
+ return stop.failure.replaceAll("-", " ");
113
+ }
114
+ function refusalEvidence(stop, columns) {
86
115
  const lines = [];
87
- const state = label === "verification" ? "failed" : "blocked";
88
- receiptRow(lines, "!", label, [{ text: state }, { text: "·" }, ...detail], columns);
89
116
  lines.push(...prerequisiteRows(stop, columns));
117
+ lines.push(...gateRows(stop, columns));
118
+ if (!("refusal" in stop) || stop.refusal === undefined)
119
+ return lines;
120
+ const refusal = stop.refusal;
121
+ if (refusal.kind === "integration-failed") {
122
+ receiptRow(lines, " ", "reason", [{ text: refusal.reason }], columns);
123
+ receiptRow(lines, " ", "target", [{ text: refusal.targetHead, opaque: true }], columns);
124
+ if ("conflictPaths" in refusal) {
125
+ for (const path of refusal.conflictPaths)
126
+ receiptRow(lines, " ", "conflict", [{ text: path, opaque: true }], columns);
127
+ }
128
+ const recovery = "recovery" in refusal ? refusal.recovery : undefined;
129
+ if (typeof recovery === "object" &&
130
+ recovery !== null &&
131
+ "materialize" in recovery &&
132
+ typeof recovery.materialize === "string" &&
133
+ "continue" in recovery &&
134
+ typeof recovery.continue === "string") {
135
+ receiptRow(lines, " ", "recovery materialize conflicts", [{ text: recovery.materialize }], columns);
136
+ receiptRow(lines, " ", "recovery continue after resolve", [{ text: recovery.continue }], columns);
137
+ }
138
+ }
139
+ else if (refusal.kind === "integration-unsupported") {
140
+ receiptRow(lines, " ", "required Git", [{ text: refusal.requiredGit }], columns);
141
+ }
142
+ return lines;
143
+ }
144
+ export function stopLines(stop, columns, addressed, dependent) {
145
+ if ("refusal" in stop && stop.refusal?.kind === "checkout-not-followable") {
146
+ const checkout = renderRefusalFacts(stop.refusal, "", columns, addressed);
147
+ if (dependent === undefined)
148
+ return checkout;
149
+ const lines = [];
150
+ receiptRow(lines, "!", "continuation", [{ text: dependent, opaque: true }], columns);
151
+ return [...lines, ...checkout];
152
+ }
153
+ const lines = [];
154
+ const segments = dependent === undefined ? [] : [{ text: "·" }, { text: directStopName(stop) }];
155
+ if ("failure" in stop && stop.failure === "target-moved")
156
+ segments.push(...targetMovedDetail(stop));
157
+ if ("failure" in stop && stop.failure === "environment-failure" && "command" in stop) {
158
+ segments.push({ text: `command=${stop.command}` }, { text: hookFailureSummary(stop.detail), opaque: true });
159
+ }
160
+ receiptRow(lines, "!", dependent === undefined ? directStopName(stop) : dependent, segments, columns);
161
+ lines.push(...refusalEvidence(stop, columns));
90
162
  if ("failure" in stop && stop.failure === "environment-failure" && "command" in stop) {
91
163
  appendHookPayload(lines, stop.detail);
92
164
  }
@@ -98,28 +170,6 @@ export function stopLines(label, stop, columns, addressed) {
98
170
  }
99
171
  return lines;
100
172
  }
101
- export function stopSummary(stop, addressed) {
102
- const detail = [];
103
- if ("refusal" in stop && stop.refusal !== undefined) {
104
- detail.push({ text: stop.refusal.kind });
105
- if ("contractId" in stop.refusal && stop.refusal.contractId !== addressed) {
106
- detail.push({ text: `contract=${stop.refusal.contractId}`, opaque: true });
107
- }
108
- }
109
- else if ("retry" in stop && stop.retry !== undefined) {
110
- detail.push({ text: stop.retry.kind });
111
- }
112
- else if ("failure" in stop) {
113
- detail.push({ text: stop.failure });
114
- if (stop.failure === "environment-failure" && "command" in stop) {
115
- detail.push({ text: `command=${stop.command}` }, { text: hookFailureSummary(stop.detail), opaque: true });
116
- }
117
- else if (stop.failure === "target-moved") {
118
- detail.push(...targetMovedDetail(stop));
119
- }
120
- }
121
- return detail;
122
- }
123
173
  export function cleanupLines(cleanup, columns) {
124
174
  const lines = [];
125
175
  receiptRow(lines, "!", "cleanup", [
@@ -1,4 +1,4 @@
1
- import { displayColumns, gitShortStat, renderOpaqueBlock, safeText } from "./terminal.js";
1
+ import { checkoutNotFollowableLines, displayColumns, gitShortStat, renderOpaqueBlock, safeText, } from "./terminal.js";
2
2
  function isRecord(value) {
3
3
  return typeof value === "object" && value !== null && !Array.isArray(value);
4
4
  }
@@ -78,15 +78,7 @@ export function renderRefusalFacts(refusal, indent, columns, addressed) {
78
78
  return renderOpaqueBlock(refusalHead(refusal.kind, identity, [`requiredGit=${refusal.requiredGit}`]), indent, columns);
79
79
  }
80
80
  if (refusal.kind === "checkout-not-followable") {
81
- const lines = [
82
- ...renderOpaqueBlock(refusalHead(refusal.kind, identity, [
83
- `target=${refusal.target}`,
84
- `path=${refusal.path}`,
85
- `reason=${refusal.reason}`,
86
- ]), indent, columns),
87
- ];
88
- lines.push(...collectionLines("paths", refusal.paths, indent, columns));
89
- return lines;
81
+ return checkoutNotFollowableLines(refusal);
90
82
  }
91
83
  return renderOpaqueBlock(refusalHead(refusal.kind, identity, []), indent, columns);
92
84
  }