@aragon-agent/cli 0.6.44 → 0.6.46

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 (255) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +120 -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/invocation-card.d.ts +77 -0
  10. package/dist/agent/invocation-card.js +169 -0
  11. package/dist/agent/plan-prompt.d.ts +7 -0
  12. package/dist/agent/plan-prompt.js +2 -1
  13. package/dist/agent/prompt-options.d.ts +6 -0
  14. package/dist/agent/queued-messages.d.ts +3 -0
  15. package/dist/agent/queued-messages.js +4 -1
  16. package/dist/agent/reducer.d.ts +38 -1
  17. package/dist/agent/reducer.js +57 -12
  18. package/dist/agent/system-prompt.d.ts +19 -0
  19. package/dist/agent/system-prompt.js +9 -1
  20. package/dist/cli.js +45 -1
  21. package/dist/commands/builtins.js +22 -4
  22. package/dist/commands/governance-command.d.ts +35 -0
  23. package/dist/commands/governance-command.js +304 -0
  24. package/dist/commands/governance-runs-table.d.ts +40 -0
  25. package/dist/commands/governance-runs-table.js +130 -0
  26. package/dist/commands/index-build.d.ts +8 -0
  27. package/dist/commands/index-build.js +11 -0
  28. package/dist/commands/registry.d.ts +35 -0
  29. package/dist/commands/registry.js +20 -7
  30. package/dist/commands/skills.js +36 -2
  31. package/dist/commands/subagent-command.js +3 -1
  32. package/dist/commands/team-command.d.ts +6 -0
  33. package/dist/commands/team-command.js +40 -3
  34. package/dist/config/app-paths.d.ts +10 -0
  35. package/dist/config/app-paths.js +12 -0
  36. package/dist/config/cli-commands.d.ts +18 -0
  37. package/dist/config/cli-commands.js +47 -1
  38. package/dist/config/env.d.ts +2 -1
  39. package/dist/config/env.js +10 -1
  40. package/dist/config/governance-config.d.ts +125 -0
  41. package/dist/config/governance-config.js +164 -0
  42. package/dist/config/governance-launch.d.ts +71 -0
  43. package/dist/config/governance-launch.js +58 -0
  44. package/dist/config/load.d.ts +15 -0
  45. package/dist/config/load.js +26 -1
  46. package/dist/config/model-profile-store.d.ts +4 -2
  47. package/dist/config/schema.d.ts +22 -0
  48. package/dist/config/schema.js +6 -0
  49. package/dist/config/store.js +14 -1
  50. package/dist/config/ui-state.d.ts +11 -0
  51. package/dist/config/ui-state.js +11 -0
  52. package/dist/diagnostics/doctor.js +20 -0
  53. package/dist/governance/card-normalize.d.ts +24 -0
  54. package/dist/governance/card-normalize.js +76 -0
  55. package/dist/governance/copy.d.ts +109 -0
  56. package/dist/governance/copy.js +223 -0
  57. package/dist/governance/engine/aftermath.d.ts +49 -0
  58. package/dist/governance/engine/aftermath.js +134 -0
  59. package/dist/governance/engine/artifact-persist.d.ts +35 -0
  60. package/dist/governance/engine/artifact-persist.js +86 -0
  61. package/dist/governance/engine/budget.d.ts +52 -0
  62. package/dist/governance/engine/budget.js +112 -0
  63. package/dist/governance/engine/cards.d.ts +18 -0
  64. package/dist/governance/engine/cards.js +186 -0
  65. package/dist/governance/engine/digest.d.ts +32 -0
  66. package/dist/governance/engine/digest.js +63 -0
  67. package/dist/governance/engine/engine-core.d.ts +219 -0
  68. package/dist/governance/engine/engine-core.js +562 -0
  69. package/dist/governance/engine/engine.d.ts +68 -0
  70. package/dist/governance/engine/engine.js +314 -0
  71. package/dist/governance/engine/estimate.d.ts +38 -0
  72. package/dist/governance/engine/estimate.js +46 -0
  73. package/dist/governance/engine/inspector.d.ts +16 -0
  74. package/dist/governance/engine/inspector.js +396 -0
  75. package/dist/governance/engine/integration.d.ts +35 -0
  76. package/dist/governance/engine/integration.js +220 -0
  77. package/dist/governance/engine/lifecycle.d.ts +29 -0
  78. package/dist/governance/engine/lifecycle.js +656 -0
  79. package/dist/governance/engine/pause.d.ts +56 -0
  80. package/dist/governance/engine/pause.js +107 -0
  81. package/dist/governance/engine/phase-members.d.ts +15 -0
  82. package/dist/governance/engine/phase-members.js +34 -0
  83. package/dist/governance/engine/phase-planner.d.ts +53 -0
  84. package/dist/governance/engine/phase-planner.js +282 -0
  85. package/dist/governance/engine/rebuild.d.ts +27 -0
  86. package/dist/governance/engine/rebuild.js +57 -0
  87. package/dist/governance/engine/report.d.ts +66 -0
  88. package/dist/governance/engine/report.js +220 -0
  89. package/dist/governance/engine/run-digest.d.ts +20 -0
  90. package/dist/governance/engine/run-digest.js +42 -0
  91. package/dist/governance/engine/spec-builder.d.ts +117 -0
  92. package/dist/governance/engine/spec-builder.js +321 -0
  93. package/dist/governance/engine/stage-runner.d.ts +22 -0
  94. package/dist/governance/engine/stage-runner.js +664 -0
  95. package/dist/governance/engine/start-input.d.ts +19 -0
  96. package/dist/governance/engine/start-input.js +111 -0
  97. package/dist/governance/engine/state-machine.d.ts +101 -0
  98. package/dist/governance/engine/state-machine.js +586 -0
  99. package/dist/governance/engine/submission-queries.d.ts +42 -0
  100. package/dist/governance/engine/submission-queries.js +97 -0
  101. package/dist/governance/engine/submission-render.d.ts +12 -0
  102. package/dist/governance/engine/submission-render.js +92 -0
  103. package/dist/governance/engine/submissions.d.ts +27 -0
  104. package/dist/governance/engine/submissions.js +455 -0
  105. package/dist/governance/engine/visibility.d.ts +104 -0
  106. package/dist/governance/engine/visibility.js +223 -0
  107. package/dist/governance/governed-dispatch.d.ts +19 -0
  108. package/dist/governance/governed-dispatch.js +17 -0
  109. package/dist/governance/limits.d.ts +107 -0
  110. package/dist/governance/limits.js +131 -0
  111. package/dist/governance/posture-transition.d.ts +38 -0
  112. package/dist/governance/posture-transition.js +44 -0
  113. package/dist/governance/posture.d.ts +35 -0
  114. package/dist/governance/posture.js +59 -0
  115. package/dist/governance/profiles.d.ts +48 -0
  116. package/dist/governance/profiles.js +140 -0
  117. package/dist/governance/prompts/orchestrator-block.d.ts +40 -0
  118. package/dist/governance/prompts/orchestrator-block.js +128 -0
  119. package/dist/governance/prompts/plan-addendum.d.ts +22 -0
  120. package/dist/governance/prompts/plan-addendum.js +60 -0
  121. package/dist/governance/prompts/refusals.d.ts +48 -0
  122. package/dist/governance/prompts/refusals.js +82 -0
  123. package/dist/governance/prompts/role-blocks.d.ts +40 -0
  124. package/dist/governance/prompts/role-blocks.js +255 -0
  125. package/dist/governance/report-reader.d.ts +31 -0
  126. package/dist/governance/report-reader.js +57 -0
  127. package/dist/governance/roles.d.ts +83 -0
  128. package/dist/governance/roles.js +142 -0
  129. package/dist/governance/rules/claims.d.ts +83 -0
  130. package/dist/governance/rules/claims.js +250 -0
  131. package/dist/governance/rules/elimination.d.ts +66 -0
  132. package/dist/governance/rules/elimination.js +105 -0
  133. package/dist/governance/rules/events-table.d.ts +46 -0
  134. package/dist/governance/rules/events-table.js +94 -0
  135. package/dist/governance/rules/failure-analysis.d.ts +28 -0
  136. package/dist/governance/rules/failure-analysis.js +93 -0
  137. package/dist/governance/rules/ledger-replay.d.ts +53 -0
  138. package/dist/governance/rules/ledger-replay.js +53 -0
  139. package/dist/governance/rules/ledger-state.d.ts +85 -0
  140. package/dist/governance/rules/ledger-state.js +175 -0
  141. package/dist/governance/rules/ledger.d.ts +228 -0
  142. package/dist/governance/rules/ledger.js +560 -0
  143. package/dist/governance/rules/rca.d.ts +68 -0
  144. package/dist/governance/rules/rca.js +220 -0
  145. package/dist/governance/rules/settlement.d.ts +129 -0
  146. package/dist/governance/rules/settlement.js +363 -0
  147. package/dist/governance/rules/suspicion.d.ts +66 -0
  148. package/dist/governance/rules/suspicion.js +169 -0
  149. package/dist/governance/store/artifacts.d.ts +81 -0
  150. package/dist/governance/store/artifacts.js +173 -0
  151. package/dist/governance/store/atomic.d.ts +79 -0
  152. package/dist/governance/store/atomic.js +119 -0
  153. package/dist/governance/store/events-log.d.ts +35 -0
  154. package/dist/governance/store/events-log.js +86 -0
  155. package/dist/governance/store/run-lock.d.ts +51 -0
  156. package/dist/governance/store/run-lock.js +122 -0
  157. package/dist/governance/store/run-store.d.ts +229 -0
  158. package/dist/governance/store/run-store.js +644 -0
  159. package/dist/governance/store/scoreboard-export.d.ts +53 -0
  160. package/dist/governance/store/scoreboard-export.js +65 -0
  161. package/dist/governance/tools/child-tools.d.ts +31 -0
  162. package/dist/governance/tools/child-tools.js +65 -0
  163. package/dist/governance/tools/guard.d.ts +71 -0
  164. package/dist/governance/tools/guard.js +194 -0
  165. package/dist/governance/tools/inspector-tools.d.ts +63 -0
  166. package/dist/governance/tools/inspector-tools.js +424 -0
  167. package/dist/governance/tools/lead-tools.d.ts +42 -0
  168. package/dist/governance/tools/lead-tools.js +226 -0
  169. package/dist/governance/tools/normalize-util.d.ts +101 -0
  170. package/dist/governance/tools/normalize-util.js +230 -0
  171. package/dist/governance/tools/scan-incomplete.d.ts +79 -0
  172. package/dist/governance/tools/scan-incomplete.js +486 -0
  173. package/dist/governance/tools/shared-tools.d.ts +44 -0
  174. package/dist/governance/tools/shared-tools.js +125 -0
  175. package/dist/governance/tools/submit-normalizers.d.ts +28 -0
  176. package/dist/governance/tools/submit-normalizers.js +474 -0
  177. package/dist/governance/tools/submit-tools.d.ts +26 -0
  178. package/dist/governance/tools/submit-tools.js +171 -0
  179. package/dist/governance/types.d.ts +661 -0
  180. package/dist/governance/types.js +27 -0
  181. package/dist/session/persist.js +10 -0
  182. package/dist/session/validate-session.js +36 -3
  183. package/dist/team/forced-mode.d.ts +19 -0
  184. package/dist/team/forced-mode.js +42 -20
  185. package/dist/team/runtime.d.ts +8 -0
  186. package/dist/team/runtime.js +9 -0
  187. package/dist/team/subagent.d.ts +23 -0
  188. package/dist/team/subagent.js +14 -3
  189. package/dist/team/types.d.ts +6 -0
  190. package/dist/tools/index.d.ts +30 -7
  191. package/dist/tools/index.js +14 -0
  192. package/dist/tools/plan-tools.d.ts +14 -2
  193. package/dist/tools/plan-tools.js +5 -2
  194. package/dist/ui/App.js +170 -31
  195. package/dist/ui/BottomStatusRow.js +1 -1
  196. package/dist/ui/Composer.d.ts +7 -1
  197. package/dist/ui/Composer.js +9 -6
  198. package/dist/ui/GovernancePanel.d.ts +50 -0
  199. package/dist/ui/GovernancePanel.js +77 -0
  200. package/dist/ui/GovernanceRun.d.ts +18 -0
  201. package/dist/ui/GovernanceRun.js +11 -0
  202. package/dist/ui/StatusBar.d.ts +15 -2
  203. package/dist/ui/StatusBar.js +10 -4
  204. package/dist/ui/Transcript.js +7 -1
  205. package/dist/ui/entries/GovernanceCard.d.ts +30 -0
  206. package/dist/ui/entries/GovernanceCard.js +32 -0
  207. package/dist/ui/entries/InvocationCard.d.ts +20 -0
  208. package/dist/ui/entries/InvocationCard.js +32 -0
  209. package/dist/ui/governance-copy.d.ts +69 -0
  210. package/dist/ui/governance-copy.js +104 -0
  211. package/dist/ui/governance-status.d.ts +145 -0
  212. package/dist/ui/governance-status.js +326 -0
  213. package/dist/ui/interaction-copy.d.ts +4 -0
  214. package/dist/ui/interaction-copy.js +5 -0
  215. package/dist/ui/interaction-hints.d.ts +17 -0
  216. package/dist/ui/interaction-hints.js +14 -2
  217. package/dist/ui/layout/governance-board.d.ts +121 -0
  218. package/dist/ui/layout/governance-board.js +382 -0
  219. package/dist/ui/layout/governance-card.d.ts +53 -0
  220. package/dist/ui/layout/governance-card.js +134 -0
  221. package/dist/ui/layout/governance-chip.d.ts +38 -0
  222. package/dist/ui/layout/governance-chip.js +64 -0
  223. package/dist/ui/layout/governance-panel.d.ts +85 -0
  224. package/dist/ui/layout/governance-panel.js +358 -0
  225. package/dist/ui/layout/governance-roster.d.ts +65 -0
  226. package/dist/ui/layout/governance-roster.js +275 -0
  227. package/dist/ui/layout/invocation-card.d.ts +41 -0
  228. package/dist/ui/layout/invocation-card.js +91 -0
  229. package/dist/ui/layout/status-detail-layout.js +3 -3
  230. package/dist/ui/layout/status-layout.d.ts +5 -2
  231. package/dist/ui/layout/status-layout.js +10 -6
  232. package/dist/ui/layout/virtual-window.js +22 -1
  233. package/dist/ui/overlays/GovernanceBoardOverlay.d.ts +41 -0
  234. package/dist/ui/overlays/GovernanceBoardOverlay.js +76 -0
  235. package/dist/ui/overlays/GovernanceSettingsPage.d.ts +48 -0
  236. package/dist/ui/overlays/GovernanceSettingsPage.js +93 -0
  237. package/dist/ui/overlays/HelpOverlay.js +4 -3
  238. package/dist/ui/overlays/QuestionOverlay.d.ts +9 -1
  239. package/dist/ui/overlays/QuestionOverlay.js +4 -2
  240. package/dist/ui/overlays/SettingsScreen.d.ts +7 -0
  241. package/dist/ui/overlays/SettingsScreen.js +21 -1
  242. package/dist/ui/overlays/governance-settings.d.ts +79 -0
  243. package/dist/ui/overlays/governance-settings.js +140 -0
  244. package/dist/ui/overlays/model-profile-state.d.ts +6 -1
  245. package/dist/ui/overlays/model-profile-state.js +10 -0
  246. package/dist/ui/palettes.d.ts +17 -0
  247. package/dist/ui/palettes.js +9 -0
  248. package/dist/ui/scrollback/live-preview.js +2 -0
  249. package/dist/ui/scrollback/settled-boundary.js +3 -0
  250. package/dist/ui/shortcuts.js +2 -2
  251. package/dist/ui/theme.d.ts +7 -0
  252. package/dist/ui/theme.js +1 -0
  253. package/dist/ui/transcript-text.js +13 -1
  254. package/dist/ui/use-wheel-routing.js +3 -1
  255. package/package.json +2 -2
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Invocation cards (tui-invocation-cards): what a transcript shows INSTEAD of the
3
+ * system prompt a command submitted on the user's behalf.
4
+ *
5
+ * Pure and total: no IO, no React, no Ink, nothing but `import type`. ASCII ONLY -
6
+ * `src/agent/**` new files sit inside the `glyphs.test.ts` scan scope.
7
+ *
8
+ * ONE CONSTRUCTOR (I-3). `buildInvocationCard` is the only function that turns raw
9
+ * command-layer material into a card, `App.submitMessage` is its only caller, and
10
+ * `sanitizeInvocationCard` (the session-load path) shares its normaliser so a card
11
+ * written today and a card read back tomorrow cannot drift apart.
12
+ *
13
+ * ORDER IS REDACT -> NORMALISE -> LIMIT, and it is the load-bearing part (I-6). Cut
14
+ * first and a secret that straddles the cut leaves a prefix shorter than the
15
+ * redaction patterns' minimum length: the half-key sails through into the
16
+ * transcript, the session file and the exit replay.
17
+ */
18
+ /**
19
+ * The renderer, the height estimate and the loader all read THESE numbers (I-4).
20
+ * A card's height is a function of its data and these limits, so it is bounded:
21
+ * header + `inputMaxLines` + hidden tail + note.
22
+ */
23
+ export const INVOCATION_LIMITS = {
24
+ commandLineMaxChars: 2000,
25
+ inputMaxChars: 480,
26
+ inputMaxLines: 8,
27
+ nameMaxChars: 64,
28
+ factsMax: 5,
29
+ factMaxChars: 28,
30
+ noteMaxChars: 140,
31
+ };
32
+ const VARIANTS = ['skill', 'team', 'todo', 'govern'];
33
+ // C0 except TAB (\x09) and LF (\x0a), then DEL and C1 (which includes the 8-bit CSI
34
+ // \x9b). Matches the range `QueueOverlay` strips, so the card is no more permissive.
35
+ // eslint-disable-next-line no-control-regex
36
+ const CONTROL_CHARS = /[\x00-\x08\x0b-\x1f\x7f-\x9f]/g;
37
+ const CAP_MARK = '...';
38
+ /** Cut at a code-point boundary: a surrogate pair is never split. */
39
+ function sliceCodePoints(text, count) {
40
+ if (count <= 0)
41
+ return '';
42
+ if (text.length <= count)
43
+ return text;
44
+ let end = count;
45
+ const last = text.charCodeAt(end - 1);
46
+ // A high surrogate at the cut would orphan its low half.
47
+ if (last >= 0xd800 && last <= 0xdbff)
48
+ end -= 1;
49
+ return text.slice(0, end);
50
+ }
51
+ function countCodePoints(text) {
52
+ let count = 0;
53
+ for (const _ of text)
54
+ count += 1;
55
+ return count;
56
+ }
57
+ /** CRLF and bare CR to LF (a bare CR returns Ink's cursor to column 0), then strip controls. */
58
+ function normalizeText(text) {
59
+ return text.replace(/\r\n?/g, '\n').replace(CONTROL_CHARS, '');
60
+ }
61
+ function capCommandLine(text) {
62
+ const max = INVOCATION_LIMITS.commandLineMaxChars;
63
+ if (text.length <= max)
64
+ return text;
65
+ return sliceCodePoints(text, max - CAP_MARK.length) + CAP_MARK;
66
+ }
67
+ function capInput(text) {
68
+ const trimmed = text.trim();
69
+ if (trimmed === '')
70
+ return { input: '', hidden: 0 };
71
+ const lines = trimmed.split('\n');
72
+ let kept = lines.length > INVOCATION_LIMITS.inputMaxLines
73
+ ? lines.slice(0, INVOCATION_LIMITS.inputMaxLines).join('\n') : trimmed;
74
+ kept = sliceCodePoints(kept, INVOCATION_LIMITS.inputMaxChars).trimEnd();
75
+ return { input: kept, hidden: countCodePoints(trimmed) - countCodePoints(kept) };
76
+ }
77
+ function capSingleLine(text, max) {
78
+ const flat = text.replace(/\s+/g, ' ').trim();
79
+ if (countCodePoints(flat) <= max)
80
+ return flat;
81
+ return sliceCodePoints(flat, max - CAP_MARK.length) + CAP_MARK;
82
+ }
83
+ function capFacts(facts) {
84
+ return facts
85
+ .map((fact) => capSingleLine(fact, INVOCATION_LIMITS.factMaxChars))
86
+ .filter((fact) => fact !== '')
87
+ .slice(0, INVOCATION_LIMITS.factsMax);
88
+ }
89
+ function capNote(note) {
90
+ if (note === undefined)
91
+ return undefined;
92
+ const capped = capSingleLine(note, INVOCATION_LIMITS.noteMaxChars);
93
+ return capped === '' ? undefined : capped;
94
+ }
95
+ /** Shared by the write path and the load path so the two cannot diverge. */
96
+ function assemble(parts) {
97
+ const { input, hidden } = capInput(normalizeText(parts.input));
98
+ const note = capNote(normalizeText(parts.note ?? ''));
99
+ return {
100
+ variant: parts.variant,
101
+ name: capSingleLine(normalizeText(parts.name), INVOCATION_LIMITS.nameMaxChars),
102
+ commandLine: capCommandLine(normalizeText(parts.commandLine).trim()),
103
+ input,
104
+ inputHidden: hidden,
105
+ facts: capFacts(parts.facts.map(normalizeText)),
106
+ ...(note !== undefined ? { note } : {}),
107
+ };
108
+ }
109
+ /**
110
+ * Total, never throws. `redact` is injected (the same closure `Command:` echo uses) so
111
+ * this module depends on nothing; the default is the identity for tests and the loader.
112
+ */
113
+ export function buildInvocationCard(input, redact = (text) => text) {
114
+ const scrub = (text) => {
115
+ if (text === undefined)
116
+ return '';
117
+ try {
118
+ return redact(text);
119
+ }
120
+ catch {
121
+ return '';
122
+ }
123
+ };
124
+ return assemble({
125
+ variant: input.variant,
126
+ name: scrub(input.name),
127
+ commandLine: scrub(input.commandLine),
128
+ input: scrub(input.input),
129
+ facts: (input.facts ?? []).map((fact) => scrub(fact)),
130
+ note: input.note === undefined ? undefined : scrub(input.note),
131
+ });
132
+ }
133
+ function asString(value) {
134
+ return typeof value === 'string' ? value : null;
135
+ }
136
+ /**
137
+ * Session-load path: `null` for anything that is not a plausible card, so the caller
138
+ * drops the field and the entry degrades to a plain user message (I-7). Re-derives
139
+ * `inputHidden` from nothing - it trusts the stored count only when it is a sane integer.
140
+ */
141
+ export function sanitizeInvocationCard(value) {
142
+ if (typeof value !== 'object' || value === null)
143
+ return null;
144
+ const raw = value;
145
+ const variant = VARIANTS.find((candidate) => candidate === raw.variant);
146
+ const commandLine = asString(raw.commandLine);
147
+ if (variant === undefined || commandLine === null)
148
+ return null;
149
+ const name = asString(raw.name) ?? '';
150
+ const input = asString(raw.input) ?? '';
151
+ const facts = Array.isArray(raw.facts)
152
+ ? raw.facts.filter((fact) => typeof fact === 'string') : [];
153
+ const note = asString(raw.note) ?? undefined;
154
+ const card = assemble({ variant, name, commandLine, input, facts, note });
155
+ // A stored card that was already capped must keep its recorded `inputHidden`; the
156
+ // re-cap above can only ever add to it.
157
+ const stored = raw.inputHidden;
158
+ const storedHidden = typeof stored === 'number' && Number.isInteger(stored) && stored > 0
159
+ ? Math.min(stored, 10_000_000) : 0;
160
+ return { ...card, inputHidden: Math.max(card.inputHidden, storedHidden) };
161
+ }
162
+ /**
163
+ * Emergency kill switch (`ARAGON_NO_INVOCATION_CARDS=1`): every command and the App fall
164
+ * back to the pre-card behaviour. Read at call time, not import time, so a test (or a
165
+ * wrapper script) that sets the variable after load is honoured.
166
+ */
167
+ export function invocationCardsEnabled(env = process.env) {
168
+ return env.ARAGON_NO_INVOCATION_CARDS !== '1';
169
+ }
@@ -12,5 +12,12 @@ export declare const PLAN_MODE_BLOCK_VERSION = "v3-2026-10-26";
12
12
  export interface PlanModeBlockOptions {
13
13
  /** False under `-p` / piped stdin: no overlay can be rendered, so no tools. */
14
14
  interactive: boolean;
15
+ /**
16
+ * PLAN+GOVERN's interview addendum (governance-mode 6.3). Appended INSIDE the block,
17
+ * just before the closing tag, and only for an interactive block: a governed team needs
18
+ * a human to interview. Absent leaves the block byte-identical, so
19
+ * `PLAN_MODE_BLOCK_VERSION` does not move.
20
+ */
21
+ governAddendum?: string;
15
22
  }
