@astrosheep/keiyaku 4.5.20 → 4.5.21

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 (154) hide show
  1. package/README.md +4 -4
  2. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -3
  3. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +22 -8
  4. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/references/automation.md +237 -0
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +6 -7
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +2 -3
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +3 -3
  8. package/build/src/akuma/akuma-handle.d.ts +5 -1
  9. package/build/src/akuma/akuma-handle.js +4 -1
  10. package/build/src/akuma/akuma-instance.d.ts +7 -2
  11. package/build/src/akuma/akuma-instance.js +14 -7
  12. package/build/src/akuma/akuma-observe.d.ts +11 -0
  13. package/build/src/akuma/akuma-observe.js +22 -0
  14. package/build/src/akuma/akuma-product.d.ts +1 -0
  15. package/build/src/akuma/akuma-product.js +3 -0
  16. package/build/src/akuma/akuma.d.ts +1 -0
  17. package/build/src/akuma/body.d.ts +25 -5
  18. package/build/src/akuma/body.js +65 -8
  19. package/build/src/akuma/call-request.d.ts +2 -0
  20. package/build/src/akuma/call-request.js +2 -0
  21. package/build/src/akuma/fleet-execution.d.ts +28 -0
  22. package/build/src/akuma/fleet-execution.js +36 -4
  23. package/build/src/akuma/fleet-request.d.ts +10 -0
  24. package/build/src/akuma/fleet-request.js +11 -2
  25. package/build/src/akuma/heart/facts.d.ts +2 -0
  26. package/build/src/akuma/heart/index.d.ts +1 -0
  27. package/build/src/akuma/heart/index.js +4 -1
  28. package/build/src/akuma/heart/rows.d.ts +2 -0
  29. package/build/src/akuma/heart/rows.js +7 -5
  30. package/build/src/akuma/heart/schema.d.ts +1 -1
  31. package/build/src/akuma/heart/schema.js +3 -1
  32. package/build/src/akuma/heart/tells.js +6 -5
  33. package/build/src/akuma/heart/timeline.js +1 -1
  34. package/build/src/akuma/provider.d.ts +1 -1
  35. package/build/src/akuma/provider.js +1 -1
  36. package/build/src/akuma/providers/acp/core.js +3 -6
  37. package/build/src/akuma/providers/claude/index.js +9 -12
  38. package/build/src/akuma/providers/codex-app-server/index.js +4 -11
  39. package/build/src/akuma/providers/execution-environment.d.ts +6 -0
  40. package/build/src/akuma/providers/execution-environment.js +24 -0
  41. package/build/src/akuma/providers/opencode-sdk/index.js +30 -23
  42. package/build/src/akuma/providers/opencode-sdk/session.d.ts +8 -1
  43. package/build/src/akuma/providers/opencode-sdk/session.js +6 -5
  44. package/build/src/akuma/providers/pi/events.js +7 -2
  45. package/build/src/akuma/providers/pi/index.js +3 -2
  46. package/build/src/akuma/request-rendezvous.js +8 -3
  47. package/build/src/akuma/turn-drive.d.ts +1 -0
  48. package/build/src/akuma/turn-drive.js +2 -0
  49. package/build/src/akuma-body.js +2 -0
  50. package/build/src/body/amend.js +6 -4
  51. package/build/src/body/decode.d.ts +3 -1
  52. package/build/src/body/decode.js +53 -17
  53. package/build/src/body/region.d.ts +9 -1
  54. package/build/src/body/region.js +87 -12
  55. package/build/src/body/render.js +1 -1
  56. package/build/src/body/verification.d.ts +3 -1
  57. package/build/src/body/verification.js +4 -1
  58. package/build/src/cli/accepted.js +19 -2
  59. package/build/src/cli/commands/akuma-invoke.d.ts +2 -0
  60. package/build/src/cli/commands/akuma-invoke.js +133 -13
  61. package/build/src/cli/commands/akuma.js +11 -4
  62. package/build/src/cli/commands/contract-help.js +17 -4
  63. package/build/src/cli/commands/contract.js +6 -4
  64. package/build/src/cli/commands/status-set.js +15 -24
  65. package/build/src/cli/main.js +5 -0
  66. package/build/src/cli/parse.d.ts +2 -0
  67. package/build/src/cli/parse.js +5 -0
  68. package/build/src/cli/render/akuma-activity.d.ts +99 -2
  69. package/build/src/cli/render/akuma-activity.js +484 -40
  70. package/build/src/cli/render/akuma.d.ts +10 -0
  71. package/build/src/cli/render/akuma.js +28 -21
  72. package/build/src/cli/render/audit.js +1 -1
  73. package/build/src/cli/render/board.js +1 -1
  74. package/build/src/cli/render/catalog.js +16 -13
  75. package/build/src/cli/render/contract-history.js +39 -10
  76. package/build/src/cli/render/contract-observation.js +10 -6
  77. package/build/src/cli/render/contract.js +109 -106
  78. package/build/src/cli/render/execution-progress.d.ts +23 -1
  79. package/build/src/cli/render/execution-progress.js +151 -28
  80. package/build/src/cli/render/kanshi-akuma.js +27 -39
  81. package/build/src/cli/render/kanshi.js +33 -26
  82. package/build/src/cli/render/nuke.js +1 -1
  83. package/build/src/cli/render/receipt.d.ts +1 -1
  84. package/build/src/cli/render/receipt.js +5 -5
  85. package/build/src/cli/render/refusal.js +10 -7
  86. package/build/src/cli/render/region.js +1 -1
  87. package/build/src/cli/render/settings.js +12 -2
  88. package/build/src/cli/render/status-line.d.ts +30 -0
  89. package/build/src/cli/render/status-line.js +58 -0
  90. package/build/src/cli/render/status-set.js +1 -1
  91. package/build/src/cli/render/task.d.ts +2 -0
  92. package/build/src/cli/render/task.js +11 -7
  93. package/build/src/cli/render/terminal.d.ts +0 -1
  94. package/build/src/cli/render/terminal.js +47 -15
  95. package/build/src/cli/result.d.ts +1 -0
  96. package/build/src/cli/runtime.d.ts +2 -0
  97. package/build/src/cli/runtime.js +13 -7
  98. package/build/src/cli/usage.js +2 -2
  99. package/build/src/cli/version.d.ts +1 -0
  100. package/build/src/cli/version.js +25 -0
  101. package/build/src/git/workspace.d.ts +1 -1
  102. package/build/src/git/workspace.js +1 -1
  103. package/build/src/identity/mint.d.ts +13 -0
  104. package/build/src/identity/mint.js +26 -0
  105. package/build/src/identity/normalize.d.ts +4 -0
  106. package/build/src/identity/normalize.js +30 -0
  107. package/build/src/index.d.ts +2 -0
  108. package/build/src/index.js +1 -0
  109. package/build/src/kanshi/read.js +17 -4
  110. package/build/src/kanshi/report.d.ts +1 -0
  111. package/build/src/library/akuma-creation.d.ts +2 -0
  112. package/build/src/library/akuma-creation.js +33 -17
  113. package/build/src/library/bind.js +18 -16
  114. package/build/src/library/contract-bind.d.ts +3 -1
  115. package/build/src/library/contract-bind.js +5 -3
  116. package/build/src/library/contract-operations.d.ts +2 -2
  117. package/build/src/library/contract-operations.js +1 -1
  118. package/build/src/library/contract-types.d.ts +1 -0
  119. package/build/src/library/fleet.d.ts +4 -1
  120. package/build/src/library/fleet.js +32 -8
  121. package/build/src/plugin/akuma-signals.d.ts +6 -0
  122. package/build/src/plugin/akuma-signals.js +8 -0
  123. package/build/src/plugin/public.d.ts +4 -0
  124. package/build/src/plugin/runtime.js +4 -1
  125. package/build/src/protocol/amend.js +4 -3
  126. package/build/src/protocol/attempt.d.ts +2 -0
  127. package/build/src/protocol/bind.js +41 -14
  128. package/build/src/protocol/completion.d.ts +3 -0
  129. package/build/src/protocol/completion.js +7 -1
  130. package/build/src/protocol/deliver.js +165 -92
  131. package/build/src/protocol/execution-observation.d.ts +0 -4
  132. package/build/src/protocol/intent.d.ts +15 -20
  133. package/build/src/protocol/intent.js +29 -17
  134. package/build/src/protocol/operations.d.ts +2 -0
  135. package/build/src/protocol/placement.js +5 -1
  136. package/build/src/protocol/read/status.d.ts +1 -0
  137. package/build/src/protocol/read/status.js +10 -2
  138. package/build/src/protocol/reintegrate.d.ts +1 -1
  139. package/build/src/protocol/reintegrate.js +70 -43
  140. package/build/src/protocol/result-codec.js +9 -7
  141. package/build/src/protocol/review.js +53 -30
  142. package/build/src/protocol/run.d.ts +83 -36
  143. package/build/src/protocol/run.js +90 -75
  144. package/build/src/runtime/proc/windows-launch.exe +0 -0
  145. package/build/src/settings.js +5 -14
  146. package/build/src/task/document.d.ts +5 -1
  147. package/build/src/task/document.js +6 -1
  148. package/build/src/verification/execution.d.ts +0 -1
  149. package/build/src/verification/execution.js +0 -24
  150. package/build/src/verification/observation.d.ts +0 -4
  151. package/build/src/verification/observation.js +1 -2
  152. package/package.json +3 -1
  153. package/build/src/git/verification-environment.d.ts +0 -10
  154. package/build/src/git/verification-environment.js +0 -281
