anvil-dev-framework 0.1.6
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.
- package/README.md +719 -0
- package/VERSION +1 -0
- package/docs/ANVIL-REPO-IMPLEMENTATION-PLAN.md +441 -0
- package/docs/FIRST-SKILL-TUTORIAL.md +408 -0
- package/docs/INSTALLATION-RETRO-NOTES.md +458 -0
- package/docs/INSTALLATION.md +984 -0
- package/docs/anvil-hud.md +469 -0
- package/docs/anvil-init.md +255 -0
- package/docs/anvil-state.md +210 -0
- package/docs/boris-cherny-ralph-wiggum-insights.md +608 -0
- package/docs/command-reference.md +2022 -0
- package/docs/hooks-tts.md +368 -0
- package/docs/implementation-guide.md +810 -0
- package/docs/linear-github-integration.md +247 -0
- package/docs/local-issues.md +677 -0
- package/docs/patterns/README.md +419 -0
- package/docs/planning-responsibilities.md +139 -0
- package/docs/session-workflow.md +573 -0
- package/docs/simplification-plan-template.md +297 -0
- package/docs/simplification-principles.md +129 -0
- package/docs/specifications/CCS-RALPH-INTEGRATION-DESIGN.md +633 -0
- package/docs/specifications/CCS-RESEARCH-REPORT.md +169 -0
- package/docs/specifications/PLAN-ANV-verification-ralph-wiggum.md +403 -0
- package/docs/specifications/PLAN-parallel-tracks-anvil-memory-ccs.md +494 -0
- package/docs/specifications/SPEC-ANV-VRW/component-01-verify.md +208 -0
- package/docs/specifications/SPEC-ANV-VRW/component-02-stop-gate.md +226 -0
- package/docs/specifications/SPEC-ANV-VRW/component-03-posttooluse.md +209 -0
- package/docs/specifications/SPEC-ANV-VRW/component-04-ralph-wiggum.md +604 -0
- package/docs/specifications/SPEC-ANV-VRW/component-05-atomic-actions.md +311 -0
- package/docs/specifications/SPEC-ANV-VRW/component-06-verify-subagent.md +264 -0
- package/docs/specifications/SPEC-ANV-VRW/component-07-claude-md.md +363 -0
- package/docs/specifications/SPEC-ANV-VRW/index.md +182 -0
- package/docs/specifications/SPEC-ANV-anvil-memory.md +573 -0
- package/docs/specifications/SPEC-ANV-context-checkpoints.md +781 -0
- package/docs/specifications/SPEC-ANV-verification-ralph-wiggum.md +789 -0
- package/docs/sync.md +122 -0
- package/global/CLAUDE.md +140 -0
- package/global/agents/verify-app.md +164 -0
- package/global/commands/anvil-settings.md +527 -0
- package/global/commands/anvil-sync.md +121 -0
- package/global/commands/change.md +197 -0
- package/global/commands/clarify.md +252 -0
- package/global/commands/cleanup.md +292 -0
- package/global/commands/commit-push-pr.md +207 -0
- package/global/commands/decay-review.md +127 -0
- package/global/commands/discover.md +158 -0
- package/global/commands/doc-coverage.md +122 -0
- package/global/commands/evidence.md +307 -0
- package/global/commands/explore.md +121 -0
- package/global/commands/force-exit.md +135 -0
- package/global/commands/handoff.md +191 -0
- package/global/commands/healthcheck.md +302 -0
- package/global/commands/hud.md +84 -0
- package/global/commands/insights.md +319 -0
- package/global/commands/linear-setup.md +184 -0
- package/global/commands/lint-fix.md +198 -0
- package/global/commands/orient.md +510 -0
- package/global/commands/plan.md +228 -0
- package/global/commands/ralph.md +346 -0
- package/global/commands/ready.md +182 -0
- package/global/commands/release.md +305 -0
- package/global/commands/retro.md +96 -0
- package/global/commands/shard.md +166 -0
- package/global/commands/spec.md +227 -0
- package/global/commands/sprint.md +184 -0
- package/global/commands/tasks.md +228 -0
- package/global/commands/test-and-commit.md +151 -0
- package/global/commands/validate.md +132 -0
- package/global/commands/verify.md +251 -0
- package/global/commands/weekly-review.md +156 -0
- package/global/hooks/__pycache__/ralph_context_monitor.cpython-314.pyc +0 -0
- package/global/hooks/__pycache__/statusline_agent_sync.cpython-314.pyc +0 -0
- package/global/hooks/anvil_memory_observe.ts +322 -0
- package/global/hooks/anvil_memory_session.ts +166 -0
- package/global/hooks/anvil_memory_stop.ts +187 -0
- package/global/hooks/parse_transcript.py +116 -0
- package/global/hooks/post_merge_cleanup.sh +132 -0
- package/global/hooks/post_tool_format.sh +215 -0
- package/global/hooks/ralph_context_monitor.py +240 -0
- package/global/hooks/ralph_stop.sh +502 -0
- package/global/hooks/statusline.sh +1110 -0
- package/global/hooks/statusline_agent_sync.py +224 -0
- package/global/hooks/stop_gate.sh +250 -0
- package/global/lib/.claude/anvil-state.json +21 -0
- package/global/lib/__pycache__/agent_registry.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/claim_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/coderabbit_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/config_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/coordination_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/doc_coverage_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/gate_logger.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/github_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/hygiene_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/issue_models.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/issue_provider.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/linear_data_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/linear_provider.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/local_provider.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/quality_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/ralph_state.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/state_manager.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/transcript_parser.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/verification_runner.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/verify_iteration.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/verify_subagent.cpython-314.pyc +0 -0
- package/global/lib/agent_registry.py +995 -0
- package/global/lib/anvil-state.sh +435 -0
- package/global/lib/claim_service.py +515 -0
- package/global/lib/coderabbit_service.py +314 -0
- package/global/lib/config_service.py +423 -0
- package/global/lib/coordination_service.py +331 -0
- package/global/lib/doc_coverage_service.py +1305 -0
- package/global/lib/gate_logger.py +316 -0
- package/global/lib/github_service.py +310 -0
- package/global/lib/handoff_generator.py +775 -0
- package/global/lib/hygiene_service.py +712 -0
- package/global/lib/issue_models.py +257 -0
- package/global/lib/issue_provider.py +339 -0
- package/global/lib/linear_data_service.py +210 -0
- package/global/lib/linear_provider.py +987 -0
- package/global/lib/linear_provider.py.backup +671 -0
- package/global/lib/local_provider.py +486 -0
- package/global/lib/orient_fast.py +457 -0
- package/global/lib/quality_service.py +470 -0
- package/global/lib/ralph_prompt_generator.py +563 -0
- package/global/lib/ralph_state.py +1202 -0
- package/global/lib/state_manager.py +417 -0
- package/global/lib/transcript_parser.py +597 -0
- package/global/lib/verification_runner.py +557 -0
- package/global/lib/verify_iteration.py +490 -0
- package/global/lib/verify_subagent.py +250 -0
- package/global/skills/README.md +155 -0
- package/global/skills/quality-gates/SKILL.md +252 -0
- package/global/skills/skill-template/SKILL.md +109 -0
- package/global/skills/testing-strategies/SKILL.md +337 -0
- package/global/templates/CHANGE-template.md +105 -0
- package/global/templates/HANDOFF-template.md +63 -0
- package/global/templates/PLAN-template.md +111 -0
- package/global/templates/SPEC-template.md +93 -0
- package/global/templates/ralph/PROMPT.md.template +89 -0
- package/global/templates/ralph/fix_plan.md.template +31 -0
- package/global/templates/ralph/progress.txt.template +23 -0
- package/global/tests/__pycache__/test_doc_coverage.cpython-314.pyc +0 -0
- package/global/tests/test_doc_coverage.py +520 -0
- package/global/tests/test_issue_models.py +299 -0
- package/global/tests/test_local_provider.py +323 -0
- package/global/tools/README.md +178 -0
- package/global/tools/__pycache__/anvil-hud.cpython-314.pyc +0 -0
- package/global/tools/anvil-hud.py +3622 -0
- package/global/tools/anvil-hud.py.bak +3318 -0
- package/global/tools/anvil-issue.py +432 -0
- package/global/tools/anvil-memory/CLAUDE.md +49 -0
- package/global/tools/anvil-memory/README.md +42 -0
- package/global/tools/anvil-memory/bun.lock +25 -0
- package/global/tools/anvil-memory/bunfig.toml +9 -0
- package/global/tools/anvil-memory/package.json +23 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/context-monitor.test.ts +535 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/edge-cases.test.ts +645 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/fixtures.ts +363 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/index.ts +8 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/integration.test.ts +417 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/prompt-generator.test.ts +571 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/ralph-stop.test.ts +440 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/test-utils.ts +252 -0
- package/global/tools/anvil-memory/src/__tests__/commands.test.ts +657 -0
- package/global/tools/anvil-memory/src/__tests__/db.test.ts +641 -0
- package/global/tools/anvil-memory/src/__tests__/hooks.test.ts +272 -0
- package/global/tools/anvil-memory/src/__tests__/performance.test.ts +427 -0
- package/global/tools/anvil-memory/src/__tests__/test-utils.ts +113 -0
- package/global/tools/anvil-memory/src/commands/checkpoint.ts +197 -0
- package/global/tools/anvil-memory/src/commands/get.ts +115 -0
- package/global/tools/anvil-memory/src/commands/init.ts +94 -0
- package/global/tools/anvil-memory/src/commands/observe.ts +163 -0
- package/global/tools/anvil-memory/src/commands/search.ts +112 -0
- package/global/tools/anvil-memory/src/db.ts +638 -0
- package/global/tools/anvil-memory/src/index.ts +205 -0
- package/global/tools/anvil-memory/src/types.ts +122 -0
- package/global/tools/anvil-memory/tsconfig.json +29 -0
- package/global/tools/ralph-loop.sh +359 -0
- package/package.json +45 -0
- package/scripts/anvil +822 -0
- package/scripts/extract_patterns.py +222 -0
- package/scripts/init-project.sh +541 -0
- package/scripts/install.sh +229 -0
- package/scripts/postinstall.js +41 -0
- package/scripts/rollback.sh +188 -0
- package/scripts/sync.sh +623 -0
- package/scripts/test-statusline.sh +248 -0
- package/scripts/update_claude_md.py +224 -0
- package/scripts/verify.sh +255 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
config_service.py - Configuration management for HUD (ANV-116-118)
|
|
4
|
+
|
|
5
|
+
Manages HUD configuration including budget thresholds, refresh intervals,
|
|
6
|
+
and feature toggles. Supports both global defaults and per-project overrides.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
from config_service import get_config
|
|
10
|
+
|
|
11
|
+
config = get_config()
|
|
12
|
+
print(config.budget.agent_warn_threshold) # $5.00
|
|
13
|
+
|
|
14
|
+
Configuration files:
|
|
15
|
+
- ~/.anvil/hud-config.yaml (global defaults)
|
|
16
|
+
- .anvil/hud-config.yaml (project overrides)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
import yaml
|
|
26
|
+
HAS_YAML = True
|
|
27
|
+
except ImportError:
|
|
28
|
+
HAS_YAML = False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class BudgetConfig:
|
|
33
|
+
"""Budget threshold configuration."""
|
|
34
|
+
# Billing model: "api" (pay-per-use) or "subscription" (Pro/Max flat rate)
|
|
35
|
+
# When "subscription", cost alerts are disabled (costs shown for info only)
|
|
36
|
+
billing_model: Literal["api", "subscription"] = "api"
|
|
37
|
+
agent_warn_threshold: float = 5.0 # Warn when agent exceeds $5
|
|
38
|
+
agent_crit_threshold: float = 10.0 # Critical when agent exceeds $10
|
|
39
|
+
total_warn_threshold: float = 15.0 # Warn when total exceeds $15
|
|
40
|
+
total_crit_threshold: float = 25.0 # Critical when total exceeds $25
|
|
41
|
+
daily_budget: Optional[float] = None # Optional daily budget limit
|
|
42
|
+
weekly_budget: Optional[float] = None # Optional weekly budget limit
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class ContextConfig:
|
|
47
|
+
"""Context usage threshold configuration."""
|
|
48
|
+
warn_threshold_pct: int = 80 # Warn at 80% context
|
|
49
|
+
crit_threshold_pct: int = 90 # Critical at 90% context
|
|
50
|
+
compaction_reminder: bool = True # Show compaction reminders
|
|
51
|
+
estimated_turns_warn: int = 10 # Warn when <10 turns estimated
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class CoordinationConfig:
|
|
56
|
+
"""Coordination/branch age configuration."""
|
|
57
|
+
stale_branch_hours: int = 4 # Branch stale after 4 hours
|
|
58
|
+
conflict_detection: bool = True # Enable conflict detection
|
|
59
|
+
lock_timeout_minutes: int = 30 # File lock timeout
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class RefreshConfig:
|
|
64
|
+
"""Refresh interval configuration."""
|
|
65
|
+
agent_refresh_seconds: float = 2.0 # Agent list refresh
|
|
66
|
+
quality_cache_seconds: float = 30.0 # Quality check cache TTL
|
|
67
|
+
github_cache_seconds: float = 30.0 # GitHub status cache TTL
|
|
68
|
+
coderabbit_cache_seconds: float = 60.0 # CodeRabbit cache TTL
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class DisplayConfig:
|
|
73
|
+
"""Display/UI configuration."""
|
|
74
|
+
show_cost_panel: bool = True
|
|
75
|
+
show_task_panel: bool = True
|
|
76
|
+
show_quality_panel: bool = True
|
|
77
|
+
show_coordination_panel: bool = True
|
|
78
|
+
truncate_agent_id_length: int = 16
|
|
79
|
+
max_issues_shown: int = 5
|
|
80
|
+
max_locks_shown: int = 5
|
|
81
|
+
max_conflicts_shown: int = 3
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class IntegrationConfig:
|
|
86
|
+
"""External integration configuration."""
|
|
87
|
+
enable_linear: bool = True
|
|
88
|
+
enable_github: bool = True
|
|
89
|
+
enable_coderabbit: bool = True
|
|
90
|
+
enable_quality_checks: bool = True
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class HUDModeConfig:
|
|
95
|
+
"""HUD v3 mode configuration (ANV-128)."""
|
|
96
|
+
default_mode: Literal["focused", "full"] = "focused" # Default to Focused Mode
|
|
97
|
+
remember_last_tab: bool = True # Remember tab across sessions
|
|
98
|
+
show_tab_bar: bool = True # Show tab bar in Focused Mode
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass
|
|
102
|
+
class HUDConfig:
|
|
103
|
+
"""Main HUD configuration."""
|
|
104
|
+
budget: BudgetConfig = field(default_factory=BudgetConfig)
|
|
105
|
+
context: ContextConfig = field(default_factory=ContextConfig)
|
|
106
|
+
coordination: CoordinationConfig = field(default_factory=CoordinationConfig)
|
|
107
|
+
refresh: RefreshConfig = field(default_factory=RefreshConfig)
|
|
108
|
+
display: DisplayConfig = field(default_factory=DisplayConfig)
|
|
109
|
+
integrations: IntegrationConfig = field(default_factory=IntegrationConfig)
|
|
110
|
+
hud: HUDModeConfig = field(default_factory=HUDModeConfig) # HUD v3 (ANV-128)
|
|
111
|
+
|
|
112
|
+
# Feature flags
|
|
113
|
+
demo_mode: bool = False
|
|
114
|
+
debug_mode: bool = False
|
|
115
|
+
notifications_enabled: bool = True
|
|
116
|
+
sound_enabled: bool = False
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _deep_update(base: Dict[str, Any], updates: Dict[str, Any]) -> Dict[str, Any]:
|
|
120
|
+
"""Recursively update a dict with another dict."""
|
|
121
|
+
result = dict(base)
|
|
122
|
+
for key, value in updates.items():
|
|
123
|
+
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
|
124
|
+
result[key] = _deep_update(result[key], value)
|
|
125
|
+
else:
|
|
126
|
+
result[key] = value
|
|
127
|
+
return result
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _dict_to_config(data: Dict[str, Any]) -> HUDConfig:
|
|
131
|
+
"""Convert a dictionary to HUDConfig."""
|
|
132
|
+
config = HUDConfig()
|
|
133
|
+
|
|
134
|
+
if "budget" in data:
|
|
135
|
+
for key, value in data["budget"].items():
|
|
136
|
+
if hasattr(config.budget, key):
|
|
137
|
+
setattr(config.budget, key, value)
|
|
138
|
+
|
|
139
|
+
if "context" in data:
|
|
140
|
+
for key, value in data["context"].items():
|
|
141
|
+
if hasattr(config.context, key):
|
|
142
|
+
setattr(config.context, key, value)
|
|
143
|
+
|
|
144
|
+
if "coordination" in data:
|
|
145
|
+
for key, value in data["coordination"].items():
|
|
146
|
+
if hasattr(config.coordination, key):
|
|
147
|
+
setattr(config.coordination, key, value)
|
|
148
|
+
|
|
149
|
+
if "refresh" in data:
|
|
150
|
+
for key, value in data["refresh"].items():
|
|
151
|
+
if hasattr(config.refresh, key):
|
|
152
|
+
setattr(config.refresh, key, value)
|
|
153
|
+
|
|
154
|
+
if "display" in data:
|
|
155
|
+
for key, value in data["display"].items():
|
|
156
|
+
if hasattr(config.display, key):
|
|
157
|
+
setattr(config.display, key, value)
|
|
158
|
+
|
|
159
|
+
if "integrations" in data:
|
|
160
|
+
for key, value in data["integrations"].items():
|
|
161
|
+
if hasattr(config.integrations, key):
|
|
162
|
+
setattr(config.integrations, key, value)
|
|
163
|
+
|
|
164
|
+
# HUD v3 mode config (ANV-128)
|
|
165
|
+
if "hud" in data:
|
|
166
|
+
for key, value in data["hud"].items():
|
|
167
|
+
if hasattr(config.hud, key):
|
|
168
|
+
setattr(config.hud, key, value)
|
|
169
|
+
|
|
170
|
+
# Top-level flags
|
|
171
|
+
for key in ["demo_mode", "debug_mode", "notifications_enabled", "sound_enabled"]:
|
|
172
|
+
if key in data:
|
|
173
|
+
setattr(config, key, data[key])
|
|
174
|
+
|
|
175
|
+
return config
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class ConfigService:
|
|
179
|
+
"""Service for loading and managing HUD configuration (ANV-116-118)."""
|
|
180
|
+
|
|
181
|
+
def __init__(self, project_path: Optional[str] = None):
|
|
182
|
+
"""Initialize the config service.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
project_path: Optional project path for project-specific config
|
|
186
|
+
"""
|
|
187
|
+
self.project_path = Path(project_path) if project_path else None
|
|
188
|
+
self._config: Optional[HUDConfig] = None
|
|
189
|
+
self._config_sources: List[str] = []
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def config(self) -> HUDConfig:
|
|
193
|
+
"""Get the current configuration, loading if needed."""
|
|
194
|
+
if self._config is None:
|
|
195
|
+
self._config = self._load_config()
|
|
196
|
+
return self._config
|
|
197
|
+
|
|
198
|
+
def reload(self) -> HUDConfig:
|
|
199
|
+
"""Force reload configuration from disk."""
|
|
200
|
+
self._config = None
|
|
201
|
+
return self.config
|
|
202
|
+
|
|
203
|
+
def get_config_sources(self) -> List[str]:
|
|
204
|
+
"""Get list of config files that were loaded."""
|
|
205
|
+
return self._config_sources
|
|
206
|
+
|
|
207
|
+
def _load_config(self) -> HUDConfig:
|
|
208
|
+
"""Load configuration from files, merging global and project configs."""
|
|
209
|
+
self._config_sources = []
|
|
210
|
+
merged_data: Dict[str, Any] = {}
|
|
211
|
+
|
|
212
|
+
# Load global config from ~/.anvil/hud-config.yaml
|
|
213
|
+
global_config_path = Path.home() / ".anvil" / "hud-config.yaml"
|
|
214
|
+
if global_config_path.exists():
|
|
215
|
+
global_data = self._load_yaml_file(global_config_path)
|
|
216
|
+
if global_data:
|
|
217
|
+
merged_data = _deep_update(merged_data, global_data)
|
|
218
|
+
self._config_sources.append(str(global_config_path))
|
|
219
|
+
|
|
220
|
+
# Load project config from .anvil/hud-config.yaml
|
|
221
|
+
if self.project_path:
|
|
222
|
+
project_config_path = self.project_path / ".anvil" / "hud-config.yaml"
|
|
223
|
+
if project_config_path.exists():
|
|
224
|
+
project_data = self._load_yaml_file(project_config_path)
|
|
225
|
+
if project_data:
|
|
226
|
+
merged_data = _deep_update(merged_data, project_data)
|
|
227
|
+
self._config_sources.append(str(project_config_path))
|
|
228
|
+
|
|
229
|
+
# Also check for env var overrides
|
|
230
|
+
merged_data = self._apply_env_overrides(merged_data)
|
|
231
|
+
|
|
232
|
+
return _dict_to_config(merged_data)
|
|
233
|
+
|
|
234
|
+
def _load_yaml_file(self, path: Path) -> Optional[Dict[str, Any]]:
|
|
235
|
+
"""Load a YAML config file."""
|
|
236
|
+
if not HAS_YAML:
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
with open(path, "r") as f:
|
|
241
|
+
return yaml.safe_load(f) or {}
|
|
242
|
+
except Exception:
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
def _apply_env_overrides(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
246
|
+
"""Apply environment variable overrides to config."""
|
|
247
|
+
# Support env var overrides for common settings
|
|
248
|
+
env_mappings = {
|
|
249
|
+
"ANVIL_HUD_DEMO": ("demo_mode", lambda x: x.lower() == "true"),
|
|
250
|
+
"ANVIL_HUD_DEBUG": ("debug_mode", lambda x: x.lower() == "true"),
|
|
251
|
+
# Billing model: "api" (pay-per-use) or "subscription" (Pro/Max)
|
|
252
|
+
"ANVIL_BILLING_MODEL": ("budget.billing_model", lambda x: x.lower() if x.lower() in ["api", "subscription"] else "api"),
|
|
253
|
+
"ANVIL_BUDGET_WARN": ("budget.agent_warn_threshold", float),
|
|
254
|
+
"ANVIL_BUDGET_CRIT": ("budget.agent_crit_threshold", float),
|
|
255
|
+
"ANVIL_CONTEXT_WARN": ("context.warn_threshold_pct", int),
|
|
256
|
+
"ANVIL_CONTEXT_CRIT": ("context.crit_threshold_pct", int),
|
|
257
|
+
"ANVIL_STALE_HOURS": ("coordination.stale_branch_hours", int),
|
|
258
|
+
"ANVIL_REFRESH_SECONDS": ("refresh.agent_refresh_seconds", float),
|
|
259
|
+
# HUD v3 mode (ANV-128)
|
|
260
|
+
"ANVIL_HUD_MODE": ("hud.default_mode", lambda x: x.lower() if x.lower() in ["focused", "full"] else "focused"),
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
for env_var, (config_path, converter) in env_mappings.items():
|
|
264
|
+
value = os.environ.get(env_var)
|
|
265
|
+
if value is not None:
|
|
266
|
+
try:
|
|
267
|
+
converted = converter(value)
|
|
268
|
+
# Set nested value
|
|
269
|
+
parts = config_path.split(".")
|
|
270
|
+
if len(parts) == 1:
|
|
271
|
+
data[parts[0]] = converted
|
|
272
|
+
else:
|
|
273
|
+
if parts[0] not in data:
|
|
274
|
+
data[parts[0]] = {}
|
|
275
|
+
data[parts[0]][parts[1]] = converted
|
|
276
|
+
except (ValueError, TypeError):
|
|
277
|
+
pass
|
|
278
|
+
|
|
279
|
+
return data
|
|
280
|
+
|
|
281
|
+
def save_global_config(self, config: HUDConfig) -> bool:
|
|
282
|
+
"""Save configuration to global config file."""
|
|
283
|
+
if not HAS_YAML:
|
|
284
|
+
return False
|
|
285
|
+
|
|
286
|
+
config_path = Path.home() / ".anvil" / "hud-config.yaml"
|
|
287
|
+
return self._save_config(config, config_path)
|
|
288
|
+
|
|
289
|
+
def save_project_config(self, config: HUDConfig) -> bool:
|
|
290
|
+
"""Save configuration to project config file."""
|
|
291
|
+
if not HAS_YAML or not self.project_path:
|
|
292
|
+
return False
|
|
293
|
+
|
|
294
|
+
config_path = self.project_path / ".anvil" / "hud-config.yaml"
|
|
295
|
+
return self._save_config(config, config_path)
|
|
296
|
+
|
|
297
|
+
def _save_config(self, config: HUDConfig, path: Path) -> bool:
|
|
298
|
+
"""Save configuration to a file."""
|
|
299
|
+
try:
|
|
300
|
+
# Ensure directory exists
|
|
301
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
302
|
+
|
|
303
|
+
# Convert config to dict
|
|
304
|
+
data = self._config_to_dict(config)
|
|
305
|
+
|
|
306
|
+
with open(path, "w") as f:
|
|
307
|
+
yaml.dump(data, f, default_flow_style=False, sort_keys=False)
|
|
308
|
+
|
|
309
|
+
return True
|
|
310
|
+
except Exception:
|
|
311
|
+
return False
|
|
312
|
+
|
|
313
|
+
def _config_to_dict(self, config: HUDConfig) -> Dict[str, Any]:
|
|
314
|
+
"""Convert HUDConfig to a dictionary for saving."""
|
|
315
|
+
return {
|
|
316
|
+
"budget": {
|
|
317
|
+
"billing_model": config.budget.billing_model, # "api" or "subscription"
|
|
318
|
+
"agent_warn_threshold": config.budget.agent_warn_threshold,
|
|
319
|
+
"agent_crit_threshold": config.budget.agent_crit_threshold,
|
|
320
|
+
"total_warn_threshold": config.budget.total_warn_threshold,
|
|
321
|
+
"total_crit_threshold": config.budget.total_crit_threshold,
|
|
322
|
+
"daily_budget": config.budget.daily_budget,
|
|
323
|
+
"weekly_budget": config.budget.weekly_budget,
|
|
324
|
+
},
|
|
325
|
+
"context": {
|
|
326
|
+
"warn_threshold_pct": config.context.warn_threshold_pct,
|
|
327
|
+
"crit_threshold_pct": config.context.crit_threshold_pct,
|
|
328
|
+
"compaction_reminder": config.context.compaction_reminder,
|
|
329
|
+
"estimated_turns_warn": config.context.estimated_turns_warn,
|
|
330
|
+
},
|
|
331
|
+
"coordination": {
|
|
332
|
+
"stale_branch_hours": config.coordination.stale_branch_hours,
|
|
333
|
+
"conflict_detection": config.coordination.conflict_detection,
|
|
334
|
+
"lock_timeout_minutes": config.coordination.lock_timeout_minutes,
|
|
335
|
+
},
|
|
336
|
+
"refresh": {
|
|
337
|
+
"agent_refresh_seconds": config.refresh.agent_refresh_seconds,
|
|
338
|
+
"quality_cache_seconds": config.refresh.quality_cache_seconds,
|
|
339
|
+
"github_cache_seconds": config.refresh.github_cache_seconds,
|
|
340
|
+
"coderabbit_cache_seconds": config.refresh.coderabbit_cache_seconds,
|
|
341
|
+
},
|
|
342
|
+
"display": {
|
|
343
|
+
"show_cost_panel": config.display.show_cost_panel,
|
|
344
|
+
"show_task_panel": config.display.show_task_panel,
|
|
345
|
+
"show_quality_panel": config.display.show_quality_panel,
|
|
346
|
+
"show_coordination_panel": config.display.show_coordination_panel,
|
|
347
|
+
"truncate_agent_id_length": config.display.truncate_agent_id_length,
|
|
348
|
+
"max_issues_shown": config.display.max_issues_shown,
|
|
349
|
+
"max_locks_shown": config.display.max_locks_shown,
|
|
350
|
+
"max_conflicts_shown": config.display.max_conflicts_shown,
|
|
351
|
+
},
|
|
352
|
+
"integrations": {
|
|
353
|
+
"enable_linear": config.integrations.enable_linear,
|
|
354
|
+
"enable_github": config.integrations.enable_github,
|
|
355
|
+
"enable_coderabbit": config.integrations.enable_coderabbit,
|
|
356
|
+
"enable_quality_checks": config.integrations.enable_quality_checks,
|
|
357
|
+
},
|
|
358
|
+
# HUD v3 mode config (ANV-128)
|
|
359
|
+
"hud": {
|
|
360
|
+
"default_mode": config.hud.default_mode,
|
|
361
|
+
"remember_last_tab": config.hud.remember_last_tab,
|
|
362
|
+
"show_tab_bar": config.hud.show_tab_bar,
|
|
363
|
+
},
|
|
364
|
+
"demo_mode": config.demo_mode,
|
|
365
|
+
"debug_mode": config.debug_mode,
|
|
366
|
+
"notifications_enabled": config.notifications_enabled,
|
|
367
|
+
"sound_enabled": config.sound_enabled,
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
# Singleton instance
|
|
372
|
+
_service: Optional[ConfigService] = None
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def get_config_service(project_path: Optional[str] = None) -> ConfigService:
|
|
376
|
+
"""Get singleton config service instance."""
|
|
377
|
+
global _service
|
|
378
|
+
# Normalize paths for comparison (handles relative/absolute, trailing slashes, symlinks)
|
|
379
|
+
norm_project = Path(project_path).resolve() if project_path else None
|
|
380
|
+
norm_existing = _service.project_path.resolve() if _service and _service.project_path else None
|
|
381
|
+
|
|
382
|
+
if _service is None or (norm_project and norm_project != norm_existing):
|
|
383
|
+
_service = ConfigService(project_path)
|
|
384
|
+
return _service
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def get_config(project_path: Optional[str] = None) -> HUDConfig:
|
|
388
|
+
"""Convenience function to get config directly."""
|
|
389
|
+
return get_config_service(project_path).config
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def generate_default_config() -> str:
|
|
393
|
+
"""Generate a default config file content."""
|
|
394
|
+
if not HAS_YAML:
|
|
395
|
+
return "# YAML not available. Install pyyaml to use config files."
|
|
396
|
+
|
|
397
|
+
config = HUDConfig()
|
|
398
|
+
service = ConfigService()
|
|
399
|
+
data = service._config_to_dict(config)
|
|
400
|
+
|
|
401
|
+
header = """# Anvil HUD Configuration
|
|
402
|
+
# This file configures the Anvil HUD multi-agent dashboard.
|
|
403
|
+
#
|
|
404
|
+
# Location:
|
|
405
|
+
# Global: ~/.anvil/hud-config.yaml
|
|
406
|
+
# Project: .anvil/hud-config.yaml (overrides global)
|
|
407
|
+
#
|
|
408
|
+
# Environment variables can override settings:
|
|
409
|
+
# ANVIL_HUD_DEMO=true - Enable demo mode
|
|
410
|
+
# ANVIL_HUD_DEBUG=true - Enable debug mode
|
|
411
|
+
# ANVIL_HUD_MODE=focused - Default mode: focused or full (ANV-128)
|
|
412
|
+
# ANVIL_BUDGET_WARN=5.0 - Agent budget warning threshold
|
|
413
|
+
# ANVIL_CONTEXT_WARN=80 - Context usage warning %
|
|
414
|
+
# ANVIL_STALE_HOURS=4 - Stale branch threshold hours
|
|
415
|
+
# ANVIL_REFRESH_SECONDS=2.0 - Agent refresh interval
|
|
416
|
+
|
|
417
|
+
"""
|
|
418
|
+
return header + yaml.dump(data, default_flow_style=False, sort_keys=False)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
if __name__ == "__main__":
|
|
422
|
+
# Generate and print default config
|
|
423
|
+
print(generate_default_config())
|