@astrosheep/keiyaku 2.9.8 → 2.9.10

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 (230) hide show
  1. package/README.md +50 -182
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/harness/control-types.js +18 -1
  4. package/build/agents/harness/index.js +2 -0
  5. package/build/agents/harness/outcome-error.js +81 -0
  6. package/build/agents/harness/projection.js +10 -7
  7. package/build/agents/harness/router.js +5 -1
  8. package/build/agents/providers/claude-agent-sdk/adapter.js +18 -4
  9. package/build/agents/providers/claude-agent-sdk/registration.js +11 -1
  10. package/build/agents/providers/claude-agent-sdk/session.js +19 -5
  11. package/build/agents/providers/codex-app-server/adapter.js +11 -6
  12. package/build/agents/providers/codex-app-server/events.js +67 -6
  13. package/build/agents/providers/codex-app-server/registration.js +11 -1
  14. package/build/agents/providers/opencode-sdk/adapter.js +8 -1
  15. package/build/agents/providers/opencode-sdk/events.js +24 -2
  16. package/build/agents/providers/opencode-sdk/registration.js +11 -1
  17. package/build/agents/providers/pi/checkpoint.js +9 -1
  18. package/build/agents/providers/pi/events.js +2 -0
  19. package/build/agents/providers/pi/registration.js +14 -1
  20. package/build/cli/commands/akuma/akuma/handler.js +1 -1
  21. package/build/cli/commands/akuma/akuma/meta.js +3 -3
  22. package/build/cli/commands/akuma/catalog.js +2 -2
  23. package/build/cli/commands/akuma/{view → show}/handler.js +5 -5
  24. package/build/cli/commands/akuma/{view → show}/meta.js +3 -3
  25. package/build/cli/commands/akuma.js +1 -1
  26. package/build/cli/commands/contract/amend/handler.js +5 -4
  27. package/build/cli/commands/contract/arc/handler.js +5 -4
  28. package/build/cli/commands/contract/audit/handler.js +2 -2
  29. package/build/cli/commands/contract/bind/handler.js +12 -3
  30. package/build/cli/commands/contract/bind/meta.js +45 -15
  31. package/build/cli/commands/contract/forfeit/handler.js +9 -5
  32. package/build/cli/commands/contract/log/handler.js +2 -2
  33. package/build/cli/commands/contract/petition/handler.js +34 -7
  34. package/build/cli/commands/contract/renew/handler.js +7 -7
  35. package/build/cli/commands/contract/renew/meta.js +3 -3
  36. package/build/cli/commands/metadata.js +10 -6
  37. package/build/cli/commands/projection/call/meta.js +1 -1
  38. package/build/cli/commands/projection/catalog.js +2 -0
  39. package/build/cli/commands/projection/history/handler.js +21 -0
  40. package/build/cli/commands/projection/history/meta.js +9 -0
  41. package/build/cli/commands/projection/shared.js +3 -4
  42. package/build/cli/commands/projection/status/handler.js +3 -3
  43. package/build/cli/commands/projection/wait/handler.js +20 -11
  44. package/build/cli/commands/task/add/handler.js +14 -5
  45. package/build/cli/commands/task/add/meta.js +2 -2
  46. package/build/cli/commands/task/catalog.js +4 -0
  47. package/build/cli/commands/task/done/handler.js +1 -4
  48. package/build/cli/commands/task/done/meta.js +1 -1
  49. package/build/cli/commands/task/drop/meta.js +1 -1
  50. package/build/cli/commands/task/hold/handler.js +3 -0
  51. package/build/cli/commands/task/hold/meta.js +8 -0
  52. package/build/cli/commands/task/log/handler.js +1 -1
  53. package/build/cli/commands/task/log/meta.js +2 -2
  54. package/build/cli/commands/task/resume/handler.js +3 -0
  55. package/build/cli/commands/task/resume/meta.js +8 -0
  56. package/build/cli/commands/task/shared.js +39 -14
  57. package/build/cli/commands/task/show/handler.js +1 -1
  58. package/build/cli/commands/task/show/meta.js +1 -1
  59. package/build/cli/commands/task/start/meta.js +1 -1
  60. package/build/cli/commands/task/stop/handler.js +1 -4
  61. package/build/cli/commands/task/stop/meta.js +1 -1
  62. package/build/cli/commands/task/update/handler.js +11 -3
  63. package/build/cli/commands/task/update/meta.js +3 -3
  64. package/build/cli/completion.js +3 -2
  65. package/build/cli/flags.js +51 -14
  66. package/build/cli/help.js +4 -1
  67. package/build/cli/index.js +40 -25
  68. package/build/cli/parse-flags.js +40 -7
  69. package/build/cli/parse-metadata.js +5 -2
  70. package/build/cli/parse-selectors.js +2 -3
  71. package/build/cli/parse.js +40 -6
  72. package/build/cli/projection-address.js +12 -10
  73. package/build/cli/render/amendment-warning.js +8 -0
  74. package/build/cli/render/arc.js +6 -2
  75. package/build/cli/render/audit.js +400 -81
  76. package/build/cli/render/kanshi.js +242 -79
  77. package/build/cli/render/lifecycle.js +56 -0
  78. package/build/cli/render/misc.js +24 -28
  79. package/build/cli/render/petition.js +5 -0
  80. package/build/cli/render/projection-activity.js +65 -29
  81. package/build/cli/render/projection-history.js +64 -0
  82. package/build/cli/render/shared.js +23 -9
  83. package/build/cli/render/status.js +13 -35
  84. package/build/cli/render/task.js +51 -26
  85. package/build/cli/render/terminal-failure.js +3 -1
  86. package/build/cli/render/wait.js +45 -69
  87. package/build/cli/subagent-guard.js +1 -1
  88. package/build/cli/types.js +1 -1
  89. package/build/config/env-keys.js +3 -1
  90. package/build/config/env.js +24 -1
  91. package/build/config/settings/schema.js +3 -1
  92. package/build/core/address-carrier.js +89 -0
  93. package/build/core/addressing.js +255 -196
  94. package/build/core/amend.js +115 -38
  95. package/build/core/amendment-criteria.js +17 -0
  96. package/build/core/amendment-warning.js +10 -0
  97. package/build/core/arc.js +184 -83
  98. package/build/core/audit/candidate.js +110 -9
  99. package/build/core/audit/coordinates.js +120 -65
  100. package/build/core/audit/evidence.js +19 -23
  101. package/build/core/audit/facade.js +34 -11
  102. package/build/core/audit/report.js +5 -5
  103. package/build/core/bind-reconciliation.js +390 -0
  104. package/build/core/bind-workspace.js +12 -20
  105. package/build/core/bind.js +242 -273
  106. package/build/core/call/call.js +1 -0
  107. package/build/core/call/context.js +46 -71
  108. package/build/core/call/prompt.js +6 -3
  109. package/build/core/context.js +49 -35
  110. package/build/core/contract-carrier-runtime.js +408 -0
  111. package/build/core/contract-carrier.js +270 -0
  112. package/build/core/contract-view.js +33 -27
  113. package/build/core/contract.js +5 -1
  114. package/build/core/derived-replay.js +207 -0
  115. package/build/core/draft.js +125 -57
  116. package/build/core/forfeit.js +113 -56
  117. package/build/core/identifier-slug.js +63 -13
  118. package/build/core/ids.js +4 -4
  119. package/build/core/lifecycle-history.js +35 -0
  120. package/build/core/lifecycle-recovery.js +117 -0
  121. package/build/core/lifecycle-runner.js +377 -0
  122. package/build/core/log.js +37 -11
  123. package/build/core/outcome-base.js +181 -0
  124. package/build/core/process-group.js +139 -0
  125. package/build/core/projection/generation/database.js +82 -7
  126. package/build/core/projection/generation/model.js +9 -36
  127. package/build/core/projection/generation/projection-generation-continuation.js +10 -22
  128. package/build/core/projection/generation/projection-generation-execution.js +13 -6
  129. package/build/core/projection/index.js +5 -4
  130. package/build/core/projection/projection-activity.js +29 -8
  131. package/build/core/projection/projection-alias.js +23 -10
  132. package/build/core/projection/projection-core.js +21 -7
  133. package/build/core/projection/projection-execution-observer.js +91 -39
  134. package/build/core/projection/projection-history.js +474 -0
  135. package/build/core/projection/projection-runner-lock.js +12 -0
  136. package/build/core/projection/projection-status-observation.js +365 -0
  137. package/build/core/projection/projection-status.js +35 -337
  138. package/build/core/projection/projection-terminal-failure.js +13 -8
  139. package/build/core/projection/projection-wait.js +7 -2
  140. package/build/core/projection/tell/launch-store.js +244 -0
  141. package/build/core/projection/tell/model.js +3 -3
  142. package/build/core/projection/tell/store.js +70 -118
  143. package/build/core/registry.js +22 -87
  144. package/build/core/renew-build.js +424 -0
  145. package/build/core/renew-plan.js +239 -0
  146. package/build/core/renew-rewrite.js +77 -0
  147. package/build/core/renew.js +295 -520
  148. package/build/core/repository-ledger/accepted-fold-read.js +19 -0
  149. package/build/core/repository-ledger/accepted-tail.js +116 -0
  150. package/build/core/repository-ledger/admission.js +38 -0
  151. package/build/core/repository-ledger/authoritative-transaction.js +1 -0
  152. package/build/core/repository-ledger/claim-evidence.js +147 -0
  153. package/build/core/repository-ledger/codec.js +297 -0
  154. package/build/core/repository-ledger/current-state-store.js +853 -0
  155. package/build/core/repository-ledger/fold-repository.js +375 -0
  156. package/build/core/repository-ledger/fold.js +248 -0
  157. package/build/core/repository-ledger/git-object-store.js +243 -0
  158. package/build/core/repository-ledger/history-read.js +15 -0
  159. package/build/core/repository-ledger/incremental-read.js +153 -0
  160. package/build/core/repository-ledger/index.js +15 -0
  161. package/build/core/repository-ledger/inventory-plan.js +75 -0
  162. package/build/core/repository-ledger/inventory.js +310 -0
  163. package/build/core/repository-ledger/publication-admission.js +125 -0
  164. package/build/core/repository-ledger/publication.js +353 -0
  165. package/build/core/repository-ledger/read-model.js +469 -0
  166. package/build/core/repository-ledger/transaction-membership.js +78 -0
  167. package/build/core/repository-ledger/traversal.js +404 -0
  168. package/build/core/repository-ledger/write-transaction.js +339 -0
  169. package/build/core/seal.js +168 -161
  170. package/build/core/settlement/claim-delivery.js +42 -215
  171. package/build/core/settlement/claim.js +235 -153
  172. package/build/core/settlement/index.js +4 -4
  173. package/build/core/settlement/petition-claim-gates.js +52 -33
  174. package/build/core/settlement/petition-forfeit.js +12 -5
  175. package/build/core/settlement/petition-head-guard.js +8 -5
  176. package/build/core/settlement/petition-preview.js +68 -56
  177. package/build/core/settlement/petition-window.js +50 -0
  178. package/build/core/settlement/petition.js +252 -131
  179. package/build/core/settlement/queue-read-model.js +72 -95
  180. package/build/core/settlement/queue-seat-allocation.js +35 -126
  181. package/build/core/settlement/queue.js +1 -1
  182. package/build/core/settlement/settlement.js +172 -80
  183. package/build/core/settlement/verdict.js +112 -66
  184. package/build/core/settlement/verification-coordination.js +305 -0
  185. package/build/core/settlement/verification-supervisor.js +275 -0
  186. package/build/core/settlement/verification.js +398 -31
  187. package/build/core/status/board.js +125 -127
  188. package/build/core/status/lifecycle.js +7 -80
  189. package/build/core/structured-query.js +23 -0
  190. package/build/core/target-ref.js +1 -1
  191. package/build/core/task/board.js +59 -24
  192. package/build/core/task/commands.js +181 -33
  193. package/build/core/task/coordinate.js +56 -0
  194. package/build/core/task/document.js +25 -9
  195. package/build/core/task/index.js +7 -4
  196. package/build/core/task/query.js +17 -27
  197. package/build/core/task/settlement-git.js +164 -179
  198. package/build/core/task/settlement-policy.js +15 -11
  199. package/build/core/task/source-board.js +31 -13
  200. package/build/core/task/task-bind-preparation.js +193 -0
  201. package/build/core/task/task-contract.js +102 -193
  202. package/build/core/task/task-file-transaction.js +171 -0
  203. package/build/core/task/task-git-runtime.js +76 -14
  204. package/build/core/task/task-git-store.js +68 -169
  205. package/build/core/task/task-store-repository.js +13 -6
  206. package/build/core/task/task-worktree.js +6 -33
  207. package/build/core/task/task.js +1 -1
  208. package/build/core/task/tree.js +3 -3
  209. package/build/core/task/validation.js +4 -3
  210. package/build/core/transcripts.js +93 -1
  211. package/build/core/worktree-bootstrap.js +44 -36
  212. package/build/core/worktree-path.js +210 -77
  213. package/build/flow-error.js +7 -7
  214. package/build/generated/version.js +2 -2
  215. package/build/git/core.js +76 -13
  216. package/build/git/refs.js +168 -46
  217. package/build/git/streaming-batch.js +73 -13
  218. package/package.json +1 -1
  219. package/skills/keiyaku/SKILL.md +1 -1
  220. package/skills/keiyaku-akuma/SKILL.md +3 -3
  221. package/skills/keiyaku-task/SKILL.md +4 -2
  222. package/skills/keiyaku-workflow/SKILL.md +88 -203
  223. package/build/core/entry.js +0 -305
  224. package/build/core/ledger-batch.js +0 -194
  225. package/build/core/ledger.js +0 -84
  226. package/build/core/projection/leash.js +0 -87
  227. package/build/core/ref-log.js +0 -119
  228. package/build/core/renew-session.js +0 -128
  229. package/build/core/status/ledger-batch.js +0 -1
  230. package/build/core/status/reconciliation.js +0 -137
