devforgeai 1.0.4 → 1.0.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.
Files changed (134) hide show
  1. package/CLAUDE.md +120 -0
  2. package/package.json +9 -1
  3. package/src/CLAUDE.md +699 -0
  4. package/src/claude/scripts/README.md +396 -0
  5. package/src/claude/scripts/audit-command-skill-overlap.sh +67 -0
  6. package/src/claude/scripts/check-hooks-fast.sh +70 -0
  7. package/src/claude/scripts/devforgeai-validate +6 -0
  8. package/src/claude/scripts/devforgeai_cli/README.md +531 -0
  9. package/src/claude/scripts/devforgeai_cli/__init__.py +12 -0
  10. package/src/claude/scripts/devforgeai_cli/cli.py +716 -0
  11. package/src/claude/scripts/devforgeai_cli/commands/__init__.py +1 -0
  12. package/src/claude/scripts/devforgeai_cli/commands/check_hooks.py +384 -0
  13. package/src/claude/scripts/devforgeai_cli/commands/invoke_hooks.py +149 -0
  14. package/src/claude/scripts/devforgeai_cli/commands/phase_commands.py +731 -0
  15. package/src/claude/scripts/devforgeai_cli/commands/validate_installation.py +412 -0
  16. package/src/claude/scripts/devforgeai_cli/context_extraction.py +426 -0
  17. package/src/claude/scripts/devforgeai_cli/feedback/AC_TO_TEST_MAPPING.md +636 -0
  18. package/src/claude/scripts/devforgeai_cli/feedback/DELIVERY_SUMMARY.txt +329 -0
  19. package/src/claude/scripts/devforgeai_cli/feedback/README_TEST_SPECS.md +486 -0
  20. package/src/claude/scripts/devforgeai_cli/feedback/TEST_IMPLEMENTATION_GUIDE.md +529 -0
  21. package/src/claude/scripts/devforgeai_cli/feedback/TEST_SPECIFICATIONS.md +2652 -0
  22. package/src/claude/scripts/devforgeai_cli/feedback/TEST_SPECS_INDEX.md +398 -0
  23. package/src/claude/scripts/devforgeai_cli/feedback/__init__.py +34 -0
  24. package/src/claude/scripts/devforgeai_cli/feedback/adaptive_questioning_engine.py +581 -0
  25. package/src/claude/scripts/devforgeai_cli/feedback/aggregation.py +179 -0
  26. package/src/claude/scripts/devforgeai_cli/feedback/commands.py +535 -0
  27. package/src/claude/scripts/devforgeai_cli/feedback/config_defaults.py +58 -0
  28. package/src/claude/scripts/devforgeai_cli/feedback/config_manager.py +423 -0
  29. package/src/claude/scripts/devforgeai_cli/feedback/config_models.py +192 -0
  30. package/src/claude/scripts/devforgeai_cli/feedback/config_schema.py +140 -0
  31. package/src/claude/scripts/devforgeai_cli/feedback/coverage.json +1 -0
  32. package/src/claude/scripts/devforgeai_cli/feedback/feature_flag.py +152 -0
  33. package/src/claude/scripts/devforgeai_cli/feedback/feedback_indexer.py +394 -0
  34. package/src/claude/scripts/devforgeai_cli/feedback/hot_reload.py +226 -0
  35. package/src/claude/scripts/devforgeai_cli/feedback/longitudinal.py +115 -0
  36. package/src/claude/scripts/devforgeai_cli/feedback/models.py +67 -0
  37. package/src/claude/scripts/devforgeai_cli/feedback/question_router.py +236 -0
  38. package/src/claude/scripts/devforgeai_cli/feedback/retrospective.py +233 -0
  39. package/src/claude/scripts/devforgeai_cli/feedback/skip_tracker.py +177 -0
  40. package/src/claude/scripts/devforgeai_cli/feedback/skip_tracking.py +221 -0
  41. package/src/claude/scripts/devforgeai_cli/feedback/template_engine.py +549 -0
  42. package/src/claude/scripts/devforgeai_cli/feedback/validation.py +163 -0
  43. package/src/claude/scripts/devforgeai_cli/headless/__init__.py +30 -0
  44. package/src/claude/scripts/devforgeai_cli/headless/answer_models.py +206 -0
  45. package/src/claude/scripts/devforgeai_cli/headless/answer_resolver.py +204 -0
  46. package/src/claude/scripts/devforgeai_cli/headless/exceptions.py +36 -0
  47. package/src/claude/scripts/devforgeai_cli/headless/pattern_matcher.py +156 -0
  48. package/src/claude/scripts/devforgeai_cli/hooks.py +313 -0
  49. package/src/claude/scripts/devforgeai_cli/metrics/__init__.py +46 -0
  50. package/src/claude/scripts/devforgeai_cli/metrics/command_metrics.py +142 -0
  51. package/src/claude/scripts/devforgeai_cli/metrics/failure_modes.py +152 -0
  52. package/src/claude/scripts/devforgeai_cli/metrics/story_segmentation.py +181 -0
  53. package/src/claude/scripts/devforgeai_cli/orchestrate_hooks.py +780 -0
  54. package/src/claude/scripts/devforgeai_cli/phase_state.py +1229 -0
  55. package/src/claude/scripts/devforgeai_cli/session/__init__.py +30 -0
  56. package/src/claude/scripts/devforgeai_cli/session/checkpoint.py +268 -0
  57. package/src/claude/scripts/devforgeai_cli/tests/__init__.py +1 -0
  58. package/src/claude/scripts/devforgeai_cli/tests/conftest.py +29 -0
  59. package/src/claude/scripts/devforgeai_cli/tests/feedback/TEST_EXECUTION_GUIDE.md +298 -0
  60. package/src/claude/scripts/devforgeai_cli/tests/feedback/__init__.py +3 -0
  61. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_adaptive_questioning_engine.py +2171 -0
  62. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_aggregation.py +476 -0
  63. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_defaults.py +133 -0
  64. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_manager.py +592 -0
  65. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_models.py +373 -0
  66. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_schema.py +130 -0
  67. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_configuration_management.py +1355 -0
  68. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_edge_cases.py +308 -0
  69. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_feature_flag.py +307 -0
  70. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_feedback_indexer.py +384 -0
  71. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_hot_reload.py +580 -0
  72. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_integration.py +402 -0
  73. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_models.py +105 -0
  74. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_question_routing.py +262 -0
  75. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_retrospective.py +333 -0
  76. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracker.py +410 -0
  77. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracking.py +159 -0
  78. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracking_integration.py +1155 -0
  79. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_template_engine.py +1389 -0
  80. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_validation_comprehensive.py +210 -0
  81. package/src/claude/scripts/devforgeai_cli/tests/fixtures/autonomous-deferral-story.md +46 -0
  82. package/src/claude/scripts/devforgeai_cli/tests/fixtures/missing-impl-notes.md +31 -0
  83. package/src/claude/scripts/devforgeai_cli/tests/fixtures/valid-deferral-story.md +46 -0
  84. package/src/claude/scripts/devforgeai_cli/tests/fixtures/valid-story-complete.md +48 -0
  85. package/src/claude/scripts/devforgeai_cli/tests/manual_test_invoke_hooks.sh +200 -0
  86. package/src/claude/scripts/devforgeai_cli/tests/session/DELIVERABLES.md +518 -0
  87. package/src/claude/scripts/devforgeai_cli/tests/session/TEST_SUMMARY.md +468 -0
  88. package/src/claude/scripts/devforgeai_cli/tests/session/__init__.py +6 -0
  89. package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/corrupted-checkpoint.json +1 -0
  90. package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/missing-fields-checkpoint.json +4 -0
  91. package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/valid-checkpoint.json +15 -0
  92. package/src/claude/scripts/devforgeai_cli/tests/session/test_checkpoint.py +851 -0
  93. package/src/claude/scripts/devforgeai_cli/tests/test_check_hooks.py +1886 -0
  94. package/src/claude/scripts/devforgeai_cli/tests/test_depends_on_normalizer.py +171 -0
  95. package/src/claude/scripts/devforgeai_cli/tests/test_dod_validator.py +97 -0
  96. package/src/claude/scripts/devforgeai_cli/tests/test_invoke_hooks.py +1902 -0
  97. package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands.py +320 -0
  98. package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands_error_handling.py +1021 -0
  99. package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands_import.py +697 -0
  100. package/src/claude/scripts/devforgeai_cli/tests/test_phase_state.py +2187 -0
  101. package/src/claude/scripts/devforgeai_cli/tests/test_skip_tracking.py +2141 -0
  102. package/src/claude/scripts/devforgeai_cli/tests/test_skip_tracking_coverage_gap.py +195 -0
  103. package/src/claude/scripts/devforgeai_cli/tests/test_subagent_enforcement.py +539 -0
  104. package/src/claude/scripts/devforgeai_cli/tests/test_validate_installation.py +361 -0
  105. package/src/claude/scripts/devforgeai_cli/utils/__init__.py +11 -0
  106. package/src/claude/scripts/devforgeai_cli/utils/depends_on_normalizer.py +149 -0
  107. package/src/claude/scripts/devforgeai_cli/utils/markdown_parser.py +219 -0
  108. package/src/claude/scripts/devforgeai_cli/utils/story_analyzer.py +249 -0
  109. package/src/claude/scripts/devforgeai_cli/utils/yaml_parser.py +152 -0
  110. package/src/claude/scripts/devforgeai_cli/validators/__init__.py +27 -0
  111. package/src/claude/scripts/devforgeai_cli/validators/ast_grep_validator.py +373 -0
  112. package/src/claude/scripts/devforgeai_cli/validators/context_validator.py +180 -0
  113. package/src/claude/scripts/devforgeai_cli/validators/dod_validator.py +309 -0
  114. package/src/claude/scripts/devforgeai_cli/validators/git_validator.py +107 -0
  115. package/src/claude/scripts/devforgeai_cli/validators/grep_fallback.py +300 -0
  116. package/src/claude/scripts/install_hooks.sh +186 -0
  117. package/src/claude/scripts/invoke_feedback_hooks.sh +59 -0
  118. package/src/claude/scripts/migrate-ac-headers.sh +122 -0
  119. package/src/claude/scripts/plan_file_kb.sh +704 -0
  120. package/src/claude/scripts/requirements.txt +8 -0
  121. package/src/claude/scripts/session_catalog.sh +543 -0
  122. package/src/claude/scripts/setup.py +55 -0
  123. package/src/claude/scripts/start-devforgeai.sh +16 -0
  124. package/src/claude/scripts/statusline.sh +27 -0
  125. package/src/claude/scripts/validate_deferrals.py +344 -0
  126. package/src/claude/skills/devforgeai-qa/SKILL.md +1 -1
  127. package/src/claude/skills/researching-market/SKILL.md +2 -1
  128. package/src/cli/lib/copier.js +13 -1
  129. package/src/claude/skills/designing-systems/scripts/__pycache__/detect_anti_patterns.cpython-312.pyc +0 -0
  130. package/src/claude/skills/designing-systems/scripts/__pycache__/validate_all_context.cpython-312.pyc +0 -0
  131. package/src/claude/skills/designing-systems/scripts/__pycache__/validate_architecture.cpython-312.pyc +0 -0
  132. package/src/claude/skills/designing-systems/scripts/__pycache__/validate_dependencies.cpython-312.pyc +0 -0
  133. package/src/claude/skills/devforgeai-story-creation/scripts/__pycache__/migrate_story_v1_to_v2.cpython-312.pyc +0 -0
  134. package/src/claude/skills/devforgeai-story-creation/scripts/tests/__pycache__/measure_accuracy.cpython-312.pyc +0 -0
