@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,539 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Validate Setup - User-friendly setup verification for Devflow.
|
|
4
|
+
|
|
5
|
+
Checks that all components are properly configured and working.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python tooling/scripts/validate_setup.py
|
|
9
|
+
python tooling/scripts/validate_setup.py --verbose
|
|
10
|
+
python tooling/scripts/validate_setup.py --fix
|
|
11
|
+
|
|
12
|
+
Exit codes:
|
|
13
|
+
0 - All checks passed
|
|
14
|
+
1 - One or more checks failed
|
|
15
|
+
2 - Critical error during validation
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from enum import Enum
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Optional
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CheckStatus(Enum):
|
|
29
|
+
"""Status of a validation check."""
|
|
30
|
+
|
|
31
|
+
PASS = "✅"
|
|
32
|
+
FAIL = "❌"
|
|
33
|
+
WARN = "⚠️"
|
|
34
|
+
SKIP = "⏭️"
|
|
35
|
+
INFO = "ℹ️"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class CheckResult:
|
|
40
|
+
"""Result of a validation check."""
|
|
41
|
+
|
|
42
|
+
name: str
|
|
43
|
+
status: CheckStatus
|
|
44
|
+
message: str
|
|
45
|
+
details: Optional[str] = None
|
|
46
|
+
fix_command: Optional[str] = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Colors:
|
|
50
|
+
"""ANSI color codes."""
|
|
51
|
+
|
|
52
|
+
RESET = "\033[0m"
|
|
53
|
+
RED = "\033[31m"
|
|
54
|
+
GREEN = "\033[32m"
|
|
55
|
+
YELLOW = "\033[33m"
|
|
56
|
+
BLUE = "\033[34m"
|
|
57
|
+
CYAN = "\033[36m"
|
|
58
|
+
BOLD = "\033[1m"
|
|
59
|
+
DIM = "\033[2m"
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def disable(cls):
|
|
63
|
+
"""Disable colors (for non-TTY output)."""
|
|
64
|
+
cls.RESET = ""
|
|
65
|
+
cls.RED = ""
|
|
66
|
+
cls.GREEN = ""
|
|
67
|
+
cls.YELLOW = ""
|
|
68
|
+
cls.BLUE = ""
|
|
69
|
+
cls.CYAN = ""
|
|
70
|
+
cls.BOLD = ""
|
|
71
|
+
cls.DIM = ""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# Detect if running in non-TTY
|
|
75
|
+
if not sys.stdout.isatty():
|
|
76
|
+
Colors.disable()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Project paths
|
|
80
|
+
SCRIPT_DIR = Path(__file__).parent
|
|
81
|
+
PROJECT_ROOT = SCRIPT_DIR.parent.parent
|
|
82
|
+
LIB_DIR = SCRIPT_DIR / "lib"
|
|
83
|
+
AUTOMATION_DIR = PROJECT_ROOT / "tooling" / ".automation"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class SetupValidator:
|
|
87
|
+
"""Validates the Devflow setup."""
|
|
88
|
+
|
|
89
|
+
def __init__(self, verbose: bool = False, fix: bool = False):
|
|
90
|
+
self.verbose = verbose
|
|
91
|
+
self.fix = fix
|
|
92
|
+
self.results: list[CheckResult] = []
|
|
93
|
+
|
|
94
|
+
def add_result(self, result: CheckResult):
|
|
95
|
+
"""Add a check result."""
|
|
96
|
+
self.results.append(result)
|
|
97
|
+
self._print_result(result)
|
|
98
|
+
|
|
99
|
+
def _print_result(self, result: CheckResult):
|
|
100
|
+
"""Print a check result."""
|
|
101
|
+
status_color = {
|
|
102
|
+
CheckStatus.PASS: Colors.GREEN,
|
|
103
|
+
CheckStatus.FAIL: Colors.RED,
|
|
104
|
+
CheckStatus.WARN: Colors.YELLOW,
|
|
105
|
+
CheckStatus.SKIP: Colors.DIM,
|
|
106
|
+
CheckStatus.INFO: Colors.BLUE,
|
|
107
|
+
}.get(result.status, "")
|
|
108
|
+
|
|
109
|
+
print(
|
|
110
|
+
f" {result.status.value} {status_color}{result.name}{Colors.RESET}: {result.message}"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if self.verbose and result.details:
|
|
114
|
+
for line in result.details.split("\n"):
|
|
115
|
+
print(f" {Colors.DIM}{line}{Colors.RESET}")
|
|
116
|
+
|
|
117
|
+
if result.status == CheckStatus.FAIL and result.fix_command:
|
|
118
|
+
print(f" {Colors.CYAN}Fix: {result.fix_command}{Colors.RESET}")
|
|
119
|
+
|
|
120
|
+
def check_python_version(self):
|
|
121
|
+
"""Check Python version is supported."""
|
|
122
|
+
version = sys.version_info
|
|
123
|
+
version_str = f"{version.major}.{version.minor}.{version.micro}"
|
|
124
|
+
|
|
125
|
+
if version >= (3, 9):
|
|
126
|
+
self.add_result(
|
|
127
|
+
CheckResult(
|
|
128
|
+
name="Python Version",
|
|
129
|
+
status=CheckStatus.PASS,
|
|
130
|
+
message=f"Python {version_str} is supported",
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
elif version >= (3, 7):
|
|
134
|
+
self.add_result(
|
|
135
|
+
CheckResult(
|
|
136
|
+
name="Python Version",
|
|
137
|
+
status=CheckStatus.WARN,
|
|
138
|
+
message=f"Python {version_str} may work but 3.9+ recommended",
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
else:
|
|
142
|
+
self.add_result(
|
|
143
|
+
CheckResult(
|
|
144
|
+
name="Python Version",
|
|
145
|
+
status=CheckStatus.FAIL,
|
|
146
|
+
message=f"Python {version_str} is not supported (need 3.9+)",
|
|
147
|
+
fix_command="pyenv install 3.11 && pyenv local 3.11",
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def check_project_structure(self):
|
|
152
|
+
"""Check required project structure exists."""
|
|
153
|
+
required_dirs = [
|
|
154
|
+
PROJECT_ROOT / "tooling",
|
|
155
|
+
PROJECT_ROOT / "tooling" / "scripts",
|
|
156
|
+
PROJECT_ROOT / "tooling" / "scripts" / "lib",
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
optional_dirs = [
|
|
160
|
+
AUTOMATION_DIR,
|
|
161
|
+
AUTOMATION_DIR / "costs",
|
|
162
|
+
AUTOMATION_DIR / "costs" / "sessions",
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
all_exist = True
|
|
166
|
+
missing = []
|
|
167
|
+
|
|
168
|
+
for dir_path in required_dirs:
|
|
169
|
+
if not dir_path.exists():
|
|
170
|
+
all_exist = False
|
|
171
|
+
missing.append(str(dir_path.relative_to(PROJECT_ROOT)))
|
|
172
|
+
|
|
173
|
+
if all_exist:
|
|
174
|
+
self.add_result(
|
|
175
|
+
CheckResult(
|
|
176
|
+
name="Project Structure",
|
|
177
|
+
status=CheckStatus.PASS,
|
|
178
|
+
message="All required directories exist",
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
else:
|
|
182
|
+
self.add_result(
|
|
183
|
+
CheckResult(
|
|
184
|
+
name="Project Structure",
|
|
185
|
+
status=CheckStatus.FAIL,
|
|
186
|
+
message=f"Missing directories: {', '.join(missing)}",
|
|
187
|
+
fix_command=f"mkdir -p {' '.join(missing)}",
|
|
188
|
+
)
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Check optional dirs
|
|
192
|
+
for dir_path in optional_dirs:
|
|
193
|
+
if not dir_path.exists():
|
|
194
|
+
if self.fix:
|
|
195
|
+
dir_path.mkdir(parents=True, exist_ok=True)
|
|
196
|
+
self.add_result(
|
|
197
|
+
CheckResult(
|
|
198
|
+
name=f"Directory {dir_path.name}",
|
|
199
|
+
status=CheckStatus.PASS,
|
|
200
|
+
message="Created missing directory",
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
else:
|
|
204
|
+
self.add_result(
|
|
205
|
+
CheckResult(
|
|
206
|
+
name=f"Directory {dir_path.name}",
|
|
207
|
+
status=CheckStatus.WARN,
|
|
208
|
+
message="Optional directory missing (will be created on first use)",
|
|
209
|
+
fix_command=f"mkdir -p {dir_path}",
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
def check_core_modules(self):
|
|
214
|
+
"""Check core Python modules exist and are importable."""
|
|
215
|
+
modules = [
|
|
216
|
+
("cost_tracker", LIB_DIR / "cost_tracker.py"),
|
|
217
|
+
("cost_config", LIB_DIR / "cost_config.py"),
|
|
218
|
+
("cost_display", LIB_DIR / "cost_display.py"),
|
|
219
|
+
("currency_converter", LIB_DIR / "currency_converter.py"),
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
# Add lib to path for imports
|
|
223
|
+
sys.path.insert(0, str(LIB_DIR))
|
|
224
|
+
|
|
225
|
+
for module_name, module_path in modules:
|
|
226
|
+
if not module_path.exists():
|
|
227
|
+
self.add_result(
|
|
228
|
+
CheckResult(
|
|
229
|
+
name=f"Module {module_name}",
|
|
230
|
+
status=CheckStatus.FAIL,
|
|
231
|
+
message=f"File not found: {module_path.name}",
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
continue
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
__import__(module_name)
|
|
238
|
+
self.add_result(
|
|
239
|
+
CheckResult(
|
|
240
|
+
name=f"Module {module_name}",
|
|
241
|
+
status=CheckStatus.PASS,
|
|
242
|
+
message="Imports successfully",
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
except ImportError as e:
|
|
246
|
+
self.add_result(
|
|
247
|
+
CheckResult(
|
|
248
|
+
name=f"Module {module_name}",
|
|
249
|
+
status=CheckStatus.FAIL,
|
|
250
|
+
message=f"Import error: {e}",
|
|
251
|
+
details=str(e),
|
|
252
|
+
)
|
|
253
|
+
)
|
|
254
|
+
except Exception as e:
|
|
255
|
+
self.add_result(
|
|
256
|
+
CheckResult(
|
|
257
|
+
name=f"Module {module_name}",
|
|
258
|
+
status=CheckStatus.WARN,
|
|
259
|
+
message=f"Warning during import: {type(e).__name__}",
|
|
260
|
+
details=str(e),
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
def check_cost_tracker_functionality(self):
|
|
265
|
+
"""Test core cost tracker functionality."""
|
|
266
|
+
try:
|
|
267
|
+
sys.path.insert(0, str(LIB_DIR))
|
|
268
|
+
from cost_tracker import PRICING, CostTracker
|
|
269
|
+
|
|
270
|
+
# Check pricing is defined
|
|
271
|
+
if not PRICING:
|
|
272
|
+
self.add_result(
|
|
273
|
+
CheckResult(
|
|
274
|
+
name="Pricing Configuration",
|
|
275
|
+
status=CheckStatus.FAIL,
|
|
276
|
+
message="No pricing data defined",
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
else:
|
|
280
|
+
models = list(PRICING.keys())
|
|
281
|
+
self.add_result(
|
|
282
|
+
CheckResult(
|
|
283
|
+
name="Pricing Configuration",
|
|
284
|
+
status=CheckStatus.PASS,
|
|
285
|
+
message=f"{len(models)} models configured",
|
|
286
|
+
details=f"Models: {', '.join(models[:5])}...",
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Test cost calculation
|
|
291
|
+
tracker = CostTracker(
|
|
292
|
+
story_key="validation-test", budget_limit_usd=10.00, auto_save=False
|
|
293
|
+
)
|
|
294
|
+
cost = tracker.calculate_cost("sonnet", 1000, 500)
|
|
295
|
+
|
|
296
|
+
if cost > 0:
|
|
297
|
+
self.add_result(
|
|
298
|
+
CheckResult(
|
|
299
|
+
name="Cost Calculation",
|
|
300
|
+
status=CheckStatus.PASS,
|
|
301
|
+
message=f"Calculated ${cost:.6f} for test tokens",
|
|
302
|
+
)
|
|
303
|
+
)
|
|
304
|
+
else:
|
|
305
|
+
self.add_result(
|
|
306
|
+
CheckResult(
|
|
307
|
+
name="Cost Calculation",
|
|
308
|
+
status=CheckStatus.WARN,
|
|
309
|
+
message="Cost calculation returned 0",
|
|
310
|
+
)
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# Test budget checking
|
|
314
|
+
ok, level, msg = tracker.check_budget()
|
|
315
|
+
self.add_result(
|
|
316
|
+
CheckResult(
|
|
317
|
+
name="Budget Monitoring",
|
|
318
|
+
status=CheckStatus.PASS,
|
|
319
|
+
message=f"Budget check working (status: {level})",
|
|
320
|
+
)
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
except Exception as e:
|
|
324
|
+
self.add_result(
|
|
325
|
+
CheckResult(
|
|
326
|
+
name="Cost Tracker",
|
|
327
|
+
status=CheckStatus.FAIL,
|
|
328
|
+
message=f"Error testing cost tracker: {e}",
|
|
329
|
+
details=str(e),
|
|
330
|
+
)
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
def check_environment_config(self):
|
|
334
|
+
"""Check environment configuration."""
|
|
335
|
+
env_vars = {
|
|
336
|
+
"MAX_BUDGET_CONTEXT": ("Budget for context phase", "3.00"),
|
|
337
|
+
"MAX_BUDGET_DEV": ("Budget for development phase", "15.00"),
|
|
338
|
+
"MAX_BUDGET_REVIEW": ("Budget for review phase", "5.00"),
|
|
339
|
+
"COST_DISPLAY_CURRENCY": ("Display currency", "USD"),
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
configured = 0
|
|
343
|
+
for var, (_desc, _default) in env_vars.items():
|
|
344
|
+
value = os.getenv(var)
|
|
345
|
+
if value:
|
|
346
|
+
configured += 1
|
|
347
|
+
if self.verbose:
|
|
348
|
+
self.add_result(
|
|
349
|
+
CheckResult(
|
|
350
|
+
name=f"Env: {var}", status=CheckStatus.INFO, message=f"Set to '{value}'"
|
|
351
|
+
)
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
if configured > 0:
|
|
355
|
+
self.add_result(
|
|
356
|
+
CheckResult(
|
|
357
|
+
name="Environment Variables",
|
|
358
|
+
status=CheckStatus.PASS,
|
|
359
|
+
message=f"{configured}/{len(env_vars)} custom settings configured",
|
|
360
|
+
)
|
|
361
|
+
)
|
|
362
|
+
else:
|
|
363
|
+
self.add_result(
|
|
364
|
+
CheckResult(
|
|
365
|
+
name="Environment Variables",
|
|
366
|
+
status=CheckStatus.INFO,
|
|
367
|
+
message="Using default configuration (all optional)",
|
|
368
|
+
)
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
def check_storage_writable(self):
|
|
372
|
+
"""Check that storage directories are writable."""
|
|
373
|
+
test_dirs = [
|
|
374
|
+
AUTOMATION_DIR / "costs" / "sessions",
|
|
375
|
+
]
|
|
376
|
+
|
|
377
|
+
for test_dir in test_dirs:
|
|
378
|
+
test_dir.mkdir(parents=True, exist_ok=True)
|
|
379
|
+
test_file = test_dir / ".write_test"
|
|
380
|
+
|
|
381
|
+
try:
|
|
382
|
+
test_file.write_text("test")
|
|
383
|
+
test_file.unlink()
|
|
384
|
+
self.add_result(
|
|
385
|
+
CheckResult(
|
|
386
|
+
name=f"Storage: {test_dir.name}",
|
|
387
|
+
status=CheckStatus.PASS,
|
|
388
|
+
message="Directory is writable",
|
|
389
|
+
)
|
|
390
|
+
)
|
|
391
|
+
except PermissionError:
|
|
392
|
+
self.add_result(
|
|
393
|
+
CheckResult(
|
|
394
|
+
name=f"Storage: {test_dir.name}",
|
|
395
|
+
status=CheckStatus.FAIL,
|
|
396
|
+
message="Directory is not writable",
|
|
397
|
+
fix_command=f"chmod 755 {test_dir}",
|
|
398
|
+
)
|
|
399
|
+
)
|
|
400
|
+
except Exception as e:
|
|
401
|
+
self.add_result(
|
|
402
|
+
CheckResult(
|
|
403
|
+
name=f"Storage: {test_dir.name}",
|
|
404
|
+
status=CheckStatus.WARN,
|
|
405
|
+
message=f"Could not verify: {e}",
|
|
406
|
+
)
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
def check_shell_scripts(self):
|
|
410
|
+
"""Check shell scripts are executable."""
|
|
411
|
+
shell_scripts = list(SCRIPT_DIR.glob("*.sh"))
|
|
412
|
+
|
|
413
|
+
non_executable = []
|
|
414
|
+
for script in shell_scripts:
|
|
415
|
+
if not os.access(script, os.X_OK):
|
|
416
|
+
non_executable.append(script.name)
|
|
417
|
+
if self.fix:
|
|
418
|
+
os.chmod(script, 0o755)
|
|
419
|
+
|
|
420
|
+
if not shell_scripts:
|
|
421
|
+
self.add_result(
|
|
422
|
+
CheckResult(
|
|
423
|
+
name="Shell Scripts", status=CheckStatus.INFO, message="No shell scripts found"
|
|
424
|
+
)
|
|
425
|
+
)
|
|
426
|
+
elif non_executable and not self.fix:
|
|
427
|
+
self.add_result(
|
|
428
|
+
CheckResult(
|
|
429
|
+
name="Shell Scripts",
|
|
430
|
+
status=CheckStatus.WARN,
|
|
431
|
+
message=f"{len(non_executable)} scripts not executable",
|
|
432
|
+
fix_command="chmod +x tooling/scripts/*.sh",
|
|
433
|
+
)
|
|
434
|
+
)
|
|
435
|
+
else:
|
|
436
|
+
self.add_result(
|
|
437
|
+
CheckResult(
|
|
438
|
+
name="Shell Scripts",
|
|
439
|
+
status=CheckStatus.PASS,
|
|
440
|
+
message=f"{len(shell_scripts)} scripts are executable",
|
|
441
|
+
)
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
def run_all_checks(self) -> bool:
|
|
445
|
+
"""Run all validation checks."""
|
|
446
|
+
print(f"\n{Colors.BOLD}🔍 Devflow Setup Validation{Colors.RESET}\n")
|
|
447
|
+
print(f" Project: {PROJECT_ROOT}")
|
|
448
|
+
print(f" Python: {sys.executable}")
|
|
449
|
+
print()
|
|
450
|
+
|
|
451
|
+
print(f"{Colors.BOLD}Core Checks:{Colors.RESET}")
|
|
452
|
+
self.check_python_version()
|
|
453
|
+
self.check_project_structure()
|
|
454
|
+
self.check_core_modules()
|
|
455
|
+
|
|
456
|
+
print(f"\n{Colors.BOLD}Functionality Checks:{Colors.RESET}")
|
|
457
|
+
self.check_cost_tracker_functionality()
|
|
458
|
+
|
|
459
|
+
print(f"\n{Colors.BOLD}Configuration Checks:{Colors.RESET}")
|
|
460
|
+
self.check_environment_config()
|
|
461
|
+
self.check_storage_writable()
|
|
462
|
+
self.check_shell_scripts()
|
|
463
|
+
|
|
464
|
+
# Summary
|
|
465
|
+
passed = sum(1 for r in self.results if r.status == CheckStatus.PASS)
|
|
466
|
+
failed = sum(1 for r in self.results if r.status == CheckStatus.FAIL)
|
|
467
|
+
warned = sum(1 for r in self.results if r.status == CheckStatus.WARN)
|
|
468
|
+
|
|
469
|
+
print(f"\n{Colors.BOLD}━━━ Summary ━━━{Colors.RESET}")
|
|
470
|
+
print(f" {Colors.GREEN}Passed:{Colors.RESET} {passed}")
|
|
471
|
+
if warned:
|
|
472
|
+
print(f" {Colors.YELLOW}Warnings:{Colors.RESET} {warned}")
|
|
473
|
+
if failed:
|
|
474
|
+
print(f" {Colors.RED}Failed:{Colors.RESET} {failed}")
|
|
475
|
+
|
|
476
|
+
if failed == 0:
|
|
477
|
+
print(
|
|
478
|
+
f"\n{Colors.GREEN}{Colors.BOLD}✅ All checks passed! Devflow is ready to use.{Colors.RESET}\n"
|
|
479
|
+
)
|
|
480
|
+
return True
|
|
481
|
+
else:
|
|
482
|
+
print(
|
|
483
|
+
f"\n{Colors.RED}{Colors.BOLD}❌ {failed} check(s) failed. Please fix the issues above.{Colors.RESET}"
|
|
484
|
+
)
|
|
485
|
+
if not self.fix:
|
|
486
|
+
print(f" {Colors.DIM}Run with --fix to auto-fix some issues.{Colors.RESET}\n")
|
|
487
|
+
return False
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def main():
|
|
491
|
+
"""Main entry point."""
|
|
492
|
+
parser = argparse.ArgumentParser(
|
|
493
|
+
description="Validate Devflow setup",
|
|
494
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
495
|
+
epilog="""
|
|
496
|
+
Examples:
|
|
497
|
+
python validate_setup.py Run basic validation
|
|
498
|
+
python validate_setup.py -v Run with verbose output
|
|
499
|
+
python validate_setup.py --fix Auto-fix fixable issues
|
|
500
|
+
""",
|
|
501
|
+
)
|
|
502
|
+
parser.add_argument("-v", "--verbose", action="store_true", help="Show detailed output")
|
|
503
|
+
parser.add_argument("--fix", action="store_true", help="Attempt to auto-fix issues")
|
|
504
|
+
parser.add_argument("--json", action="store_true", help="Output results as JSON")
|
|
505
|
+
|
|
506
|
+
args = parser.parse_args()
|
|
507
|
+
|
|
508
|
+
try:
|
|
509
|
+
validator = SetupValidator(verbose=args.verbose, fix=args.fix)
|
|
510
|
+
success = validator.run_all_checks()
|
|
511
|
+
|
|
512
|
+
if args.json:
|
|
513
|
+
results = [
|
|
514
|
+
{
|
|
515
|
+
"name": r.name,
|
|
516
|
+
"status": r.status.name,
|
|
517
|
+
"message": r.message,
|
|
518
|
+
"details": r.details,
|
|
519
|
+
}
|
|
520
|
+
for r in validator.results
|
|
521
|
+
]
|
|
522
|
+
print(json.dumps(results, indent=2))
|
|
523
|
+
|
|
524
|
+
sys.exit(0 if success else 1)
|
|
525
|
+
|
|
526
|
+
except KeyboardInterrupt:
|
|
527
|
+
print("\n\nValidation cancelled.")
|
|
528
|
+
sys.exit(2)
|
|
529
|
+
except Exception as e:
|
|
530
|
+
print(f"\n{Colors.RED}Critical error during validation: {e}{Colors.RESET}")
|
|
531
|
+
if args.verbose:
|
|
532
|
+
import traceback
|
|
533
|
+
|
|
534
|
+
traceback.print_exc()
|
|
535
|
+
sys.exit(2)
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
if __name__ == "__main__":
|
|
539
|
+
main()
|