@@ -1,25 +1,82 @@
1
+ import { getConfig } from "../../config/env.js";
1
2
  import { formatAgeMs } from "./format.js";
3
+ import { displayColumns, resolveLineColumns, truncateColumns } from "./line-width.js";
4
+ import { renderPendingProjectionTell, renderProjectionActivity, selectProjectionActivityWindow, } from "./projection-activity.js";
5
+ const KANSHI_MAX_COLUMNS = 120;
6
+ const KANSHI_ACTIVITY_ROWS = 3;
2
7
  function shortHead(hash) {
3
- if (!hash)
4
- return "????????";
5
- return hash.slice(0, 7);
8
+ return hash?.slice(0, 7);
9
+ }
10
+ function supportsColor() {
11
+ const { terminal } = getConfig();
12
+ if (terminal.noColor !== undefined || terminal.forceColor === "0")
13
+ return false;
14
+ if (terminal.forceColor !== undefined)
15
+ return true;
16
+ return process.stdout.isTTY === true && terminal.term?.toLowerCase() !== "dumb";
17
+ }
18
+ function tone(text, value) {
19
+ if (!supportsColor() || value === undefined || value === "normal")
20
+ return text;
21
+ return value === "description"
22
+ ? `\x1b[2m${text}\x1b[0m`
23
+ : `\x1b[31m${text}\x1b[0m`;
24
+ }
25
+ function renderFacts(facts) {
26
+ return {
27
+ plain: facts.map((fact) => fact.text).join(" · "),
28
+ rendered: facts.map((fact) => tone(fact.text, fact.tone)).join(" · "),
29
+ };
30
+ }
31
+ /** Align facts without allowing ANSI bytes to enter width arithmetic. */
32
+ function alignedBoardRow(input) {
33
+ const facts = renderFacts(input.facts);
34
+ const prefix = `${input.prefix} `;
35
+ const titleBudget = Math.max(0, input.width - displayColumns(prefix) - displayColumns(facts.plain) - 1);
36
+ const visibleTitle = truncateColumns(input.title, titleBudget);
37
+ const spaces = " ".repeat(Math.max(1, input.width - displayColumns(prefix) - displayColumns(visibleTitle) - displayColumns(facts.plain)));
38
+ return `${prefix}${tone(visibleTitle, input.titleTone ?? "description")}${spaces}${facts.rendered}`;
6
39
  }
