@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,292 @@
1
+ /**
2
+ * `/gov`: enter, leave and inspect governance mode (governance-mode 11.3).
3
+ *
4
+ * ASCII ONLY: `src/commands/**` is inside `glyphs.test.ts::inScope`.
5
+ *
6
+ * THE GUARD ORDER IS A CONTRACT, and it is the order `/team` uses (and for the same
7
+ * reasons), not an alphabetical one:
8
+ *
9
+ * status / help / runs / report / board
10
+ * - questions, answerable in ANY session, including the
11
+ * ones where governance cannot run (that is where users
12
+ * ask why). `report` only READS files already on disk.
13
+ * off - leaving is always possible, even mid-stage.
14
+ * available? - `on` / `plan` / `resume` / `pause` name the exact reason.
15
+ * not busy - half a team dispatch under one posture and half under
16
+ * another leaves a result nothing can explain.
17
+ * act - switch posture / submit the resume turn / abort.
18
+ *
19
+ * `pause` sits BETWEEN `available?` and `not busy` and never takes the busy refusal: a stage
20
+ * running is exactly when someone wants to pause, and the request is cooperative (nothing is
21
+ * stopped), unlike `abort`.
22
+ *
23
+ * EVERY POSTURE CHANGE BELOW IS FOLLOWED BY A NOTICE OR TOAST IN THE SAME BRANCH: a
24
+ * posture change produces no `AgentEvent`, so without one the badge would wait for an
25
+ * unrelated state change to repaint (the lesson of team-command-forced-dispatch P1-6).
26
+ */
27
+ import { GOV_ABORTED_NOTICE, GOV_BUDGET_OFF_NOTICE, GOV_BUDGET_USAGE, GOV_COMMAND_SUMMARY, GOV_NO_RUN_NOTICE, GOV_NO_RUNS_NOTICE, GOV_NO_STAGE_NOTICE, GOV_OFF_NOTICE, GOV_OFF_WITH_RUN_NOTICE, GOV_PAUSE_ALREADY_NOTICE, GOV_PAUSE_CANCELLED_TOAST, GOV_PAUSE_ESCALATED_NOTICE, GOV_PAUSE_NONE_PENDING_NOTICE, GOV_PAUSE_NO_RUN_NOTICE, GOV_REPORT_NO_RUN_NOTICE, GOV_STAGE_BUSY_NOTICE, buildGovHelpText, formatGovStatus, govBudgetSetNotice, govPausePendingToast, govPausedNowToast, govReportBadIdNotice, govReportMissingNotice, govUnavailableNotice, posturePendingToast, postureAppliedToast, stageRunningToast, RESTART_REQUIRED_TEXT, } from '../governance/copy.js';
28
+ import { RUN_ID_PATTERN } from '../governance/limits.js';
29
+ import { isGovernancePosture } from '../governance/posture.js';
30
+ import { formatReportNotice, formatRunsTable } from './governance-runs-table.js';
31
+ const USAGE = 'Usage: /gov [status|on|plan|off|board|runs|report [runId]|resume [runId]|pause [cancel]' +
32
+ '|abort|budget <n|off>|help]';
33
+ /** The fixed English turn that makes the orchestrator call `gov_run resume` (11.3). */
34
+ export function resumeTurnText(runId) {
35
+ return `Resume governance run ${runId}.`;
36
+ }
37
+ function unavailableText(ctx) {
38
+ const availability = ctx.controller.isGovernanceAvailable();
39
+ if (availability.ok)
40
+ return null;
41
+ return availability.reason === 'restart_required'
42
+ ? RESTART_REQUIRED_TEXT
43
+ : govUnavailableNotice(availability.reason);
44
+ }
45
+ function reportStatus(ctx) {
46
+ const snapshot = ctx.controller.getGovernanceSnapshot();
47
+ ctx.notify('info', formatGovStatus({
48
+ posture: ctx.controller.getPosture(),
49
+ availability: ctx.controller.isGovernanceAvailable(),
50
+ run: snapshot
51
+ ? {
52
+ runId: snapshot.runId,
53
+ phase: snapshot.phase,
54
+ phaseCount: snapshot.phaseCount,
55
+ stage: snapshot.stage,
56
+ status: snapshot.status,
57
+ health: snapshot.health,
58
+ pauseRequested: snapshot.pauseRequested === true,
59
+ }
60
+ : null,
61
+ dispatchPlan: snapshot ? ctx.controller.getGovernanceDispatchPlan() : null,
62
+ }));
63
+ }
64
+ async function reportRuns(ctx) {
65
+ const runs = await ctx.controller.listGovernanceRuns();
66
+ if (runs.length === 0) {
67
+ ctx.notify('info', GOV_NO_RUNS_NOTICE);
68
+ return;
69
+ }
70
+ // Two columns less than the terminal: the transcript indents notices.
71
+ ctx.notify('info', formatRunsTable(runs, (process.stdout.columns ?? 80) - 2));
72
+ }
73
+ /** `/gov report [runId]`: a stored report as one transcript notice (read-only). */
74
+ async function report(ctx) {
75
+ const raw = ctx.args.trim().split(/\s+/)[1] ?? '';
76
+ // Checked HERE as well as in the reader: a malformed id never reaches the controller,
77
+ // let alone the file system (it would become a path segment).
78
+ if (raw !== '' && !RUN_ID_PATTERN.test(raw)) {
79
+ ctx.notify('warn', govReportBadIdNotice(raw));
80
+ return;
81
+ }
82
+ const result = await ctx.controller.readGovernanceReport(raw === '' ? undefined : raw);
83
+ switch (result.kind) {
84
+ case 'ok':
85
+ ctx.notify('info', formatReportNotice(result));
86
+ return;
87
+ case 'missing':
88
+ ctx.notify('info', govReportMissingNotice(result.runId));
89
+ return;
90
+ case 'bad_id':
91
+ ctx.notify('warn', govReportBadIdNotice(raw));
92
+ return;
93
+ case 'no_run':
94
+ ctx.notify('info', GOV_REPORT_NO_RUN_NOTICE);
95
+ }
96
+ }
97
+ /** `/gov pause [cancel]`: stop at the next clean stage boundary; nothing is killed. */
98
+ async function pause(ctx) {
99
+ const unavailable = unavailableText(ctx);
100
+ if (unavailable !== null) {
101
+ ctx.notify('warn', unavailable);
102
+ return;
103
+ }
104
+ const arg = (ctx.args.trim().split(/\s+/)[1] ?? '').toLowerCase();
105
+ if (arg === 'cancel') {
106
+ if (ctx.controller.cancelGovernancePause())
107
+ ctx.toast('info', GOV_PAUSE_CANCELLED_TOAST);
108
+ else
109
+ ctx.notify('info', GOV_PAUSE_NONE_PENDING_NOTICE);
110
+ return;
111
+ }
112
+ if (arg !== '') {
113
+ ctx.notify('warn', `Unknown argument "${arg}". Usage: /gov pause [cancel]`);
114
+ return;
115
+ }
116
+ const outcome = await ctx.controller.requestGovernancePause();
117
+ const snapshot = ctx.controller.getGovernanceSnapshot();
118
+ switch (outcome) {
119
+ case 'pending':
120
+ ctx.toast('info', govPausePendingToast(snapshot?.stage ?? null));
121
+ return;
122
+ case 'paused_now':
123
+ ctx.toast('info', govPausedNowToast(snapshot?.runId ?? ''));
124
+ return;
125
+ case 'already_paused':
126
+ ctx.notify('info', GOV_PAUSE_ALREADY_NOTICE);
127
+ return;
128
+ case 'escalated':
129
+ ctx.notify('info', GOV_PAUSE_ESCALATED_NOTICE);
130
+ return;
131
+ case 'no_run':
132
+ ctx.notify('info', GOV_PAUSE_NO_RUN_NOTICE);
133
+ }
134
+ }
135
+ /** Switch posture through the App's single write path and report what was ADOPTED. */
136
+ function switchPosture(ctx, target) {
137
+ if (ctx.applyPosture === undefined) {
138
+ ctx.notify('warn', 'Governance is unavailable here: this surface cannot change posture.');
139
+ return null;
140
+ }
141
+ const applied = ctx.applyPosture(target);
142
+ if (applied.pending !== null) {
143
+ ctx.toast('info', ctx.controller.isGovernanceStageRunning()
144
+ ? stageRunningToast(applied.pending)
145
+ : posturePendingToast(applied.pending));
146
+ }
147
+ else {
148
+ ctx.toast('info', postureAppliedToast(applied.effective));
149
+ }
150
+ return applied;
151
+ }
152
+ function leave(ctx) {
153
+ const wasGoverned = isGovernancePosture(ctx.controller.getPosture());
154
+ const snapshot = ctx.controller.getGovernanceSnapshot();
155
+ if (!wasGoverned && ctx.controller.getPostureState().pending === null) {
156
+ ctx.notify('info', GOV_OFF_NOTICE);
157
+ return;
158
+ }
159
+ const applied = switchPosture(ctx, 'build');
160
+ // A deferred change (a stage is running) already said so in its toast.
161
+ if (applied === null || applied.pending !== null)
162
+ return;
163
+ // Leaving never kills an in-flight stage; the run is left resumable.
164
+ const hasRun = snapshot !== null && snapshot.status !== 'closed' && snapshot.status !== 'abandoned';
165
+ ctx.notify('info', hasRun ? GOV_OFF_WITH_RUN_NOTICE : GOV_OFF_NOTICE);
166
+ }
167
+ function enter(ctx, target) {
168
+ const unavailable = unavailableText(ctx);
169
+ if (unavailable !== null) {
170
+ ctx.notify('warn', unavailable);
171
+ return;
172
+ }
173
+ if (ctx.controller.isTeamBusy()) {
174
+ ctx.notify('warn', ctx.controller.isGovernanceStageRunning()
175
+ ? GOV_STAGE_BUSY_NOTICE
176
+ : 'Cannot change posture while a team dispatch is running.');
177
+ return;
178
+ }
179
+ switchPosture(ctx, target);
180
+ }
181
+ async function resume(ctx) {
182
+ const unavailable = unavailableText(ctx);
183
+ if (unavailable !== null) {
184
+ ctx.notify('warn', unavailable);
185
+ return;
186
+ }
187
+ if (ctx.controller.isTeamBusy()) {
188
+ ctx.notify('warn', GOV_STAGE_BUSY_NOTICE);
189
+ return;
190
+ }
191
+ const requested = ctx.args.trim().split(/\s+/)[1] ?? '';
192
+ const runId = await ctx.controller.findResumableGovernanceRun(requested);
193
+ if (runId === null) {
194
+ ctx.notify('info', GOV_NO_RUN_NOTICE);
195
+ return;
196
+ }
197
+ if (ctx.controller.getPosture() !== 'govern' && switchPosture(ctx, 'govern') === null)
198
+ return;
199
+ // `userInitiated: false` keeps the canned turn out of the prompt history, exactly like
200
+ // `/todo continue` (todo-plan-followthrough C-12).
201
+ ctx.submit(resumeTurnText(runId), { userInitiated: false });
202
+ }
203
+ function abort(ctx) {
204
+ if (!ctx.controller.isGovernanceStageRunning()) {
205
+ ctx.notify('info', GOV_NO_STAGE_NOTICE);
206
+ return;
207
+ }
208
+ ctx.controller.abortGovernanceStage();
209
+ ctx.notify('info', GOV_ABORTED_NOTICE);
210
+ }
211
+ /** `500`, `500k`, `2m`, `off`/`0`. NaN when unparseable. */
212
+ export function parseBudgetTokens(raw) {
213
+ const text = raw.trim().toLowerCase();
214
+ if (text === 'off' || text === '0')
215
+ return 0;
216
+ const match = /^(\d+(?:\.\d+)?)([km]?)$/.exec(text);
217
+ if (!match)
218
+ return Number.NaN;
219
+ const scale = match[2] === 'm' ? 1_000_000 : match[2] === 'k' ? 1_000 : 1;
220
+ return Math.floor(Number(match[1]) * scale);
221
+ }
222
+ function setBudget(ctx) {
223
+ const raw = ctx.args.trim().split(/\s+/)[1];
224
+ if (raw === undefined) {
225
+ ctx.notify('info', GOV_BUDGET_USAGE);
226
+ return;
227
+ }
228
+ const tokens = parseBudgetTokens(raw);
229
+ if (!Number.isFinite(tokens) || tokens < 0) {
230
+ ctx.notify('warn', GOV_BUDGET_USAGE);
231
+ return;
232
+ }
233
+ // BOTH CALLS, AND NEITHER MAY BE DROPPED (the `/fast` rule): the setter makes the running
234
+ // session honour it and re-renders the prompt; persisting alone changes nothing until
235
+ // relaunch, and the setter alone is forgotten by morning.
236
+ const adopted = ctx.controller.setGovernanceConfig({ budgetTokens: tokens });
237
+ ctx.persistConfig({ governance: adopted });
238
+ ctx.notify('info', adopted.budgetTokens === 0 ? GOV_BUDGET_OFF_NOTICE : govBudgetSetNotice(adopted.budgetTokens));
239
+ }
240
+ /** The command body; `registerGovernanceCommand` is the only registration site. */
241
+ export async function runGovernanceCommand(ctx) {
242
+ const verb = (ctx.args.trim().split(/\s+/)[0] ?? '').toLowerCase();
243
+ switch (verb) {
244
+ case '':
245
+ case 'status':
246
+ reportStatus(ctx);
247
+ return;
248
+ case 'help':
249
+ ctx.notify('info', buildGovHelpText(ctx.controller.getGovernanceConfig().minAskRounds));
250
+ return;
251
+ case 'runs':
252
+ await reportRuns(ctx);
253
+ return;
254
+ case 'report':
255
+ await report(ctx);
256
+ return;
257
+ case 'board':
258
+ ctx.setOverlay('governance');
259
+ return;
260
+ case 'off':
261
+ leave(ctx);
262
+ return;
263
+ case 'on':
264
+ enter(ctx, 'govern');
265
+ return;
266
+ case 'plan':
267
+ enter(ctx, 'plan_govern');
268
+ return;
269
+ case 'resume':
270
+ await resume(ctx);
271
+ return;
272
+ case 'pause':
273
+ await pause(ctx);
274
+ return;
275
+ case 'abort':
276
+ abort(ctx);
277
+ return;
278
+ case 'budget':
279
+ setBudget(ctx);
280
+ return;
281
+ default:
282
+ ctx.notify('warn', `Unknown argument "${verb}". ${USAGE}`);
283
+ }
284
+ }
285
+ /** Registered from `builtins.ts` with one line, next to every other subsystem. */
286
+ export function registerGovernanceCommand(registry) {
287
+ registry.register({
288
+ name: 'gov',
289
+ description: GOV_COMMAND_SUMMARY,
290
+ run: (ctx) => runGovernanceCommand(ctx),
291
+ });
292
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The `/gov runs` table and the `/gov report` notice text (governance-mode-v1-1 7.1, 7.2).
3
+ *
4
+ * ASCII ONLY: `src/commands/**` is inside `glyphs.test.ts::inScope`. Everything this module
5
+ * ADDS is ASCII (column rules, the `...` cut mark, `n/a`); the only non-ASCII that can pass
6
+ * through is the user's own goal text, which is measured and cut in terminal CELLS
7
+ * (`ui/layout/cells.ts`), never in code units, so a CJK goal cannot push a row past the edge.
8
+ *
9
+ * PURE. The command hands in rows and a width; nothing here reads the terminal or the clock
10
+ * except through arguments, which is what lets the width matrix be tested.
11
+ *
12
+ * THE COLUMNS DEGRADE BY WIDTH, in the order of how much each one is missed: first
13
+ * HEALTH and COST go (they are the cheapest to look up again in `/gov board`), then UPDATED
14
+ * and TOKENS, and at the narrowest only `RUN STATUS PHASE GOAL` remain. The goal gets
15
+ * whatever is left and is cut with `...`.
16
+ */
17
+ import type { GovernanceRunRow } from '../agent/governance-wiring.js';
18
+ interface Column {
19
+ key: 'run' | 'status' | 'phase' | 'health' | 'tokens' | 'cost' | 'updated' | 'updatedShort';
20
+ title: string;
21
+ width: number;
22
+ }
23
+ /** `184k`, `2.1M`, `950`. */
24
+ export declare function formatTokensCompact(tokens: number): string;
25
+ /** `$1.42`; unknown pricing is `n/a`, never `$0.00` (v1 8.5 / C-11). */
26
+ export declare function formatCostCell(costUsd: number | null): string;
27
+ /** `2026-10-24 14:02` in local time. */
28
+ export declare function formatStamp(ms: number): string;
29
+ /** One row of the table, already cut to `cols` cells. */
30
+ export declare function formatRunRow(row: GovernanceRunRow, keys: ReadonlyArray<Column['key']>, cols: number): string;
31
+ /** The whole `/gov runs` notice for `cols` terminal columns. */
32
+ export declare function formatRunsTable(runs: readonly GovernanceRunRow[], cols: number): string;
33
+ /** The transcript text for a found report: header, body capped by lines, full path. */
34
+ export declare function formatReportNotice(input: {
35
+ runId: string;
36
+ file: string;
37
+ text: string;
38
+ path: string;
39
+ }): string;
40
+ export {};
@@ -0,0 +1,130 @@
1
+ /**
2
+ * The `/gov runs` table and the `/gov report` notice text (governance-mode-v1-1 7.1, 7.2).
3
+ *
4
+ * ASCII ONLY: `src/commands/**` is inside `glyphs.test.ts::inScope`. Everything this module
5
+ * ADDS is ASCII (column rules, the `...` cut mark, `n/a`); the only non-ASCII that can pass
6
+ * through is the user's own goal text, which is measured and cut in terminal CELLS
7
+ * (`ui/layout/cells.ts`), never in code units, so a CJK goal cannot push a row past the edge.
8
+ *
9
+ * PURE. The command hands in rows and a width; nothing here reads the terminal or the clock
10
+ * except through arguments, which is what lets the width matrix be tested.
11
+ *
12
+ * THE COLUMNS DEGRADE BY WIDTH, in the order of how much each one is missed: first
13
+ * HEALTH and COST go (they are the cheapest to look up again in `/gov board`), then UPDATED
14
+ * and TOKENS, and at the narrowest only `RUN STATUS PHASE GOAL` remain. The goal gets
15
+ * whatever is left and is cut with `...`.
16
+ */
17
+ import { GOV_REPORT_MAX_LINES } from '../governance/copy.js';
18
+ import { cellWidth, fitCells, padCells } from '../ui/layout/cells.js';
19
+ const UNFINISHED = new Set(['executing', 'paused', 'escalated']);
20
+ const MIN_GOAL_CELLS = 8;
21
+ const GAP = ' ';
22
+ const COLUMNS = {
23
+ run: { key: 'run', title: 'RUN', width: 13 },
24
+ status: { key: 'status', title: 'STATUS', width: 9 },
25
+ phase: { key: 'phase', title: 'PHASE', width: 5 },
26
+ health: { key: 'health', title: 'HEALTH', width: 6 },
27
+ tokens: { key: 'tokens', title: 'TOKENS', width: 7 },
28
+ cost: { key: 'cost', title: 'COST', width: 8 },
29
+ updated: { key: 'updated', title: 'UPDATED', width: 16 },
30
+ // The same column without the year, for widths where the goal text is worth more.
31
+ updatedShort: { key: 'updatedShort', title: 'UPDATED', width: 11 },
32
+ };
33
+ /** Column sets from widest to narrowest; the first whose goal still gets room wins. */
34
+ const TIERS = [
35
+ ['run', 'status', 'phase', 'health', 'tokens', 'cost', 'updated'],
36
+ ['run', 'status', 'phase', 'tokens', 'updatedShort'],
37
+ ['run', 'status', 'phase', 'tokens'],
38
+ ['run', 'status', 'phase'],
39
+ ];
40
+ /** `184k`, `2.1M`, `950`. */
41
+ export function formatTokensCompact(tokens) {
42
+ if (tokens >= 1_000_000)
43
+ return `${(tokens / 1_000_000).toFixed(1)}M`;
44
+ if (tokens >= 10_000)
45
+ return `${Math.round(tokens / 1_000)}k`;
46
+ if (tokens >= 1_000)
47
+ return `${(tokens / 1_000).toFixed(1)}k`;
48
+ return String(Math.max(0, Math.round(tokens)));
49
+ }
50
+ /** `$1.42`; unknown pricing is `n/a`, never `$0.00` (v1 8.5 / C-11). */
51
+ export function formatCostCell(costUsd) {
52
+ if (costUsd === null)
53
+ return 'n/a';
54
+ return costUsd < 0.01 && costUsd > 0 ? '<$0.01' : `$${costUsd.toFixed(2)}`;
55
+ }
56
+ function two(n) {
57
+ return String(n).padStart(2, '0');
58
+ }
59
+ /** `2026-10-24 14:02` in local time. */
60
+ export function formatStamp(ms) {
61
+ const d = new Date(ms);
62
+ if (Number.isNaN(d.getTime()))
63
+ return '-';
64
+ const date = `${d.getFullYear()}-${two(d.getMonth() + 1)}-${two(d.getDate())}`;
65
+ return `${date} ${two(d.getHours())}:${two(d.getMinutes())}`;
66
+ }
67
+ function cellFor(key, row, digest) {
68
+ switch (key) {
69
+ case 'run':
70
+ return row.runId;
71
+ case 'status':
72
+ return row.status;
73
+ case 'phase':
74
+ return digest === undefined ? '-' : `${digest.phase}/${digest.phaseCount}`;
75
+ case 'health':
76
+ return digest?.health == null ? '-' : String(digest.health);
77
+ case 'tokens':
78
+ return digest === undefined ? '-' : formatTokensCompact(digest.tokens);
79
+ case 'cost':
80
+ return digest === undefined ? '-' : formatCostCell(digest.costUsd);
81
+ case 'updated':
82
+ return formatStamp(row.updatedAt);
83
+ case 'updatedShort': {
84
+ const stamp = formatStamp(row.updatedAt);
85
+ return stamp === '-' ? stamp : stamp.slice(5);
86
+ }
87
+ }
88
+ }
89
+ function prefixWidth(keys) {
90
+ const widths = keys.reduce((sum, key) => sum + COLUMNS[key].width, 0);
91
+ return 2 + widths + GAP.length * keys.length;
92
+ }
93
+ function pickTier(cols) {
94
+ const last = TIERS[TIERS.length - 1];
95
+ return (TIERS.find((keys) => cols - prefixWidth(keys) - 2 >= MIN_GOAL_CELLS + 4) ?? last);
96
+ }
97
+ /** One row of the table, already cut to `cols` cells. */
98
+ export function formatRunRow(row, keys, cols) {
99
+ const cells = keys.map((key) => padCells(cellFor(key, row, row.digest), COLUMNS[key].width));
100
+ const star = UNFINISHED.has(row.status) ? ' *' : '';
101
+ const goalRoom = cols - prefixWidth(keys) - star.length;
102
+ const goal = goalRoom >= 4 ? fitCells(row.goal.replace(/\s+/g, ' '), goalRoom) : '';
103
+ const line = ` ${cells.join(GAP)}${GAP}${goal}${star}`.trimEnd();
104
+ return cellWidth(line) <= cols ? line : fitCells(line, cols);
105
+ }
106
+ /** The whole `/gov runs` notice for `cols` terminal columns. */
107
+ export function formatRunsTable(runs, cols) {
108
+ const width = Number.isFinite(cols) ? Math.max(20, Math.floor(cols)) : 80;
109
+ const keys = pickTier(width);
110
+ const header = ` ${keys.map((k) => padCells(COLUMNS[k].title, COLUMNS[k].width)).join(GAP)}`;
111
+ const lines = ['Governed runs (newest first):', header.trimEnd()];
112
+ for (const run of runs)
113
+ lines.push(formatRunRow(run, keys, width));
114
+ if (runs.some((run) => UNFINISHED.has(run.status))) {
115
+ lines.push('* unfinished: /gov resume <runId>');
116
+ }
117
+ return lines.join('\n');
118
+ }
119
+ /** The transcript text for a found report: header, body capped by lines, full path. */
120
+ export function formatReportNotice(input) {
121
+ const body = input.text.replace(/\r\n/g, '\n').trimEnd().split('\n');
122
+ const shown = body.slice(0, GOV_REPORT_MAX_LINES);
123
+ const more = body.length - shown.length;
124
+ return [
125
+ `Report for run ${input.runId} (${input.file}):`,
126
+ ...shown,
127
+ ...(more > 0 ? [`... (${more} more lines; see the file below)`] : []),
128
+ `Full file: ${input.path}`,
129
+ ].join('\n');
130
+ }
@@ -6,6 +6,7 @@ import type { SubmitMessageOptions } from '../agent/prompt-options.js';
6
6
  import type { CopyRequest, CopyRequestResult } from '../ui/clipboard-task.js';
7
7
  import type { AgentController } from '../agent/controller.js';
8
8
  import type { AgentMode } from '../agent/agent-mode.js';
9
+ import type { Posture } from '../governance/posture.js';
9
10
  import type { Entry, NoticeLevel, Overlay, ToastLevel, ViewAction, ViewState } from '../agent/reducer.js';
10
11
  import type { PersistedConfig } from '../config/schema.js';
11
12
  import type { FollowThroughBudget } from '../todo/follow-through.js';
@@ -92,6 +93,17 @@ export interface CommandContext {
92
93
  effective: AgentMode;
93
94
  pending: AgentMode | null;
94
95
  };
96
+ /**
97
+ * The same single write path, widened to the four postures (governance-mode 4.2).
98
+ * OPTIONAL so the many fixtures that build this context by literal need no change; the
99
+ * App always supplies it, and `/gov` reports "unavailable" when it is absent.
100
+ */
101
+ applyPosture?: (next: Posture, opts?: {
102
+ force?: boolean;
103
+ }) => {
104
+ effective: Posture;
105
+ pending: Posture | null;
106
+ };
95
107
  /**
96
108
  * Mouse capture, for `/mouse` (tui-selection-and-scroll-follow §6.2 / G3).
97
109
  *
@@ -151,6 +151,16 @@ export declare function getSessionsDir(): string;
151
151
  * a per-process run id and why retention is two-tier (§3.5.2 / RV-6).
152
152
  */
153
153
  export declare function getCompactionArchiveDir(): string;
154
+ /**
155
+ * `<home>/governance` - governance-mode run records (governance-mode 11.1 / 12.1).
156
+ *
157
+ * ITS OWN ROOT, for the reason `getCompactionArchiveDir` above records: `logs/`, `sessions/`
158
+ * and `skills/` are scan roots, and every extra directory inside one is another thing each
159
+ * walk has to recognise and skip. Derived from the same `resolution.root`, so the
160
+ * `ARAGON_HOME` / VITEST isolation contract at the top of this file covers it too.
161
+ * The caller creates it (0700 on POSIX); this function only names it.
162
+ */
163
+ export declare function getGovernanceDir(): string;
154
164
  /** `<home>` — parent of `skills/`, and where sidecar state such as usage lives. */
155
165
  export declare function getUserDataDir(): string;
156
166
  export declare function getUserSkillsDir(): string;
@@ -242,6 +242,18 @@ export function getSessionsDir() {
242
242
  export function getCompactionArchiveDir() {
243
243
  return join(resolution.root, 'compaction');
244
244
  }
245
+ /**
246
+ * `<home>/governance` - governance-mode run records (governance-mode 11.1 / 12.1).
247
+ *
248
+ * ITS OWN ROOT, for the reason `getCompactionArchiveDir` above records: `logs/`, `sessions/`
249
+ * and `skills/` are scan roots, and every extra directory inside one is another thing each
250
+ * walk has to recognise and skip. Derived from the same `resolution.root`, so the
251
+ * `ARAGON_HOME` / VITEST isolation contract at the top of this file covers it too.
252
+ * The caller creates it (0700 on POSIX); this function only names it.
253
+ */
254
+ export function getGovernanceDir() {
255
+ return join(resolution.root, 'governance');
256
+ }
245
257
  /** `<home>` — parent of `skills/`, and where sidecar state such as usage lives. */
246
258
  export function getUserDataDir() {
247
259
  return resolution.root;
@@ -96,6 +96,11 @@ export declare const UPDATE_CONFIG_SET_KEYS: readonly ["update.mode", "update.ch
96
96
  * persisted-off-survives-a-flagless-run check — runs exactly that command.
97
97
  */
98
98
  export declare const COMPACTION_CONFIG_SET_KEYS: readonly ["compaction.enabled", "compaction.threshold", "compaction.warnThreshold", "compaction.keepRecentTurns", "compaction.useFastTier", "compaction.onFailure", "compaction.subagents", "compaction.archive"];
99
+ /**
100
+ * Config keys under `governance.` that `aragon config set` accepts (governance-mode 11.1).
101
+ * Every key is writable and ONE level deep, so `runConfigGet`'s single-dot split resolves it.
102
+ */
103
+ export declare const GOVERNANCE_CONFIG_SET_KEYS: readonly ["governance.enabled", "governance.shiftTab", "governance.panel", "governance.profile", "governance.maxWorkPackages", "governance.maxPhases", "governance.maxRework", "governance.maxFixCycles", "governance.minAskRounds", "governance.budgetTokens", "governance.confirmCharter"];
99
104
  /**
100
105
  * Translate a `log.*` dotted key into a patch, or `null` for anything else.
101
106
  *
@@ -131,6 +136,19 @@ export declare function applyJobsConfigSet(key: string, value: string): Partial<
131
136
  * rather than silently mean `false`.
132
137
  */
133
138
  export declare function applyTaskSummaryConfigSet(key: string, value: string): Partial<PersistedConfig> | null;
139
+ /**
140
+ * Translate a `governance.*` dotted key into a patch, or `null` for anything else.
141
+ *
142
+ * EVERY KEY ROUTES THROUGH `clampGovernanceConfig`, so `config set governance.maxRework 9`
143
+ * stores 5 (the engine's `hardMaxRework`) and the caller echoes the STORED value. The string
144
+ * is turned into a number HERE, because the clamp deliberately repairs a string to the
145
+ * default (a hand-edited `"2"` has no defensible reading); an unparseable argument becomes
146
+ * `NaN`, which the clamp repairs to the default - the "typo changes nothing" rule.
147
+ *
148
+ * The patch is a PARTIAL section, safe only because `updatePersistedConfig` deep-merges
149
+ * `governance` (the ninth time that trap is recorded in `store.ts`).
150
+ */
151
+ export declare function applyGovernanceConfigSet(key: string, value: string): Partial<PersistedConfig> | null;
134
152
  /**
135
153
  * Translate a `team.*` dotted key into a patch, or `null` for anything else.
136
154
  *
@@ -14,7 +14,7 @@ import { getConfigBackupPath, getConfigPath, getHomeRoot, getLogsDir, getPromptH
14
14
  import { getHomeMigrationBreadcrumbPath } from './migrate-home.js';
15
15
  import { loadPersistedConfig, updatePersistedConfig } from './store.js';
16
16
  import { normalizeModelProfiles, validateModelProfiles } from './model-profiles.js';
17
- import { clampFastConfig, clampRetryConfig, clampCompactionConfig, clampTeamConfig, parseThresholdInput, clampUpdateConfig, coercePositiveInt, maskSecret, DEFAULT_FAST_CONFIG, DEFAULT_RETRY_CONFIG, DEFAULT_TEAM_CONFIG, DEFAULT_COMPACTION_CONFIG, DEFAULT_UPDATE_CONFIG, DEFAULT_JOBS_CONFIG, clampJobsConfig, } from './schema.js';
17
+ import { clampFastConfig, clampRetryConfig, clampCompactionConfig, clampTeamConfig, parseThresholdInput, clampUpdateConfig, coercePositiveInt, maskSecret, DEFAULT_FAST_CONFIG, DEFAULT_RETRY_CONFIG, DEFAULT_TEAM_CONFIG, DEFAULT_COMPACTION_CONFIG, DEFAULT_UPDATE_CONFIG, DEFAULT_JOBS_CONFIG, clampJobsConfig, clampGovernanceConfig, DEFAULT_GOVERNANCE_CONFIG, } from './schema.js';
18
18
  import { clampLogLevel } from '../logging/levels.js';
19
19
  /** Config keys under `log.` that `aragon config set` accepts. */
20
20
  export const LOG_CONFIG_SET_KEYS = [
@@ -167,6 +167,23 @@ export const COMPACTION_CONFIG_SET_KEYS = [
167
167
  'compaction.subagents',
168
168
  'compaction.archive',
169
169
  ];
170
+ /**
171
+ * Config keys under `governance.` that `aragon config set` accepts (governance-mode 11.1).
172
+ * Every key is writable and ONE level deep, so `runConfigGet`'s single-dot split resolves it.
173
+ */
174
+ export const GOVERNANCE_CONFIG_SET_KEYS = [
175
+ 'governance.enabled',
176
+ 'governance.shiftTab',
177
+ 'governance.panel',
178
+ 'governance.profile',
179
+ 'governance.maxWorkPackages',
180
+ 'governance.maxPhases',
181
+ 'governance.maxRework',
182
+ 'governance.maxFixCycles',
183
+ 'governance.minAskRounds',
184
+ 'governance.budgetTokens',
185
+ 'governance.confirmCharter',
186
+ ];
170
187
  function isTrue(value) {
171
188
  return value === 'true' || value === '1';
172
189
  }
@@ -255,6 +272,35 @@ export function applyTaskSummaryConfigSet(key, value) {
255
272
  return null;
256
273
  }
257
274
  }
275
+ /**
276
+ * Translate a `governance.*` dotted key into a patch, or `null` for anything else.
277
+ *
278
+ * EVERY KEY ROUTES THROUGH `clampGovernanceConfig`, so `config set governance.maxRework 9`
279
+ * stores 5 (the engine's `hardMaxRework`) and the caller echoes the STORED value. The string
280
+ * is turned into a number HERE, because the clamp deliberately repairs a string to the
281
+ * default (a hand-edited `"2"` has no defensible reading); an unparseable argument becomes
282
+ * `NaN`, which the clamp repairs to the default - the "typo changes nothing" rule.
283
+ *
284
+ * The patch is a PARTIAL section, safe only because `updatePersistedConfig` deep-merges
285
+ * `governance` (the ninth time that trap is recorded in `store.ts`).
286
+ */
287
+ export function applyGovernanceConfigSet(key, value) {
288
+ if (!GOVERNANCE_CONFIG_SET_KEYS.includes(key))
289
+ return null;
290
+ const field = key.slice('governance.'.length);
291
+ const typed = parseGovernanceArgument(field, value);
292
+ const clamped = clampGovernanceConfig({ ...DEFAULT_GOVERNANCE_CONFIG, [field]: typed });
293
+ return { governance: { [field]: clamped[field] } };
294
+ }
295
+ function parseGovernanceArgument(field, value) {
296
+ const kind = typeof DEFAULT_GOVERNANCE_CONFIG[field];
297
+ const trimmed = value.trim();
298
+ if (kind === 'boolean')
299
+ return isTrue(trimmed);
300
+ if (kind === 'number')
301
+ return trimmed === '' ? Number.NaN : Number(trimmed);
302
+ return trimmed;
303
+ }
258
304
  /**
259
305
  * Translate a `team.*` dotted key into a patch, or `null` for anything else.
260
306
  *
@@ -21,7 +21,8 @@
21
21
  * ARAGON_COMPACTION_ARCHIVE,
22
22
  * ARAGON_JOBS_ENABLED, ARAGON_JOBS_WAIT_SECONDS, ARAGON_JOBS_STALL_SECONDS,
23
23
  * ARAGON_JOBS_PERSIST_LOG, ARAGON_JOBS_NOTICE_STEER,
24
- * ARAGON_PROMPTS (the quick-prompt library's `enabled` switch, and only that).
24
+ * ARAGON_PROMPTS (the quick-prompt library's `enabled` switch, and only that),
25
+ * ARAGON_GOVERNANCE (a KILL switch: `0` disables governance mode, nothing enables it).
25
26
  *
26
27
  * READ ELSEWHERE, ON PURPOSE: `ARAGON_UPDATE_REGISTRY` and `npm_config_registry`
27
28
  * are resolved by `update/registry.ts::resolveRegistryUrl`, NOT here, because