1use std::collections::HashMap;
4
5use serde::Deserialize;
6
7use super::super::DEFAULT_EVENT_LOG_MAX_BYTES;
8
9#[derive(Debug, Clone, Deserialize)]
10pub struct TeamConfig {
11 pub name: String,
12 #[serde(default)]
16 pub agent: Option<String>,
17 #[serde(default = "default_workflow_mode")]
18 pub workflow_mode: WorkflowMode,
19 #[serde(default)]
20 pub board: BoardConfig,
21 #[serde(default)]
22 pub standup: StandupConfig,
23 #[serde(default)]
24 pub automation: AutomationConfig,
25 #[serde(default)]
26 pub automation_sender: Option<String>,
27 #[serde(default)]
30 pub external_senders: Vec<String>,
31 #[serde(default = "default_orchestrator_pane")]
32 pub orchestrator_pane: bool,
33 #[serde(default)]
34 pub orchestrator_position: OrchestratorPosition,
35 #[serde(default)]
36 pub layout: Option<LayoutConfig>,
37 #[serde(default)]
38 pub workflow_policy: WorkflowPolicy,
39 #[serde(default)]
40 pub cost: CostConfig,
41 #[serde(default)]
42 pub grafana: GrafanaConfig,
43 #[serde(default)]
47 pub use_shim: bool,
48 #[serde(default)]
51 pub auto_respawn_on_crash: bool,
52 #[serde(default = "default_shim_health_check_interval_secs")]
54 pub shim_health_check_interval_secs: u64,
55 #[serde(default = "default_shim_health_timeout_secs")]
57 pub shim_health_timeout_secs: u64,
58 #[serde(default = "default_shim_shutdown_timeout_secs")]
60 pub shim_shutdown_timeout_secs: u32,
61 #[serde(default = "default_event_log_max_bytes")]
62 pub event_log_max_bytes: u64,
63 #[serde(default = "default_retro_min_duration_secs")]
64 pub retro_min_duration_secs: u64,
65 pub roles: Vec<RoleDef>,
66}
67
68#[derive(Debug, Clone, Deserialize)]
69pub struct GrafanaConfig {
70 #[serde(default)]
71 pub enabled: bool,
72 #[serde(default = "default_grafana_port")]
73 pub port: u16,
74}
75
76impl Default for GrafanaConfig {
77 fn default() -> Self {
78 Self {
79 enabled: false,
80 port: default_grafana_port(),
81 }
82 }
83}
84
85fn default_grafana_port() -> u16 {
86 3000
87}
88
89#[derive(Debug, Clone, Deserialize, Default)]
90pub struct CostConfig {
91 #[serde(default)]
92 pub models: HashMap<String, ModelPricing>,
93}
94
95#[derive(Debug, Clone, Deserialize)]
96pub struct ModelPricing {
97 pub input_usd_per_mtok: f64,
98 #[serde(default)]
99 pub cached_input_usd_per_mtok: f64,
100 #[serde(default)]
101 pub cache_creation_input_usd_per_mtok: Option<f64>,
102 #[serde(default)]
103 pub cache_creation_5m_input_usd_per_mtok: Option<f64>,
104 #[serde(default)]
105 pub cache_creation_1h_input_usd_per_mtok: Option<f64>,
106 #[serde(default)]
107 pub cache_read_input_usd_per_mtok: f64,
108 pub output_usd_per_mtok: f64,
109 #[serde(default)]
110 pub reasoning_output_usd_per_mtok: Option<f64>,
111}
112
113#[derive(Debug, Clone, Deserialize)]
114pub struct WorkflowPolicy {
115 #[serde(default)]
116 pub wip_limit_per_engineer: Option<u32>,
117 #[serde(default)]
118 pub wip_limit_per_reviewer: Option<u32>,
119 #[serde(default = "default_pipeline_starvation_threshold")]
120 pub pipeline_starvation_threshold: Option<usize>,
121 #[serde(default = "default_escalation_threshold_secs")]
122 pub escalation_threshold_secs: u64,
123 #[serde(default = "default_review_nudge_threshold_secs")]
124 pub review_nudge_threshold_secs: u64,
125 #[serde(default = "default_review_timeout_secs")]
126 pub review_timeout_secs: u64,
127 #[serde(default)]
128 pub review_timeout_overrides: HashMap<String, ReviewTimeoutOverride>,
129 #[serde(default)]
130 pub auto_archive_done_after_secs: Option<u64>,
131 #[serde(default)]
132 pub capability_overrides: HashMap<String, Vec<String>>,
133 #[serde(default = "default_stall_threshold_secs")]
134 pub stall_threshold_secs: u64,
135 #[serde(default = "default_max_stall_restarts")]
136 pub max_stall_restarts: u32,
137 #[serde(default = "default_health_check_interval_secs")]
138 pub health_check_interval_secs: u64,
139 #[serde(default = "default_uncommitted_warn_threshold")]
140 pub uncommitted_warn_threshold: usize,
141 #[serde(default)]
142 pub auto_merge: AutoMergePolicy,
143}
144
145#[derive(Debug, Clone, Deserialize)]
149pub struct ReviewTimeoutOverride {
150 pub review_nudge_threshold_secs: Option<u64>,
152 pub review_timeout_secs: Option<u64>,
154}
155
156impl Default for WorkflowPolicy {
157 fn default() -> Self {
158 Self {
159 wip_limit_per_engineer: None,
160 wip_limit_per_reviewer: None,
161 pipeline_starvation_threshold: default_pipeline_starvation_threshold(),
162 escalation_threshold_secs: default_escalation_threshold_secs(),
163 review_nudge_threshold_secs: default_review_nudge_threshold_secs(),
164 review_timeout_secs: default_review_timeout_secs(),
165 review_timeout_overrides: HashMap::new(),
166 auto_archive_done_after_secs: None,
167 capability_overrides: HashMap::new(),
168 stall_threshold_secs: default_stall_threshold_secs(),
169 max_stall_restarts: default_max_stall_restarts(),
170 health_check_interval_secs: default_health_check_interval_secs(),
171 uncommitted_warn_threshold: default_uncommitted_warn_threshold(),
172 auto_merge: AutoMergePolicy::default(),
173 }
174 }
175}
176
177fn default_sensitive_paths() -> Vec<String> {
178 vec![
179 "Cargo.toml".to_string(),
180 "team.yaml".to_string(),
181 ".env".to_string(),
182 ]
183}
184
185#[derive(Debug, Clone, Deserialize)]
186pub struct AutoMergePolicy {
187 #[serde(default)]
188 pub enabled: bool,
189 #[serde(default = "default_max_diff_lines")]
190 pub max_diff_lines: usize,
191 #[serde(default = "default_max_files_changed")]
192 pub max_files_changed: usize,
193 #[serde(default = "default_max_modules_touched")]
194 pub max_modules_touched: usize,
195 #[serde(default = "default_sensitive_paths")]
196 pub sensitive_paths: Vec<String>,
197 #[serde(default = "default_confidence_threshold")]
198 pub confidence_threshold: f64,
199 #[serde(default = "default_require_tests_pass")]
200 pub require_tests_pass: bool,
201}
202
203fn default_max_diff_lines() -> usize {
204 200
205}
206fn default_max_files_changed() -> usize {
207 5
208}
209fn default_max_modules_touched() -> usize {
210 2
211}
212fn default_confidence_threshold() -> f64 {
213 0.8
214}
215fn default_require_tests_pass() -> bool {
216 true
217}
218
219impl Default for AutoMergePolicy {
220 fn default() -> Self {
221 Self {
222 enabled: false,
223 max_diff_lines: default_max_diff_lines(),
224 max_files_changed: default_max_files_changed(),
225 max_modules_touched: default_max_modules_touched(),
226 sensitive_paths: default_sensitive_paths(),
227 confidence_threshold: default_confidence_threshold(),
228 require_tests_pass: default_require_tests_pass(),
229 }
230 }
231}
232
233#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)]
234#[serde(rename_all = "snake_case")]
235pub enum WorkflowMode {
236 #[default]
237 Legacy,
238 Hybrid,
239 WorkflowFirst,
240}
241
242#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
243#[serde(rename_all = "snake_case")]
244pub enum OrchestratorPosition {
245 #[default]
246 Bottom,
247 Left,
248}
249
250impl WorkflowMode {
251 #[cfg_attr(not(test), allow(dead_code))]
252 pub fn legacy_runtime_enabled(self) -> bool {
253 matches!(self, Self::Legacy | Self::Hybrid)
254 }
255
256 #[cfg_attr(not(test), allow(dead_code))]
257 pub fn workflow_state_primary(self) -> bool {
258 matches!(self, Self::WorkflowFirst)
259 }
260
261 pub fn enables_runtime_surface(self) -> bool {
262 matches!(self, Self::Hybrid | Self::WorkflowFirst)
263 }
264
265 pub fn as_str(self) -> &'static str {
266 match self {
267 Self::Legacy => "legacy",
268 Self::Hybrid => "hybrid",
269 Self::WorkflowFirst => "workflow_first",
270 }
271 }
272}
273
274#[derive(Debug, Clone, Deserialize)]
275pub struct BoardConfig {
276 #[serde(default = "default_rotation_threshold")]
277 pub rotation_threshold: u32,
278 #[serde(default = "default_board_auto_dispatch")]
279 pub auto_dispatch: bool,
280 #[serde(default = "default_dispatch_stabilization_delay_secs")]
281 pub dispatch_stabilization_delay_secs: u64,
282 #[serde(default = "default_dispatch_dedup_window_secs")]
283 pub dispatch_dedup_window_secs: u64,
284 #[serde(default = "default_dispatch_manual_cooldown_secs")]
285 pub dispatch_manual_cooldown_secs: u64,
286}
287
288impl Default for BoardConfig {
289 fn default() -> Self {
290 Self {
291 rotation_threshold: default_rotation_threshold(),
292 auto_dispatch: default_board_auto_dispatch(),
293 dispatch_stabilization_delay_secs: default_dispatch_stabilization_delay_secs(),
294 dispatch_dedup_window_secs: default_dispatch_dedup_window_secs(),
295 dispatch_manual_cooldown_secs: default_dispatch_manual_cooldown_secs(),
296 }
297 }
298}
299
300#[derive(Debug, Clone, Deserialize)]
301pub struct StandupConfig {
302 #[serde(default = "default_standup_interval")]
303 pub interval_secs: u64,
304 #[serde(default = "default_output_lines")]
305 pub output_lines: u32,
306}
307
308impl Default for StandupConfig {
309 fn default() -> Self {
310 Self {
311 interval_secs: default_standup_interval(),
312 output_lines: default_output_lines(),
313 }
314 }
315}
316
317#[derive(Debug, Clone, Deserialize)]
318pub struct AutomationConfig {
319 #[serde(default = "default_enabled")]
320 pub timeout_nudges: bool,
321 #[serde(default = "default_enabled")]
322 pub standups: bool,
323 #[serde(default = "default_enabled")]
324 pub failure_pattern_detection: bool,
325 #[serde(default = "default_enabled")]
326 pub triage_interventions: bool,
327 #[serde(default = "default_enabled")]
328 pub review_interventions: bool,
329 #[serde(default = "default_enabled")]
330 pub owned_task_interventions: bool,
331 #[serde(default = "default_enabled")]
332 pub manager_dispatch_interventions: bool,
333 #[serde(default = "default_enabled")]
334 pub architect_utilization_interventions: bool,
335 #[serde(default)]
336 pub replenishment_threshold: Option<usize>,
337 #[serde(default = "default_intervention_idle_grace_secs")]
338 pub intervention_idle_grace_secs: u64,
339 #[serde(default = "default_intervention_cooldown_secs")]
340 pub intervention_cooldown_secs: u64,
341 #[serde(default = "default_utilization_recovery_interval_secs")]
342 pub utilization_recovery_interval_secs: u64,
343 #[serde(default = "default_enabled")]
344 pub commit_before_reset: bool,
345}
346
347impl Default for AutomationConfig {
348 fn default() -> Self {
349 Self {
350 timeout_nudges: default_enabled(),
351 standups: default_enabled(),
352 failure_pattern_detection: default_enabled(),
353 triage_interventions: default_enabled(),
354 review_interventions: default_enabled(),
355 owned_task_interventions: default_enabled(),
356 manager_dispatch_interventions: default_enabled(),
357 architect_utilization_interventions: default_enabled(),
358 replenishment_threshold: None,
359 intervention_idle_grace_secs: default_intervention_idle_grace_secs(),
360 intervention_cooldown_secs: default_intervention_cooldown_secs(),
361 utilization_recovery_interval_secs: default_utilization_recovery_interval_secs(),
362 commit_before_reset: default_enabled(),
363 }
364 }
365}
366
367#[derive(Debug, Clone, Deserialize)]
368pub struct LayoutConfig {
369 pub zones: Vec<ZoneDef>,
370}
371
372#[derive(Debug, Clone, Deserialize)]
373pub struct ZoneDef {
374 pub name: String,
375 pub width_pct: u32,
376 #[serde(default)]
377 pub split: Option<SplitDef>,
378}
379
380#[derive(Debug, Clone, Deserialize)]
381pub struct SplitDef {
382 pub horizontal: u32,
383}
384
385#[derive(Debug, Clone, Deserialize)]
386pub struct RoleDef {
387 pub name: String,
388 pub role_type: RoleType,
389 #[serde(default)]
390 pub agent: Option<String>,
391 #[serde(default = "default_instances")]
392 pub instances: u32,
393 #[serde(default)]
394 pub prompt: Option<String>,
395 #[serde(default)]
396 pub talks_to: Vec<String>,
397 #[serde(default)]
398 pub channel: Option<String>,
399 #[serde(default)]
400 pub channel_config: Option<ChannelConfig>,
401 #[serde(default)]
402 pub nudge_interval_secs: Option<u64>,
403 #[serde(default)]
404 pub receives_standup: Option<bool>,
405 #[serde(default)]
406 pub standup_interval_secs: Option<u64>,
407 #[serde(default)]
408 #[allow(dead_code)] pub owns: Vec<String>,
410 #[serde(default)]
411 pub use_worktrees: bool,
412}
413
414#[derive(Debug, Clone, Deserialize)]
415pub struct ChannelConfig {
416 pub target: String,
417 pub provider: String,
418 #[serde(default)]
421 pub bot_token: Option<String>,
422 #[serde(default)]
424 pub allowed_user_ids: Vec<i64>,
425}
426
427#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
428#[serde(rename_all = "lowercase")]
429pub enum RoleType {
430 User,
431 Architect,
432 Manager,
433 Engineer,
434}
435
436fn default_rotation_threshold() -> u32 {
439 20
440}
441
442fn default_workflow_mode() -> WorkflowMode {
443 WorkflowMode::Legacy
444}
445
446fn default_board_auto_dispatch() -> bool {
447 true
448}
449
450fn default_dispatch_stabilization_delay_secs() -> u64 {
451 30
452}
453
454fn default_dispatch_dedup_window_secs() -> u64 {
455 60
456}
457
458fn default_dispatch_manual_cooldown_secs() -> u64 {
459 30
460}
461
462fn default_standup_interval() -> u64 {
463 300
464}
465
466fn default_pipeline_starvation_threshold() -> Option<usize> {
467 Some(1)
468}
469
470fn default_output_lines() -> u32 {
471 30
472}
473
474fn default_instances() -> u32 {
475 1
476}
477
478fn default_escalation_threshold_secs() -> u64 {
479 3600
480}
481
482fn default_review_nudge_threshold_secs() -> u64 {
483 1800
484}
485
486fn default_review_timeout_secs() -> u64 {
487 7200
488}
489
490fn default_stall_threshold_secs() -> u64 {
491 300
492}
493
494fn default_max_stall_restarts() -> u32 {
495 2
496}
497
498fn default_health_check_interval_secs() -> u64 {
499 60
500}
501
502fn default_uncommitted_warn_threshold() -> usize {
503 200
504}
505
506fn default_enabled() -> bool {
507 true
508}
509
510fn default_orchestrator_pane() -> bool {
511 true
512}
513
514fn default_intervention_idle_grace_secs() -> u64 {
515 60
516}
517
518fn default_intervention_cooldown_secs() -> u64 {
519 120
520}
521
522fn default_utilization_recovery_interval_secs() -> u64 {
523 1200
524}
525
526fn default_event_log_max_bytes() -> u64 {
527 DEFAULT_EVENT_LOG_MAX_BYTES
528}
529
530fn default_retro_min_duration_secs() -> u64 {
531 60
532}
533
534fn default_shim_health_check_interval_secs() -> u64 {
535 60
536}
537
538fn default_shim_health_timeout_secs() -> u64 {
539 120
540}
541
542fn default_shim_shutdown_timeout_secs() -> u32 {
543 30
544}