@@ -1,5 +1,7 @@
1
- import { boundedActivity, elapsedMilliseconds, entityLines, identityLine, plumbFacts, RECENT_TONE_MS, renderSectionBlock, safeText, tone, } from "./terminal.js";
1
+ import { snapshotActivityLines } from "./akuma-activity.js";
2
+ import { elapsedMilliseconds, entityLines, identityLine, RECENT_TONE_MS, renderSectionBlock, safeText, tone, } from "./terminal.js";
2
3
  const NARROW_COLUMNS = 72;
4
+ const ACTIVITY_INDENT = " ";
3
5
  function mostRecentTimestamp(...values) {
4
6
  return values.reduce((latest, value) => {
5
7
  if (value === null || value === undefined)
@@ -24,40 +26,27 @@ function akumaStatusTone(row, observedAt) {
24
26
  function akumaMark(life) {
25
27
  return life === "running"
26
28
  ? "●"
27
- : life === "stillborn"
28
- ? "!"
29
- : life === "asleep"
30
- ? ""
31
- : life === "killed"
32
- ? ""
29
+ : life === "asleep"
30
+ ? ""
31
+ : life === "killed"
32
+ ? "×"
33
+ : life === "stranded" || life === "stillborn" || life === "untidy"
34
+ ? "!"
33
35
  : "?";
34
36
  }
35
- function activitySnapshotLine(row) {
36
- const snapshot = row.snapshot;
37
- if (snapshot === undefined)
38
- return undefined;
39
- const latest = snapshot.entries.flatMap((entry) => (entry.kind === "row" ? [entry.row] : [])).at(-1);
40
- if (latest === undefined) {
41
- if (snapshot.kind !== "idle" || snapshot.outcome === undefined)
42
- return undefined;
43
- return snapshot.outcome.outcome.kind === "answered"
44
- ? snapshot.outcome.outcome.answer
45
- : snapshot.outcome.outcome.diagnostic;
46
- }
47
- if (latest.kind === "tool")
48
- return latest.name;
49
- return "text" in latest ? latest.text : "activity";
37
+ /** Bounded latest semantic entry, rendered by the same activity renderer as a targeted snapshot. */
38
+ function latestActivityLines(snapshot, context) {
39
+ const bounded = { ...context, columns: Math.max(1, context.columns - ACTIVITY_INDENT.length) };
40
+ return snapshotActivityLines(snapshot, bounded, { latest: true }).map((line) => `${ACTIVITY_INDENT}${line}`);
50
41
  }
51
42
  function akumaLabel(row) {
52
43
  const aliases = row.aliases ?? [];
53
44
  return aliases.length === 0 ? "" : `(${aliases.join(" ")})`;
54
45
  }
55
46
  function endpointFact(id, observed) {
56
- return observed === "missing"
57
- ? `-> ${id} (missing)`
58
- : observed === "unavailable"
59
- ? `-> ${id} (unavailable)`
60
- : `-> ${id}`;
47
+ // A bounded or failed Contract read leaves the disposition unknown; a parenthetical that
48
+ // cannot name the reason is absent, while a complete read that established absence says so.
49
+ return observed === "missing" ? `${id} (missing)` : id;
61
50
  }
62
51
  function renderAkuma(report, context) {
63
52
  const section = report.akuma;
@@ -66,25 +55,26 @@ function renderAkuma(report, context) {
66
55
  if (section.kind === "failed")
67
56
  return ["AKUMA // unavailable", "", tone(`! ${safeText(section.failure.message)}`, "alert", context.color)];
68
57
  const rows = section.value.rows;
58
+ if (rows.length === 0)
59
+ return [];
69
60
  const rowLines = rows.map((row) => {
70
61
  const statusTone = akumaStatusTone(row, report.observedAt);
71
62
  const mark = statusTone === null ? akumaMark(row.life) : tone(akumaMark(row.life), statusTone, context.color);
72
63
  const lifeAt = "lifeAt" in row ? row.lifeAt : null;
73
- const life = `${row.life} · ${formatAge(lifeAt, report.observedAt)}`;
74
- const activity = "lastActivityAt" in row && row.lastActivityAt !== null
75
- ? [`activity ${formatAge(row.lastActivityAt, report.observedAt)}`]
76
- : [];
64
+ const lifeAge = formatAge(lifeAt, report.observedAt);
65
+ const life = `${row.life} · ${lifeAge}`;
66
+ const activityAge = "lastActivityAt" in row && row.lastActivityAt !== null ? formatAge(row.lastActivityAt, report.observedAt) : null;
67
+ const activity = activityAge === null || activityAge === lifeAge ? [] : [`activity ${activityAge}`];
77
68
  const key = row.life === "stranded" && "strandedReason" in row && row.strandedReason === "resume-unsupported"
78
69
  ? [life, ...activity, "resume unsupported"]
79
70
  : [life, ...activity];
80
- const relation = row.contract === undefined ? ["unbound"] : [endpointFact(row.contract.id, row.contract.observed)];
81
- const snapshot = activitySnapshotLine(row);
71
+ const relation = row.contract === undefined ? [] : [endpointFact(row.contract.id, row.contract.observed)];
72
+ const snapshot = row.snapshot;
73
+ const snapshotLines = snapshot === undefined ? [] : latestActivityLines(snapshot, context);
82
74
  const aliases = akumaLabel(row);
83
75
  if (context.columns > NARROW_COLUMNS) {
84
76
  const lines = [identityLine(mark, row.id, `${aliases} · ${[...key, ...relation].join(" · ")}`.trim())];
85
- return snapshot === undefined
86
- ? lines
87
- : [...lines, ...plumbFacts([boundedActivity(snapshot, context.columns, " ")], context.columns)];
77
+ return [...lines, ...snapshotLines];
88
78
  }
89
79
  const identity = aliases.length === 0 ? row.id : `${row.id} ${aliases}`;
90
80
  const lines = entityLines({
@@ -95,9 +85,7 @@ function renderAkuma(report, context) {
95
85
  facts: [...key.slice(1), ...relation],
96
86
  context,
97
87
  });
98
- return snapshot === undefined
99
- ? lines
100
- : [...lines, ...plumbFacts([boundedActivity(snapshot, context.columns, " ")], context.columns)];
88
+ return [...lines, ...snapshotLines];
101
89
  });
102
90
  const rendered = renderSectionBlock({
103
91
  name: "AKUMA",
@@ -1,6 +1,7 @@
1
1
  import { abbreviateGitIds, afterWording, candidateFact, dependentWording, displayGitId, gateFact, gitIdsInRow, mergeSummary, verificationFact, targetFacts, } from "./contract-observation.js";
2
2
  import { entityLines, elapsedMilliseconds, identityLine, plumbFacts, RECENT_TONE_MS, renderSectionBlock, linkedEntityLines, safeText, tone, } from "./terminal.js";
3
3
  import { akumaMark, endpointFact, formatAge, NARROW_COLUMNS, renderAkuma } from "./kanshi-akuma.js";
4
+ import { dispositionText } from "./task.js";
4
5
  const REVIEW_ATTENTION_MS = 15 * 60 * 1_000;
5
6
  const PENDING_ATTENTION_MS = 60 * 60 * 1_000;
6
7
  function contractHasError(row) {
@@ -26,22 +27,22 @@ function contractMark(row) {
26
27
  if (row.phase === "claimed")
27
28
  return "✓";
28
29
  if (row.phase === "abandoned")
29
- return "";
30
+ return "×";
30
31
  if (row.title === null)
31
32
  return "?";
32
33
  if (row.gates.reports.some((report) => report.current.kind === "attested" && report.current.verdict === "unsatisfied"))
33
34
  return "!";
34
35
  if (row.targetLag.kind === "unknown")
35
36
  return "?";
36
- if (row.phase === "waiting")
37
- return "";
37
+ if (row.phase === "waiting" || row.phase === "tendered")
38
+ return "";
38
39
  return "●";
39
40
  }
40
41
  function taskMark(row) {
41
42
  if (row.disposition === "done")
42
43
  return "✓";
43
44
  if (row.disposition === "drop")
44
- return "";
45
+ return "×";
45
46
  if (row.disposition === "on_hold")
46
47
  return "⧗";
47
48
  return row.disposition === "in_progress" ? "●" : row.disposition === "blocked" ? "‖" : "○";
@@ -93,7 +94,9 @@ function linkedTask(report, taskId) {
93
94
  if (report.tasks.kind !== "present")
94
95
  return `! ${taskId} · unavailable`;
95
96
  const task = report.tasks.value.rows.find((candidate) => candidate.id === taskId);
96
- return task === undefined ? `! ${taskId} · unavailable` : `${taskMark(task)} ${task.id} · ${task.disposition}`;
97
+ return task === undefined
98
+ ? `! ${taskId} · unavailable`
99
+ : `${taskMark(task)} ${task.id} · ${dispositionText(task.disposition)}`;
97
100
  }
98
101
  function linkedAkuma(report, id, aliases) {
99
102
  const alias = aliases.length === 0 ? "" : ` (${aliases.join(" ")})`;
@@ -139,13 +142,10 @@ function linkedAkumaSummary(row, report) {
139
142
  facts.push(`${row.fleet.length - known.length} unavailable`);
140
143
  return facts.join(" · ");
141
144
  }
142
- function semanticBlock(name, facts, _context) {
145
+ function semanticBlock(_name, facts, _context) {
143
146
  if (facts.length === 0)
144
147
  return [];
145
- const lines = [` ${name}`];
146
- for (const fact of facts)
147
- lines.push(` ${safeText(fact)}`);
148
- return lines;
148
+ return facts.map((fact) => ` ${safeText(fact)}`);
149
149
  }
150
150
  function namespaceTaskFacts(row) {
151
151
  if (row.namespaceTasks === undefined)
@@ -155,7 +155,7 @@ function namespaceTaskFacts(row) {
155
155
  if (row.namespaceTasks.kind === "failed") {
156
156
  return [`failed ${row.namespaceTasks.failure.message}`];
157
157
  }
158
- return row.namespaceTasks.value.map((task) => `${taskMark(task)} ${task.id} · ${task.disposition} · P${task.priority} · ${task.title}`);
158
+ return row.namespaceTasks.value.map((task) => `${taskMark(task)} ${task.id} · ${dispositionText(task.disposition)} · P${task.priority} · ${task.title}`);
159
159
  }
160
160
  function renderSelectedContractRow(row, report, context, abbreviations) {
161
161
  const title = row.title ?? "title unavailable";
@@ -195,7 +195,7 @@ function renderSelectedContractRow(row, report, context, abbreviations) {
195
195
  lines.push(...semanticBlock("namespace tasks", namespaceTaskFacts(row), context));
196
196
  const observation = row.workspaceObservation;
197
197
  if ((observation.kind === "clean" || observation.kind === "dirty") && observation.merge?.recovery !== undefined) {
198
- lines.push(` deliver ${safeText(observation.merge.recovery.continue)} · reads worktree bytes, not index`);
198
+ lines.push(` deliver ${safeText(observation.merge.recovery.deliver)} · reads worktree bytes, not index`);
199
199
  }
200
200
  return lines;
201
201
  }
@@ -218,7 +218,7 @@ function renderWorldContractRow(row, report, context) {
218
218
  const contractFacts = [
219
219
  candidateFact(row.delivery),
220
220
  ...targetFacts(row, gitAbbreviations(report)),
221
- ...(row.worktreePath === null ? [] : [`worktree ${row.worktreePath}`]),
221
+ ...[],
222
222
  ...(linkedAkumaSummary(row, report) === undefined ? [] : [linkedAkumaSummary(row, report)]),
223
223
  ...row.after.map(afterWording),
224
224
  ...(row.dependents.length === 0 ? [] : [`dependents ${row.dependents.map(dependentWording).join(" · ")}`]),
@@ -245,6 +245,8 @@ function renderContracts(report, context) {
245
245
  return ["CONTRACTS // absent", "", " contracts absent"];
246
246
  if (section.kind === "failed")
247
247
  return ["CONTRACTS // unavailable", "", tone(`! ${safeText(section.failure.message)}`, "alert", context.color)];
248
+ if (section.value.rows.length === 0)
249
+ return [];
248
250
  const rendered = renderSectionBlock({
249
251
  name: "CONTRACTS",
250
252
  rows: section.value.rows.map((row) => renderWorldContractRow(row, report, context)),
@@ -263,27 +265,37 @@ function renderSelectedContract(report, context) {
263
265
  ? [" keiyaku absent"]
264
266
  : renderSelectedContractRow(row, report, context, gitAbbreviations(report));
265
267
  }
268
+ function taskFailureFact(failure) {
269
+ return failure.coordinate === undefined ? failure.message : `${failure.coordinate} · failed ${failure.message}`;
270
+ }
266
271
  function renderTasks(report, context) {
267
272
  const section = report.tasks;
268
273
  if (section.kind === "absent")
269
274
  return ["TASKS // absent", "", " tasks absent"];
270
275
  if (section.kind === "failed")
271
- return ["TASKS // unavailable", "", tone(`! ${safeText(section.failure.message)}`, "alert", context.color)];
276
+ return [
277
+ "TASKS // unavailable",
278
+ "",
279
+ tone(`! ${safeText(taskFailureFact(section.failure))}`, "alert", context.color),
280
+ ];
272
281
  const rows = section.value.rows;
282
+ if (rows.length === 0)
283
+ return [];
273
284
  const rowLines = rows.map((row) => {
274
- const relation = row.contract === undefined ? ["unbound"] : [endpointFact(row.contract.id, row.contract.observed)];
285
+ const relation = row.contract === undefined ? [] : [endpointFact(row.contract.id, row.contract.observed)];
275
286
  const childFacts = row.children === undefined ? [] : [`children ${row.children.live} live · ${row.children.total} total`];
276
287
  const blockerFacts = (row.blockers ?? []).map((blocker) => `blocked ${blocker.id}`);
288
+ const association = relation.length === 0 ? "" : ` · ${relation.join(" · ")}`;
277
289
  if (context.columns > NARROW_COLUMNS) {
278
290
  return [
279
- identityLine(taskMark(row), row.id, `· ${row.disposition} · P${row.priority} · ${row.title} · ${relation.join(" · ")}`),
291
+ identityLine(taskMark(row), row.id, `· ${dispositionText(row.disposition)} · P${row.priority} · ${row.title}${association}`),
280
292
  ...plumbFacts([...childFacts, ...blockerFacts], context.columns),
281
293
  ];
282
294
  }
283
295
  return entityLines({
284
296
  mark: taskMark(row),
285
297
  identity: row.id,
286
- state: `${row.disposition} · P${row.priority}`,
298
+ state: `${dispositionText(row.disposition)} · P${row.priority}`,
287
299
  title: row.title,
288
300
  facts: [...relation, ...childFacts, ...blockerFacts],
289
301
  context,
@@ -301,13 +313,8 @@ function renderTasks(report, context) {
301
313
  export function renderKanshiText(report, context = { columns: 80, color: false }, selection = "world") {
302
314
  if (selection === "contract")
303
315
  return renderSelectedContract(report, context).join("\n");
304
- return [
305
- `${tone("契", "alert", context.color)} KEIYAKU // WORLD`,
306
- "",
307
- ...renderContracts(report, context),
308
- "",
309
- ...renderAkuma(report, context),
310
- "",
311
- ...renderTasks(report, context),
312
- ].join("\n");
316
+ return [renderContracts(report, context), renderAkuma(report, context), renderTasks(report, context)]
317
+ .filter((block) => block.length > 0)
318
+ .map((block) => block.join("\n"))
319
+ .join("\n\n");
313
320
  }
@@ -5,7 +5,7 @@ export function renderNukeText(result) {
5
5
  if (result.kind === "success") {
6
6
  return [`✓ nuke ${result.world}`, ...seatClose].join("\n");
7
7
  }
8
- return [`✕ nuke ${result.world}`, ` diagnostic ${result.diagnostic}`, ...seatClose].join("\n");
8
+ return [ nuke ${result.world}`, ` diagnostic ${result.diagnostic}`, ...seatClose].join("\n");
9
9
  }
10
10
  export function nukeExitCode(result) {
11
11
  return result.kind === "failed" ? 2 : 0;
@@ -10,7 +10,7 @@ type HookFailure = Extract<Lag, {
10
10
  }>["failure"];
11
11
  export declare function receiptRow(lines: string[], mark: string, label: string, segments: readonly ReceiptSegment[], columns: number): void;
12
12
  export declare function receiptPayload(lines: string[], label: string, payload: string): void;
13
- export declare function outcomeLines(mark: "✓" | "" | "!" | "?", verb: string, word: "accepted" | "refused" | "retry", contract: string | undefined, columns?: number): string[];
13
+ export declare function outcomeLines(mark: "✓" | "×" | "!" | "?", verb: string, word: "accepted" | "refused" | "retry", contract: string | undefined, columns?: number): string[];
14
14
  export declare function titleLines(mark: string, title: string, contract: string, columns?: number): string[];
15
15
  export declare function hookFailureSummary(failure: HookFailure): string;
16
16
  export declare function appendHookPayload(lines: string[], failure: HookFailure): void;
@@ -21,7 +21,7 @@ export function receiptRow(lines, mark, label, segments, columns) {
21
21
  lines.push(current);
22
22
  }
23
23
  export function receiptPayload(lines, label, payload) {
24
- lines.push(label, "", payload, "");
24
+ lines.push(label, ...renderOpaqueBlock(payload, " ", 80), "");
25
25
  }
26
26
  export function outcomeLines(mark, verb, word, contract, columns = 80) {
27
27
  const base = `${mark} ${verb} ${word}`;
@@ -59,7 +59,7 @@ export function appendHookPayload(lines, failure) {
59
59
  export function reuseLines(reuse, columns) {
60
60
  if (reuse === undefined)
61
61
  return [];
62
- return renderOpaqueBlock(`reuse verification ${reuse.entry} ${reuse.verdict}`, " ", columns);
62
+ return renderOpaqueBlock(`reuse verified · ${reuse.verdict} · ${reuse.entry}`, " ", columns);
63
63
  }
64
64
  function prerequisiteRows(stop, columns) {
65
65
  if (!("refusal" in stop) || stop.refusal?.kind !== "prerequisites-unsatisfied")
@@ -131,10 +131,10 @@ function refusalEvidence(stop, columns) {
131
131
  recovery !== null &&
132
132
  "materialize" in recovery &&
133
133
  typeof recovery.materialize === "string" &&
134
- "continue" in recovery &&
135
- typeof recovery.continue === "string") {
134
+ "deliver" in recovery &&
135
+ typeof recovery.deliver === "string") {
136
136
  receiptRow(lines, " ", "materialize", [{ text: recovery.materialize }], columns);
137
- receiptRow(lines, " ", "deliver", [{ text: recovery.continue }], columns);
137
+ receiptRow(lines, " ", "deliver", [{ text: recovery.deliver }], columns);
138
138
  }
139
139
  }
140
140
  else if (refusal.kind === "integration-unsupported") {
@@ -7,9 +7,12 @@ function wrap(lines, text, indent, columns) {
7
7
  }
8
8
  function collectionLines(name, members, indent) {
9
9
  if (members.length === 0)
10
- return [`${indent}${name} 0`];
10
+ return [];
11
11
  return [`${indent}${name}`, ...members.map((member) => `${indent} ${safeText(member)}`)];
12
12
  }
13
+ function shortGitId(value) {
14
+ return /^[0-9a-f]{40}$/iu.test(value) ? value.slice(0, 7) : value;
15
+ }
13
16
  function skipAddressedContract(addressed, contractId) {
14
17
  return addressed !== undefined && contractId === addressed;
15
18
  }
@@ -66,7 +69,7 @@ export function renderRefusalFacts(refusal, indent, columns, addressed) {
66
69
  lines.push(...collectionLines("conflicts", refusal.conflictPaths, indent));
67
70
  if ("recovery" in refusal && refusal.recovery !== undefined) {
68
71
  lines.push(`${indent}materialize ${safeText(refusal.recovery.materialize)}`);
69
- lines.push(`${indent}deliver ${safeText(refusal.recovery.continue)}`);
72
+ lines.push(`${indent}deliver ${safeText(refusal.recovery.deliver)}`);
70
73
  }
71
74
  return lines;
72
75
  }
@@ -90,7 +93,7 @@ export function renderRefusalFacts(refusal, indent, columns, addressed) {
90
93
  }
91
94
  export function renderRefusal(result, context) {
92
95
  const columns = context?.columns ?? 80;
93
- const base = `✕ ${result.verb} refused`;
96
+ const base = ${result.verb} refused`;
94
97
  const lines = result.contract === undefined
95
98
  ? [base]
96
99
  : displayColumns(`${base} ${result.contract}`) <= columns
@@ -106,14 +109,14 @@ export function renderConflictMaterialized(result, _context) {
106
109
  const indent = " ";
107
110
  return [
108
111
  "! integration-conflict-materialized",
109
- `${indent}target ${safeText(result.targetHead)}`,
110
- `${indent}recorded no delivery`,
112
+ `${indent}target ${shortGitId(result.targetHead)}`,
113
+ `${indent}delivery none`,
111
114
  `${indent}index unmerged`,
112
115
  `${indent}saved worktree bytes before projection`,
113
- `${indent}handoff base ${safeText(result.handoffBase)}`,
116
+ `${indent}handoff base ${shortGitId(result.handoffBase)}`,
114
117
  ...collectionLines("conflicts", result.conflictPaths, indent),
115
118
  `${indent}workspace ${safeText(result.workspace.path)}`,
116
- `${indent}deliver ${safeText(result.recovery.continue)} · reads worktree bytes, not index`,
119
+ `${indent}deliver ${safeText(result.recovery.deliver)} · reads worktree bytes, not index`,
117
120
  ].join("\n");
118
121
  }
119
122
  export function renderBindDraftReceipt(receipt) {
@@ -14,7 +14,7 @@ function rows(section) {
14
14
  if (section.kind === "absent")
15
15
  return [];
16
16
  if (section.kind === "failed")
17
- return [`✕ region`, ` diagnostic ${section.failure.message}`];
17
+ return [ region`, ` diagnostic ${section.failure.message}`];
18
18
  const value = section.value;
19
19
  if (value.kind === "declarations") {
20
20
  if (value.declarations.length === 0)
@@ -1,5 +1,15 @@
1
1
  import { displayColumns, renderTextBlock } from "./terminal.js";
2
- import { fieldName, namedValueLines } from "./value.js";
2
+ import { fieldName } from "./value.js";
3
+ function settingValueLines(name, value, indent) {
4
+ const secret = /(?:key|token|secret|password|credential)/iu.test(name);
5
+ if (secret && value !== undefined && value !== null && String(value).length > 0)
6
+ return [`${indent}${fieldName(name)} [redacted]`];
7
+ if (value === null || typeof value !== "object")
8
+ return [`${indent}${fieldName(name)} ${String(value)}`];
9
+ if (Array.isArray(value))
10
+ return value.flatMap((item, index) => settingValueLines(`${name}.${index}`, item, indent));
11
+ return Object.entries(value).flatMap(([key, item]) => settingValueLines(`${name}.${key}`, item, indent));
12
+ }
3
13
  function namespaceNames(value) {
4
14
  return [
5
15
  ...new Set([value.scopes.user, value.scopes.project].flatMap((scope) => (scope.kind === "read" ? scope.namespaces : []))),
@@ -38,7 +48,7 @@ export function renderSettingsText(value, columns = 80) {
38
48
  }
39
49
  for (const entry of view.entries) {
40
50
  lines.push(` entry ${fieldName(entry.name)} · ${entry.source}${entry.shadows ? " · shadows user" : ""}`);
41
- lines.push(...namedValueLines("value", entry.value, " "));
51
+ lines.push(...settingValueLines("value", entry.value, " "));
42
52
  }
43
53
  }
44
54
  return lines.join("\n");
@@ -0,0 +1,30 @@
1
+ export type StatusLineStream = NodeJS.WritableStream & Readonly<{
2
+ isTTY?: boolean;
3
+ }>;
4
+ type IntervalHandle = ReturnType<typeof setInterval>;
5
+ export type StatusLineOptions = Readonly<{
6
+ now?: () => number;
7
+ intervalMs?: number;
8
+ schedule?: (tick: () => void, every: number) => IntervalHandle;
9
+ cancel?: (handle: IntervalHandle) => void;
10
+ }>;
11
+ /** A terminal-only, ephemeral line. It owns neither execution nor its outcome. */
12
+ export declare class StatusLine {
13
+ private readonly stream;
14
+ readonly isTTY: boolean;
15
+ private readonly now;
16
+ private readonly intervalMs;
17
+ private readonly schedule;
18
+ private readonly cancel;
19
+ private timer;
20
+ private format;
21
+ private startedAt;
22
+ constructor(stream: StatusLineStream, options?: StatusLineOptions);
23
+ show(format: (elapsedMs: number) => string): void;
24
+ writeBlock(lines: readonly string[]): void;
25
+ finish(line: string): void;
26
+ private redraw;
27
+ private erase;
28
+ private stop;
29
+ }
30
+ export {};
@@ -0,0 +1,58 @@
1
+ /** A terminal-only, ephemeral line. It owns neither execution nor its outcome. */
2
+ export class StatusLine {
3
+ stream;
4
+ isTTY;
5
+ now;
6
+ intervalMs;
7
+ schedule;
8
+ cancel;
9
+ timer;
10
+ format;
11
+ startedAt;
12
+ constructor(stream, options = {}) {
13
+ this.stream = stream;
14
+ this.isTTY = stream.isTTY === true;
15
+ this.now = options.now ?? (() => performance.now());
16
+ this.intervalMs = options.intervalMs ?? 1_000;
17
+ this.schedule = options.schedule ?? ((tick, every) => setInterval(tick, every));
18
+ this.cancel = options.cancel ?? ((handle) => clearInterval(handle));
19
+ }
20
+ show(format) {
21
+ if (!this.isTTY)
22
+ return;
23
+ this.format = format;
24
+ this.startedAt ??= this.now();
25
+ this.redraw();
26
+ this.timer ??= this.schedule(() => this.redraw(), this.intervalMs);
27
+ }
28
+ writeBlock(lines) {
29
+ if (lines.length === 0)
30
+ return;
31
+ if (this.isTTY)
32
+ this.erase();
33
+ this.stream.write(`${lines.join("\n")}\n`);
34
+ if (this.isTTY && this.format !== undefined)
35
+ this.redraw();
36
+ }
37
+ finish(line) {
38
+ if (!this.isTTY)
39
+ return;
40
+ this.erase();
41
+ this.stop();
42
+ this.stream.write(`${line}\n`);
43
+ }
44
+ redraw() {
45
+ if (!this.isTTY || this.format === undefined || this.startedAt === undefined)
46
+ return;
47
+ this.stream.write(`\r\u001b[2K${this.format(Math.max(0, this.now() - this.startedAt))}`);
48
+ }
49
+ erase() {
50
+ if (this.isTTY && this.format !== undefined)
51
+ this.stream.write("\r\u001b[2K");
52
+ }
53
+ stop() {
54
+ if (this.timer !== undefined)
55
+ this.cancel(this.timer);
56
+ this.timer = undefined;
57
+ }
58
+ }
@@ -13,7 +13,7 @@ export function renderStatusSetText(result, context) {
13
13
  status: entry.status,
14
14
  ...(entry.alias === undefined ? {} : { alias: entry.alias }),
15
15
  }, context);
16
- return [`status ${entry.selector}`, body].join("\n");
16
+ return body;
17
17
  })
18
18
  .join("\n\n");
19
19
  }
@@ -3,6 +3,8 @@ import type { TaskInvocationResult } from "../commands/task-invoke.js";
3
3
  import type { ParsedTaskCommand } from "../commands/task.js";
4
4
  import { type TextRenderContext } from "./terminal.js";
5
5
  export declare function taskMark(word: string): string;
6
+ /** Task dispositions and relation states are snake_case facts; people read them as words. */
7
+ export declare function dispositionText(word: string): string;
6
8
  export declare function renderTaskText(command: ParsedTaskCommand, result: TaskInvocationResult, context?: TextRenderContext): string;
7
9
  export declare function renderTaskIncompleteDiagnostic(result: TaskCompositionResult): string;
8
10
  export declare function taskExitCode(result: TaskInvocationResult): number;
@@ -21,16 +21,20 @@ export function taskMark(word) {
21
21
  if (word === "done")
22
22
  return "✓";
23
23
  if (word === "drop")
24
- return "";
24
+ return "×";
25
25
  return "?";
26
26
  }
27
+ /** Task dispositions and relation states are snake_case facts; people read them as words. */
28
+ export function dispositionText(word) {
29
+ return word.replaceAll("_", " ");
30
+ }
27
31
  function priorityText(priority) {
28
32
  return priority === null ? "P?" : `P${priority}`;
29
33
  }
30
34
  function scanUnit(id, priority, word) {
31
35
  return word === undefined
32
36
  ? `${taskMark("ready")} ${id} · ${priorityText(priority)}`
33
- : `${taskMark(word)} ${id} · ${word} · ${priorityText(priority)}`;
37
+ : `${taskMark(word)} ${id} · ${dispositionText(word)} · ${priorityText(priority)}`;
34
38
  }
35
39
  function entityLines(entity, columns, indent = "") {
36
40
  const scan = `${indent}${scanUnit(entity.id, entity.priority, entity.word)}${entity.facts === undefined ? "" : ` · ${entity.facts}`}`;
@@ -74,7 +78,7 @@ function renderFailure(verb, result, columns) {
74
78
  if (result.kind === "retry") {
75
79
  return [...outcomeLines("?", verb, "retry", undefined, columns), result.reason].join("\n");
76
80
  }
77
- const lines = [...outcomeLines("", verb, "refused", undefined, columns)];
81
+ const lines = [...outcomeLines("×", verb, "refused", undefined, columns)];
78
82
  const facts = projectRefusal(result.refusal);
79
83
  lines.push(facts.line);
80
84
  appendDiagnostic(lines, facts.diagnostic);
@@ -85,7 +89,7 @@ function renderFailure(verb, result, columns) {
85
89
  }
86
90
  function edge(label, ref, mark) {
87
91
  const prefix = mark === undefined ? ` ${label}` : ` ${mark} ${label}`;
88
- return `${prefix} ${ref.id} · ${ref.state}`;
92
+ return `${prefix} ${ref.id} · ${dispositionText(ref.state)}`;
89
93
  }
90
94
  function pageHeading(view) {
91
95
  return view;
@@ -120,7 +124,7 @@ function renderListRow(item, columns, omitDisposition) {
120
124
  const lines = [...entityLines(listEntity(item, omitDisposition), columns)];
121
125
  if ("blockers" in item) {
122
126
  for (const blocker of item.blockers)
123
- lines.push(` needs ${blocker.id} · ${blocker.state}`);
127
+ lines.push(` needs ${blocker.id} · ${dispositionText(blocker.state)}`);
124
128
  }
125
129
  return lines;
126
130
  }
@@ -223,7 +227,7 @@ function renderBatchItem(verb, item) {
223
227
  if (item.outcome.kind === "retry")
224
228
  return `? ${verb} ${item.id} ${item.outcome.reason}`;
225
229
  const facts = projectRefusal(item.outcome.refusal);
226
- const lines = [`✕ ${verb} ${item.id} ${facts.line}`];
230
+ const lines = [ ${verb} ${item.id} ${facts.line}`];
227
231
  appendDiagnostic(lines, facts.diagnostic);
228
232
  return lines.join("\n");
229
233
  }
@@ -238,7 +242,7 @@ function stoppedLines(stopped) {
238
242
  if (stopped.kind === "retry")
239
243
  return [`? stopped ${stopped.reason}`];
240
244
  const facts = projectRefusal(stopped);
241
- const lines = [`✕ stopped ${facts.line}`];
245
+ const lines = [ stopped ${facts.line}`];
242
246
  appendDiagnostic(lines, facts.diagnostic);
243
247
  return lines;
244
248
  }
@@ -37,7 +37,6 @@ export type SemanticTone = "dim" | "recent" | "attention" | "alert";
37
37
  export declare const RECENT_TONE_MS: number;
38
38
  export declare function elapsedMilliseconds(source: string | null | undefined, observedAt: string): number | null;
39
39
  export declare function tone(value: string, kind: SemanticTone, color: boolean): string;
40
- export declare function boundedActivity(value: string, columns: number, prefix: string): string;
41
40
  export declare function plumbFacts(facts: readonly string[], columns: number): readonly string[];
42
41
  export declare function linkedEntityLines(facts: readonly string[], _columns: number): readonly string[];
43
42
  export declare function identityLine(mark: string, identity: string, extra?: string): string;