@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
@@ -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
@@ -41,6 +42,7 @@ import process from 'node:process';
41
42
  import dotenv from 'dotenv';
42
43
  import { registerSecretsFrom } from '../logging/secret-registry.js';
43
44
  import { clampLogLevel } from '../logging/levels.js';
45
+ import { isGovernanceEnvDisabled } from './governance-config.js';
44
46
  import { clampContextWindow, clampMaxTokens, clampTheme, clampThinkingLevel, isAutoToken, parseThresholdInput, } from './schema.js';
45
47
  /**
46
48
  * Load a project `.env` from the given directory into `process.env` (existing
@@ -318,6 +320,13 @@ export function readEnvConfig() {
318
320
  enabled: prompts === '1' || prompts === 'true' || prompts === 'on' || prompts === 'yes',
319
321
  };
320
322
  }
323
+ // ARAGON_GOVERNANCE=0 is the governance-mode KILL switch (governance-mode 11.1): it can only
324
+ // switch `enabled` OFF, and it outranks the config file (the layers merge file < env). It is
325
+ // deliberately not a toggle - `ARAGON_GOVERNANCE=1` writes nothing, so an environment can
326
+ // never re-enable what the user's config or `--no-governance` turned off.
327
+ if (isGovernanceEnvDisabled(process.env)) {
328
+ partial.governance = { enabled: false };
329
+ }
321
330
  // ARAGON_TASK_SUMMARY is the header label's kill switch
322
331
  // (tui-shortcut-drawer-and-task-summary §4.8 / P2-7), and it maps `enabled` and
323
332
  // NOTHING ELSE - the same wording and the same reason as the block above. A
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Governance-mode configuration (governance-mode sections 11.1 / 13.2).
3
+ *
4
+ * LIVES IN ITS OWN FILE, NOT IN `schema.ts`: that file is far past the repo's
5
+ * 1000-line limit, and a section whose clamp depends on nothing else in it has no
6
+ * reason to grow it. `schema.ts` imports and re-exports everything below, so every
7
+ * importer still reaches the section through `config/schema.js`.
8
+ *
9
+ * SELF-CONTAINED ON PURPOSE. The integer/boolean helpers in `schema.ts` are private,
10
+ * and importing back from it would make a cycle (`schema.ts` re-exports this file).
11
+ * The two helpers here are four lines each.
12
+ *
13
+ * ASCII ONLY: `src/config/**` is inside the glyph scanner's scope.
14
+ */
15
+ import type { Posture } from '../governance/posture.js';
16
+ import { type ProfileId } from '../governance/types.js';
17
+ export interface GovernanceConfig {
18
+ /** `false` => the feature is absent: no tools, no prompt blocks, no posture. */
19
+ enabled: boolean;
20
+ /** `false` => Shift+Tab stays BUILD<->PLAN; `/gov` still works. */
21
+ shiftTab: boolean;
22
+ /** `false` => the status chip only, no legion panel. */
23
+ panel: boolean;
24
+ /** Domain profile that names the roles' focus areas. */
25
+ profile: ProfileId;
26
+ /** 1..8. Work packages per run. */
27
+ maxWorkPackages: number;
28
+ /** 1..6. Phases per run. */
29
+ maxPhases: number;
30
+ /** 1..5. Rework rounds per work package. */
31
+ maxRework: number;
32
+ /** 1..5. Fix cycles per phase. */
33
+ maxFixCycles: number;
34
+ /** 0..5. Question rounds before PLAN+GOVERN may submit a plan. */
35
+ minAskRounds: number;
36
+ /** 0..1_000_000_000. Token budget per run; `0` = unlimited. */
37
+ budgetTokens: number;
38
+ /** `true` => the user approves the run charter before any dispatch. */
39
+ confirmCharter: boolean;
40
+ }
41
+ export declare const DEFAULT_GOVERNANCE_CONFIG: GovernanceConfig;
42
+ type NumericKey = 'maxWorkPackages' | 'maxPhases' | 'maxRework' | 'maxFixCycles' | 'minAskRounds' | 'budgetTokens';
43
+ /** The numeric keys, in display order; shared with the settings and `config` surfaces. */
44
+ export declare const GOVERNANCE_NUMERIC_KEYS: readonly NumericKey[];
45
+ /** The inclusive range a numeric key is clamped to. */
46
+ export declare function governanceRangeOf(key: NumericKey): {
47
+ min: number;
48
+ max: number;
49
+ };
50
+ /**
51
+ * THE single gate for every governance-config read AND write, mirroring
52
+ * `clampTeamConfig` for the reason that one records: hardening only the read path
53
+ * leaves a bad value on disk that the read path then rewrites on every launch, which
54
+ * presents to the user as "my setting won't stick". Unknown keys are dropped.
55
+ */
56
+ export declare function clampGovernanceConfig(raw: unknown): GovernanceConfig;
57
+ /**
58
+ * `ARAGON_GOVERNANCE=0` is a KILL switch, not a toggle: it can only disable. Any other
59
+ * value (including `1`) is ignored, so an environment can never switch a feature ON that
60
+ * the user's config or a `--no-governance` flag turned off.
61
+ *
62
+ * Takes the env as a parameter so tests and `doctor` do not touch `process.env`.
63
+ */
64
+ export declare function isGovernanceEnvDisabled(env?: Readonly<Record<string, string | undefined>>): boolean;
65
+ /**
66
+ * The config-level reasons governance can be unavailable. A SUBSET of the
67
+ * `GovernanceUnavailableReason` vocabulary in `governance/copy.ts` (the controller adds
68
+ * `no_api_key`, `headless`, `restart_required` at runtime); the codes are spelled
69
+ * identically so the integration can unify the two without renaming anything.
70
+ */
71
+ export type GovernanceConfigUnavailableReason = 'disabled_config' | 'disabled_env' | 'team_off';
72
+ export type GovernanceConfigAvailability = {
73
+ ok: true;
74
+ } | {
75
+ ok: false;
76
+ reason: GovernanceConfigUnavailableReason;
77
+ };
78
+ /**
79
+ * Config-only availability, in precedence order: the environment kill switch first (it is
80
+ * the strongest), then the config key, then the team dependency. Pure: no I/O.
81
+ */
82
+ export declare function describeGovernanceConfigAvailability(config: {
83
+ governance?: Pick<GovernanceConfig, 'enabled'>;
84
+ team: {
85
+ enabled: boolean;
86
+ };
87
+ }, env?: Readonly<Record<string, string | undefined>>): GovernanceConfigAvailability;
88
+ /**
89
+ * Merge the `governance` section across the layers (defaults > file > env > flag).
90
+ *
91
+ * The environment and `--no-governance` layers can ONLY switch `enabled` off (they are kill
92
+ * switches), and they are applied last so nothing in the file can outvote them. The flag is a
93
+ * launch-only override: it is resolved here into the in-memory config and never reaches the
94
+ * store, which is what "not persisted" means.
95
+ */
96
+ export declare function resolveGovernanceConfig(layers: {
97
+ file?: unknown;
98
+ env?: unknown;
99
+ disableForLaunch?: boolean;
100
+ }): GovernanceConfig;
101
+ export interface InitialPostureInput {
102
+ /** `--govern`. */
103
+ govern?: boolean;
104
+ /** `--plan-govern`. Wins over `govern` if a programmatic caller passes both. */
105
+ planGovern?: boolean;
106
+ /** The already-resolved `--plan` / `planModeDefault` result (unchanged by governance). */
107
+ startInPlanMode: boolean;
108
+ governance: Pick<GovernanceConfig, 'enabled'>;
109
+ teamEnabled: boolean;
110
+ }
111
+ export interface InitialPostureResolution {
112
+ posture: Posture;
113
+ /** Set when a governance posture was requested but could not be honoured. */
114
+ degradedReason?: GovernanceConfigUnavailableReason;
115
+ }
116
+ /**
117
+ * The posture the session starts in. Governance flags win over `--plan` / `planModeDefault`
118
+ * (a user who asked for a governed run did not ask for a plain one); when governance is
119
+ * unavailable at the CONFIG level the request degrades to the classic posture the same launch
120
+ * would have had, and the reason is returned so the caller can say so instead of silently
121
+ * dropping the flag. Without a governance flag this is exactly `startInPlanMode ? 'plan' :
122
+ * 'build'`, so `--plan` behaviour is untouched.
123
+ */
124
+ export declare function resolveInitialPosture(input: InitialPostureInput, env?: Readonly<Record<string, string | undefined>>): InitialPostureResolution;
125
+ export {};
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Governance-mode configuration (governance-mode sections 11.1 / 13.2).
3
+ *
4
+ * LIVES IN ITS OWN FILE, NOT IN `schema.ts`: that file is far past the repo's
5
+ * 1000-line limit, and a section whose clamp depends on nothing else in it has no
6
+ * reason to grow it. `schema.ts` imports and re-exports everything below, so every
7
+ * importer still reaches the section through `config/schema.js`.
8
+ *
9
+ * SELF-CONTAINED ON PURPOSE. The integer/boolean helpers in `schema.ts` are private,
10
+ * and importing back from it would make a cycle (`schema.ts` re-exports this file).
11
+ * The two helpers here are four lines each.
12
+ *
13
+ * ASCII ONLY: `src/config/**` is inside the glyph scanner's scope.
14
+ */
15
+ import process from 'node:process';
16
+ import { GOV_LIMITS } from '../governance/limits.js';
17
+ import { PROFILE_IDS } from '../governance/types.js';
18
+ export const DEFAULT_GOVERNANCE_CONFIG = {
19
+ enabled: true,
20
+ shiftTab: true,
21
+ panel: true,
22
+ profile: 'general',
23
+ maxWorkPackages: 4,
24
+ maxPhases: 3,
25
+ maxRework: 3,
26
+ maxFixCycles: 3,
27
+ minAskRounds: 2,
28
+ budgetTokens: 0,
29
+ confirmCharter: false,
30
+ };
31
+ /**
32
+ * Inclusive range per numeric key. The upper bounds of the four structural keys are
33
+ * `GOV_LIMITS.hardMax*` (NOT repeated here), so the clamp and the engine can never
34
+ * disagree: a hand-edited `config.json` cannot exceed what the engine enforces.
35
+ */
36
+ const NUMERIC_RANGES = {
37
+ maxWorkPackages: { min: 1, max: GOV_LIMITS.hardMaxWorkPackages },
38
+ maxPhases: { min: 1, max: GOV_LIMITS.hardMaxPhases },
39
+ maxRework: { min: 1, max: GOV_LIMITS.hardMaxRework },
40
+ maxFixCycles: { min: 1, max: GOV_LIMITS.hardMaxFixCycles },
41
+ minAskRounds: { min: 0, max: 5 },
42
+ budgetTokens: { min: 0, max: 1_000_000_000 },
43
+ };
44
+ /** The numeric keys, in display order; shared with the settings and `config` surfaces. */
45
+ export const GOVERNANCE_NUMERIC_KEYS = Object.keys(NUMERIC_RANGES);
46
+ /** The inclusive range a numeric key is clamped to. */
47
+ export function governanceRangeOf(key) {
48
+ return NUMERIC_RANGES[key];
49
+ }
50
+ /**
51
+ * Only a real, finite INTEGER survives. A float, NaN, a string ("2") or null is
52
+ * REPAIRED TO THE DEFAULT, not rounded or coerced: a hand-edited `maxRework: 2.5` has no
53
+ * defensible reading, and "typo changes nothing" is the discipline every sibling clamp
54
+ * follows. Out-of-range integers are clamped, not rejected.
55
+ */
56
+ function clampRangedInt(value, key) {
57
+ const range = NUMERIC_RANGES[key];
58
+ const fallback = DEFAULT_GOVERNANCE_CONFIG[key];
59
+ if (typeof value !== 'number' || !Number.isInteger(value))
60
+ return fallback;
61
+ return Math.min(range.max, Math.max(range.min, value));
62
+ }
63
+ function pickBool(value, fallback) {
64
+ return typeof value === 'boolean' ? value : fallback;
65
+ }
66
+ function pickProfile(value) {
67
+ return typeof value === 'string' && PROFILE_IDS.includes(value)
68
+ ? value
69
+ : DEFAULT_GOVERNANCE_CONFIG.profile;
70
+ }
71
+ /**
72
+ * THE single gate for every governance-config read AND write, mirroring
73
+ * `clampTeamConfig` for the reason that one records: hardening only the read path
74
+ * leaves a bad value on disk that the read path then rewrites on every launch, which
75
+ * presents to the user as "my setting won't stick". Unknown keys are dropped.
76
+ */
77
+ export function clampGovernanceConfig(raw) {
78
+ const src = (raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {});
79
+ const defaults = DEFAULT_GOVERNANCE_CONFIG;
80
+ return {
81
+ enabled: pickBool(src.enabled, defaults.enabled),
82
+ shiftTab: pickBool(src.shiftTab, defaults.shiftTab),
83
+ panel: pickBool(src.panel, defaults.panel),
84
+ profile: pickProfile(src.profile),
85
+ maxWorkPackages: clampRangedInt(src.maxWorkPackages, 'maxWorkPackages'),
86
+ maxPhases: clampRangedInt(src.maxPhases, 'maxPhases'),
87
+ maxRework: clampRangedInt(src.maxRework, 'maxRework'),
88
+ maxFixCycles: clampRangedInt(src.maxFixCycles, 'maxFixCycles'),
89
+ minAskRounds: clampRangedInt(src.minAskRounds, 'minAskRounds'),
90
+ budgetTokens: clampRangedInt(src.budgetTokens, 'budgetTokens'),
91
+ confirmCharter: pickBool(src.confirmCharter, defaults.confirmCharter),
92
+ };
93
+ }
94
+ // ---------------------------------------------------------------------------
95
+ // Environment kill switch + availability (config-level part)
96
+ // ---------------------------------------------------------------------------
97
+ /** The spellings of "off" the kill switch accepts, matching the other ARAGON_* switches. */
98
+ const ENV_OFF_VALUES = new Set(['0', 'false', 'off', 'no']);
99
+ /**
100
+ * `ARAGON_GOVERNANCE=0` is a KILL switch, not a toggle: it can only disable. Any other
101
+ * value (including `1`) is ignored, so an environment can never switch a feature ON that
102
+ * the user's config or a `--no-governance` flag turned off.
103
+ *
104
+ * Takes the env as a parameter so tests and `doctor` do not touch `process.env`.
105
+ */
106
+ export function isGovernanceEnvDisabled(env = process.env) {
107
+ const value = env.ARAGON_GOVERNANCE?.trim().toLowerCase();
108
+ return value !== undefined && ENV_OFF_VALUES.has(value);
109
+ }
110
+ /**
111
+ * Config-only availability, in precedence order: the environment kill switch first (it is
112
+ * the strongest), then the config key, then the team dependency. Pure: no I/O.
113
+ */
114
+ export function describeGovernanceConfigAvailability(config, env = process.env) {
115
+ if (isGovernanceEnvDisabled(env))
116
+ return { ok: false, reason: 'disabled_env' };
117
+ if (config.governance?.enabled === false)
118
+ return { ok: false, reason: 'disabled_config' };
119
+ if (!config.team.enabled)
120
+ return { ok: false, reason: 'team_off' };
121
+ return { ok: true };
122
+ }
123
+ // ---------------------------------------------------------------------------
124
+ // Launch resolution (load.ts calls these; kept here so load.ts barely grows)
125
+ // ---------------------------------------------------------------------------
126
+ /**
127
+ * Merge the `governance` section across the layers (defaults > file > env > flag).
128
+ *
129
+ * The environment and `--no-governance` layers can ONLY switch `enabled` off (they are kill
130
+ * switches), and they are applied last so nothing in the file can outvote them. The flag is a
131
+ * launch-only override: it is resolved here into the in-memory config and never reaches the
132
+ * store, which is what "not persisted" means.
133
+ */
134
+ export function resolveGovernanceConfig(layers) {
135
+ return clampGovernanceConfig({
136
+ ...DEFAULT_GOVERNANCE_CONFIG,
137
+ ...(isPlainObject(layers.file) ? layers.file : {}),
138
+ ...(isPlainObject(layers.env) ? layers.env : {}),
139
+ ...(layers.disableForLaunch === true ? { enabled: false } : {}),
140
+ });
141
+ }
142
+ function isPlainObject(value) {
143
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
144
+ }
145
+ /**
146
+ * The posture the session starts in. Governance flags win over `--plan` / `planModeDefault`
147
+ * (a user who asked for a governed run did not ask for a plain one); when governance is
148
+ * unavailable at the CONFIG level the request degrades to the classic posture the same launch
149
+ * would have had, and the reason is returned so the caller can say so instead of silently
150
+ * dropping the flag. Without a governance flag this is exactly `startInPlanMode ? 'plan' :
151
+ * 'build'`, so `--plan` behaviour is untouched.
152
+ */
153
+ export function resolveInitialPosture(input, env = process.env) {
154
+ const classic = input.startInPlanMode ? 'plan' : 'build';
155
+ const requested = input.planGovern === true
156
+ ? 'plan_govern'
157
+ : input.govern === true ? 'govern' : null;
158
+ if (requested === null)
159
+ return { posture: classic };
160
+ const availability = describeGovernanceConfigAvailability({ governance: input.governance, team: { enabled: input.teamEnabled } }, env);
161
+ return availability.ok
162
+ ? { posture: requested }
163
+ : { posture: classic, degradedReason: availability.reason };
164
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Command-line validation for the governance flags (governance-mode 11.2).
3
+ *
4
+ * PURE, AND OUT OF `cli.tsx` ON PURPOSE: that file is far past the size limit, and these four
5
+ * rules are exactly the kind that regress silently (a missing one lets `-p --govern` start a
6
+ * run that can never reach a human). `cli.tsx` only gathers the facts and prints the message.
7
+ *
8
+ * ASCII ONLY: `src/config/**` is inside the glyph scanner's scope.
9
+ */
10
+ export interface GovernanceLaunchInput {
11
+ /** `--govern`. */
12
+ govern?: boolean;
13
+ /** `--plan-govern`. */
14
+ planGovern?: boolean;
15
+ /** `--plan` (`true` only when passed; `--no-plan` and silence are both falsy here). */
16
+ plan?: boolean;
17
+ /** `--no-team`. */
18
+ noTeam?: boolean;
19
+ /** `--no-governance`. */
20
+ noGovernance?: boolean;
21
+ /**
22
+ * The run cannot reach a human: `-p/--print`, the `exec` subcommand, piped stdin or a
23
+ * non-TTY stdout. Governance needs `ask_user` and the review overlays, which headless
24
+ * runs deny wholesale.
25
+ */
26
+ headless: boolean;
27
+ }
28
+ export type GovernanceLaunchCheck = {
29
+ ok: true;
30
+ } | {
31
+ ok: false;
32
+ message: string;
33
+ };
34
+ export declare const GOVERNANCE_EXCLUSIVE_MESSAGE = "--govern, --plan-govern and --plan are mutually exclusive. Pass only one of them.";
35
+ export declare const GOVERNANCE_HEADLESS_MESSAGE: string;
36
+ export declare const GOVERNANCE_NO_TEAM_MESSAGE: string;
37
+ export declare const GOVERNANCE_NO_GOVERNANCE_MESSAGE: string;
38
+ /**
39
+ * The first violated rule, or `{ ok: true }`. ORDER IS THE CONTRACT: the exclusivity error
40
+ * is the one a user typing three flags needs to see first, and the headless rule outranks
41
+ * `--no-team` because it is the one that cannot be fixed by dropping a second flag.
42
+ * Nothing is checked unless a governance posture was actually requested.
43
+ */
44
+ export declare function validateGovernanceLaunch(input: GovernanceLaunchInput): GovernanceLaunchCheck;
45
+ /** The root-level options the rules read, as commander materialises them. */
46
+ export interface GovernanceRootOptions {
47
+ govern?: boolean;
48
+ planGovern?: boolean;
49
+ plan?: boolean;
50
+ /** `--team` / `--no-team`: only an explicit `false` is a refusal. */
51
+ team?: boolean;
52
+ /** `--no-governance` is declared alone, so this is `true` when absent and `false` when given. */
53
+ governance?: boolean;
54
+ print?: boolean;
55
+ }
56
+ export interface GovernanceCommandContext {
57
+ /** The command about to run: `exec`, `config`, or the root command's own name. */
58
+ commandName: string;
59
+ /** `true` for the root command's own action (the TUI / `-p` entry), not a subcommand. */
60
+ isRootAction: boolean;
61
+ stdinIsTty: boolean;
62
+ stdoutIsTty: boolean;
63
+ }
64
+ /**
65
+ * The `preAction` hook's decision. `exec` is always headless; the root action is headless
66
+ * when `-p` was given or either stream is not a terminal (the TUI would refuse to start
67
+ * anyway, but it would do so AFTER a governance run had been promised). Other subcommands
68
+ * (`config`, `logs`, ...) never start a governed run, so only the flag-combination rules
69
+ * apply to them.
70
+ */
71
+ export declare function checkGovernanceCommandLine(options: GovernanceRootOptions, context: GovernanceCommandContext): GovernanceLaunchCheck;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Command-line validation for the governance flags (governance-mode 11.2).
3
+ *
4
+ * PURE, AND OUT OF `cli.tsx` ON PURPOSE: that file is far past the size limit, and these four
5
+ * rules are exactly the kind that regress silently (a missing one lets `-p --govern` start a
6
+ * run that can never reach a human). `cli.tsx` only gathers the facts and prints the message.
7
+ *
8
+ * ASCII ONLY: `src/config/**` is inside the glyph scanner's scope.
9
+ */
10
+ export const GOVERNANCE_EXCLUSIVE_MESSAGE = '--govern, --plan-govern and --plan are mutually exclusive. Pass only one of them.';
11
+ export const GOVERNANCE_HEADLESS_MESSAGE = 'Governance mode needs an interactive terminal. It cannot be combined with -p/--print, '
12
+ + 'the exec subcommand or piped input.';
13
+ export const GOVERNANCE_NO_TEAM_MESSAGE = '--govern and --plan-govern run their roles as team subagents, so they cannot be '
14
+ + 'combined with --no-team. Remove --no-team to use governance mode.';
15
+ export const GOVERNANCE_NO_GOVERNANCE_MESSAGE = '--no-governance disables governance mode for this launch, so it cannot be combined with '
16
+ + '--govern or --plan-govern.';
17
+ /**
18
+ * The first violated rule, or `{ ok: true }`. ORDER IS THE CONTRACT: the exclusivity error
19
+ * is the one a user typing three flags needs to see first, and the headless rule outranks
20
+ * `--no-team` because it is the one that cannot be fixed by dropping a second flag.
21
+ * Nothing is checked unless a governance posture was actually requested.
22
+ */
23
+ export function validateGovernanceLaunch(input) {
24
+ const requested = input.govern === true || input.planGovern === true;
25
+ const exclusive = [input.govern === true, input.planGovern === true, input.plan === true];
26
+ if (exclusive.filter(Boolean).length > 1) {
27
+ return { ok: false, message: GOVERNANCE_EXCLUSIVE_MESSAGE };
28
+ }
29
+ if (!requested)
30
+ return { ok: true };
31
+ if (input.noGovernance === true)
32
+ return { ok: false, message: GOVERNANCE_NO_GOVERNANCE_MESSAGE };
33
+ if (input.headless)
34
+ return { ok: false, message: GOVERNANCE_HEADLESS_MESSAGE };
35
+ if (input.noTeam === true)
36
+ return { ok: false, message: GOVERNANCE_NO_TEAM_MESSAGE };
37
+ return { ok: true };
38
+ }
39
+ /**
40
+ * The `preAction` hook's decision. `exec` is always headless; the root action is headless
41
+ * when `-p` was given or either stream is not a terminal (the TUI would refuse to start
42
+ * anyway, but it would do so AFTER a governance run had been promised). Other subcommands
43
+ * (`config`, `logs`, ...) never start a governed run, so only the flag-combination rules
44
+ * apply to them.
45
+ */
46
+ export function checkGovernanceCommandLine(options, context) {
47
+ const headless = context.commandName === 'exec'
48
+ || (context.isRootAction
49
+ && (options.print === true || !context.stdinIsTty || !context.stdoutIsTty));
50
+ return validateGovernanceLaunch({
51
+ govern: options.govern,
52
+ planGovern: options.planGovern,
53
+ plan: options.plan,
54
+ noTeam: options.team === false,
55
+ noGovernance: options.governance === false,
56
+ headless,
57
+ });
58
+ }
@@ -240,6 +240,21 @@ export interface CliFlags {
240
240
  compaction?: boolean;
241
241
  /** `--compaction-threshold <0..1|N%>` — session override for `threshold`. */
242
242
  compactionThreshold?: string;
243
+ /**
244
+ * `--govern` / `--plan-govern` (governance-mode 11.2). Plain booleans: they only ever ask
245
+ * for a starting posture and are persisted nowhere, so unlike the pairs above there is no
246
+ * stored value for a commander default to overwrite. `cli.tsx` rejects the combinations
247
+ * (mutually exclusive, headless, `--no-team`) before this layer sees them.
248
+ */
249
+ govern?: boolean;
250
+ planGovern?: boolean;
251
+ /**
252
+ * `--no-governance`, carried as `false` ONLY. A lone negative flag makes commander default
253
+ * its option to `true`, so `cli.tsx` forwards `undefined` for that case and nothing here can
254
+ * mistake silence for an override. Launch-only: it lands in the resolved `CliConfig` and is
255
+ * never written to the store.
256
+ */
257
+ governance?: boolean;
243
258
  /**
244
259
  * `--resume <id|path>` - continue a saved session under a NEW id
245
260
  * (tui-session-id-resume 4).
@@ -15,6 +15,7 @@ import { getConfigPath } from './app-paths.js';
15
15
  import { setHistoryEnabled } from './prompt-history.js';
16
16
  import { setEntryRetain } from '../agent/entry-limits.js';
17
17
  import { readSubmitCount } from './ui-state.js';
18
+ import { resolveGovernanceConfig, resolveInitialPosture } from './governance-config.js';
18
19
  import { getLogger } from '../logging/logger.js';
19
20
  import { registerSecret, registerSecretsFrom } from '../logging/secret-registry.js';
20
21
  import { detectCapabilities, resolveTuiCapabilities } from '../ui/capabilities.js';
@@ -710,6 +711,28 @@ export function loadConfig(flags = {}, options = {}) {
710
711
  // `viewReducer` is pure and takes no config, so the resolved ring cap reaches
711
712
  // it through the same module-singleton channel `setHistoryEnabled` uses.
712
713
  setEntryRetain(transcriptRetain);
714
+ // Governance: file < env (kill switch) < `--no-governance`, clamped like every section. The
715
+ // initial posture is resolved against the SAME team value the config carries, and a request
716
+ // that cannot be honoured degrades to the classic posture with a log record, never silently.
717
+ const governance = resolveGovernanceConfig({
718
+ file: file.governance,
719
+ env: env.partial.governance,
720
+ disableForLaunch: flags.governance === false,
721
+ });
722
+ const team = resolveTeamConfig(flags, env.partial, file);
723
+ const posture = resolveInitialPosture({
724
+ govern: flags.govern,
725
+ planGovern: flags.planGovern,
726
+ startInPlanMode,
727
+ governance,
728
+ teamEnabled: team.enabled,
729
+ });
730
+ const initialPosture = posture.posture;
731
+ if (posture.degradedReason !== undefined) {
732
+ getLogger().warn('config', 'governance_initial_posture_degraded', {
733
+ reason: posture.degradedReason,
734
+ });
735
+ }
713
736
  const config = {
714
737
  modelProfiles: projection.issues.length ? undefined : file.modelProfiles,
715
738
  provider,
@@ -768,7 +791,7 @@ export function loadConfig(flags = {}, options = {}) {
768
791
  : {}),
769
792
  },
770
793
  log: resolveLogConfig(flags, env.partial, file),
771
- team: resolveTeamConfig(flags, env.partial, file),
794
+ team,
772
795
  todo: resolveTodoConfig(flags, env.partial, file),
773
796
  retry: resolveRetryConfig(flags, env.partial, file),
774
797
  fast: resolveFastConfig(flags, env.partial, file),
@@ -790,6 +813,8 @@ export function loadConfig(flags = {}, options = {}) {
790
813
  // `taskSummary.enabled: false` cannot arrive as two different shapes.
791
814
  taskSummary: clampTaskSummaryConfig({ ...DEFAULT_CONFIG.taskSummary,
792
815
  ...(env.partial.taskSummary ?? {}), ...(file.taskSummary ?? {}) }),
816
+ governance,
817
+ initialPosture,
793
818
  cwd,
794
819
  color,
795
820
  colorLevel,
@@ -1,12 +1,14 @@
1
1
  import { type ModelProfilesConfig, type ModelRole, type ProfileValidationIssue } from './model-profiles.js';
2
2
  import type { SessionKitState } from './model-kits.js';
3
- import type { CliConfig, CompactionConfig, FastConfig, LogConfig, PersistedConfig, TeamConfig } from './schema.js';
4
- export interface ModelSettingsPatch extends Partial<Omit<PersistedConfig, 'modelProfiles' | 'fast' | 'compaction' | 'log' | 'team'>> {
3
+ import type { CliConfig, CompactionConfig, FastConfig, GovernanceConfig, LogConfig, PersistedConfig, TeamConfig } from './schema.js';
4
+ export interface ModelSettingsPatch extends Partial<Omit<PersistedConfig, 'modelProfiles' | 'fast' | 'compaction' | 'log' | 'team' | 'governance'>> {
5
5
  fast?: Partial<FastConfig>;
6
6
  compaction?: Partial<CompactionConfig>;
7
7
  log?: Partial<LogConfig>;
8
8
  /** The subagent-model picker writes here (subagent-model D-5/I-5): draft-only until Save. */
9
9
  team?: Partial<TeamConfig>;
10
+ /** Governance writers (`config set`, `/gov budget`) send single keys; the store clamps. */
11
+ governance?: Partial<GovernanceConfig>;
10
12
  }
11
13
  export interface ModelSettingsDraft {
12
14
  profiles?: ModelProfilesConfig;
@@ -7,7 +7,10 @@
7
7
  */
8
8
  import { type RetryPolicy, type ThinkingLevel } from '@aragon-agent/core';
9
9
  import { type LogLevelName } from '../logging/levels.js';
10
+ import { type GovernanceConfig } from './governance-config.js';
11
+ import type { Posture } from '../governance/posture.js';
10
12
  import type { FollowThroughMode } from '../todo/types.js';
13
+ export { clampGovernanceConfig, DEFAULT_GOVERNANCE_CONFIG, type GovernanceConfig, } from './governance-config.js';
11
14
  /**
12
15
  * `ModelRegistry` seeds builtin lists for five providers, but `initProviders()`
13
16
  * registers only three adapters. Running a model whose provider has no adapter
@@ -1146,6 +1149,8 @@ export interface PersistedConfig {
1146
1149
  prompts: PromptsConfig;
1147
1150
  /** The header task label. The THIRTEENTH; two scalars (see TaskSummaryConfig). */
1148
1151
  taskSummary: TaskSummaryConfig;
1152
+ /** Governance mode. The FOURTEENTH; scalars only (see GovernanceConfig). */
1153
+ governance: GovernanceConfig;
1149
1154
  }
1150
1155
  export declare const DEFAULT_CONFIG: PersistedConfig;
1151
1156
  /**
@@ -1534,6 +1539,23 @@ export interface CliConfig {
1534
1539
  * switched-off feature.
1535
1540
  */
1536
1541
  taskSummary?: TaskSummaryConfig;
1542
+ /**
1543
+ * Governance mode (governance-mode 11.1). The FOURTEENTH nested object; scalars only.
1544
+ *
1545
+ * OPTIONAL HERE FOR THE SAME REASON AS `prompts` / `taskSummary` ABOVE: `CliConfig` is built
1546
+ * by hand in many fixtures, `loadConfig` always fills it, and readers treat a missing
1547
+ * section as the documented defaults. `enabled` here (after env kill switch and
1548
+ * `--no-governance`) decides whether governance tools are REGISTERED, once at construction.
1549
+ */
1550
+ governance?: GovernanceConfig;
1551
+ /**
1552
+ * The posture the session starts in (governance-mode 11.2): `--plan` / `planModeDefault`
1553
+ * still decide `startInPlanMode` exactly as before, and `--govern` / `--plan-govern` win over
1554
+ * them when governance is available. Optional on this shape for the fixture reason above;
1555
+ * `loadConfig` always fills it. Without a governance flag it is `'plan'` when
1556
+ * `startInPlanMode`, else `'build'`.
1557
+ */
1558
+ initialPosture?: Posture;
1537
1559
  /**
1538
1560
  * Lifetime submit count driving the composer hint fade (`HINT_FADE_AFTER`).
1539
1561
  *
@@ -9,6 +9,9 @@ import { isAbsolute } from 'node:path';
9
9
  import { ABSOLUTE_MAX_OUTPUT_TOKENS, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_RETRY_POLICY, MIN_MAX_OUTPUT_TOKENS, RETRY_LIMITS, } from '@aragon-agent/core';
10
10
  import { clampLogLevel } from '../logging/levels.js';
11
11
  import { TEAM_LIMITS } from '../team/limits.js';
12
+ import { DEFAULT_GOVERNANCE_CONFIG } from './governance-config.js';
13
+ // Governance mode (governance-mode 11.1): defined in its own file so this one does not grow.
14
+ export { clampGovernanceConfig, DEFAULT_GOVERNANCE_CONFIG, } from './governance-config.js';
12
15
  // ---------------------------------------------------------------------------
13
16
  // Provider set — only the adapter-backed providers are usable at run time (R5).
14
17
  // ---------------------------------------------------------------------------
@@ -1194,6 +1197,9 @@ export const DEFAULT_CONFIG = {
1194
1197
  // `config list`, so the file would report a section it never wrote. One line
1195
1198
  // here, like the seven above it.
1196
1199
  taskSummary: DEFAULT_TASK_SUMMARY_CONFIG,
1200
+ // And one section later again. Without this line a `config.json` predating governance
1201
+ // resolves `governance` to `undefined`; every reader would then disagree with `config list`.
1202
+ governance: DEFAULT_GOVERNANCE_CONFIG,
1197
1203
  };
1198
1204
  /**
1199
1205
  * Runtime state that used to be stored in `config.json` by mistake
@@ -17,7 +17,7 @@ import { getConfigPath, getSessionsDir } from './app-paths.js';
17
17
  import { registerProfileSecrets, registerSecretsFrom } from '../logging/secret-registry.js';
18
18
  import { adaptLegacyConnectionPatch, ModelProfileConfigError, readModelSettingsDisk } from './model-profile-store.js';
19
19
  import { normalizeModelProfiles } from './model-profiles.js';
20
- import { clampBashConfig, clampJobsConfig, clampPromptsConfig, clampTaskSummaryConfig, CONFIG_VERSION, DEFAULT_CONFIG, clampCompactionConfig, clampFastConfig, clampLogConfig, clampMaxTokens, clampRetryConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampContextWindow, clampUpdateConfig, isAutoToken, stripLegacyStateKeys, stripRetiredConfigKeys, } from './schema.js';
20
+ import { clampBashConfig, clampJobsConfig, clampPromptsConfig, clampTaskSummaryConfig, CONFIG_VERSION, DEFAULT_CONFIG, clampCompactionConfig, clampFastConfig, clampGovernanceConfig, clampLogConfig, clampMaxTokens, clampRetryConfig, clampSkillsConfig, clampTeamConfig, clampTodoConfig, clampContextWindow, clampUpdateConfig, isAutoToken, stripLegacyStateKeys, stripRetiredConfigKeys, } from './schema.js';
21
21
  /**
22
22
  * Normalize `maxTokens` for both directions of the config file.
23
23
  *
@@ -213,6 +213,13 @@ export function normalizePersistedConfig(input) {
213
213
  ...DEFAULT_CONFIG.taskSummary,
214
214
  ...(partial.taskSummary ?? {}),
215
215
  }),
216
+ // And the fourteenth. The READ half supplies a whole section to every user whose
217
+ // `config.json` predates governance, and clamps a hand-edited one: the file cannot
218
+ // carry a `maxRework` the engine would refuse.
219
+ governance: clampGovernanceConfig({
220
+ ...DEFAULT_CONFIG.governance,
221
+ ...(partial.governance ?? {}),
222
+ }),
216
223
  };
217
224
  }
218
225
  /**
@@ -334,6 +341,12 @@ export function mergePersistedConfig(current, patch) {
334
341
  // that must never happen is a FAILED summary call landing on "show nothing"
335
342
  // because a switch the user never touched defaulted away.
336
343
  taskSummary: clampTaskSummaryConfig({ ...current.taskSummary, ...(patch.taskSummary ?? {}) }),
344
+ // The fourteenth section, and the ninth time this file records the same trap.
345
+ // `aragon config set governance.maxRework 9` sends `{ governance: { maxRework: 9 } }`;
346
+ // without this merge the shallow spread REPLACES the section and `enabled` is written
347
+ // absent, and without the clamp 9 reaches the disk and the engine's ceiling is the
348
+ // only thing that stops it. The WRITE half of the two-direction gate.
349
+ governance: clampGovernanceConfig({ ...current.governance, ...(patch.governance ?? {}) }),
337
350
  };
338
351
  return merged;
339
352
  }