16
23
  export declare function buildPlanModeBlock(options: PlanModeBlockOptions): string;
@@ -42,7 +42,7 @@ const HEADLESS_PARAGRAPH = [
42
42
  ' write the finished plan as your final message in markdown.',
43
43
  ].join('\n');
44
44
  export function buildPlanModeBlock(options) {
45
- const { interactive } = options;
45
+ const { interactive, governAddendum } = options;
46
46
  // NO ROUND COUNT APPEARS HERE, and none may be reintroduced: the number used
47
47
  // to be substituted from `planModeMaxAskRounds`, which meant the prompt itself
48
48
  // told the model it would run out. "3-5 questions" was also wrong against a
@@ -95,6 +95,7 @@ export function buildPlanModeBlock(options) {
95
95
  '- Never invent a path. Cite files you have read.',
96
96
  ...verdictRule,
97
97
  interactive ? SUPERSEDE_PARAGRAPH : HEADLESS_PARAGRAPH,
98
+ ...(interactive && governAddendum ? ['', governAddendum] : []),
98
99
  '</plan_mode>',
99
100
  ].join('\n');
100
101
  }
@@ -1,3 +1,4 @@
1
+ import type { InvocationCardInput } from './invocation-card.js';
1
2
  /** Explicit TUI task boundary; omission retains legacy programmatic behavior. */