@@ -0,0 +1,373 @@
1
+ """
2
+ AstGrepValidator - ast-grep installation detection and management.
3
+
4
+ Handles installation detection, version compatibility checking,
5
+ interactive prompts, and pip-based installation for STORY-115.
6
+ """
7
+
8
+ import os
9
+ import sys
10
+ import re
11
+ import shutil
12
+ import subprocess
13
+ import logging
14
+ from pathlib import Path
15
+ from typing import Tuple, List, Dict, Optional
16
+ from enum import Enum
17
+ from dataclasses import dataclass
18
+
19
+ try:
20
+ import yaml
21
+ except ImportError:
22
+ yaml = None # YAML is optional for basic functionality
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ # =============================================================================
28
+ # Enums and Data Classes
29
+ # =============================================================================
30
+
31
+ class InstallAction(Enum):
32
+ """User choices for missing ast-grep dependency."""
33
+ INSTALL_NOW = "install"
34
+ USE_FALLBACK = "fallback"
35
+ SKIP = "skip"
36
+
37
+
38
+ @dataclass
39
+ class VersionInfo:
40
+ """Parsed semantic version information."""
41
+ major: int
42
+ minor: int
43
+ patch: int
44
+ raw: str
45
+
46
+ def is_compatible(self, min_version: str = "0.40.0", max_version: str = "1.0.0") -> bool:
47
+ """
48
+ Check if version is within compatible range [min_version, max_version).
49
+
50
+ Args:
51
+ min_version: Minimum version (inclusive)
52
+ max_version: Maximum version (exclusive)
53
+
54
+ Returns:
55
+ True if version is compatible
56
+ """
57
+ min_ver = parse_version(min_version)
58
+ max_ver = parse_version(max_version)
59
+
60
+ if not min_ver or not max_ver:
61
+ return False
62
+
63
+ # Check minimum (inclusive)
64
+ if self.major < min_ver.major:
65
+ return False
66
+ if self.major == min_ver.major and self.minor < min_ver.minor:
67
+ return False
68
+ if self.major == min_ver.major and self.minor == min_ver.minor and self.patch < min_ver.patch:
69
+ return False
70
+
71
+ # Check maximum (exclusive)
72
+ if self.major > max_ver.major:
73
+ return False
74
+ if self.major == max_ver.major and self.minor >= max_ver.minor:
75
+ return False
76
+
77
+ return True
78
+
79
+
80
+ # =============================================================================
81
+ # Utility Functions
82
+ # =============================================================================
83
+
84
+ def parse_version(version_string: str) -> Optional[VersionInfo]:
85
+ """
86
+ Parse semantic version string without external libraries.
87
+
88
+ Args:
89
+ version_string: Version string like "0.40.0" or "v0.45.0-beta"
90
+
91
+ Returns:
92
+ VersionInfo object or None if parsing fails
93
+ """
94
+ if not version_string:
95
+ return None
96
+
97
+ # Remove 'v' prefix if present
98
+ version_string = version_string.strip()
99
+ if version_string.startswith('v'):
100
+ version_string = version_string[1:]
101
+
102
+ # Extract major.minor.patch (ignore prerelease/build metadata)
103
+ pattern = r'^(\d+)\.(\d+)\.(\d+)'
104
+ match = re.match(pattern, version_string)
105
+
106
+ if not match:
107
+ return None
108
+
109
+ try:
110
+ major = int(match.group(1))
111
+ minor = int(match.group(2))
112
+ patch = int(match.group(3))
113
+ return VersionInfo(major=major, minor=minor, patch=patch, raw=version_string)
114
+ except (ValueError, IndexError):
115
+ return None
116
+
117
+
118
+ def detect_headless_mode() -> bool:
119
+ """
120
+ Detect if running in CI/headless environment.
121
+
122
+ Checks for common CI environment variables and terminal availability.
123
+
124
+ Returns:
125
+ True if headless mode detected
126
+ """
127
+ # CI environment variables
128
+ if os.environ.get("CI") == "true":
129
+ return True
130
+ if os.environ.get("DEVFORGEAI_HEADLESS") == "true":
131
+ return True
132
+ if os.environ.get("GITHUB_ACTIONS") == "true":
133
+ return True
134
+
135
+ # Non-interactive terminal
136
+ try:
137
+ return not os.isatty(sys.stdin.fileno())
138
+ except Exception:
139
+ return False
140
+
141
+
142
+ def load_config(config_path: Optional[str] = None) -> Dict:
143
+ """
144
+ Load configuration with defaults.
145
+
146
+ Args:
147
+ config_path: Path to config.yaml file
148
+
149
+ Returns:
150
+ Configuration dictionary with defaults
151
+ """
152
+ default_config = {
153
+ 'fallback_mode': False,
154
+ 'min_version': '0.40.0',
155
+ 'max_version': '1.0.0',
156
+ 'allow_auto_install': False
157
+ }
158
+
159
+ if not config_path:
160
+ config_path = "devforgeai/ast-grep/config.yaml"
161
+
162
+ config_file = Path(config_path)
163
+ if not config_file.exists():
164
+ return default_config
165
+
166
+ if not yaml:
167
+ logger.warning("PyYAML not available, using default configuration")
168
+ return default_config
169
+
170
+ try:
171
+ with open(config_file, 'r') as f:
172
+ user_config = yaml.safe_load(f) or {}
173
+ # Merge with defaults
174
+ return {**default_config, **user_config}
175
+ except Exception as e:
176
+ logger.warning(f"Failed to load config from {config_path}: {e}")
177
+ return default_config
178
+
179
+
180
+ # =============================================================================
181
+ # Main Validator Class
182
+ # =============================================================================
183
+
184
+ class AstGrepValidator:
185
+ """Manages ast-grep installation and validation."""
186
+
187
+ def __init__(self, config_path: Optional[str] = None, interactive: bool = True):
188
+ """
189
+ Initialize validator.
190
+
191
+ Args:
192
+ config_path: Path to devforgeai/ast-grep/config.yaml
193
+ interactive: Whether to show prompts (False for CI/headless)
194
+ """
195
+ self.config = load_config(config_path)
196
+ self.interactive = interactive and not detect_headless_mode()
197
+ self._ast_grep_available = None # Lazy initialization
198
+
199
+ def is_installed(self) -> bool:
200
+ """
201
+ Check if ast-grep is available on system.
202
+
203
+ Returns:
204
+ True if ast-grep found in PATH
205
+ """
206
+ return shutil.which("sg") is not None or shutil.which("ast-grep") is not None
207
+
208
+ def get_version(self) -> Optional[VersionInfo]:
209
+ """
210
+ Get installed ast-grep version.
211
+
212
+ Returns:
213
+ VersionInfo object or None if detection fails
214
+ """
215
+ ast_grep_cmd = shutil.which("sg") or shutil.which("ast-grep")
216
+ if not ast_grep_cmd:
217
+ return None
218
+
219
+ try:
220
+ result = subprocess.run(
221
+ [ast_grep_cmd, "--version"],
222
+ capture_output=True,
223
+ text=True,
224
+ timeout=5
225
+ )
226
+
227
+ if result.returncode == 0:
228
+ # Parse version from output like "ast-grep 0.45.0"
229
+ version_match = re.search(r'(\d+\.\d+\.\d+)', result.stdout)
230
+ if version_match:
231
+ return parse_version(version_match.group(1))
232
+
233
+ return None
234
+ except Exception as e:
235
+ logger.debug(f"Failed to get ast-grep version: {e}")
236
+ return None
237
+
238
+ def check_version_compatibility(self) -> Tuple[bool, str]:
239
+ """
240
+ Verify version meets requirements.
241
+
242
+ Returns:
243
+ Tuple of (is_compatible, message)
244
+ """
245
+ version = self.get_version()
246
+
247
+ if not version:
248
+ return False, "Could not determine ast-grep version"
249
+
250
+ min_version = self.config.get('min_version', '0.40.0')
251
+ max_version = self.config.get('max_version', '1.0.0')
252
+
253
+ if version.is_compatible(min_version, max_version):
254
+ return True, f"ast-grep version {version.raw} is compatible"
255
+ else:
256
+ return False, f"ast-grep version {version.raw} is not compatible (requires >={min_version}, <{max_version})"
257
+
258
+ def install_via_pip(self) -> Tuple[bool, str]:
259
+ """
260
+ Install ast-grep-cli via pip.
261
+
262
+ Returns:
263
+ Tuple of (success, message)
264
+ """
265
+ try:
266
+ result = subprocess.run(
267
+ [sys.executable, "-m", "pip", "install", "ast-grep-cli>=0.40.0,<1.0.0"],
268
+ capture_output=True,
269
+ text=True,
270
+ timeout=120
271
+ )
272
+
273
+ if result.returncode == 0:
274
+ return True, "Successfully installed ast-grep-cli"
275
+ else:
276
+ return False, f"Installation failed: {result.stderr}"
277
+
278
+ except subprocess.TimeoutExpired:
279
+ return False, "Installation timeout after 120 seconds"
280
+ except Exception as e:
281
+ return False, f"Installation error: {str(e)}"
282
+
283
+ def prompt_missing_dependency(self) -> InstallAction:
284
+ """
285
+ Display interactive prompt for missing ast-grep.
286
+
287
+ Returns:
288
+ User's chosen action
289
+ """
290
+ if not self.interactive:
291
+ # In headless mode, use config or default to fallback
292
+ if self.config.get('fallback_mode', False):
293
+ return InstallAction.USE_FALLBACK
294
+ return InstallAction.USE_FALLBACK
295
+
296
+ print("\n" + "=" * 60)
297
+ print(" ast-grep Not Found")
298
+ print("=" * 60)
299
+ print("\nast-grep provides semantic code analysis (90-95% accuracy).")
300
+ print("Without it, grep-based fallback will be used (60-75% accuracy).\n")
301
+ print("Options:")
302
+ print(" 1) Install now (pip install ast-grep-cli)")
303
+ print(" 2) Use fallback (grep-based analysis)")
304
+ print(" 3) Skip\n")
305
+
306
+ while True:
307
+ try:
308
+ choice = input("Select option [1-3]: ").strip()
309
+
310
+ if choice == "1":
311
+ return InstallAction.INSTALL_NOW
312
+ elif choice == "2":
313
+ return InstallAction.USE_FALLBACK
314
+ elif choice == "3":
315
+ return InstallAction.SKIP
316
+ else:
317
+ print("Invalid choice. Please enter 1, 2, or 3.")
318
+ except (EOFError, KeyboardInterrupt):
319
+ print("\nOperation cancelled.")
320
+ return InstallAction.SKIP
321
+
322
+ def validate(self, target_path: str) -> Tuple[bool, List[Dict]]:
323
+ """
324
+ Main validation entry point.
325
+
326
+ Args:
327
+ target_path: Directory to scan
328
+
329
+ Returns:
330
+ Tuple of (success, violations)
331
+ """
332
+ violations = []
333
+
334
+ # Check if ast-grep is installed
335
+ if not self.is_installed():
336
+ # Handle missing dependency
337
+ action = self.prompt_missing_dependency()
338
+
339
+ if action == InstallAction.INSTALL_NOW:
340
+ success, message = self.install_via_pip()
341
+ if not success:
342
+ violations.append({
343
+ "severity": "HIGH",
344
+ "error": "ast-grep installation failed",
345
+ "fix": message,
346
+ "analysis_method": "none"
347
+ })
348
+ return False, violations
349
+
350
+ elif action == InstallAction.SKIP:
351
+ violations.append({
352
+ "severity": "INFO",
353
+ "error": "ast-grep not available, scan skipped",
354
+ "fix": "Install ast-grep-cli to enable semantic analysis",
355
+ "analysis_method": "none"
356
+ })
357
+ return True, violations
358
+
359
+ # If USE_FALLBACK, will continue to use grep fallback below
360
+
361
+ # Check version compatibility
362
+ if self.is_installed():
363
+ is_compatible, message = self.check_version_compatibility()
364
+ if not is_compatible:
365
+ violations.append({
366
+ "severity": "MEDIUM",
367
+ "error": "ast-grep version incompatible",
368
+ "fix": message,
369
+ "analysis_method": "version-check"
370
+ })
371
+
372
+ # For now, return success (actual scanning will be implemented in integration)
373
+ return True, violations
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Context Files Validator
4
+
5
+ Validates all 6 DevForgeAI context files exist and are non-empty.
6
+ Quality gate before development begins.
7
+ """
8
+
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import Dict, List, Tuple
12
+
13
+
14
+ # The 6 required context files
15
+ REQUIRED_CONTEXT_FILES = [
16
+ 'tech-stack.md',
17
+ 'source-tree.md',
18
+ 'dependencies.md',
19
+ 'coding-standards.md',
20
+ 'architecture-constraints.md',
21
+ 'anti-patterns.md'
22
+ ]
23
+
24
+
25
+ def check_context_files(directory: str = ".") -> Tuple[bool, List[Dict]]:
26
+ """
27
+ Check if all 6 context files exist and are non-empty.
28
+
29
+ Args:
30
+ directory: Project root directory
31
+
32
+ Returns:
33
+ Tuple of (all_valid, violations)
34
+
35
+ Violations include:
36
+ - CRITICAL: Missing context file
37
+ - HIGH: Empty context file (placeholder)
38
+ - MEDIUM: File exists but <100 bytes (likely incomplete)
39
+ """
40
+ violations = []
41
+ project_root = Path(directory)
42
+ context_dir = project_root / "devforgeai" / "context"
43
+
44
+ # Check if context directory exists
45
+ if not context_dir.exists():
46
+ violations.append({
47
+ 'severity': 'CRITICAL',
48
+ 'file': 'devforgeai/specs/context/',
49
+ 'error': 'Context directory does not exist',
50
+ 'fix': 'Run /create-context to generate context files'
51
+ })
52
+ return False, violations
53
+
54
+ # Check each required file
55
+ for filename in REQUIRED_CONTEXT_FILES:
56
+ file_path = context_dir / filename
57
+
58
+ if not file_path.exists():
59
+ violations.append({
60
+ 'severity': 'CRITICAL',
61
+ 'file': filename,
62
+ 'error': f'Context file missing',
63
+ 'path': str(file_path),
64
+ 'fix': f'Create {filename} or run /create-context'
65
+ })
66
+
67
+ elif file_path.stat().st_size == 0:
68
+ violations.append({
69
+ 'severity': 'HIGH',
70
+ 'file': filename,
71
+ 'error': 'Context file is empty (placeholder)',
72
+ 'path': str(file_path),
73
+ 'fix': f'Populate {filename} with project-specific content'
74
+ })
75
+
76
+ elif file_path.stat().st_size < 100:
77
+ violations.append({
78
+ 'severity': 'MEDIUM',
79
+ 'file': filename,
80
+ 'error': f'Context file is very small ({file_path.stat().st_size} bytes)',
81
+ 'path': str(file_path),
82
+ 'fix': f'Review and expand {filename} content'
83
+ })
84
+
85
+ is_valid = len([v for v in violations if v['severity'] in ['CRITICAL', 'HIGH']]) == 0
86
+
87
+ return is_valid, violations
88
+
89
+
90
+ def validate_context(directory: str = ".", output_format: str = 'text') -> int:
91
+ """
92
+ Main validator entry point.
93
+
94
+ Args:
95
+ directory: Project root directory
96
+ output_format: 'text' or 'json'
97
+
98
+ Returns:
99
+ Exit code: 0 = all files valid, 1 = violations found, 2 = error
100
+ """
101
+ try:
102
+ is_valid, violations = check_context_files(directory)
103
+
104
+ if output_format == 'json':
105
+ import json
106
+ result = {
107
+ 'valid': is_valid,
108
+ 'violations': violations,
109
+ 'directory': str(Path(directory).resolve())
110
+ }
111
+ print(json.dumps(result, indent=2))
112
+
113
+ else:
114
+ # Text output
115
+ if is_valid:
116
+ print(f"✅ All 6 context files validated")
117
+ print(f" Location: {Path(directory).resolve()}/devforgeai/specs/context/")
118
+ return 0
119
+ else:
120
+ print(f"❌ CONTEXT VALIDATION FAILED\n")
121
+
122
+ critical = [v for v in violations if v['severity'] == 'CRITICAL']
123
+ high = [v for v in violations if v['severity'] == 'HIGH']
124
+ medium = [v for v in violations if v['severity'] == 'MEDIUM']
125
+
126
+ if critical:
127
+ print("CRITICAL - Missing Files:")
128
+ for v in critical:
129
+ print(f" • {v['file']}")
130
+ print(f" Error: {v['error']}")
131
+ print(f" Fix: {v['fix']}")
132
+ print()
133
+
134
+ if high:
135
+ print("HIGH - Empty Files:")
136
+ for v in high:
137
+ print(f" • {v['file']}")
138
+ print(f" Path: {v['path']}")
139
+ print(f" Fix: {v['fix']}")
140
+ print()
141
+
142
+ if medium:
143
+ print("MEDIUM - Incomplete Files:")
144
+ for v in medium:
145
+ print(f" • {v['file']} ({v['error']})")
146
+ print()
147
+
148
+ print("=" * 80)
149
+ print("DEVELOPMENT BLOCKED - Context files required")
150
+ print("=" * 80)
151
+ print("\nAll 6 context files must exist:")
152
+ for filename in REQUIRED_CONTEXT_FILES:
153
+ status = "✅" if not any(v['file'] == filename for v in critical + high) else "❌"
154
+ print(f" {status} {filename}")
155
+ print("\nTo create context files:")
156
+ print(" Run: /create-context <project-name>")
157
+ print()
158
+
159
+ return 1
160
+
161
+ except Exception as e:
162
+ print(f"ERROR: Validation failed with exception: {e}", file=sys.stderr)
163
+ return 2
164
+
165
+
166
+ if __name__ == '__main__':
167
+ import argparse
168
+
169
+ parser = argparse.ArgumentParser(
170
+ description='Validate DevForgeAI context files exist and are non-empty'
171
+ )
172
+ parser.add_argument('--directory', default='.',
173
+ help='Project root directory (default: current directory)')
174
+ parser.add_argument('--format', choices=['text', 'json'], default='text',
175
+ help='Output format (default: text)')
176
+
177
+ args = parser.parse_args()
178
+
179
+ exit_code = validate_context(args.directory, args.format)
180
+ sys.exit(exit_code)