@pjmendonca/devflow 1.9.0
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/CHANGELOG.md +526 -0
- package/LICENSE +21 -0
- package/README.md +620 -0
- package/bin/devflow-checkpoint.js +10 -0
- package/bin/devflow-collab.js +10 -0
- package/bin/devflow-cost.js +10 -0
- package/bin/devflow-create-persona.js +10 -0
- package/bin/devflow-init.js +10 -0
- package/bin/devflow-memory.js +10 -0
- package/bin/devflow-new-doc.js +10 -0
- package/bin/devflow-personalize.js +10 -0
- package/bin/devflow-setup-checkpoint.js +10 -0
- package/bin/devflow-story.js +10 -0
- package/bin/devflow-tech-debt.js +10 -0
- package/bin/devflow-validate-overrides.js +10 -0
- package/bin/devflow-validate.js +10 -0
- package/bin/devflow-version.js +10 -0
- package/lib/constants.js +30 -0
- package/lib/exec-python.js +78 -0
- package/lib/python-check.js +178 -0
- package/package.json +64 -0
- package/tooling/.automation/agents/architect.md +135 -0
- package/tooling/.automation/agents/ba.md +70 -0
- package/tooling/.automation/agents/dev.md +79 -0
- package/tooling/.automation/agents/maintainer.md +97 -0
- package/tooling/.automation/agents/pm.md +116 -0
- package/tooling/.automation/agents/reviewer.md +141 -0
- package/tooling/.automation/agents/sm.md +61 -0
- package/tooling/.automation/agents/writer.md +193 -0
- package/tooling/.automation/config.ps1.template +61 -0
- package/tooling/.automation/config.sh.template +48 -0
- package/tooling/.automation/memory/.gitkeep +6 -0
- package/tooling/.automation/memory/knowledge/kg_integration-test.json +94 -0
- package/tooling/.automation/memory/knowledge/kg_test-story.json +300 -0
- package/tooling/.automation/memory/shared/shared_integration-test.json +30 -0
- package/tooling/.automation/memory/shared/shared_test-story.json +78 -0
- package/tooling/.automation/overrides/templates/README.md +113 -0
- package/tooling/.automation/overrides/templates/architect/README.md +27 -0
- package/tooling/.automation/overrides/templates/architect/cloud-native.yaml +92 -0
- package/tooling/.automation/overrides/templates/architect/enterprise-architect.yaml +85 -0
- package/tooling/.automation/overrides/templates/architect/pragmatic-minimalist.yaml +88 -0
- package/tooling/.automation/overrides/templates/ba/README.md +27 -0
- package/tooling/.automation/overrides/templates/ba/agile-storyteller.yaml +86 -0
- package/tooling/.automation/overrides/templates/ba/domain-expert.yaml +91 -0
- package/tooling/.automation/overrides/templates/ba/requirements-engineer.yaml +89 -0
- package/tooling/.automation/overrides/templates/dev/README.md +32 -0
- package/tooling/.automation/overrides/templates/dev/junior-mentored.yaml +39 -0
- package/tooling/.automation/overrides/templates/dev/performance-engineer.yaml +43 -0
- package/tooling/.automation/overrides/templates/dev/rapid-prototyper.yaml +52 -0
- package/tooling/.automation/overrides/templates/dev/security-focused.yaml +43 -0
- package/tooling/.automation/overrides/templates/dev/senior-fullstack.yaml +39 -0
- package/tooling/.automation/overrides/templates/maintainer/README.md +27 -0
- package/tooling/.automation/overrides/templates/maintainer/devops-maintainer.yaml +113 -0
- package/tooling/.automation/overrides/templates/maintainer/legacy-steward.yaml +94 -0
- package/tooling/.automation/overrides/templates/maintainer/oss-maintainer.yaml +94 -0
- package/tooling/.automation/overrides/templates/pm/README.md +27 -0
- package/tooling/.automation/overrides/templates/pm/agile-pm.yaml +91 -0
- package/tooling/.automation/overrides/templates/pm/hybrid-delivery.yaml +87 -0
- package/tooling/.automation/overrides/templates/pm/traditional-pm.yaml +91 -0
- package/tooling/.automation/overrides/templates/reviewer/README.md +11 -0
- package/tooling/.automation/overrides/templates/reviewer/mentoring-reviewer.yaml +45 -0
- package/tooling/.automation/overrides/templates/reviewer/quick-sanity.yaml +50 -0
- package/tooling/.automation/overrides/templates/reviewer/thorough-critic.yaml +48 -0
- package/tooling/.automation/overrides/templates/sm/README.md +11 -0
- package/tooling/.automation/overrides/templates/sm/agile-coach.yaml +52 -0
- package/tooling/.automation/overrides/templates/sm/startup-pm.yaml +50 -0
- package/tooling/.automation/overrides/templates/sm/technical-lead.yaml +47 -0
- package/tooling/.automation/overrides/templates/user-profile.template.yaml +62 -0
- package/tooling/.automation/overrides/templates/writer/README.md +27 -0
- package/tooling/.automation/overrides/templates/writer/api-documentarian.yaml +99 -0
- package/tooling/.automation/overrides/templates/writer/docs-as-code.yaml +108 -0
- package/tooling/.automation/overrides/templates/writer/user-guide-author.yaml +100 -0
- package/tooling/completions/DevflowCompletion.ps1 +213 -0
- package/tooling/completions/_run-story +116 -0
- package/tooling/completions/run-story-completion.bash +136 -0
- package/tooling/docs/DOC-STANDARD.md +717 -0
- package/tooling/docs/sprint-status.yaml.template +24 -0
- package/tooling/docs/templates/bug-report.md +234 -0
- package/tooling/docs/templates/migration-spec.md +274 -0
- package/tooling/docs/templates/refactor-spec.md +86 -0
- package/tooling/docs/templates/tech-debt.md +86 -0
- package/tooling/scripts/context_checkpoint.py +556 -0
- package/tooling/scripts/cost_dashboard.py +617 -0
- package/tooling/scripts/create-persona.py +690 -0
- package/tooling/scripts/create-persona.sh +435 -0
- package/tooling/scripts/init-project-workflow.ps1 +651 -0
- package/tooling/scripts/init-project-workflow.py +70 -0
- package/tooling/scripts/init-project-workflow.sh +746 -0
- package/tooling/scripts/lib/__init__.py +35 -0
- package/tooling/scripts/lib/agent_handoff.py +526 -0
- package/tooling/scripts/lib/agent_router.py +698 -0
- package/tooling/scripts/lib/checkpoint-integration.ps1 +245 -0
- package/tooling/scripts/lib/checkpoint-integration.sh +191 -0
- package/tooling/scripts/lib/claude-cli.ps1 +952 -0
- package/tooling/scripts/lib/claude-cli.sh +1293 -0
- package/tooling/scripts/lib/cost_config.py +222 -0
- package/tooling/scripts/lib/cost_display.py +443 -0
- package/tooling/scripts/lib/cost_tracker.py +710 -0
- package/tooling/scripts/lib/currency_converter.py +328 -0
- package/tooling/scripts/lib/errors.py +438 -0
- package/tooling/scripts/lib/override-loader.sh +286 -0
- package/tooling/scripts/lib/pair_programming.py +589 -0
- package/tooling/scripts/lib/shared_memory.py +637 -0
- package/tooling/scripts/lib/swarm_orchestrator.py +689 -0
- package/tooling/scripts/memory_summarize.py +324 -0
- package/tooling/scripts/new-doc.ps1 +405 -0
- package/tooling/scripts/new-doc.py +93 -0
- package/tooling/scripts/new-doc.sh +534 -0
- package/tooling/scripts/personalize_agent.py +385 -0
- package/tooling/scripts/rollback-migration.sh +540 -0
- package/tooling/scripts/run-collab.ps1 +251 -0
- package/tooling/scripts/run-collab.py +605 -0
- package/tooling/scripts/run-collab.sh +110 -0
- package/tooling/scripts/run-story.ps1 +490 -0
- package/tooling/scripts/run-story.py +387 -0
- package/tooling/scripts/run-story.sh +467 -0
- package/tooling/scripts/setup-checkpoint-service.ps1 +219 -0
- package/tooling/scripts/setup-checkpoint-service.py +87 -0
- package/tooling/scripts/setup-checkpoint-service.sh +236 -0
- package/tooling/scripts/tech-debt-tracker.py +608 -0
- package/tooling/scripts/update_version.py +244 -0
- package/tooling/scripts/validate-overrides.py +511 -0
- package/tooling/scripts/validate-overrides.sh +432 -0
- package/tooling/scripts/validate_setup.py +539 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Validate Overrides - Override YAML Validation and Linting
|
|
4
|
+
|
|
5
|
+
This script validates all override YAML files in the overrides directory.
|
|
6
|
+
It checks for:
|
|
7
|
+
- Valid YAML syntax
|
|
8
|
+
- Required fields
|
|
9
|
+
- Valid field types
|
|
10
|
+
- Valid model names
|
|
11
|
+
- Valid budget ranges
|
|
12
|
+
- Schema compliance
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
python validate-overrides.py # Validate all overrides
|
|
16
|
+
python validate-overrides.py dev # Validate specific agent override
|
|
17
|
+
python validate-overrides.py --fix # Auto-fix common issues
|
|
18
|
+
python validate-overrides.py --verbose # Show detailed output
|
|
19
|
+
python validate-overrides.py --json # Output results as JSON
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import re
|
|
26
|
+
import sys
|
|
27
|
+
from dataclasses import dataclass, field
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Colors for terminal output
|
|
33
|
+
class Colors:
|
|
34
|
+
RED = "\033[0;31m"
|
|
35
|
+
GREEN = "\033[0;32m"
|
|
36
|
+
YELLOW = "\033[1;33m"
|
|
37
|
+
BLUE = "\033[0;34m"
|
|
38
|
+
CYAN = "\033[0;36m"
|
|
39
|
+
NC = "\033[0m" # No Color
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Valid values
|
|
43
|
+
VALID_MODELS = ["sonnet", "opus", "haiku"]
|
|
44
|
+
MIN_BUDGET = 0.01
|
|
45
|
+
MAX_BUDGET = 100.00
|
|
46
|
+
VALID_TECH_LEVELS = ["beginner", "intermediate", "advanced", "expert"]
|
|
47
|
+
|
|
48
|
+
# Schema definitions
|
|
49
|
+
OVERRIDE_SCHEMA = {
|
|
50
|
+
"persona": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"properties": {
|
|
53
|
+
"role": {"type": "string"},
|
|
54
|
+
"identity": {"type": "string"},
|
|
55
|
+
"communication_style": {"type": "string"},
|
|
56
|
+
"principles": {"type": "list"},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
"additional_rules": {"type": "list"},
|
|
60
|
+
"memories": {"type": "list"},
|
|
61
|
+
"critical_actions": {"type": "list"},
|
|
62
|
+
"model": {"type": "string", "enum": VALID_MODELS},
|
|
63
|
+
"max_budget_usd": {"type": "number", "min": MIN_BUDGET, "max": MAX_BUDGET},
|
|
64
|
+
"tools": {"type": "string"},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
USER_PROFILE_SCHEMA = {
|
|
68
|
+
"user": {
|
|
69
|
+
"type": "object",
|
|
70
|
+
"properties": {
|
|
71
|
+
"name": {"type": "string"},
|
|
72
|
+
"technical_level": {"type": "string", "enum": VALID_TECH_LEVELS},
|
|
73
|
+
"communication_style": {"type": "string"},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
"preferences": {
|
|
77
|
+
"type": "object",
|
|
78
|
+
"properties": {
|
|
79
|
+
"code_style": {"type": "list"},
|
|
80
|
+
"documentation": {"type": "string"},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
"memories": {"type": "list"},
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class ValidationResult:
|
|
89
|
+
"""Result of validating a single file."""
|
|
90
|
+
|
|
91
|
+
file_path: str
|
|
92
|
+
errors: list[str] = field(default_factory=list)
|
|
93
|
+
warnings: list[str] = field(default_factory=list)
|
|
94
|
+
info: list[str] = field(default_factory=list)
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def is_valid(self) -> bool:
|
|
98
|
+
return len(self.errors) == 0
|
|
99
|
+
|
|
100
|
+
def to_dict(self) -> dict[str, Any]:
|
|
101
|
+
return {
|
|
102
|
+
"file": self.file_path,
|
|
103
|
+
"valid": self.is_valid,
|
|
104
|
+
"errors": self.errors,
|
|
105
|
+
"warnings": self.warnings,
|
|
106
|
+
"info": self.info,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class YAMLValidator:
|
|
111
|
+
"""Simple YAML validator without external dependencies."""
|
|
112
|
+
|
|
113
|
+
def __init__(self, content: str):
|
|
114
|
+
self.content = content
|
|
115
|
+
self.lines = content.split("\n")
|
|
116
|
+
self.data: dict[str, Any] = {}
|
|
117
|
+
|
|
118
|
+
def parse(self) -> tuple[bool, str]:
|
|
119
|
+
"""Parse YAML content and return (success, error_message)."""
|
|
120
|
+
try:
|
|
121
|
+
self._parse_lines()
|
|
122
|
+
return True, ""
|
|
123
|
+
except Exception as e:
|
|
124
|
+
return False, str(e)
|
|
125
|
+
|
|
126
|
+
def _parse_lines(self):
|
|
127
|
+
"""Parse YAML lines into a dictionary structure."""
|
|
128
|
+
current_list_key = None
|
|
129
|
+
current_list: list[str] = []
|
|
130
|
+
|
|
131
|
+
for i, line in enumerate(self.lines, 1):
|
|
132
|
+
# Skip empty lines and comments
|
|
133
|
+
if not line.strip() or line.strip().startswith("#"):
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
# Check for tabs (should be spaces)
|
|
137
|
+
if "\t" in line:
|
|
138
|
+
raise ValueError(f"Line {i}: Contains tabs (use spaces instead)")
|
|
139
|
+
|
|
140
|
+
# Check for unclosed quotes
|
|
141
|
+
double_quotes = line.count('"')
|
|
142
|
+
single_quotes = line.count("'")
|
|
143
|
+
if double_quotes % 2 != 0:
|
|
144
|
+
raise ValueError(f"Line {i}: Unclosed double quote")
|
|
145
|
+
if single_quotes % 2 != 0:
|
|
146
|
+
raise ValueError(f"Line {i}: Unclosed single quote")
|
|
147
|
+
|
|
148
|
+
# Parse key-value pairs and lists
|
|
149
|
+
stripped = line.strip()
|
|
150
|
+
|
|
151
|
+
if stripped.startswith("- "):
|
|
152
|
+
# List item
|
|
153
|
+
if current_list_key:
|
|
154
|
+
value = stripped[2:].strip().strip("\"'")
|
|
155
|
+
current_list.append(value)
|
|
156
|
+
elif ":" in stripped:
|
|
157
|
+
# Save previous list
|
|
158
|
+
if current_list_key and current_list:
|
|
159
|
+
self.data[current_list_key] = current_list
|
|
160
|
+
current_list = []
|
|
161
|
+
|
|
162
|
+
# Key-value pair
|
|
163
|
+
parts = stripped.split(":", 1)
|
|
164
|
+
key = parts[0].strip()
|
|
165
|
+
value = parts[1].strip() if len(parts) > 1 else ""
|
|
166
|
+
|
|
167
|
+
if value == "":
|
|
168
|
+
# This might be a list or nested object
|
|
169
|
+
current_list_key = key
|
|
170
|
+
current_list = []
|
|
171
|
+
else:
|
|
172
|
+
# Simple key-value
|
|
173
|
+
value = value.strip("\"'")
|
|
174
|
+
self.data[key] = value
|
|
175
|
+
current_list_key = None
|
|
176
|
+
|
|
177
|
+
# Save final list
|
|
178
|
+
if current_list_key and current_list:
|
|
179
|
+
self.data[current_list_key] = current_list
|
|
180
|
+
|
|
181
|
+
def check_syntax(self) -> list[str]:
|
|
182
|
+
"""Check for common YAML syntax issues."""
|
|
183
|
+
issues = []
|
|
184
|
+
|
|
185
|
+
for i, line in enumerate(self.lines, 1):
|
|
186
|
+
if not line.strip() or line.strip().startswith("#"):
|
|
187
|
+
continue
|
|
188
|
+
|
|
189
|
+
# Trailing whitespace
|
|
190
|
+
if line.rstrip() != line:
|
|
191
|
+
issues.append(f"Line {i}: Trailing whitespace")
|
|
192
|
+
|
|
193
|
+
# Missing space after colon
|
|
194
|
+
match = re.match(r"^(\s*)([a-zA-Z_][a-zA-Z0-9_]*):([^\s])", line)
|
|
195
|
+
if match and not line.strip().startswith("#"):
|
|
196
|
+
# Allow URLs
|
|
197
|
+
if "http:" not in line and "https:" not in line:
|
|
198
|
+
issues.append(f"Line {i}: Missing space after colon")
|
|
199
|
+
|
|
200
|
+
return issues
|
|
201
|
+
|
|
202
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
203
|
+
"""Get a value from parsed data."""
|
|
204
|
+
return self.data.get(key, default)
|
|
205
|
+
|
|
206
|
+
def has_key(self, key: str) -> bool:
|
|
207
|
+
"""Check if key exists."""
|
|
208
|
+
return key in self.data
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def validate_override_file(
|
|
212
|
+
file_path: Path, agents_dir: Path, verbose: bool = False
|
|
213
|
+
) -> ValidationResult:
|
|
214
|
+
"""Validate an agent override file."""
|
|
215
|
+
result = ValidationResult(file_path=str(file_path))
|
|
216
|
+
|
|
217
|
+
# Extract agent name
|
|
218
|
+
agent_name = file_path.stem.replace(".override", "")
|
|
219
|
+
agent_file = agents_dir / f"{agent_name}.md"
|
|
220
|
+
|
|
221
|
+
# Check if corresponding agent exists
|
|
222
|
+
if not agent_file.exists():
|
|
223
|
+
result.warnings.append(f"No corresponding agent file found: {agent_name}.md")
|
|
224
|
+
else:
|
|
225
|
+
result.info.append(f"Agent file found: {agent_name}.md")
|
|
226
|
+
|
|
227
|
+
# Read and parse file
|
|
228
|
+
try:
|
|
229
|
+
content = file_path.read_text()
|
|
230
|
+
except Exception as e:
|
|
231
|
+
result.errors.append(f"Failed to read file: {e}")
|
|
232
|
+
return result
|
|
233
|
+
|
|
234
|
+
# Parse YAML
|
|
235
|
+
validator = YAMLValidator(content)
|
|
236
|
+
success, error = validator.parse()
|
|
237
|
+
if not success:
|
|
238
|
+
result.errors.append(f"YAML syntax error: {error}")
|
|
239
|
+
return result
|
|
240
|
+
|
|
241
|
+
result.info.append("YAML syntax is valid")
|
|
242
|
+
|
|
243
|
+
# Check for syntax issues
|
|
244
|
+
syntax_issues = validator.check_syntax()
|
|
245
|
+
for issue in syntax_issues:
|
|
246
|
+
result.warnings.append(issue)
|
|
247
|
+
|
|
248
|
+
# Validate model
|
|
249
|
+
model = validator.get("model")
|
|
250
|
+
if model:
|
|
251
|
+
if model in VALID_MODELS:
|
|
252
|
+
result.info.append(f"Model override is valid: {model}")
|
|
253
|
+
else:
|
|
254
|
+
result.errors.append(
|
|
255
|
+
f"Invalid model: '{model}'. Valid options: {', '.join(VALID_MODELS)}"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# Validate budget
|
|
259
|
+
budget = validator.get("max_budget_usd")
|
|
260
|
+
if budget:
|
|
261
|
+
try:
|
|
262
|
+
budget_val = float(budget)
|
|
263
|
+
if budget_val < MIN_BUDGET:
|
|
264
|
+
result.errors.append(f"Budget too low: {budget_val} (minimum: {MIN_BUDGET})")
|
|
265
|
+
elif budget_val > MAX_BUDGET:
|
|
266
|
+
result.warnings.append(
|
|
267
|
+
f"Budget unusually high: {budget_val} (max recommended: {MAX_BUDGET})"
|
|
268
|
+
)
|
|
269
|
+
else:
|
|
270
|
+
result.info.append(f"Budget override is valid: ${budget_val}")
|
|
271
|
+
except ValueError:
|
|
272
|
+
result.errors.append(f"Invalid budget format: '{budget}' (must be a number)")
|
|
273
|
+
|
|
274
|
+
# Check lists
|
|
275
|
+
if validator.has_key("additional_rules"):
|
|
276
|
+
rules = validator.get("additional_rules", [])
|
|
277
|
+
result.info.append(f"Additional rules defined: {len(rules)} rules")
|
|
278
|
+
|
|
279
|
+
if validator.has_key("memories"):
|
|
280
|
+
memories = validator.get("memories", [])
|
|
281
|
+
result.info.append(f"Memories defined: {len(memories)} items")
|
|
282
|
+
|
|
283
|
+
if validator.has_key("critical_actions"):
|
|
284
|
+
actions = validator.get("critical_actions", [])
|
|
285
|
+
result.info.append(f"Critical actions defined: {len(actions)} actions")
|
|
286
|
+
|
|
287
|
+
return result
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def validate_user_profile(file_path: Path, verbose: bool = False) -> ValidationResult:
|
|
291
|
+
"""Validate the user profile file."""
|
|
292
|
+
result = ValidationResult(file_path=str(file_path))
|
|
293
|
+
|
|
294
|
+
if not file_path.exists():
|
|
295
|
+
result.warnings.append("No user-profile.yaml found")
|
|
296
|
+
return result
|
|
297
|
+
|
|
298
|
+
# Read and parse file
|
|
299
|
+
try:
|
|
300
|
+
content = file_path.read_text()
|
|
301
|
+
except Exception as e:
|
|
302
|
+
result.errors.append(f"Failed to read file: {e}")
|
|
303
|
+
return result
|
|
304
|
+
|
|
305
|
+
# Parse YAML
|
|
306
|
+
validator = YAMLValidator(content)
|
|
307
|
+
success, error = validator.parse()
|
|
308
|
+
if not success:
|
|
309
|
+
result.errors.append(f"YAML syntax error: {error}")
|
|
310
|
+
return result
|
|
311
|
+
|
|
312
|
+
result.info.append("YAML syntax is valid")
|
|
313
|
+
|
|
314
|
+
# Check for syntax issues
|
|
315
|
+
syntax_issues = validator.check_syntax()
|
|
316
|
+
for issue in syntax_issues:
|
|
317
|
+
result.warnings.append(issue)
|
|
318
|
+
|
|
319
|
+
# Check user section (basic check since our parser is simple)
|
|
320
|
+
if "user" not in content:
|
|
321
|
+
result.warnings.append("No user section found in profile")
|
|
322
|
+
else:
|
|
323
|
+
result.info.append("User section found")
|
|
324
|
+
|
|
325
|
+
return result
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def fix_file(file_path: Path) -> list[str]:
|
|
329
|
+
"""Auto-fix common issues in a file."""
|
|
330
|
+
fixes = []
|
|
331
|
+
|
|
332
|
+
try:
|
|
333
|
+
content = file_path.read_text()
|
|
334
|
+
original = content
|
|
335
|
+
|
|
336
|
+
# Fix trailing whitespace
|
|
337
|
+
lines = content.split("\n")
|
|
338
|
+
fixed_lines = [line.rstrip() for line in lines]
|
|
339
|
+
if lines != fixed_lines:
|
|
340
|
+
fixes.append("Fixed trailing whitespace")
|
|
341
|
+
|
|
342
|
+
# Fix tabs
|
|
343
|
+
content = "\n".join(fixed_lines)
|
|
344
|
+
if "\t" in content:
|
|
345
|
+
content = content.replace("\t", " ")
|
|
346
|
+
fixes.append("Converted tabs to spaces")
|
|
347
|
+
|
|
348
|
+
# Write if changed
|
|
349
|
+
if content != original:
|
|
350
|
+
# Backup
|
|
351
|
+
backup_path = file_path.with_suffix(file_path.suffix + ".bak")
|
|
352
|
+
backup_path.write_text(original)
|
|
353
|
+
fixes.append(f"Backup saved to {backup_path.name}")
|
|
354
|
+
|
|
355
|
+
# Write fixed content
|
|
356
|
+
file_path.write_text(content)
|
|
357
|
+
else:
|
|
358
|
+
fixes.append("No changes needed")
|
|
359
|
+
|
|
360
|
+
except Exception as e:
|
|
361
|
+
fixes.append(f"Error fixing file: {e}")
|
|
362
|
+
|
|
363
|
+
return fixes
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def print_result(result: ValidationResult, verbose: bool = False):
|
|
367
|
+
"""Print validation result to terminal."""
|
|
368
|
+
print()
|
|
369
|
+
print(f"{Colors.BLUE}Validating:{Colors.NC} {os.path.basename(result.file_path)}")
|
|
370
|
+
|
|
371
|
+
for error in result.errors:
|
|
372
|
+
print(f"{Colors.RED} ✗ ERROR:{Colors.NC} {error}")
|
|
373
|
+
|
|
374
|
+
for warning in result.warnings:
|
|
375
|
+
print(f"{Colors.YELLOW} ⚠ WARNING:{Colors.NC} {warning}")
|
|
376
|
+
|
|
377
|
+
if verbose:
|
|
378
|
+
for info in result.info:
|
|
379
|
+
print(f"{Colors.GREEN} ✓{Colors.NC} {info}")
|
|
380
|
+
else:
|
|
381
|
+
# Show success messages only for key validations
|
|
382
|
+
for info in result.info:
|
|
383
|
+
if any(key in info for key in ["syntax is valid", "override is valid", "defined:"]):
|
|
384
|
+
print(f"{Colors.GREEN} ✓{Colors.NC} {info}")
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def main():
|
|
388
|
+
parser = argparse.ArgumentParser(description="Validate override YAML files")
|
|
389
|
+
parser.add_argument("target", nargs="?", help="Specific agent name to validate")
|
|
390
|
+
parser.add_argument("--fix", action="store_true", help="Auto-fix common issues")
|
|
391
|
+
parser.add_argument("--verbose", "-v", action="store_true", help="Show detailed output")
|
|
392
|
+
parser.add_argument("--json", action="store_true", help="Output results as JSON")
|
|
393
|
+
args = parser.parse_args()
|
|
394
|
+
|
|
395
|
+
# Find directories
|
|
396
|
+
script_dir = Path(__file__).parent
|
|
397
|
+
project_root = script_dir.parent
|
|
398
|
+
overrides_dir = project_root / ".automation" / "overrides"
|
|
399
|
+
agents_dir = project_root / ".automation" / "agents"
|
|
400
|
+
|
|
401
|
+
if not overrides_dir.exists():
|
|
402
|
+
print(f"{Colors.RED}Error: Overrides directory not found: {overrides_dir}{Colors.NC}")
|
|
403
|
+
sys.exit(1)
|
|
404
|
+
|
|
405
|
+
results: list[ValidationResult] = []
|
|
406
|
+
|
|
407
|
+
# Print header
|
|
408
|
+
if not args.json:
|
|
409
|
+
print()
|
|
410
|
+
print(
|
|
411
|
+
f"{Colors.CYAN}═══════════════════════════════════════════════════════════════{Colors.NC}"
|
|
412
|
+
)
|
|
413
|
+
print(f"{Colors.CYAN} OVERRIDE VALIDATOR (Python){Colors.NC}")
|
|
414
|
+
print(
|
|
415
|
+
f"{Colors.CYAN}═══════════════════════════════════════════════════════════════{Colors.NC}"
|
|
416
|
+
)
|
|
417
|
+
print()
|
|
418
|
+
print(f"{Colors.BLUE}Scanning:{Colors.NC} {overrides_dir}")
|
|
419
|
+
|
|
420
|
+
if args.target:
|
|
421
|
+
# Validate specific override
|
|
422
|
+
file_path = overrides_dir / f"{args.target}.override.yaml"
|
|
423
|
+
if not file_path.exists():
|
|
424
|
+
print(f"{Colors.RED}Error: Override file not found: {file_path}{Colors.NC}")
|
|
425
|
+
sys.exit(1)
|
|
426
|
+
|
|
427
|
+
if args.fix:
|
|
428
|
+
fixes = fix_file(file_path)
|
|
429
|
+
if not args.json:
|
|
430
|
+
print(f"\n{Colors.YELLOW}Auto-fixing:{Colors.NC} {file_path.name}")
|
|
431
|
+
for fix in fixes:
|
|
432
|
+
print(f"{Colors.GREEN} ✓{Colors.NC} {fix}")
|
|
433
|
+
|
|
434
|
+
result = validate_override_file(file_path, agents_dir, args.verbose)
|
|
435
|
+
results.append(result)
|
|
436
|
+
else:
|
|
437
|
+
# Validate all overrides
|
|
438
|
+
|
|
439
|
+
# User profile first
|
|
440
|
+
profile_path = overrides_dir / "user-profile.yaml"
|
|
441
|
+
if profile_path.exists():
|
|
442
|
+
if args.fix:
|
|
443
|
+
fixes = fix_file(profile_path)
|
|
444
|
+
if not args.json:
|
|
445
|
+
print(f"\n{Colors.YELLOW}Auto-fixing:{Colors.NC} {profile_path.name}")
|
|
446
|
+
for fix in fixes:
|
|
447
|
+
print(f"{Colors.GREEN} ✓{Colors.NC} {fix}")
|
|
448
|
+
|
|
449
|
+
result = validate_user_profile(profile_path, args.verbose)
|
|
450
|
+
results.append(result)
|
|
451
|
+
|
|
452
|
+
# All override files
|
|
453
|
+
for file_path in sorted(overrides_dir.glob("*.override.yaml")):
|
|
454
|
+
if args.fix:
|
|
455
|
+
fixes = fix_file(file_path)
|
|
456
|
+
if not args.json:
|
|
457
|
+
print(f"\n{Colors.YELLOW}Auto-fixing:{Colors.NC} {file_path.name}")
|
|
458
|
+
for fix in fixes:
|
|
459
|
+
print(f"{Colors.GREEN} ✓{Colors.NC} {fix}")
|
|
460
|
+
|
|
461
|
+
result = validate_override_file(file_path, agents_dir, args.verbose)
|
|
462
|
+
results.append(result)
|
|
463
|
+
|
|
464
|
+
# Output results
|
|
465
|
+
if args.json:
|
|
466
|
+
output = {
|
|
467
|
+
"results": [r.to_dict() for r in results],
|
|
468
|
+
"summary": {
|
|
469
|
+
"total": len(results),
|
|
470
|
+
"valid": sum(1 for r in results if r.is_valid),
|
|
471
|
+
"errors": sum(len(r.errors) for r in results),
|
|
472
|
+
"warnings": sum(len(r.warnings) for r in results),
|
|
473
|
+
},
|
|
474
|
+
}
|
|
475
|
+
print(json.dumps(output, indent=2))
|
|
476
|
+
else:
|
|
477
|
+
# Print results
|
|
478
|
+
for result in results:
|
|
479
|
+
print_result(result, args.verbose)
|
|
480
|
+
|
|
481
|
+
# Summary
|
|
482
|
+
total_errors = sum(len(r.errors) for r in results)
|
|
483
|
+
total_warnings = sum(len(r.warnings) for r in results)
|
|
484
|
+
|
|
485
|
+
print()
|
|
486
|
+
print(
|
|
487
|
+
f"{Colors.CYAN}═══════════════════════════════════════════════════════════════{Colors.NC}"
|
|
488
|
+
)
|
|
489
|
+
print(f"{Colors.CYAN} VALIDATION SUMMARY{Colors.NC}")
|
|
490
|
+
print(
|
|
491
|
+
f"{Colors.CYAN}═══════════════════════════════════════════════════════════════{Colors.NC}"
|
|
492
|
+
)
|
|
493
|
+
print()
|
|
494
|
+
print(f" Files validated: {Colors.GREEN}{len(results)}{Colors.NC}")
|
|
495
|
+
print(f" Errors: {Colors.RED}{total_errors}{Colors.NC}")
|
|
496
|
+
print(f" Warnings: {Colors.YELLOW}{total_warnings}{Colors.NC}")
|
|
497
|
+
print()
|
|
498
|
+
|
|
499
|
+
if total_errors > 0:
|
|
500
|
+
print(f"{Colors.RED}❌ Validation failed with {total_errors} error(s){Colors.NC}")
|
|
501
|
+
sys.exit(1)
|
|
502
|
+
elif total_warnings > 0:
|
|
503
|
+
print(
|
|
504
|
+
f"{Colors.YELLOW}⚠️ Validation passed with {total_warnings} warning(s){Colors.NC}"
|
|
505
|
+
)
|
|
506
|
+
else:
|
|
507
|
+
print(f"{Colors.GREEN}✅ All validations passed!{Colors.NC}")
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
if __name__ == "__main__":
|
|
511
|
+
main()
|