2
3
  export interface PromptOptions {
3
4
  todoPolicy?: 'new-task' | 'continue';
@@ -5,6 +6,11 @@ export interface PromptOptions {
5
6
  /** History recording is independent of whether this message continues a plan. */
6
7
  export interface SubmitMessageOptions extends PromptOptions {
7
8
  userInitiated?: boolean;
9
+ /**
10
+ * Raw material for the transcript card. `submitMessage` builds the card exactly once
11
+ * (redact -> normalise -> limit); the payload sent to the model is unaffected.
12
+ */
13
+ invocation?: InvocationCardInput;
8
14
  }
9
15
  /** Startup outcome only; engine events still describe the result of the run. */
10
16
  export type PromptOutcome = {
@@ -1,8 +1,11 @@
1
1
  import type { Entry } from './reducer.js';
2
+ import type { InvocationCard } from './invocation-card.js';
2
3
  /** Full user text retained until Core acknowledges its opaque ID. */
3
4
  export interface PendingSteering {
4
5
  readonly queueId: string;
5
6
  readonly text: string;
7
+ /** Carried so the `queued -> user` rewrite keeps the card (tui-invocation-cards). */
8
+ readonly invocation?: InvocationCard;
6
9
  }
7
10
  /** Hide only active pending entries, using the same projection for measurement and paint. */
8
11
  export declare function selectTranscriptEntries(entries: Entry[], pending: readonly PendingSteering[]): Entry[];
@@ -13,7 +13,10 @@ export function reconcileSteeringReceipt(options) {
13
13
  if (!ids.size)
14
14
  return { entries: options.entries, pending: [...options.pending], changed: false };
15
15
  const entries = mergePendingEntries(options.entries, options.pending).map((entry) => entry.kind === 'queued' && ids.has(entry.queueId ?? '')
16
- ? { id: entry.id, kind: 'user', text: entry.text } : entry);
16
+ ? {
17
+ id: entry.id, kind: 'user', text: entry.text,
18
+ ...(entry.invocation ? { invocation: entry.invocation } : {}),
19
+ } : entry);
17
20
  return { entries, pending: options.pending.filter((item) => !ids.has(item.queueId)), changed: true };
18
21
  }
19
22
  /** Supplement trimmed/cleared history for save and exit without deduplicating text. */
@@ -11,6 +11,7 @@
11
11
  * synthesizes a generic error notice (a swallowed throw is never a no-op).
12
12
  */
13
13
  import type { PendingSteering } from './queued-messages.js';
14
+ import type { InvocationCard } from './invocation-card.js';
14
15
  import type { AgentEvent, ModelCost, ToolResult, TokenUsage } from '@aragon-agent/core';
15
16
  import type { AgentMode } from './agent-mode.js';
16
17
  import type { DispatchOutcome, SubagentRun, SubagentSpec, TeamSnapshot } from '../team/types.js';
@@ -21,9 +22,11 @@ import type { JobKind, ServiceSnapshot, ServiceStatus } from '../proc/types.js';
21
22
  import type { RetryPhase } from './retry-view.js';
22
23
  import type { FilePatch } from '../tools/patch.js';
23
24
  import type { TaskSummaryView } from '../fast/task-summary-types.js';
25
+ import type { GovernanceCardData, GovernanceSnapshot } from '../governance/types.js';
26
+ import type { Posture } from '../governance/posture.js';
24
27
  export type NoticeLevel = 'info' | 'warn' | 'error';
25
28
  export type ToastLevel = NoticeLevel | 'success';
26
- export type Overlay = null | 'settings' | 'model' | 'help' | 'queue' | 'confirm' | 'question' | 'plan' | 'kit' | 'prompts' | 'prompt-pick' | 'agents'
29
+ export type Overlay = null | 'settings' | 'model' | 'help' | 'queue' | 'confirm' | 'question' | 'plan' | 'kit' | 'prompts' | 'prompt-pick' | 'agents' | 'governance'
27
30
  /**
28
31
  * tui-shortcut-drawer-and-task-summary §4.5: the keyboard drawer (`Ctrl+G` /
29
32
  * `/keys`). A single field, so the drawer and `/help` cannot both hold the
@@ -45,6 +48,7 @@ export type Entry = {
45
48
  id: string;
46
49
  kind: 'user';
47
50
  text: string;
51
+ invocation?: InvocationCard;
48
52
  }
49
53
  /**
50
54
  * A message submitted while a run was in flight (tui-shift-enter-copy-queue
@@ -61,6 +65,7 @@ export type Entry = {
61
65
  kind: 'queued';
62
66
  text: string;
63
67
  queueId?: string;
68
+ invocation?: InvocationCard;
64
69
  } | {
65
70
  id: string;
66
71
  kind: 'assistant';
@@ -140,6 +145,11 @@ export type Entry = {
140
145
  kind: 'notice';
141
146
  level: NoticeLevel;
142
147
  text: string;
148
+ } | {
149
+ id: string;
150
+ kind: 'governance';
151
+ at: number;
152
+ card: GovernanceCardData;
143
153
  } | {
144
154
  id: string;
145
155
  kind: 'team';
@@ -372,6 +382,20 @@ export interface ViewState {
372
382
  agentMode: AgentMode;
373
383
  /** Set only by a DEFERRED `plan -> build`; drives the `PLAN -> BUILD` readout. */
374
384
  pendingAgentMode: AgentMode | null;
385
+ /**
386
+ * The user-visible posture and its deferred twin (governance-mode 4.3). Written in the
387
+ * SAME dispatch as `agentMode` / `pendingAgentMode` and only from what the controller
388
+ * adopted, so the badge and the gate cannot diverge. `agentMode` keeps gating.
389
+ */
390
+ posture: Posture;
391
+ pendingPosture: Posture | null;
392
+ /**
393
+ * The live governed-run snapshot, or `null` when there is no run.
394
+ *
395
+ * NEVER PERSISTED, like `team`: `/save` writes `entries` (the settlement cards are the
396
+ * history) and a resumed session has no live run to show.
397
+ */
398
+ governance: GovernanceSnapshot | null;
375
399
  /**
376
400
  * The live team roster, or `null` when no dispatch is running.
377
401
  *
@@ -486,6 +510,7 @@ export declare function initialViewState(seed?: ViewStateSeed): ViewState;
486
510
  export type ViewAction = {
487
511
  type: 'submit';
488
512
  text: string;
513
+ invocation?: InvocationCard;
489
514
  }
490
515
  /**
491
516
  * `controller.steer` was accepted; append the persistent queue marker
@@ -496,6 +521,7 @@ export type ViewAction = {
496
521
  type: 'steerQueued';
497
522
  queueId: string;
498
523
  text: string;
524
+ invocation?: InvocationCard;
499
525
  } | {
500
526
  type: 'steeringAccepted';
501
527
  ids: readonly string[];
@@ -622,6 +648,17 @@ export type ViewAction = {
622
648
  type: 'setAgentMode';
623
649
  mode: AgentMode;
624
650
  pending: AgentMode | null;
651
+ posture?: Posture;
652
+ pendingPosture?: Posture | null;
653
+ } | {
654
+ type: 'govSnapshot';
655
+ snapshot: GovernanceSnapshot;
656
+ } | {
657
+ type: 'govEnd';
658
+ } | {
659
+ type: 'governanceCard';
660
+ card: GovernanceCardData;
661
+ at?: number;
625
662
  } | {
626
663
  type: 'teamStart';
627
664
  dispatchId: string;
@@ -15,6 +15,8 @@ import { reconcileSteeringReceipt } from './queued-messages.js';
15
15
  import { emptyContextUsage } from '../compaction/meter.js';
16
16
  import { addUsage, computeCost } from './usage.js';
17
17
  import { ENTRY_LIMITS, appendBounded, entryRetain, trimEntries } from './entry-limits.js';
18
+ import { isGovernedDispatch } from '../governance/governed-dispatch.js';
19
+ import { adoptPosture, foldGovernanceSnapshot } from './governance-view.js';
18
20
  /** Default auto-dismiss window for a toast. */
19
21
  export const DEFAULT_TOAST_TTL_MS = 2500;
20
22
  /**
@@ -62,6 +64,9 @@ export function initialViewState(seed = {}) {
62
64
  // `controller.getAgentMode()` so `--plan` renders on the first frame.
63
65
  agentMode: 'build',
64
66
  pendingAgentMode: null,
67
+ posture: 'build',
68
+ pendingPosture: null,
69
+ governance: null,
65
70
  team: null,
66
71
  todos: null,
67
72
  retry: null,
@@ -479,7 +484,10 @@ export function viewReducer(state, action) {
479
484
  switch (action.type) {
480
485
  case 'submit': {
481
486
  const { id, seq } = nextId(state);
482
- const entry = { id, kind: 'user', text: action.text };
487
+ const entry = {
488
+ id, kind: 'user', text: action.text,
489
+ ...(action.invocation ? { invocation: action.invocation } : {}),
490
+ };
483
491
  return {
484
492
  ...state,
485
493
  seq,
@@ -502,7 +510,10 @@ export function viewReducer(state, action) {
502
510
  if (state.seenSteeringIds.has(action.queueId))
503
511
  return state;
504
512
  const { id, seq } = nextId(state);
505
- const entry = { id, kind: 'queued', queueId: action.queueId, text: action.text };
513
+ const entry = {
514
+ id, kind: 'queued', queueId: action.queueId, text: action.text,
515
+ ...(action.invocation ? { invocation: action.invocation } : {}),
516
+ };
506
517
  // NOTHING ELSE RESETS. This is not a new turn -- `turnProduced`,
507
518
  // `errorNoticed` and `aborted` describe the run that is still in
508
519
  // flight, and clearing them here would let the next `runEnd` failure
@@ -510,7 +521,10 @@ export function viewReducer(state, action) {
510
521
  return {
511
522
  ...state, seq, ...appendEntry(state, state.entries, entry),
512
523
  seenSteeringIds: new Set([...state.seenSteeringIds, action.queueId]),
513
- pendingSteering: [...state.pendingSteering, { queueId: action.queueId, text: action.text }],
524
+ pendingSteering: [...state.pendingSteering, {
525
+ queueId: action.queueId, text: action.text,
526
+ ...(action.invocation ? { invocation: action.invocation } : {}),
527
+ }],
514
528
  };
515
529
  }
516
530
  case 'steeringAccepted': {
@@ -904,6 +918,9 @@ export function viewReducer(state, action) {
904
918
  // those three.
905
919
  status: 'idle',
906
920
  team: null,
921
+ // The live run snapshot goes with the conversation it was shown in (the run itself
922
+ // survives on disk and announces itself again on its next event).
923
+ governance: null,
907
924
  fastEntryId: undefined,
908
925
  turnProduced: false,
909
926
  errorNoticed: false,
@@ -931,7 +948,24 @@ export function viewReducer(state, action) {
931
948
  // `controller.setAgentMode()` reported it ADOPTED, so this reducer has no
932
949
  // deferral logic of its own to get out of step with (§3.1 / R-P7).
933
950
  case 'setAgentMode':
934
- return { ...state, agentMode: action.mode, pendingAgentMode: action.pending };
951
+ return {
952
+ ...state,
953
+ agentMode: action.mode,
954
+ pendingAgentMode: action.pending,
955
+ ...adoptPosture(state.posture, action),
956
+ };
957
+ // --- Governance (governance-mode 14.4) -------------------------------------
958
+ case 'govSnapshot': {
959
+ const governance = foldGovernanceSnapshot(state.governance, action.snapshot);
960
+ return governance === state.governance ? state : { ...state, governance };
961
+ }
962
+ case 'govEnd':
963
+ return state.governance === null ? state : { ...state, governance: null };
964
+ case 'governanceCard': {
965
+ const { id, seq } = nextId(state);
966
+ const entry = { id, kind: 'governance', at: action.at ?? Date.now(), card: action.card };
967
+ return { ...state, seq, ...appendEntry(state, state.entries, entry) };
968
+ }
935
969
  case 'toggleThinking':
936
970
  return { ...state, thinkingVisible: !state.thinkingVisible };
937
971
  case 'pushToast': {
@@ -976,19 +1010,26 @@ export function viewReducer(state, action) {
976
1010
  aborted: false,
977
1011
  active: true,
978
1012
  };
1013
+ const team = {
1014
+ dispatchId: action.dispatchId,
1015
+ active: true,
1016
+ runs,
1017
+ requested: action.requested,
1018
+ startedAt: Date.now(),
1019
+ messageCount: 0,
1020
+ };
1021
+ // A GOVERNED DISPATCH LEAVES NO TRANSCRIPT CARD (RV-3): a run is 10-20 dispatches,
1022
+ // and a `TeamCard` for each would bury the conversation. The live snapshot is still
1023
+ // written - the legion panel and `/agents` read it - and with no `teamEntryId`,
1024
+ // `teamUpdate` / `teamEnd` already pass `entries` through untouched.
1025
+ if (isGovernedDispatch(action.specs))
1026
+ return { ...state, team };
979
1027
  return {
980
1028
  ...state,
981
1029
  seq,
982
1030
  ...appendEntry(state, state.entries, entry),
983
1031
  teamEntryId: id,
984
- team: {
985
- dispatchId: action.dispatchId,
986
- active: true,
987
- runs,
988
- requested: action.requested,
989
- startedAt: Date.now(),
990
- messageCount: 0,
991
- },
1032
+ team,
992
1033
  };
993
1034
  }
994
1035
  case 'teamUpdate': {
@@ -1476,6 +1517,10 @@ export function viewReducer(state, action) {
1476
1517
  // restored entries BEFORE this branch runs.
1477
1518
  taskSummary: null,
1478
1519
  taskSummaryPending: false,
1520
+ // Same rule for a live governed run: `/resume` adopts another conversation, and
1521
+ // a snapshot of the old one's run must not be drawn over it. Settlement cards
1522
+ // arrive with the restored `entries`.
1523
+ governance: null,
1479
1524
  };
1480
1525
  }
1481
1526
  // --- Background services (§3.10 / D-11) -----------------------------
@@ -159,6 +159,25 @@ export interface SystemPromptParams {
159
159
  * own appended instructions still come last and still win.
160
160
  */
161
161
  structuredResultBlock?: string;
162
+ /**
163
+ * The rendered `<governance_mode>` block (the Orchestrator's rules), or `''`
164
+ * (governance-mode 6.1). Spliced AFTER the plan block and BEFORE `teamBlock`, and
165
+ * conditional like every block above: governance off produces the pre-feature prompt
166
+ * BYTE FOR BYTE. The caller omits `teamBlock` / `forcedTeamBlock` in GOVERN with gate
167
+ * mode `build`, because `task` is refused there and advertising it would describe a
168
+ * refused capability.
169
+ */
170
+ governBlock?: string;
171
+ /**
172
+ * The rendered `<governance_role>` block of a governed CHILD, or `''`. Spliced after
173
+ * `subagentBlock`. Never set together with `governBlock`.
174
+ */
175
+ governanceRoleBlock?: string;
176
+ /**
177
+ * PLAN+GOVERN's interview addendum (governance-mode 6.3), placed inside `<plan_mode>`.
178
+ * Only read when `agentMode` is `'plan'`; absent leaves the plan block unchanged.
179
+ */
180
+ governAddendum?: string;
162
181
  }
163
182
  /** Describe the active shell so the model does not emit incompatible syntax. */
164
183
  export declare function describeShell(): {
@@ -33,7 +33,10 @@ export function buildSystemPrompt(params) {
33
33
  const skillsBlock = params.skillsBlock ?? '';
34
34
  const hasSkills = skillsBlock.length > 0;
35
35
  const planBlock = params.agentMode === 'plan'
36
- ? buildPlanModeBlock({ interactive: params.planInteractive !== false })
36
+ ? buildPlanModeBlock({
37
+ interactive: params.planInteractive !== false,
38
+ ...(params.governAddendum ? { governAddendum: params.governAddendum } : {}),
39
+ })
37
40
  : '';
38
41
  // `''` when the caller has no candidates or the feature is off — which is what
39
42
  // keeps this block's absence byte-identical to the pre-feature prompt (I-T1).
@@ -41,6 +44,8 @@ export function buildSystemPrompt(params) {
41
44
  const teamBlock = params.teamBlock ?? '';
42
45
  const forcedTeamBlock = params.forcedTeamBlock ?? '';
43
46
  const subagentBlock = params.subagentBlock ?? '';
47
+ const governBlock = params.governBlock ?? '';
48
+ const governanceRoleBlock = params.governanceRoleBlock ?? '';
44
49
  const todoBlock = params.todoBlock ?? '';
45
50
  const fastBlock = params.fastBlock ?? '';
46
51
  const backgroundBlock = params.backgroundBlock ?? '';
@@ -66,12 +71,15 @@ export function buildSystemPrompt(params) {
66
71
  // (whose step 1 defers to it). Conditional like every other optional block.
67
72
  ...(skillTriageBlock ? ['', skillTriageBlock] : []),
68
73
  ...(planBlock ? ['', planBlock] : []),
74
+ // AFTER the plan block it extends and BEFORE `<team_mode>` (governance-mode 6.1).
75
+ ...(governBlock ? ['', governBlock] : []),
69
76
  ...(teamBlock ? ['', teamBlock] : []),
70
77
  // IMMEDIATELY AFTER `<team_mode>`: "can" then "must". BEFORE the subagent
71
78
  // block, because a child reading an order to dispatch would be ordered to use
72
79
  // a tool it does not have (§3.6).
73
80
  ...(forcedTeamBlock ? ['', forcedTeamBlock] : []),
74
81
  ...(subagentBlock ? ['', subagentBlock] : []),
82
+ ...(governanceRoleBlock ? ['', governanceRoleBlock] : []),
75
83
  ...(todoBlock ? ['', todoBlock] : []),
76
84
  ...(fastBlock ? ['', fastBlock] : []),
77
85
  ...(backgroundBlock ? ['', backgroundBlock] : []),
package/dist/cli.js CHANGED
@@ -25,7 +25,8 @@ import { formatMigrationNotice, migrateLegacyState } from './config/migrate-lega
25
25
  import { formatHomeMigrationNotice, migrateToHome } from './config/migrate-home.js';
26
26
  import { migrateStateOutOfConfig } from './config/migrate-state-out-of-config.js';
27
27
  import { runHistoryCommand } from './config/history-commands.js';
28
- import { COMPACTION_CONFIG_SET_KEYS, FAST_CONFIG_SET_KEYS, LOG_CONFIG_SET_KEYS, RETRY_CONFIG_SET_KEYS, TEAM_CONFIG_SET_KEYS, JOBS_CONFIG_SET_KEYS, TASK_SUMMARY_CONFIG_SET_KEYS, UPDATE_CONFIG_SET_KEYS, applyCompactionConfigSet, applyFastConfigSet, applyLogConfigSet, applyRetryConfigSet, applyTeamConfigSet, applyJobsConfigSet, applyTaskSummaryConfigSet, applyUpdateConfigSet, runConfigEdit, runConfigGet, runConfigHome, runConfigList, warnIfRedactionDisabled, } from './config/cli-commands.js';
28
+ import { checkGovernanceCommandLine } from './config/governance-launch.js';
29
+ import { COMPACTION_CONFIG_SET_KEYS, FAST_CONFIG_SET_KEYS, LOG_CONFIG_SET_KEYS, RETRY_CONFIG_SET_KEYS, TEAM_CONFIG_SET_KEYS, GOVERNANCE_CONFIG_SET_KEYS, JOBS_CONFIG_SET_KEYS, TASK_SUMMARY_CONFIG_SET_KEYS, UPDATE_CONFIG_SET_KEYS, applyCompactionConfigSet, applyFastConfigSet, applyLogConfigSet, applyRetryConfigSet, applyTeamConfigSet, applyGovernanceConfigSet, applyJobsConfigSet, applyTaskSummaryConfigSet, applyUpdateConfigSet, runConfigEdit, runConfigGet, runConfigHome, runConfigList, warnIfRedactionDisabled, } from './config/cli-commands.js';
29
30
  import { attachAgentEvents, attachTeamEvents, installLogging, setScreenRestore, setSignalTerminator, } from './logging/install.js';
30
31
  import { getLogger } from './logging/logger.js';
31
32
  import { runLogsCommand } from './logging/cli-commands.js';
@@ -230,6 +231,12 @@ function toFlags(opts) {
230
231
  // reports nothing, does nothing, and leaves compaction armed.
231
232
  compaction: opts.compaction,
232
233
  compactionThreshold: opts.compactionThreshold,
234
+ // Site three of three for the governance flags. `--no-governance` is declared alone, so
235
+ // commander defaults `opts.governance` to `true` when it is absent; only an explicit
236
+ // `false` is forwarded, which keeps silence from reading as an override.
237
+ govern: opts.govern,
238
+ planGovern: opts.planGovern,
239
+ governance: opts.governance === false ? false : undefined,
233
240
  // Site three of three for `--resume` / `--save` -- ELEVEN features, one trap
234
241
  // (`mouse` P1-2, `team` P1-4, `todo` C-9, render L4, retry, fast, update,
235
242
  // compaction, now session identity). A flag needs the `.option()`
@@ -1599,6 +1606,11 @@ const CONFIG_SET_KEYS = new Set([
1599
1606
  // membership set accepts a write that never happens - the two halves the static
1600
1607
  // guard in `config.test.ts` exists to pair up.
1601
1608
  ...TASK_SUMMARY_CONFIG_SET_KEYS,
1609
+ // `governance.*` keys are handled by `applyGovernanceConfigSet`, same reasoning again
1610
+ // (governance-mode 11.1): without this line they fall through to `Unknown config key`,
1611
+ // and without the matching case in `runConfigSet` the membership set accepts a write
1612
+ // that never happens.
1613
+ ...GOVERNANCE_CONFIG_SET_KEYS,
1602
1614
  ]);
1603
1615
  /**
1604
1616
  * `aragon config set maxTokens <n|auto|default>`.
@@ -1771,6 +1783,15 @@ function runConfigSetValue(key, value) {
1771
1783
  process.stdout.write(`Set ${key} = ${String(merged.taskSummary[field])}\n`);
1772
1784
  return;
1773
1785
  }
1786
+ const governancePatch = applyGovernanceConfigSet(key, value);
1787
+ if (governancePatch) {
1788
+ // Echoed from the STORED value, like every section above: `config set
1789
+ // governance.maxRework 9` prints 5, which is what the read path will report.
1790
+ const merged = updatePersistedConfig(governancePatch);
1791
+ const field = key.slice('governance.'.length);
1792
+ process.stdout.write(`Set ${key} = ${String(merged.governance[field])}\n`);
1793
+ return;
1794
+ }
1774
1795
  const patch = {};
1775
1796
  switch (key) {
1776
1797
  case 'provider':
@@ -2188,6 +2209,13 @@ function buildProgram() {
2188
2209
  .option('--compaction', 'Compact the conversation when the context window fills (the default)')
2189
2210
  .option('--no-compaction', 'Never compact; a full context window ends the run as before')
2190
2211
  .option('--compaction-threshold <n>', 'Occupancy that triggers compaction for this run: 0.9 or 90% (0.5-0.95)')
2212
+ // Governance mode (governance-mode 11.2). `--govern` and `--plan-govern` only pick the
2213
+ // starting posture, so neither is a persisted pair. `--no-governance` is deliberately
2214
+ // negative-only: it can only ever switch the feature off, and `toFlags` forwards it only
2215
+ // when explicitly given. The rules between them live in `validateGovernanceLaunch`.
2216
+ .option('--govern', 'Start in GOVERN posture (a governed team of dev / review / test roles)')
2217
+ .option('--plan-govern', 'Start in PLAN+GOVERN posture (clarify and plan first, then govern)')
2218
+ .option('--no-governance', 'Disable governance mode for this launch (not persisted)')
2191
2219
  .option('--no-skills', 'Disable the skill system entirely (no catalog, no skill tools)')
2192
2220
  .option('--skill <name>', 'Force-load a skill at Level 2 for this run (repeatable)', (value, previous = []) => [...previous, value])
2193
2221
  .option('--skills-yes', 'Approve skill installs for this run (CI / headless)')
@@ -2251,6 +2279,22 @@ function buildProgram() {
2251
2279
  }
2252
2280
  void runInteractive(flags, { initialPrompt: prompt });
2253
2281
  });
2282
+ // The governance flag rules (governance-mode 11.2), checked for EVERY command because the
2283
+ // flags are root-level options and `exec` reads them through `toFlags(program.opts())`.
2284
+ // A `preAction` hook runs before the root action as well as before each subcommand, so one
2285
+ // place covers `aragon -p`, piped input and `aragon exec`.
2286
+ program.hook('preAction', (root, action) => {
2287
+ const check = checkGovernanceCommandLine(root.opts(), {
2288
+ commandName: action.name(),
2289
+ isRootAction: action === root,
2290
+ stdinIsTty: !!process.stdin.isTTY,
2291
+ stdoutIsTty: !!process.stdout.isTTY,
2292
+ });
2293
+ if (check.ok)
2294
+ return;
2295
+ process.stderr.write(`${check.message}\n`);
2296
+ process.exit(2);
2297
+ });
2254
2298
  // aragon config [set <key> <value> | path]
2255
2299
  const configCmd = program
2256
2300
  .command('config')