@aragon-agent/cli 0.6.44 → 0.6.45

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 (236) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +69 -2
  3. package/dist/agent/controller.d.ts +69 -0
  4. package/dist/agent/controller.js +224 -11
  5. package/dist/agent/governance-view.d.ts +40 -0
  6. package/dist/agent/governance-view.js +40 -0
  7. package/dist/agent/governance-wiring.d.ts +162 -0
  8. package/dist/agent/governance-wiring.js +470 -0
  9. package/dist/agent/plan-prompt.d.ts +7 -0
  10. package/dist/agent/plan-prompt.js +2 -1
  11. package/dist/agent/reducer.d.ts +33 -1
  12. package/dist/agent/reducer.js +45 -9
  13. package/dist/agent/system-prompt.d.ts +19 -0
  14. package/dist/agent/system-prompt.js +9 -1
  15. package/dist/cli.js +45 -1
  16. package/dist/commands/builtins.js +10 -4
  17. package/dist/commands/governance-command.d.ts +35 -0
  18. package/dist/commands/governance-command.js +292 -0
  19. package/dist/commands/governance-runs-table.d.ts +40 -0
  20. package/dist/commands/governance-runs-table.js +130 -0
  21. package/dist/commands/registry.d.ts +12 -0
  22. package/dist/config/app-paths.d.ts +10 -0
  23. package/dist/config/app-paths.js +12 -0
  24. package/dist/config/cli-commands.d.ts +18 -0
  25. package/dist/config/cli-commands.js +47 -1
  26. package/dist/config/env.d.ts +2 -1
  27. package/dist/config/env.js +10 -1
  28. package/dist/config/governance-config.d.ts +125 -0
  29. package/dist/config/governance-config.js +164 -0
  30. package/dist/config/governance-launch.d.ts +71 -0
  31. package/dist/config/governance-launch.js +58 -0
  32. package/dist/config/load.d.ts +15 -0
  33. package/dist/config/load.js +26 -1
  34. package/dist/config/model-profile-store.d.ts +4 -2
  35. package/dist/config/schema.d.ts +22 -0
  36. package/dist/config/schema.js +6 -0
  37. package/dist/config/store.js +14 -1
  38. package/dist/config/ui-state.d.ts +11 -0
  39. package/dist/config/ui-state.js +11 -0
  40. package/dist/diagnostics/doctor.js +20 -0
  41. package/dist/governance/card-normalize.d.ts +24 -0
  42. package/dist/governance/card-normalize.js +76 -0
  43. package/dist/governance/copy.d.ts +109 -0
  44. package/dist/governance/copy.js +223 -0
  45. package/dist/governance/engine/aftermath.d.ts +49 -0
  46. package/dist/governance/engine/aftermath.js +134 -0
  47. package/dist/governance/engine/artifact-persist.d.ts +35 -0
  48. package/dist/governance/engine/artifact-persist.js +86 -0
  49. package/dist/governance/engine/budget.d.ts +52 -0
  50. package/dist/governance/engine/budget.js +112 -0
  51. package/dist/governance/engine/cards.d.ts +18 -0
  52. package/dist/governance/engine/cards.js +186 -0
  53. package/dist/governance/engine/digest.d.ts +32 -0
  54. package/dist/governance/engine/digest.js +63 -0
  55. package/dist/governance/engine/engine-core.d.ts +219 -0
  56. package/dist/governance/engine/engine-core.js +562 -0
  57. package/dist/governance/engine/engine.d.ts +68 -0
  58. package/dist/governance/engine/engine.js +314 -0
  59. package/dist/governance/engine/estimate.d.ts +38 -0
  60. package/dist/governance/engine/estimate.js +46 -0
  61. package/dist/governance/engine/inspector.d.ts +16 -0
  62. package/dist/governance/engine/inspector.js +396 -0
  63. package/dist/governance/engine/integration.d.ts +35 -0
  64. package/dist/governance/engine/integration.js +220 -0
  65. package/dist/governance/engine/lifecycle.d.ts +29 -0
  66. package/dist/governance/engine/lifecycle.js +656 -0
  67. package/dist/governance/engine/pause.d.ts +56 -0
  68. package/dist/governance/engine/pause.js +107 -0
  69. package/dist/governance/engine/phase-members.d.ts +15 -0
  70. package/dist/governance/engine/phase-members.js +34 -0
  71. package/dist/governance/engine/phase-planner.d.ts +53 -0
  72. package/dist/governance/engine/phase-planner.js +282 -0
  73. package/dist/governance/engine/rebuild.d.ts +27 -0
  74. package/dist/governance/engine/rebuild.js +57 -0
  75. package/dist/governance/engine/report.d.ts +66 -0
  76. package/dist/governance/engine/report.js +220 -0
  77. package/dist/governance/engine/run-digest.d.ts +20 -0
  78. package/dist/governance/engine/run-digest.js +42 -0
  79. package/dist/governance/engine/spec-builder.d.ts +117 -0
  80. package/dist/governance/engine/spec-builder.js +321 -0
  81. package/dist/governance/engine/stage-runner.d.ts +22 -0
  82. package/dist/governance/engine/stage-runner.js +664 -0
  83. package/dist/governance/engine/start-input.d.ts +19 -0
  84. package/dist/governance/engine/start-input.js +111 -0
  85. package/dist/governance/engine/state-machine.d.ts +101 -0
  86. package/dist/governance/engine/state-machine.js +586 -0
  87. package/dist/governance/engine/submission-queries.d.ts +42 -0
  88. package/dist/governance/engine/submission-queries.js +97 -0
  89. package/dist/governance/engine/submission-render.d.ts +12 -0
  90. package/dist/governance/engine/submission-render.js +92 -0
  91. package/dist/governance/engine/submissions.d.ts +27 -0
  92. package/dist/governance/engine/submissions.js +455 -0
  93. package/dist/governance/engine/visibility.d.ts +104 -0
  94. package/dist/governance/engine/visibility.js +223 -0
  95. package/dist/governance/governed-dispatch.d.ts +19 -0
  96. package/dist/governance/governed-dispatch.js +17 -0
  97. package/dist/governance/limits.d.ts +107 -0
  98. package/dist/governance/limits.js +131 -0
  99. package/dist/governance/posture-transition.d.ts +38 -0
  100. package/dist/governance/posture-transition.js +44 -0
  101. package/dist/governance/posture.d.ts +35 -0
  102. package/dist/governance/posture.js +59 -0
  103. package/dist/governance/profiles.d.ts +48 -0
  104. package/dist/governance/profiles.js +140 -0
  105. package/dist/governance/prompts/orchestrator-block.d.ts +40 -0
  106. package/dist/governance/prompts/orchestrator-block.js +128 -0
  107. package/dist/governance/prompts/plan-addendum.d.ts +22 -0
  108. package/dist/governance/prompts/plan-addendum.js +60 -0
  109. package/dist/governance/prompts/refusals.d.ts +48 -0
  110. package/dist/governance/prompts/refusals.js +82 -0
  111. package/dist/governance/prompts/role-blocks.d.ts +40 -0
  112. package/dist/governance/prompts/role-blocks.js +255 -0
  113. package/dist/governance/report-reader.d.ts +31 -0
  114. package/dist/governance/report-reader.js +57 -0
  115. package/dist/governance/roles.d.ts +83 -0
  116. package/dist/governance/roles.js +142 -0
  117. package/dist/governance/rules/claims.d.ts +83 -0
  118. package/dist/governance/rules/claims.js +250 -0
  119. package/dist/governance/rules/elimination.d.ts +66 -0
  120. package/dist/governance/rules/elimination.js +105 -0
  121. package/dist/governance/rules/events-table.d.ts +46 -0
  122. package/dist/governance/rules/events-table.js +94 -0
  123. package/dist/governance/rules/failure-analysis.d.ts +28 -0
  124. package/dist/governance/rules/failure-analysis.js +93 -0
  125. package/dist/governance/rules/ledger-replay.d.ts +53 -0
  126. package/dist/governance/rules/ledger-replay.js +53 -0
  127. package/dist/governance/rules/ledger-state.d.ts +85 -0
  128. package/dist/governance/rules/ledger-state.js +175 -0
  129. package/dist/governance/rules/ledger.d.ts +228 -0
  130. package/dist/governance/rules/ledger.js +560 -0
  131. package/dist/governance/rules/rca.d.ts +68 -0
  132. package/dist/governance/rules/rca.js +220 -0
  133. package/dist/governance/rules/settlement.d.ts +129 -0
  134. package/dist/governance/rules/settlement.js +363 -0
  135. package/dist/governance/rules/suspicion.d.ts +66 -0
  136. package/dist/governance/rules/suspicion.js +169 -0
  137. package/dist/governance/store/artifacts.d.ts +81 -0
  138. package/dist/governance/store/artifacts.js +173 -0
  139. package/dist/governance/store/atomic.d.ts +79 -0
  140. package/dist/governance/store/atomic.js +119 -0
  141. package/dist/governance/store/events-log.d.ts +35 -0
  142. package/dist/governance/store/events-log.js +86 -0
  143. package/dist/governance/store/run-lock.d.ts +51 -0
  144. package/dist/governance/store/run-lock.js +122 -0
  145. package/dist/governance/store/run-store.d.ts +229 -0
  146. package/dist/governance/store/run-store.js +644 -0
  147. package/dist/governance/store/scoreboard-export.d.ts +53 -0
  148. package/dist/governance/store/scoreboard-export.js +65 -0
  149. package/dist/governance/tools/child-tools.d.ts +31 -0
  150. package/dist/governance/tools/child-tools.js +65 -0
  151. package/dist/governance/tools/guard.d.ts +71 -0
  152. package/dist/governance/tools/guard.js +194 -0
  153. package/dist/governance/tools/inspector-tools.d.ts +63 -0
  154. package/dist/governance/tools/inspector-tools.js +424 -0
  155. package/dist/governance/tools/lead-tools.d.ts +42 -0
  156. package/dist/governance/tools/lead-tools.js +226 -0
  157. package/dist/governance/tools/normalize-util.d.ts +101 -0
  158. package/dist/governance/tools/normalize-util.js +230 -0
  159. package/dist/governance/tools/scan-incomplete.d.ts +79 -0
  160. package/dist/governance/tools/scan-incomplete.js +486 -0
  161. package/dist/governance/tools/shared-tools.d.ts +44 -0
  162. package/dist/governance/tools/shared-tools.js +125 -0
  163. package/dist/governance/tools/submit-normalizers.d.ts +28 -0
  164. package/dist/governance/tools/submit-normalizers.js +474 -0
  165. package/dist/governance/tools/submit-tools.d.ts +26 -0
  166. package/dist/governance/tools/submit-tools.js +171 -0
  167. package/dist/governance/types.d.ts +661 -0
  168. package/dist/governance/types.js +27 -0
  169. package/dist/session/validate-session.js +32 -1
  170. package/dist/team/runtime.d.ts +8 -0
  171. package/dist/team/runtime.js +9 -0
  172. package/dist/team/subagent.d.ts +23 -0
  173. package/dist/team/subagent.js +14 -3
  174. package/dist/team/types.d.ts +6 -0
  175. package/dist/tools/index.d.ts +30 -7
  176. package/dist/tools/index.js +14 -0
  177. package/dist/tools/plan-tools.d.ts +14 -2
  178. package/dist/tools/plan-tools.js +5 -2
  179. package/dist/ui/App.js +145 -25
  180. package/dist/ui/BottomStatusRow.js +1 -1
  181. package/dist/ui/Composer.d.ts +7 -1
  182. package/dist/ui/Composer.js +9 -6
  183. package/dist/ui/GovernancePanel.d.ts +50 -0
  184. package/dist/ui/GovernancePanel.js +77 -0
  185. package/dist/ui/GovernanceRun.d.ts +18 -0
  186. package/dist/ui/GovernanceRun.js +11 -0
  187. package/dist/ui/StatusBar.d.ts +15 -2
  188. package/dist/ui/StatusBar.js +10 -4
  189. package/dist/ui/Transcript.js +3 -0
  190. package/dist/ui/entries/GovernanceCard.d.ts +30 -0
  191. package/dist/ui/entries/GovernanceCard.js +32 -0
  192. package/dist/ui/governance-copy.d.ts +69 -0
  193. package/dist/ui/governance-copy.js +104 -0
  194. package/dist/ui/governance-status.d.ts +145 -0
  195. package/dist/ui/governance-status.js +326 -0
  196. package/dist/ui/interaction-copy.d.ts +4 -0
  197. package/dist/ui/interaction-copy.js +5 -0
  198. package/dist/ui/interaction-hints.d.ts +17 -0
  199. package/dist/ui/interaction-hints.js +14 -2
  200. package/dist/ui/layout/governance-board.d.ts +121 -0
  201. package/dist/ui/layout/governance-board.js +382 -0
  202. package/dist/ui/layout/governance-card.d.ts +53 -0
  203. package/dist/ui/layout/governance-card.js +134 -0
  204. package/dist/ui/layout/governance-chip.d.ts +38 -0
  205. package/dist/ui/layout/governance-chip.js +64 -0
  206. package/dist/ui/layout/governance-panel.d.ts +85 -0
  207. package/dist/ui/layout/governance-panel.js +358 -0
  208. package/dist/ui/layout/governance-roster.d.ts +65 -0
  209. package/dist/ui/layout/governance-roster.js +275 -0
  210. package/dist/ui/layout/status-detail-layout.js +3 -3
  211. package/dist/ui/layout/status-layout.d.ts +5 -2
  212. package/dist/ui/layout/status-layout.js +10 -6
  213. package/dist/ui/layout/virtual-window.js +11 -0
  214. package/dist/ui/overlays/GovernanceBoardOverlay.d.ts +41 -0
  215. package/dist/ui/overlays/GovernanceBoardOverlay.js +76 -0
  216. package/dist/ui/overlays/GovernanceSettingsPage.d.ts +48 -0
  217. package/dist/ui/overlays/GovernanceSettingsPage.js +93 -0
  218. package/dist/ui/overlays/HelpOverlay.js +4 -3
  219. package/dist/ui/overlays/QuestionOverlay.d.ts +9 -1
  220. package/dist/ui/overlays/QuestionOverlay.js +4 -2
  221. package/dist/ui/overlays/SettingsScreen.d.ts +7 -0
  222. package/dist/ui/overlays/SettingsScreen.js +21 -1
  223. package/dist/ui/overlays/governance-settings.d.ts +79 -0
  224. package/dist/ui/overlays/governance-settings.js +140 -0
  225. package/dist/ui/overlays/model-profile-state.d.ts +6 -1
  226. package/dist/ui/overlays/model-profile-state.js +10 -0
  227. package/dist/ui/palettes.d.ts +17 -0
  228. package/dist/ui/palettes.js +9 -0
  229. package/dist/ui/scrollback/live-preview.js +2 -0
  230. package/dist/ui/scrollback/settled-boundary.js +3 -0
  231. package/dist/ui/shortcuts.js +2 -2
  232. package/dist/ui/theme.d.ts +7 -0
  233. package/dist/ui/theme.js +1 -0
  234. package/dist/ui/transcript-text.js +9 -0
  235. package/dist/ui/use-wheel-routing.js +3 -1
  236. package/package.json +2 -2