7
40
  function arcSparkline(arcs) {
8
- if (arcs <= 0)
9
- return "";
41
+ if (arcs <= 1)
42
+ return undefined;
10
43
  return Array.from({ length: arcs }, () => "·").join("──");
11
44
  }
12
45
  function pendingAfterIds(contract) {
13
46
  return contract.after.filter((item) => !item.terminal).map((item) => item.id);
14
47
  }
15
- function kanshiGlyph(contract) {
16
- if (contract.state.state === "corrupt")
17
- return "×";
18
- if (pendingAfterIds(contract).length > 0)
19
- return "○";
20
- if (contract.state.state === "petitioned")
21
- return "◎";
22
- return "●";
48
+ /**
49
+ * The Kanshi shape is the after DAG, not the petition/claim settlement queue.
50
+ * Stable input order settles independent lanes and any malformed cycle remains
51
+ * observable rather than dropping a contract.
52
+ */
53
+ export function orderKanshiContracts(contracts) {
54
+ const byId = new Map(contracts.map((contract) => [contract.id, contract]));
55
+ const remaining = new Set(contracts.map((contract) => contract.id));
56
+ const emitted = new Set();
57
+ const ordered = [];
58
+ while (remaining.size > 0) {
59
+ const next = contracts.find((contract) => (remaining.has(contract.id)
60
+ && pendingAfterIds(contract)
61
+ .filter((id) => byId.has(id))
62
+ .every((id) => emitted.has(id))));
63
+ if (!next) {
64
+ // The bind boundary normally rejects cycles. Keep a corrupt historical
65
+ // graph visible in its original deterministic order instead of inventing
66
+ // a dependency edge or omitting work from the board.
67
+ const fallback = contracts.find((contract) => remaining.has(contract.id));
68
+ if (!fallback)
69
+ break;
70
+ ordered.push(fallback);
71
+ emitted.add(fallback.id);
72
+ remaining.delete(fallback.id);
73
+ continue;
74
+ }
75
+ ordered.push(next);
76
+ emitted.add(next.id);
77
+ remaining.delete(next.id);
78
+ }
79
+ return ordered;
23
80
  }
24
81
  function renderDriftMark(drift) {
25
82
  if (drift.state === "drift")
@@ -28,73 +85,179 @@ function renderDriftMark(drift) {
28
85
  return "▲orphaned";
29
86
  return undefined;
30
87
  }
31
- export function renderKanshiBoard(status) {
32
- const head = shortHead(status.defaultHead);
33
- if (status.contracts.length === 0) {
34
- if (status.targetFilter !== null) {
35
- return `現世 ${head} · target selected\nno commissions on selected target\n`;
36
- }
37
- return `現世 ${head} 无在飞契约\n`;
88
+ function contractGlyph(contract) {
89
+ if (contract.state.state === "corrupt")
90
+ return "×";
91
+ return pendingAfterIds(contract).length > 0 ? "⧗" : "□";
92
+ }
93
+ function contractFacts(contract) {
94
+ const facts = [];
95
+ const spark = arcSparkline(contract.arcs);
96
+ if (spark)
97
+ facts.push({ text: spark });
98
+ const drift = renderDriftMark(contract.baseDrift);
99
+ if (drift)
100
+ facts.push({ text: drift });
101
+ const lifecycle = contract.state.state === "active" ? "open" : contract.state.state;
102
+ const age = formatAgeMs(contract.lastActiveMs);
103
+ facts.push({ text: age ? `${lifecycle} ${age}` : lifecycle });
104
+ if (contract.queueHeadStalled) {
105
+ facts.push({ text: age ? `stalled ${age}` : "stalled", tone: "alert" });
38
106
  }
39
- const header = status.targetFilter !== null
40
- ? `現世 ${head} · target selected`
41
- : `現世 ${head}`;
42
- const lines = [header, ""];
43
- for (const contract of status.contracts) {
44
- const glyph = kanshiGlyph(contract);
45
- const name = contract.place ? `${contract.slug} @${contract.place}` : contract.slug;
46
- const spark = arcSparkline(contract.arcs);
47
- const facts = [];
48
- const lifecycle = contract.state.state === "active" ? "open" : contract.state.state;
49
- if (lifecycle === "bound" || lifecycle === "open" || lifecycle === "petitioned") {
50
- facts.push(lifecycle);
51
- }
52
- const arcMark = contract.currentArc.state === "open"
53
- ? `a${contract.currentArc.number}+`
54
- : `a${contract.currentArc.number}`;
55
- facts.push(contract.title);
56
- facts.push(arcMark);
57
- facts.push(`next ${contract.nextStep.detail}`);
58
- const age = formatAgeMs(contract.lastActiveMs);
59
- if (age)
60
- facts.push(age);
61
- if (contract.queuePosition)
62
- facts.push(`Q${contract.queuePosition}`);
63
- const drift = renderDriftMark(contract.baseDrift);
64
- if (drift)
65
- facts.push(drift);
66
- if (contract.nextStep.hint)
67
- facts.push(contract.nextStep.hint);
68
- const pending = pendingAfterIds(contract);
69
- if (pending.length > 0) {
70
- facts.push(`⧗ ${pending.map((id) => {
71
- const dot = id.indexOf(".");
72
- return dot > 0 ? id.slice(0, dot) : id;
73
- }).join(",")}`);
74
- }
75
- if (contract.state.state === "corrupt") {
76
- facts.push(`corrupt (${contract.state.reason ?? contract.corruptReason ?? "unknown"})`);
77
- }
78
- if (contract.queueHeadStalled) {
79
- facts.push("朱 stall 去广场");
80
- }
81
- const sparkCol = spark.padEnd(14);
82
- const factText = facts.join(" · ");
83
- lines.push(`${glyph} ${name.padEnd(18)} ${sparkCol} ${factText}`.trimEnd());
84
- if (contract.bootstrapDiagnostics.some((diagnostic) => diagnostic.kind === "plan-without-result")) {
85
- lines.push(" ! bootstrap · no result recorded · forfeit and re-bind");
86
- }
87
- for (const diagnostic of contract.queueDiagnostics) {
88
- if (diagnostic.kind === "legacy-petition") {
89
- lines.push(" ! legacy petition · no seat · reject then re-petition");
90
- continue;
91
- }
92
- if (diagnostic.kind === "duplicate-seat") {
93
- lines.push(` ! seat ${diagnostic.seat} duplicated with ${diagnostic.with} · ordered by contract id`);
94
- continue;
95
- }
96
- lines.push(" ! target mismatch");
107
+ return facts;
108
+ }
109
+ function renderKeiyakuSection(board, width) {
110
+ if (!board.contractsObservable)
111
+ return undefined;
112
+ const lines = [`keiyaku ${board.contracts.length}`];
113
+ for (const contract of orderKanshiContracts(board.contracts)) {
114
+ lines.push(alignedBoardRow({
115
+ prefix: `${contractGlyph(contract)} ${contract.place ?? contract.slug}`,
116
+ title: contract.title,
117
+ facts: contractFacts(contract),
118
+ width,
119
+ }));
120
+ if (contract.replayBlockers.length > 0) {
121
+ lines.push(` ! incomplete replay · ${contract.replayBlockers.map((blocker) => blocker.surface).join(", ")}`);
122
+ lines.push(` • ${contract.replayBlockers[0].recoveryCommand}`);
97
123
  }
98
124
  }
99
- return `${lines.join("\n")}\n`;
125
+ return lines.join("\n");
126
+ }
127
+ function activityState(row) {
128
+ switch (row.state) {
129
+ case "lost": return "lost";
130
+ case "startup-timeout": return "stalled";
131
+ case "unknown":
132
+ case "incompatible": return "unknown";
133
+ case "failed":
134
+ case "dead": return "dead";
135
+ case "done": return "returned";
136
+ case "killed":
137
+ case "dismissed": return "dismissed";
138
+ case "minting":
139
+ case "out": return "running";
140
+ }
141
+ }
142
+ function projectionBinding(board, row) {
143
+ if (row.binding?.kind !== "commission")
144
+ return "";
145
+ const place = board.commissionPlaces.get(row.binding.commissionId);
146
+ return place ? `@${place}` : "";
147
+ }
148
+ function projectionFacts(row) {
149
+ const facts = [];
150
+ if (row.durationMs !== undefined)
151
+ facts.push({ text: `up ${formatAgeMs(row.durationMs) ?? "0s"}` });
152
+ if (row.activityAgeMs !== undefined)
153
+ facts.push({ text: `active ${formatAgeMs(row.activityAgeMs) ?? "0s"}` });
154
+ if (row.state !== "out" && row.state !== "minting")
155
+ facts.push({ text: row.state, tone: row.state === "startup-timeout" ? "alert" : "normal" });
156
+ return facts;
157
+ }
158
+ function renderEmbeddedActivity(row, width) {
159
+ const observedAtMs = row.observedAtMs ?? Date.now();
160
+ const selection = row.activity
161
+ ? selectProjectionActivityWindow(row.activity, KANSHI_ACTIVITY_ROWS)
162
+ : undefined;
163
+ const ordinary = row.activity
164
+ ? renderProjectionActivity(selection.activity, {
165
+ anchorMs: observedAtMs,
166
+ state: activityState(row),
167
+ showPlan: false,
168
+ showSilence: false,
169
+ ...(row.workspaceRoot ? { workspaceRoot: row.workspaceRoot } : {}),
170
+ maxColumns: width,
171
+ leadingOmittedCount: selection.leadingOmittedCount,
172
+ })
173
+ : undefined;
174
+ const rendered = ordinary?.lines.map((line, index) => ({
175
+ line,
176
+ at: ordinary.rowAts[index],
177
+ order: index,
178
+ })) ?? [];
179
+ const tells = (row.pendingTells ?? []).map((tell, index) => ({
180
+ line: renderPendingProjectionTell(tell, {
181
+ anchorMs: observedAtMs,
182
+ maxColumns: width,
183
+ renderUnconsumedTail: (tail) => tone(tail, "alert"),
184
+ }),
185
+ at: tell.createdAt,
186
+ order: rendered.length + index,
187
+ }));
188
+ return [...rendered, ...tells]
189
+ .sort((left, right) => {
190
+ const leftAt = Date.parse(left.at ?? "");
191
+ const rightAt = Date.parse(right.at ?? "");
192
+ const safeLeft = Number.isFinite(leftAt) ? leftAt : Number.POSITIVE_INFINITY;
193
+ const safeRight = Number.isFinite(rightAt) ? rightAt : Number.POSITIVE_INFINITY;
194
+ return safeLeft - safeRight || left.order - right.order;
195
+ })
196
+ .map((entry) => entry.line);
197
+ }
198
+ function terminalSummary(board) {
199
+ const omitted = board.projections?.omitted;
200
+ if (!omitted)
201
+ return undefined;
202
+ const compatibleStates = ["failed", "dead", "done", "killed", "dismissed"];
203
+ const details = compatibleStates
204
+ .flatMap((state) => omitted.byState[state] > 0 ? [`${omitted.byState[state]} ${state}`] : []);
205
+ return details.length > 0 ? `+ ${details.join(" · ")}` : undefined;
206
+ }
207
+ function renderAkumaSection(board, width) {
208
+ if (!board.projections)
209
+ return undefined;
210
+ const lines = [`akuma ${board.projections.rows.length}`];
211
+ for (const row of board.projections.rows) {
212
+ const alias = row.alias ? ` ${row.alias}` : "";
213
+ lines.push(alignedBoardRow({
214
+ prefix: `◆ ${row.id}${alias}`,
215
+ title: projectionBinding(board, row),
216
+ titleTone: "normal",
217
+ facts: projectionFacts(row),
218
+ width,
219
+ }));
220
+ lines.push(...renderEmbeddedActivity(row, width));
221
+ }
222
+ const folded = terminalSummary(board);
223
+ if (folded)
224
+ lines.push(tone(folded, "description"));
225
+ return lines.join("\n");
226
+ }
227
+ function taskLabel(task) {
228
+ return task.title === task.id ? `P${task.pri} ${task.id}` : `P${task.pri} ${task.id} ${task.title}`;
229
+ }
230
+ function renderTaskSection(tasks, width) {
231
+ if (!tasks)
232
+ return undefined;
233
+ const lines = [`task ${tasks.total}`];
234
+ for (const task of tasks.topTasks) {
235
+ lines.push(alignedBoardRow({
236
+ prefix: "▶",
237
+ title: taskLabel(task),
238
+ facts: [{ text: `${task.state === "on_hold" ? "on hold · updated" : "in_progress · active"} ${formatAgeMs(task.activityAgeMs) ?? "0s"}` }],
239
+ width,
240
+ }));
241
+ }
242
+ const folded = [
243
+ tasks.ready > 0 ? `${tasks.ready} ready` : undefined,
244
+ tasks.blocked > 0 ? `${tasks.blocked} blocked` : undefined,
245
+ tasks.onHold > 0 ? `${tasks.onHold} on hold` : undefined,
246
+ ].filter((fact) => fact !== undefined);
247
+ if (folded.length > 0)
248
+ lines.push(tone(`+ ${folded.join(" · ")}`, "description"));
249
+ return lines.join("\n");
250
+ }
251
+ export function renderKanshiBoard(board) {
252
+ const width = Math.min(KANSHI_MAX_COLUMNS, resolveLineColumns());
253
+ const left = "kanshi ";
254
+ const head = shortHead(board.defaultHead);
255
+ const right = head ? ` 現世 ${head}` : " 現世";
256
+ const dashes = "─".repeat(Math.max(1, width - displayColumns(left) - displayColumns(right)));
257
+ const sections = [
258
+ renderKeiyakuSection(board, width),
259
+ renderAkumaSection(board, width),
260
+ renderTaskSection(board.tasks, width),
261
+ ].filter((section) => section !== undefined);
262
+ return `${[`${left}${dashes}${right}`, ...sections].join("\n\n")}\n`;
100
263
  }
@@ -0,0 +1,56 @@
1
+ import { attachAddressCarrier } from "../../core/addressing.js";
2
+ import { isCompleteLifecycleOutcome } from "../../core/outcome-base.js";
3
+ import { FlowError } from "../../flow-error.js";
4
+ import { handleCliError } from "./errors.js";
5
+ import { coreTextResponse } from "./shared.js";
6
+ function isSuccessfulDerivedCleanup(result) {
7
+ return result.family === "precommit-effects-only"
8
+ && "status" in result
9
+ && result.status === "success"
10
+ && "cleanup" in result
11
+ && typeof result.cleanup === "object"
12
+ && result.cleanup !== null
13
+ && "status" in result.cleanup
14
+ && result.cleanup.status === "succeeded"
15
+ && [...result.prerequisites, ...result.downstream].every((row) => row.status !== "failed");
16
+ }
17
+ export async function buildLifecycleOutcomeResponse(result, context) {
18
+ if (result.family === "committed") {
19
+ return coreTextResponse(`lifecycle committed at ${result.committed.head}${result.replay.action === "reconcile" ? "; downstream reconciliation required" : ""}`, result);
20
+ }
21
+ const cause = result.family === "cancelled-before-effects"
22
+ ? result.cancellation
23
+ : result.family === "refused-before-effects"
24
+ ? result.failedInvariant
25
+ : [...result.prerequisites, ...result.downstream].find((row) => row.status === "failed")?.cause
26
+ ?? { code: "INTERNAL_STATE", message: "durable prerequisite effects require lifecycle reconciliation" };
27
+ if (!context) {
28
+ return { ...coreTextResponse(cause.message, result), exitCode: result.family === "cancelled-before-effects" ? 130 : 1 };
29
+ }
30
+ const facts = "facts" in cause ? cause.facts : undefined;
31
+ const error = new FlowError(cause.code, cause.message, {
32
+ ...(facts ? { facts: facts } : {}),
33
+ });
34
+ const rendered = await handleCliError({
35
+ error: context.address ? attachAddressCarrier(error, context.address) : error,
36
+ cwd: context.cwd,
37
+ logLabel: "lifecycle",
38
+ });
39
+ return {
40
+ ...rendered,
41
+ payload: result,
42
+ isDomainFailure: true,
43
+ exitCode: result.family === "cancelled-before-effects" ? 130 : 1,
44
+ };
45
+ }
46
+ export async function renderLifecycleResult(work, render, context) {
47
+ const value = await work;
48
+ if (isCompleteLifecycleOutcome(value))
49
+ return render(value);
50
+ if (isSuccessfulDerivedCleanup(value)) {
51
+ // Derived cleanup is a successful verb result, but deliberately is not a
52
+ // committed lifecycle result. The verb renderer owns that exact payload.
53
+ return render(value);
54
+ }
55
+ return buildLifecycleOutcomeResponse(value, context);
56
+ }
@@ -3,6 +3,7 @@ import { RESPONSE_MARKERS } from "./response-style.js";
3
3
  import { formatReview, HINTS_LIST_MAX_ITEMS, REVIEW_SECTION_TITLE, toReviewPayload, } from "./shared.js";
4
4
  import { prependAddressReceipt, renderAddressCarrier } from "./address.js";
5
5
  import { commissionSlug } from "../../core/ids.js";
6
+ import { renderAmendmentWarning } from "./amendment-warning.js";
6
7
  export function buildErrorResponse(input) {
7
8
  const shouldRaiseProtocolError = input.errorType === "runtime_error";
8
9
  const text = input.errorCode === "PROJECTION_INCOMPATIBLE"
@@ -28,14 +29,17 @@ export function buildErrorResponse(input) {
28
29
  }
29
30
  function buildErrorText(input) {
30
31
  const errorSection = buildSection("Error", input.message);
31
- const recoverySection = input.facts?.kind === "renew_conflict"
32
- ? buildSection("Recovery", [
32
+ const conflictSection = input.facts?.kind === "renew_conflict_scratch"
33
+ ? buildSection("Conflict scratch", [
34
+ input.facts.scratchPath,
35
+ "",
33
36
  "Conflicting paths:",
34
37
  ...input.facts.conflictPaths.map((filePath) => ` ${filePath}`),
35
- ...(input.facts.conflictPathCapture.status === "failed"
36
- ? [`Conflict-path capture failed: ${input.facts.conflictPathCapture.diagnostic}`]
37
- : []),
38
- "Delivery branch and worktree were restored; no ledger writes were appended.",
38
+ "",
39
+ `Ledger: ${input.facts.observedLedgerHead}`,
40
+ `Delivery: ${input.facts.deliveryRef} at ${input.facts.oldHead}`,
41
+ `Target: ${input.facts.targetRef} at ${input.facts.newBase}`,
42
+ "Authoritative ledger and delivery refs were unchanged.",
39
43
  ].join("\n"))
40
44
  : null;
41
45
  const reviewSection = input.review ? buildSection(REVIEW_SECTION_TITLE, formatReview(input.review)) : null;
@@ -43,32 +47,22 @@ function buildErrorText(input) {
43
47
  && input.addressCarrier.binding.kind === "commission"
44
48
  ? input.addressCarrier
45
49
  : undefined;
46
- const recoveryAddress = commissionedCarrier
50
+ const renewAddress = commissionedCarrier
47
51
  ? `@${commissionedCarrier.place.display ?? commissionSlug(commissionedCarrier.binding.commissionId)}`
48
- : input.facts?.kind === "renew_session"
49
- ? `@${commissionSlug(input.facts.commission)}`
50
- : undefined;
51
- const nextActionSection = input.facts?.kind === "renew_session" && recoveryAddress
52
- ? buildSection("Next action", input.facts.nextAction === "resolve-conflicts"
53
- ? [
54
- "Resolve and stage the conflicting delivery with ordinary Git, then:",
55
- `» keiyaku ${recoveryAddress} renew --continue`,
56
- "Or restore the pre-renew delivery:",
57
- `» keiyaku ${recoveryAddress} renew --abort`,
58
- ]
59
- : input.facts.nextAction === "continue-or-abort" ? [
60
- `» keiyaku ${recoveryAddress} renew --continue`,
61
- `» keiyaku ${recoveryAddress} renew --abort`,
62
- ] : [`» keiyaku ${recoveryAddress} renew --abort`])
52
+ : undefined;
53
+ const nextActionSection = input.facts?.kind === "renew_conflict_scratch" && renewAddress
54
+ ? buildSection("Next action", [
55
+ "Resolve files in the conflict scratch, then:",
56
+ `» keiyaku ${renewAddress} renew --continue`,
57
+ "Or discard the conflict scratch:",
58
+ `» keiyaku ${renewAddress} renew --abort`,
59
+ ])
63
60
  : null;
64
- const hints = input.facts?.kind === "renew_session"
61
+ const hints = input.facts?.kind === "renew_conflict_scratch"
65
62
  ? []
66
63
  : formatHints(input.hints, { maxItems: HINTS_LIST_MAX_ITEMS, maxItemChars: FORMAT_LIST_MAX_ITEM_CHARS });
67
- if (input.facts?.kind === "renew_conflict") {
68
- hints.push("your old head survives:", `» git log ${input.facts.recoveryRef}`);
69
- }
70
64
  const hintsSection = buildSection("Hints", hints);
71
- const body = assembleResponse(colorizeErrorStatus(`${RESPONSE_MARKERS.failure} Failed [${input.errorCode}]`), "Request could not be completed.", [errorSection, recoverySection, nextActionSection, reviewSection, hintsSection].filter((section) => section !== null));
65
+ const body = assembleResponse(colorizeErrorStatus(`${RESPONSE_MARKERS.failure} Failed [${input.errorCode}]`), "Request could not be completed.", [errorSection, conflictSection, nextActionSection, reviewSection, hintsSection].filter((section) => section !== null));
72
66
  return input.addressCarrier ? `${renderAddressCarrier(input.addressCarrier)}\n${body}` : body;
73
67
  }
74
68
  export function buildHelpResponse(input) {
@@ -82,7 +76,9 @@ export function buildLogResponse(result) {
82
76
  const { address: _address, ...resultData } = result;
83
77
  const payload = resultData;
84
78
  return prependAddressReceipt({
85
- text: result.markdown,
79
+ text: result.amendmentWarning
80
+ ? `${result.markdown.trimEnd()}\n\nWarning\n${renderAmendmentWarning(result.amendmentWarning)}\n`
81
+ : result.markdown,
86
82
  payload: buildPayload(payload),
87
83
  }, result.address);
88
84
  }
@@ -8,6 +8,11 @@ import { prependAddressReceipt } from "./address.js";
8
8
  export function renderPetitionRenewedConsequence(input) {
9
9
  return `renewed onto ${input.newBase.slice(0, 8)} · ${input.sealed?.receipts.length ?? 0} receipts replayed`;
10
10
  }
11
+ export function renderPetitionRetryConsequence(reason) {
12
+ return reason === "target-drift"
13
+ ? "rejected petition after target drift · contract active"
14
+ : "rejected petition after delivery drift · contract active";
15
+ }
11
16
  function formatPetitionSealSource(source) {
12
17
  return source.arc === null
13
18
  ? `no open arc · fence ${source.fence}`