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