@@ -0,0 +1,382 @@
1
+ /**
2
+ * The `/gov board` overlay's model and geometry (governance-mode section 10.5): the tab
3
+ * model, the data adapters, the row builders and the scroll arithmetic - every decision
4
+ * the overlay makes, in pure code.
5
+ *
6
+ * Pure and total: no Ink, no clock (`now` is not even needed: rows carry sequence
7
+ * numbers, not times), no I/O. ASCII ONLY.
8
+ *
9
+ * TWO DATA SOURCES, ONE SHAPE. The live `GovernanceSnapshot` knows the roster and the
10
+ * ticker; the work-package matrix, the full ledger and the claims live in `RunState` /
11
+ * `LedgerState`. `GovernanceBoardData` is the UI's own flat projection of all of it, with
12
+ * two builders: `boardDataFromSnapshot` (what the renderer always has) and
13
+ * `boardDataFromRun` (what the wiring can supply). A board built from the snapshot alone
14
+ * still reads correctly - the tabs it cannot fill say so instead of going blank.
15
+ *
16
+ * EVERY ROW IS ONE TERMINAL ROW. Rows are `Segment[]` rendered `wrap="truncate"`, so
17
+ * scrolling is arithmetic on a list length (`sliceWindow`), never a re-measure.
18
+ */
19
+ import { govChildStatus, labelTag, scoreText, scoreToneKey, segment, signedDeltaText, sortRoster, statusTone, } from '../governance-status.js';
20
+ import { BOARD_EMPTY, BOARD_TAB_LABEL, ROLE_LABEL, STATE_WORD, WORKING_VERB, replacedByText, } from '../governance-copy.js';
21
+ import { statusMarkerCell } from '../team-status.js';
22
+ import { cellWidth, fitCells, padCells } from './cells.js';
23
+ import { sliceWindow } from './overlay-window.js';
24
+ export const BOARD_TABS = ['roster', 'work', 'ledger', 'claims'];
25
+ /** Terminals narrower than this get the single-column form with a `1/4` pager. */
26
+ export const BOARD_NARROW_COLS = 80;
27
+ /** Recent ledger lines shown under each identity on the roster tab (spec 10.5). */
28
+ export const BOARD_RECENT_PER_AGENT = 5;
29
+ /** Ledger rows kept in the projection, newest first. */
30
+ export const BOARD_LEDGER_MAX = 200;
31
+ /** The tab `delta` steps away, wrapping at both ends. */
32
+ export function stepTab(tab, delta) {
33
+ const index = BOARD_TABS.indexOf(tab);
34
+ const size = BOARD_TABS.length;
35
+ return BOARD_TABS[(((index + delta) % size) + size) % size];
36
+ }
37
+ export function tabIndex(tab) {
38
+ return BOARD_TABS.indexOf(tab);
39
+ }
40
+ /** What the renderer always has: the live snapshot. WPs and claims stay empty. */
41
+ export function boardDataFromSnapshot(snapshot, boardPath = null) {
42
+ const ledger = [...snapshot.recent]
43
+ .sort((a, b) => b.at - a.at)
44
+ .map((row, index) => ({
45
+ seq: snapshot.recent.length - index,
46
+ agentId: row.agentId,
47
+ code: row.code,
48
+ delta: row.delta,
49
+ provisional: row.provisional,
50
+ by: '',
51
+ reason: row.reason,
52
+ evidence: '',
53
+ capped: false,
54
+ }));
55
+ return { snapshot, workPackages: [], ledger, claims: [], boardPath };
56
+ }
57
+ function workPackageRow(wp) {
58
+ return {
59
+ id: wp.id,
60
+ title: wp.title,
61
+ status: wp.status,
62
+ reworkCount: wp.reworkCount,
63
+ assignee: wp.assignee ?? '',
64
+ reviewer: wp.reviewer ?? '',
65
+ tester: wp.tester ?? '',
66
+ fileCount: wp.files.length,
67
+ blockedAt: wp.blockedAt ?? '',
68
+ };
69
+ }
70
+ function claimRow(claim) {
71
+ return {
72
+ id: claim.id,
73
+ kind: 'claim',
74
+ status: claim.status,
75
+ detail: claim.kind,
76
+ subject: `${claim.raisedBy} -> ${claim.against} (${claim.wpId})`,
77
+ text: claim.rulingText ?? claim.justification,
78
+ };
79
+ }
80
+ function escalationRow(esc) {
81
+ return {
82
+ id: esc.id,
83
+ kind: 'escalation',
84
+ status: esc.status,
85
+ detail: esc.kind,
86
+ subject: esc.wpId ?? (esc.phase === undefined ? '' : `phase ${esc.phase + 1}`),
87
+ text: esc.ruling ?? esc.topic,
88
+ };
89
+ }
90
+ function ledgerRows(ledger) {
91
+ const rows = [];
92
+ for (const agent of Object.values(ledger.agents)) {
93
+ for (const entry of agent.history) {
94
+ rows.push({
95
+ seq: entry.seq,
96
+ agentId: agent.id,
97
+ code: entry.code,
98
+ delta: entry.delta,
99
+ provisional: entry.settlement === 'provisional',
100
+ by: entry.by,
101
+ reason: entry.reason,
102
+ evidence: entry.evidence ?? '',
103
+ capped: entry.capped !== undefined,
104
+ });
105
+ }
106
+ }
107
+ return rows.sort((a, b) => b.seq - a.seq).slice(0, BOARD_LEDGER_MAX);
108
+ }
109
+ /** The full projection, for a wiring that can read the run and the ledger. */
110
+ export function boardDataFromRun(input) {
111
+ const { snapshot, run, ledger } = input;
112
+ return {
113
+ snapshot,
114
+ workPackages: run.wpOrder
115
+ .map((id) => run.workPackages[id])
116
+ .filter((wp) => wp !== undefined)
117
+ .map(workPackageRow),
118
+ ledger: ledgerRows(ledger),
119
+ claims: [...run.claims.map(claimRow), ...run.escalations.map(escalationRow)],
120
+ boardPath: input.boardPath ?? null,
121
+ };
122
+ }
123
+ function boardLine(key, segments) {
124
+ return { key, segments };
125
+ }
126
+ function emptyLine(text) {
127
+ return [boardLine('empty', [segment(text, 'muted')])];
128
+ }
129
+ function head(key, text) {
130
+ return boardLine(key, [segment(text, 'muted')]);
131
+ }
132
+ function agentState(row) {
133
+ return row.state === 'working' ? WORKING_VERB[row.role] : STATE_WORD[row.state];
134
+ }
135
+ function recentLines(data, agentId) {
136
+ return data.ledger
137
+ .filter((row) => row.agentId === agentId)
138
+ .slice(0, BOARD_RECENT_PER_AGENT)
139
+ .map((row) => boardLine(`${agentId}:r${row.seq}`, [
140
+ segment(' ', 'muted'),
141
+ segment(padCells(signedDeltaText(row.delta, row.provisional), 7), row.delta >= 0 ? 'ok' : 'error'),
142
+ segment(padCells(row.code, 5), 'muted'),
143
+ segment(row.reason.replace(/\s+/g, ' '), 'text'),
144
+ ...(row.capped ? [segment(` [capped]`, 'warn')] : []),
145
+ ]));
146
+ }
147
+ function rosterIdentityLine(row, glyphs, narrow) {
148
+ const status = govChildStatus(row.state);
149
+ const tone = statusTone(status);
150
+ const score = row.confirmed === null ? '-' : scoreText(row.confirmed);
151
+ const prov = row.provisional > 0 ? `+${row.provisional}P` : '';
152
+ const tag = labelTag(row.label);
153
+ const marker = ` ${statusMarkerCell(status, glyphs)} `;
154
+ if (narrow) {
155
+ return boardLine(row.id, [
156
+ segment(marker, tone),
157
+ segment(padCells(row.id, 9), 'primary'),
158
+ segment(padCells(ROLE_LABEL[row.role], 5), 'muted'),
159
+ segment(padCells(score, 5), scoreToneKey(row.label), true),
160
+ segment(prov, 'muted'),
161
+ ...(tag === '' ? [] : [segment(` ${tag}`, scoreToneKey(row.label), true)]),
162
+ ]);
163
+ }
164
+ const state = row.state === 'eliminated' ? replacedByText(row.replacedBy) : agentState(row);
165
+ return boardLine(row.id, [
166
+ segment(marker, tone),
167
+ segment(padCells(row.id, 10), 'primary'),
168
+ segment(padCells(ROLE_LABEL[row.role], 6), 'muted'),
169
+ segment(padCells(`g${row.generation}`, 4), 'muted'),
170
+ segment(padCells(score, 6), scoreToneKey(row.label), true),
171
+ segment(padCells(prov, 7), 'muted'),
172
+ segment(padCells(tag, 8), scoreToneKey(row.label), true),
173
+ segment(padCells(state, 22), tone),
174
+ segment(row.wpId ?? '-', 'text'),
175
+ ]);
176
+ }
177
+ function rosterRows(data, glyphs, narrow) {
178
+ const roster = sortRoster(data.snapshot.roster);
179
+ if (roster.length === 0)
180
+ return emptyLine(BOARD_EMPTY.roster);
181
+ const lines = narrow
182
+ ? []
183
+ : [head('h', ' id role gen score prov label state work')];
184
+ for (const row of roster) {
185
+ lines.push(rosterIdentityLine(row, glyphs, narrow));
186
+ lines.push(...recentLines(data, row.id));
187
+ }
188
+ return lines;
189
+ }
190
+ const WP_TONE = {
191
+ pending: 'pending',
192
+ dev_running: 'running',
193
+ awaiting_review: 'pending',
194
+ review_running: 'running',
195
+ awaiting_test: 'pending',
196
+ test_running: 'running',
197
+ rework: 'warn',
198
+ tested_ok: 'ok',
199
+ integrated: 'ok',
200
+ blocked: 'error',
201
+ dropped: 'muted',
202
+ };
203
+ function workRows(data, narrow) {
204
+ if (data.workPackages.length === 0)
205
+ return emptyLine(BOARD_EMPTY.work);
206
+ const lines = narrow
207
+ ? []
208
+ : [head('h', 'wp status rw dev rev test files title')];
209
+ for (const wp of data.workPackages) {
210
+ const status = wp.blockedAt === '' ? wp.status : `${wp.status}@${wp.blockedAt}`;
211
+ if (narrow) {
212
+ lines.push(boardLine(wp.id, [
213
+ segment(padCells(wp.id, 12), 'primary'),
214
+ segment(status, WP_TONE[wp.status]),
215
+ ...(wp.reworkCount > 0 ? [segment(` rw${wp.reworkCount}`, 'warn')] : []),
216
+ ]));
217
+ lines.push(boardLine(`${wp.id}:t`, [segment(` ${wp.title}`, 'muted')]));
218
+ continue;
219
+ }
220
+ lines.push(boardLine(wp.id, [
221
+ segment(padCells(wp.id, 12), 'primary'),
222
+ segment(padCells(status, 18), WP_TONE[wp.status]),
223
+ segment(padCells(String(wp.reworkCount), 4), wp.reworkCount > 0 ? 'warn' : 'muted'),
224
+ segment(padCells(wp.assignee || '-', 9), 'text'),
225
+ segment(padCells(wp.reviewer || '-', 9), 'text'),
226
+ segment(padCells(wp.tester || '-', 9), 'text'),
227
+ segment(padCells(String(wp.fileCount), 7), 'muted'),
228
+ segment(wp.title, 'muted'),
229
+ ]));
230
+ }
231
+ return lines;
232
+ }
233
+ function ledgerBoardRows(data, narrow) {
234
+ if (data.ledger.length === 0)
235
+ return emptyLine(BOARD_EMPTY.ledger);
236
+ const lines = narrow
237
+ ? []
238
+ : [head('h', '# agent delta code by reason')];
239
+ for (const row of data.ledger) {
240
+ const tone = row.delta >= 0 ? 'ok' : 'error';
241
+ const amount = signedDeltaText(row.delta, row.provisional);
242
+ const reason = row.reason.replace(/\s+/g, ' ');
243
+ const cap = row.capped ? [segment(' [capped]', 'warn')] : [];
244
+ if (narrow) {
245
+ lines.push(boardLine(`l${row.seq}:${row.agentId}`, [
246
+ segment(padCells(row.agentId, 9), 'primary'),
247
+ segment(padCells(amount, 7), tone),
248
+ segment(padCells(row.code, 5), 'muted'),
249
+ segment(reason, 'text'),
250
+ ...cap,
251
+ ]));
252
+ }
253
+ else {
254
+ lines.push(boardLine(`l${row.seq}:${row.agentId}`, [
255
+ segment(padCells(String(row.seq), 7), 'muted'),
256
+ segment(padCells(row.agentId, 10), 'primary'),
257
+ segment(padCells(amount, 8), tone),
258
+ segment(padCells(row.code, 6), 'muted'),
259
+ segment(padCells(row.by, 19), 'muted'),
260
+ segment(reason, 'text'),
261
+ ...cap,
262
+ ]));
263
+ }
264
+ if (row.evidence !== '') {
265
+ lines.push(boardLine(`l${row.seq}:${row.agentId}:e`, [
266
+ segment(` evidence: ${row.evidence.replace(/\s+/g, ' ')}`, 'muted'),
267
+ ]));
268
+ }
269
+ }
270
+ return lines;
271
+ }
272
+ function claimsRows(data, narrow) {
273
+ if (data.claims.length === 0)
274
+ return emptyLine(BOARD_EMPTY.claims);
275
+ const lines = [];
276
+ for (const claim of data.claims) {
277
+ const open = claim.status === 'open';
278
+ lines.push(boardLine(claim.id, [
279
+ segment(padCells(claim.id, 8), 'primary'),
280
+ segment(padCells(claim.status, 10), open ? 'warn' : 'ok', open),
281
+ ...(narrow ? [] : [segment(padCells(claim.detail, 20), 'muted')]),
282
+ segment(claim.subject, 'text'),
283
+ ]));
284
+ if (claim.text !== '') {
285
+ lines.push(boardLine(`${claim.id}:t`, [
286
+ segment(` ${claim.text.replace(/\s+/g, ' ')}`, 'muted'),
287
+ ]));
288
+ }
289
+ }
290
+ return lines;
291
+ }
292
+ function tabCount(tab, data) {
293
+ switch (tab) {
294
+ case 'roster':
295
+ return data.snapshot.roster.length;
296
+ case 'work':
297
+ return data.workPackages.length;
298
+ case 'ledger':
299
+ return data.ledger.length;
300
+ case 'claims':
301
+ return data.claims.filter((claim) => claim.status === 'open').length;
302
+ }
303
+ }
304
+ export function buildTabStrip(tab, data, narrow, glyphs) {
305
+ if (narrow) {
306
+ const pager = `${tabIndex(tab) + 1}/${BOARD_TABS.length}`;
307
+ return {
308
+ pager,
309
+ segments: [
310
+ segment(`${glyphs.arrowLeft} `, 'muted'),
311
+ segment(BOARD_TAB_LABEL[tab], 'govern', true),
312
+ segment(` ${pager} ${glyphs.arrowRight}`, 'muted'),
313
+ ],
314
+ };
315
+ }
316
+ const segments = [];
317
+ BOARD_TABS.forEach((candidate, index) => {
318
+ if (index > 0)
319
+ segments.push(segment(` ${glyphs.midDot} `, 'border'));
320
+ const active = candidate === tab;
321
+ const count = tabCount(candidate, data);
322
+ segments.push(segment(`${active ? glyphs.cursor : ' '}${BOARD_TAB_LABEL[candidate]}`, active ? 'govern' : 'muted', active));
323
+ if (count > 0)
324
+ segments.push(segment(` ${count}`, active ? 'text' : 'hint'));
325
+ });
326
+ return { segments, pager: '' };
327
+ }
328
+ const NO_RUN_LINES = [boardLine('norun', [segment(BOARD_EMPTY.noRun, 'muted')])];
329
+ export function buildGovernanceBoardLayout(input) {
330
+ const { tab, data, glyphs } = input;
331
+ const narrow = input.cols < BOARD_NARROW_COLS;
332
+ if (data === null) {
333
+ const strip = { segments: [segment(BOARD_TAB_LABEL[tab], 'govern', true)], pager: '' };
334
+ return { narrow, tabStrip: strip, lines: NO_RUN_LINES };
335
+ }
336
+ const tabStrip = buildTabStrip(tab, data, narrow, glyphs);
337
+ switch (tab) {
338
+ case 'roster':
339
+ return { narrow, tabStrip, lines: rosterRows(data, glyphs, narrow) };
340
+ case 'work':
341
+ return { narrow, tabStrip, lines: workRows(data, narrow) };
342
+ case 'ledger':
343
+ return { narrow, tabStrip, lines: ledgerBoardRows(data, narrow) };
344
+ case 'claims':
345
+ return { narrow, tabStrip, lines: claimsRows(data, narrow) };
346
+ }
347
+ }
348
+ // ---------------------------------------------------------------------------
349
+ // Scroll windowing
350
+ // ---------------------------------------------------------------------------
351
+ /** Body rows left for content once the tab strip has taken one of the frame's rows. */
352
+ export function boardBodyRows(frameBodyRows) {
353
+ return Math.max(1, Math.floor(frameBodyRows) - 1);
354
+ }
355
+ export function boardWindow(total, offset, height) {
356
+ return sliceWindow(total, offset, height);
357
+ }
358
+ /** Position text for the footer, e.g. `3-14/40`; '' when everything fits. */
359
+ export function boardPosition(window, glyphs) {
360
+ if (window.visible >= window.total)
361
+ return '';
362
+ const up = window.start > 0 ? glyphs.arrowUp : ' ';
363
+ const down = window.start + window.visible < window.total ? glyphs.arrowDown : ' ';
364
+ return `${up}${down} ${window.start + 1}-${window.start + window.visible}/${window.total}`;
365
+ }
366
+ /** Footer hint, shortened to the width the frame can print on one row. */
367
+ export function boardHint(cols, glyphs) {
368
+ const dot = ` ${glyphs.midDot} `;
369
+ const full = [
370
+ `${glyphs.arrowLeft}/${glyphs.arrowRight} tab`,
371
+ `${glyphs.arrowUp}/${glyphs.arrowDown} PgUp/PgDn scroll`,
372
+ 'e copy path',
373
+ 'Esc close',
374
+ ];
375
+ for (let size = full.length; size >= 1; size -= 1) {
376
+ const kept = size === full.length ? full : [...full.slice(0, size - 1), full[full.length - 1]];
377
+ const text = kept.join(dot);
378
+ if (cellWidth(text) <= cols)
379
+ return text;
380
+ }
381
+ return fitCells('Esc close', Math.max(0, cols), glyphs.ellipsis);
382
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The settlement card's layout (governance-mode section 10.4): what a
3
+ * `kind: 'governance'` transcript entry says, as pure data, plus the plain-text export
4
+ * of the same card.
5
+ *
6
+ * Pure and total. ASCII ONLY.
7
+ *
8
+ * WIDTH-INDEPENDENT ON PURPOSE. A transcript entry is measured by two parties that must
9
+ * agree - `virtual-window.ts::estimateEntryRows` and the Ink tree - and neither is handed
10
+ * the terminal width for a card (`EntryView` has no `cols`). Every line here is
11
+ * single-row and rendered `wrap="truncate"`, so the card's HEIGHT is a function of the
12
+ * data alone (`governanceCardRows`) and the estimate is exact rather than an upper bound.
13
+ * The spec's "narrow terminal -> one plain line per agent" falls out for free: the table
14
+ * is only a fixed prefix (id, role, confirmed) and an elastic delta, so truncation at 40
15
+ * columns leaves exactly `dev1 DEV 115 +15P->...`.
16
+ */
17
+ import type { GovernanceCardData } from '../../governance/types.js';
18
+ import type { Glyphs } from '../glyphs.js';
19
+ import { type Segment, type Tone } from '../governance-status.js';
20
+ export interface CardTableRow {
21
+ key: string;
22
+ cells: Segment[][];
23
+ }
24
+ export interface GovernanceCardLayout {
25
+ /** Rail / headline tone for the whole card. */
26
+ tone: Tone;
27
+ title: Segment[];
28
+ headline: Segment[] | null;
29
+ /** Fixed prefix widths: id, role, confirmed. The delta cell is elastic. */
30
+ widths: [number, number, number];
31
+ tableHeader: Segment[][] | null;
32
+ rows: CardTableRow[];
33
+ notes: Segment[] | null;
34
+ height: number;
35
+ }
36
+ export declare function cardTone(card: GovernanceCardData): Tone;
37
+ /**
38
+ * Colour the signed tokens of a delta text: `+15P->C D1 +10 D12 -20 D5`. Gains read in
39
+ * the success tone, losses in the error tone, event codes stay muted. The spelling comes
40
+ * from the engine (`deltaText`), this only paints it.
41
+ */
42
+ export declare function colorizeDelta(text: string): Segment[];
43
+ export declare function buildGovernanceCardLayout(card: GovernanceCardData, glyphs: Glyphs): GovernanceCardLayout;
44
+ /**
45
+ * The card's row count, from the data alone: title, headline, table header + one row per
46
+ * agent, notes. Exact (not an upper bound) because every line truncates instead of wrapping.
47
+ */
48
+ export declare function governanceCardRows(card: GovernanceCardData): number;
49
+ /**
50
+ * The card as plain lines for `/export` and `/save` (no colour, ASCII-safe through
51
+ * `glyphs`). The caller indents them; the text matches what the card prints.
52
+ */
53
+ export declare function governanceCardLines(card: GovernanceCardData, glyphs: Glyphs): string[];
@@ -0,0 +1,134 @@
1
+ /**
2
+ * The settlement card's layout (governance-mode section 10.4): what a
3
+ * `kind: 'governance'` transcript entry says, as pure data, plus the plain-text export
4
+ * of the same card.
5
+ *
6
+ * Pure and total. ASCII ONLY.
7
+ *
8
+ * WIDTH-INDEPENDENT ON PURPOSE. A transcript entry is measured by two parties that must
9
+ * agree - `virtual-window.ts::estimateEntryRows` and the Ink tree - and neither is handed
10
+ * the terminal width for a card (`EntryView` has no `cols`). Every line here is
11
+ * single-row and rendered `wrap="truncate"`, so the card's HEIGHT is a function of the
12
+ * data alone (`governanceCardRows`) and the estimate is exact rather than an upper bound.
13
+ * The spec's "narrow terminal -> one plain line per agent" falls out for free: the table
14
+ * is only a fixed prefix (id, role, confirmed) and an elastic delta, so truncation at 40
15
+ * columns leaves exactly `dev1 DEV 115 +15P->...`.
16
+ */
17
+ import { segment } from '../governance-status.js';
18
+ import { CARD_COLUMNS, CARD_TITLE } from '../governance-copy.js';
19
+ import { cellWidth, padCells } from './cells.js';
20
+ const TONE_OF_CARD = {
21
+ ok: 'ok',
22
+ warn: 'warn',
23
+ error: 'error',
24
+ info: 'govern',
25
+ };
26
+ export function cardTone(card) {
27
+ return TONE_OF_CARD[card.tone];
28
+ }
29
+ /** The glyph in front of the headline: what the card's verdict looks like at a glance. */
30
+ function toneGlyph(tone, glyphs) {
31
+ switch (tone) {
32
+ case 'ok':
33
+ return glyphs.check;
34
+ case 'warn':
35
+ return glyphs.warn;
36
+ case 'error':
37
+ return glyphs.times;
38
+ case 'info':
39
+ return glyphs.bullet;
40
+ }
41
+ }
42
+ /**
43
+ * Colour the signed tokens of a delta text: `+15P->C D1 +10 D12 -20 D5`. Gains read in
44
+ * the success tone, losses in the error tone, event codes stay muted. The spelling comes
45
+ * from the engine (`deltaText`), this only paints it.
46
+ */
47
+ export function colorizeDelta(text) {
48
+ return text
49
+ .split(/(\s+)/)
50
+ .filter((part) => part !== '')
51
+ .map((part) => {
52
+ if (/^\+\d/.test(part))
53
+ return segment(part, 'ok');
54
+ if (/^-\d/.test(part))
55
+ return segment(part, 'error');
56
+ return segment(part, 'muted');
57
+ });
58
+ }
59
+ function titleText(card, glyphs) {
60
+ const detail = card.title.trim();
61
+ if (detail === '')
62
+ return CARD_TITLE;
63
+ return /^governance\b/i.test(detail) ? detail : `${CARD_TITLE} ${glyphs.midDot} ${detail}`;
64
+ }
65
+ const ID_MIN = 6;
66
+ const ID_MAX = 14;
67
+ const ROLE_CELLS = 6;
68
+ const CONFIRMED_CELLS = 11;
69
+ export function buildGovernanceCardLayout(card, glyphs) {
70
+ const tone = cardTone(card);
71
+ const idCells = Math.min(ID_MAX, Math.max(ID_MIN, ...card.rows.map((row) => cellWidth(row.id) + 2)));
72
+ const headlineText = card.headline.trim();
73
+ const rows = card.rows.map((row, index) => ({
74
+ key: `${row.id}:${index}`,
75
+ cells: [
76
+ [segment(padCells(row.id, idCells), 'primary')],
77
+ [segment(padCells(row.role, ROLE_CELLS), 'muted')],
78
+ [segment(padCells(row.confirmed === null ? '-' : String(Math.round(row.confirmed)), CONFIRMED_CELLS), 'text', true)],
79
+ colorizeDelta(row.deltaText),
80
+ ],
81
+ }));
82
+ const tableHeader = rows.length === 0
83
+ ? null
84
+ : [
85
+ [segment(padCells(CARD_COLUMNS.id, idCells), 'muted')],
86
+ [segment(padCells(CARD_COLUMNS.role, ROLE_CELLS), 'muted')],
87
+ [segment(padCells(CARD_COLUMNS.confirmed, CONFIRMED_CELLS), 'muted')],
88
+ [segment(CARD_COLUMNS.delta, 'muted')],
89
+ ];
90
+ const notes = card.notes.length === 0
91
+ ? null
92
+ : [segment(`notes: ${card.notes.join('; ')}`, 'muted')];
93
+ return {
94
+ tone,
95
+ title: [segment(titleText(card, glyphs), 'govern', true)],
96
+ headline: headlineText === ''
97
+ ? null
98
+ : [segment(`${toneGlyph(card.tone, glyphs)} `, tone, true), segment(headlineText, 'text')],
99
+ widths: [idCells, ROLE_CELLS, CONFIRMED_CELLS],
100
+ tableHeader,
101
+ rows,
102
+ notes,
103
+ height: governanceCardRows(card),
104
+ };
105
+ }
106
+ /**
107
+ * The card's row count, from the data alone: title, headline, table header + one row per
108
+ * agent, notes. Exact (not an upper bound) because every line truncates instead of wrapping.
109
+ */
110
+ export function governanceCardRows(card) {
111
+ return 1
112
+ + Number(card.headline.trim() !== '')
113
+ + (card.rows.length > 0 ? 1 + card.rows.length : 0)
114
+ + Number(card.notes.length > 0);
115
+ }
116
+ /**
117
+ * The card as plain lines for `/export` and `/save` (no colour, ASCII-safe through
118
+ * `glyphs`). The caller indents them; the text matches what the card prints.
119
+ */
120
+ export function governanceCardLines(card, glyphs) {
121
+ const layout = buildGovernanceCardLayout(card, glyphs);
122
+ const flat = (segments) => segments.map((s) => s.text).join('');
123
+ const lines = [flat(layout.title)];
124
+ if (layout.headline)
125
+ lines.push(flat(layout.headline));
126
+ if (layout.tableHeader) {
127
+ lines.push(layout.tableHeader.map(flat).join('').trimEnd());
128
+ for (const row of layout.rows)
129
+ lines.push(row.cells.map(flat).join('').trimEnd());
130
+ }
131
+ if (layout.notes)
132
+ lines.push(flat(layout.notes));
133
+ return lines;
134
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The status bar's governance chip: pure width planning, no JSX
3
+ * (governance-mode section 10.2).
4
+ *
5
+ * ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`.
6
+ *
7
+ * SAME SHAPE AS `kit-chip.ts` / `session-chip.ts`, for the same reason: the status row is
8
+ * measured cell by cell, and a chip appended "just to the right" eats `phase`, the one
9
+ * field a user needs mid-run. The chip therefore competes for the SAME budget as every
10
+ * other field: it takes the longest rung that still leaves the left half its floor.
11
+ *
12
+ * THE LADDER (spec 10.2): `GOV p2/3 REVIEW` -> `GOV p2/3` -> `GOV`. A paused run reads
13
+ * `GOV paused`, one with a `/gov pause` still pending `GOV pausing` (warn tone: the stop is
14
+ * coming), an escalated one `GOV ! escalated` (warn tone - it is the run asking for
15
+ * the user), and a budget at or past 80% appends ` $` at every rung where there is room,
16
+ * because the hint is worth more than the stage name when space is short.
17
+ */
18
+ import type { GovernanceSnapshot } from '../../governance/types.js';
19
+ import { type Tone } from '../governance-status.js';
20
+ export declare const GOVERNANCE_CHIP_LIMITS: {
21
+ /** Blank cells between the chip and the field run. */
22
+ readonly gapCells: 2;
23
+ /** The floor the left half must keep; the same 34 the session and kit chips use. */
24
+ readonly minStatusCells: 34;
25
+ };
26
+ export interface GovernanceChipPlan {
27
+ visible: boolean;
28
+ text: string;
29
+ cells: number;
30
+ gap: number;
31
+ tone: Tone;
32
+ }
33
+ export interface GovernanceChipInput {
34
+ columns: number;
35
+ snapshot?: GovernanceSnapshot | null | undefined;
36
+ }
37
+ /** Plan the chip for `columns` and this session's live run snapshot. */
38
+ export declare function planGovernanceChip(input: GovernanceChipInput): GovernanceChipPlan;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The status bar's governance chip: pure width planning, no JSX
3
+ * (governance-mode section 10.2).
4
+ *
5
+ * ASCII ONLY - `src/ui/**` is inside `glyphs.test.ts::inScope`.
6
+ *
7
+ * SAME SHAPE AS `kit-chip.ts` / `session-chip.ts`, for the same reason: the status row is
8
+ * measured cell by cell, and a chip appended "just to the right" eats `phase`, the one
9
+ * field a user needs mid-run. The chip therefore competes for the SAME budget as every
10
+ * other field: it takes the longest rung that still leaves the left half its floor.
11
+ *
12
+ * THE LADDER (spec 10.2): `GOV p2/3 REVIEW` -> `GOV p2/3` -> `GOV`. A paused run reads
13
+ * `GOV paused`, one with a `/gov pause` still pending `GOV pausing` (warn tone: the stop is
14
+ * coming), an escalated one `GOV ! escalated` (warn tone - it is the run asking for
15
+ * the user), and a budget at or past 80% appends ` $` at every rung where there is room,
16
+ * because the hint is worth more than the stage name when space is short.
17
+ */
18
+ import { budgetState, isFinishedRun, isPausePending, activeStepKey, } from '../governance-status.js';
19
+ import { PIPELINE_LABEL, STATUS_WORD, phaseText } from '../governance-copy.js';
20
+ export const GOVERNANCE_CHIP_LIMITS = {
21
+ /** Blank cells between the chip and the field run. */
22
+ gapCells: 2,
23
+ /** The floor the left half must keep; the same 34 the session and kit chips use. */
24
+ minStatusCells: 34,
25
+ };
26
+ const HIDDEN = { visible: false, text: '', cells: 0, gap: 0, tone: 'muted' };
27
+ /** Candidate texts, longest first. ASCII `!` is the escalation mark in both glyph tiers. */
28
+ function rungs(snapshot) {
29
+ const budget = budgetState(snapshot);
30
+ const hint = budget === 'warn' || budget === 'over' ? ' $' : '';
31
+ if (isPausePending(snapshot)) {
32
+ return [`GOV ${STATUS_WORD.pausing}${hint}`, `GOV${hint}`];
33
+ }
34
+ if (snapshot.status === 'paused')
35
+ return [`GOV paused${hint}`, `GOV${hint}`];
36
+ if (snapshot.status === 'escalated') {
37
+ return [`GOV ! escalated${hint}`, `GOV !${hint}`, 'GOV !'];
38
+ }
39
+ const phase = phaseText(snapshot.phase, snapshot.phaseCount);
40
+ const stage = activeStepKey(snapshot);
41
+ const named = stage === null ? phase : `${phase} ${PIPELINE_LABEL[stage]}`;
42
+ return [`GOV ${named}${hint}`, `GOV ${phase}${hint}`, `GOV${hint}`, 'GOV'];
43
+ }
44
+ function chipTone(snapshot) {
45
+ if (snapshot.status === 'escalated' || budgetState(snapshot) === 'over')
46
+ return 'warn';
47
+ if (isPausePending(snapshot))
48
+ return 'warn';
49
+ return snapshot.status === 'paused' ? 'muted' : 'govern';
50
+ }
51
+ /** Plan the chip for `columns` and this session's live run snapshot. */
52
+ export function planGovernanceChip(input) {
53
+ const snapshot = input.snapshot;
54
+ if (!snapshot || isFinishedRun(snapshot.status))
55
+ return HIDDEN;
56
+ const columns = Number.isFinite(input.columns) ? Math.floor(input.columns) : 0;
57
+ const gap = GOVERNANCE_CHIP_LIMITS.gapCells;
58
+ for (const text of rungs(snapshot)) {
59
+ if (columns - (text.length + gap) >= GOVERNANCE_CHIP_LIMITS.minStatusCells) {
60
+ return { visible: true, text, cells: text.length, gap, tone: chipTone(snapshot) };
61
+ }
62
+ }
63
+ return HIDDEN;
64
+ }