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.
- package/CLAUDE.md +120 -0
- package/package.json +9 -1
- package/src/CLAUDE.md +699 -0
- package/src/claude/scripts/README.md +396 -0
- package/src/claude/scripts/audit-command-skill-overlap.sh +67 -0
- package/src/claude/scripts/check-hooks-fast.sh +70 -0
- package/src/claude/scripts/devforgeai-validate +6 -0
- package/src/claude/scripts/devforgeai_cli/README.md +531 -0
- package/src/claude/scripts/devforgeai_cli/__init__.py +12 -0
- package/src/claude/scripts/devforgeai_cli/cli.py +716 -0
- package/src/claude/scripts/devforgeai_cli/commands/__init__.py +1 -0
- package/src/claude/scripts/devforgeai_cli/commands/check_hooks.py +384 -0
- package/src/claude/scripts/devforgeai_cli/commands/invoke_hooks.py +149 -0
- package/src/claude/scripts/devforgeai_cli/commands/phase_commands.py +731 -0
- package/src/claude/scripts/devforgeai_cli/commands/validate_installation.py +412 -0
- package/src/claude/scripts/devforgeai_cli/context_extraction.py +426 -0
- package/src/claude/scripts/devforgeai_cli/feedback/AC_TO_TEST_MAPPING.md +636 -0
- package/src/claude/scripts/devforgeai_cli/feedback/DELIVERY_SUMMARY.txt +329 -0
- package/src/claude/scripts/devforgeai_cli/feedback/README_TEST_SPECS.md +486 -0
- package/src/claude/scripts/devforgeai_cli/feedback/TEST_IMPLEMENTATION_GUIDE.md +529 -0
- package/src/claude/scripts/devforgeai_cli/feedback/TEST_SPECIFICATIONS.md +2652 -0
- package/src/claude/scripts/devforgeai_cli/feedback/TEST_SPECS_INDEX.md +398 -0
- package/src/claude/scripts/devforgeai_cli/feedback/__init__.py +34 -0
- package/src/claude/scripts/devforgeai_cli/feedback/adaptive_questioning_engine.py +581 -0
- package/src/claude/scripts/devforgeai_cli/feedback/aggregation.py +179 -0
- package/src/claude/scripts/devforgeai_cli/feedback/commands.py +535 -0
- package/src/claude/scripts/devforgeai_cli/feedback/config_defaults.py +58 -0
- package/src/claude/scripts/devforgeai_cli/feedback/config_manager.py +423 -0
- package/src/claude/scripts/devforgeai_cli/feedback/config_models.py +192 -0
- package/src/claude/scripts/devforgeai_cli/feedback/config_schema.py +140 -0
- package/src/claude/scripts/devforgeai_cli/feedback/coverage.json +1 -0
- package/src/claude/scripts/devforgeai_cli/feedback/feature_flag.py +152 -0
- package/src/claude/scripts/devforgeai_cli/feedback/feedback_indexer.py +394 -0
- package/src/claude/scripts/devforgeai_cli/feedback/hot_reload.py +226 -0
- package/src/claude/scripts/devforgeai_cli/feedback/longitudinal.py +115 -0
- package/src/claude/scripts/devforgeai_cli/feedback/models.py +67 -0
- package/src/claude/scripts/devforgeai_cli/feedback/question_router.py +236 -0
- package/src/claude/scripts/devforgeai_cli/feedback/retrospective.py +233 -0
- package/src/claude/scripts/devforgeai_cli/feedback/skip_tracker.py +177 -0
- package/src/claude/scripts/devforgeai_cli/feedback/skip_tracking.py +221 -0
- package/src/claude/scripts/devforgeai_cli/feedback/template_engine.py +549 -0
- package/src/claude/scripts/devforgeai_cli/feedback/validation.py +163 -0
- package/src/claude/scripts/devforgeai_cli/headless/__init__.py +30 -0
- package/src/claude/scripts/devforgeai_cli/headless/answer_models.py +206 -0
- package/src/claude/scripts/devforgeai_cli/headless/answer_resolver.py +204 -0
- package/src/claude/scripts/devforgeai_cli/headless/exceptions.py +36 -0
- package/src/claude/scripts/devforgeai_cli/headless/pattern_matcher.py +156 -0
- package/src/claude/scripts/devforgeai_cli/hooks.py +313 -0
- package/src/claude/scripts/devforgeai_cli/metrics/__init__.py +46 -0
- package/src/claude/scripts/devforgeai_cli/metrics/command_metrics.py +142 -0
- package/src/claude/scripts/devforgeai_cli/metrics/failure_modes.py +152 -0
- package/src/claude/scripts/devforgeai_cli/metrics/story_segmentation.py +181 -0
- package/src/claude/scripts/devforgeai_cli/orchestrate_hooks.py +780 -0
- package/src/claude/scripts/devforgeai_cli/phase_state.py +1229 -0
- package/src/claude/scripts/devforgeai_cli/session/__init__.py +30 -0
- package/src/claude/scripts/devforgeai_cli/session/checkpoint.py +268 -0
- package/src/claude/scripts/devforgeai_cli/tests/__init__.py +1 -0
- package/src/claude/scripts/devforgeai_cli/tests/conftest.py +29 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/TEST_EXECUTION_GUIDE.md +298 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/__init__.py +3 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_adaptive_questioning_engine.py +2171 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_aggregation.py +476 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_defaults.py +133 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_manager.py +592 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_models.py +373 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_schema.py +130 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_configuration_management.py +1355 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_edge_cases.py +308 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_feature_flag.py +307 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_feedback_indexer.py +384 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_hot_reload.py +580 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_integration.py +402 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_models.py +105 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_question_routing.py +262 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_retrospective.py +333 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracker.py +410 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracking.py +159 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracking_integration.py +1155 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_template_engine.py +1389 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_validation_comprehensive.py +210 -0
- package/src/claude/scripts/devforgeai_cli/tests/fixtures/autonomous-deferral-story.md +46 -0
- package/src/claude/scripts/devforgeai_cli/tests/fixtures/missing-impl-notes.md +31 -0
- package/src/claude/scripts/devforgeai_cli/tests/fixtures/valid-deferral-story.md +46 -0
- package/src/claude/scripts/devforgeai_cli/tests/fixtures/valid-story-complete.md +48 -0
- package/src/claude/scripts/devforgeai_cli/tests/manual_test_invoke_hooks.sh +200 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/DELIVERABLES.md +518 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/TEST_SUMMARY.md +468 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/__init__.py +6 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/corrupted-checkpoint.json +1 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/missing-fields-checkpoint.json +4 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/valid-checkpoint.json +15 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/test_checkpoint.py +851 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_check_hooks.py +1886 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_depends_on_normalizer.py +171 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_dod_validator.py +97 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_invoke_hooks.py +1902 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands.py +320 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands_error_handling.py +1021 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands_import.py +697 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_phase_state.py +2187 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_skip_tracking.py +2141 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_skip_tracking_coverage_gap.py +195 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_subagent_enforcement.py +539 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_validate_installation.py +361 -0
- package/src/claude/scripts/devforgeai_cli/utils/__init__.py +11 -0
- package/src/claude/scripts/devforgeai_cli/utils/depends_on_normalizer.py +149 -0
- package/src/claude/scripts/devforgeai_cli/utils/markdown_parser.py +219 -0
- package/src/claude/scripts/devforgeai_cli/utils/story_analyzer.py +249 -0
- package/src/claude/scripts/devforgeai_cli/utils/yaml_parser.py +152 -0
- package/src/claude/scripts/devforgeai_cli/validators/__init__.py +27 -0
- package/src/claude/scripts/devforgeai_cli/validators/ast_grep_validator.py +373 -0
- package/src/claude/scripts/devforgeai_cli/validators/context_validator.py +180 -0
- package/src/claude/scripts/devforgeai_cli/validators/dod_validator.py +309 -0
- package/src/claude/scripts/devforgeai_cli/validators/git_validator.py +107 -0
- package/src/claude/scripts/devforgeai_cli/validators/grep_fallback.py +300 -0
- package/src/claude/scripts/install_hooks.sh +186 -0
- package/src/claude/scripts/invoke_feedback_hooks.sh +59 -0
- package/src/claude/scripts/migrate-ac-headers.sh +122 -0
- package/src/claude/scripts/plan_file_kb.sh +704 -0
- package/src/claude/scripts/requirements.txt +8 -0
- package/src/claude/scripts/session_catalog.sh +543 -0
- package/src/claude/scripts/setup.py +55 -0
- package/src/claude/scripts/start-devforgeai.sh +16 -0
- package/src/claude/scripts/statusline.sh +27 -0
- package/src/claude/scripts/validate_deferrals.py +344 -0
- package/src/claude/skills/devforgeai-qa/SKILL.md +1 -1
- package/src/claude/skills/researching-market/SKILL.md +2 -1
- package/src/cli/lib/copier.js +13 -1
- package/src/claude/skills/designing-systems/scripts/__pycache__/detect_anti_patterns.cpython-312.pyc +0 -0
- package/src/claude/skills/designing-systems/scripts/__pycache__/validate_all_context.cpython-312.pyc +0 -0
- package/src/claude/skills/designing-systems/scripts/__pycache__/validate_architecture.cpython-312.pyc +0 -0
- package/src/claude/skills/designing-systems/scripts/__pycache__/validate_dependencies.cpython-312.pyc +0 -0
- package/src/claude/skills/devforgeai-story-creation/scripts/__pycache__/migrate_story_v1_to_v2.cpython-312.pyc +0 -0
- package/src/claude/skills/devforgeai-story-creation/scripts/tests/__pycache__/measure_accuracy.cpython-312.pyc +0 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GrepFallbackAnalyzer - Grep-based pattern matching fallback.
|
|
3
|
+
|
|
4
|
+
Used when ast-grep is unavailable. Provides ~60-75% accuracy
|
|
5
|
+
compared to ast-grep's 90-95% accuracy.
|
|
6
|
+
|
|
7
|
+
Output format matches ast-grep schema for interoperability.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
import json
|
|
12
|
+
import logging
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import List, Dict, Tuple, Optional
|
|
15
|
+
from dataclasses import dataclass, field, asdict
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# =============================================================================
|
|
21
|
+
# Data Classes
|
|
22
|
+
# =============================================================================
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class GrepPattern:
|
|
26
|
+
"""Pattern definition for grep-based detection."""
|
|
27
|
+
id: str
|
|
28
|
+
pattern: str # Regex pattern
|
|
29
|
+
severity: str # CRITICAL, HIGH, MEDIUM, LOW
|
|
30
|
+
message: str
|
|
31
|
+
category: str # security, anti-pattern, etc.
|
|
32
|
+
languages: List[str] = field(default_factory=list)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class Violation:
|
|
37
|
+
"""Standardized violation result."""
|
|
38
|
+
file: str
|
|
39
|
+
line: int
|
|
40
|
+
column: int
|
|
41
|
+
rule_id: str
|
|
42
|
+
severity: str
|
|
43
|
+
message: str
|
|
44
|
+
evidence: str
|
|
45
|
+
analysis_method: str = "grep-fallback"
|
|
46
|
+
category: str = "security"
|
|
47
|
+
|
|
48
|
+
def to_dict(self) -> Dict:
|
|
49
|
+
"""Convert to dictionary for JSON serialization."""
|
|
50
|
+
return asdict(self)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# =============================================================================
|
|
54
|
+
# Utility Functions
|
|
55
|
+
# =============================================================================
|
|
56
|
+
|
|
57
|
+
def log_fallback_warning() -> None:
|
|
58
|
+
"""Log warning about reduced accuracy in fallback mode."""
|
|
59
|
+
logger.warning(
|
|
60
|
+
"Using grep-based fallback for code analysis. "
|
|
61
|
+
"Accuracy: 60-75% (vs 90-95% with ast-grep). "
|
|
62
|
+
"Install ast-grep-cli for better results: pip install ast-grep-cli"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# =============================================================================
|
|
67
|
+
# Main Analyzer Class
|
|
68
|
+
# =============================================================================
|
|
69
|
+
|
|
70
|
+
class GrepFallbackAnalyzer:
|
|
71
|
+
"""Grep-based pattern matching for code analysis."""
|
|
72
|
+
|
|
73
|
+
# Built-in security patterns
|
|
74
|
+
SECURITY_PATTERNS: List[GrepPattern] = [
|
|
75
|
+
GrepPattern(
|
|
76
|
+
id="SEC-001",
|
|
77
|
+
pattern=r'(SELECT|INSERT|UPDATE|DELETE).*WHERE.*[\+\{]|[\+\{].*WHERE|execute\(["\']SELECT.*[\+\{]|f["\']SELECT.*\{|\.format\(.*SELECT',
|
|
78
|
+
severity="CRITICAL",
|
|
79
|
+
message="Potential SQL injection via string concatenation",
|
|
80
|
+
category="security",
|
|
81
|
+
languages=["python", "py", "csharp", "cs"]
|
|
82
|
+
),
|
|
83
|
+
GrepPattern(
|
|
84
|
+
id="SEC-002",
|
|
85
|
+
pattern=r'(API_KEY|SECRET|PASSWORD|TOKEN|ACCESS_KEY)\s*=\s*["\']\w{8,}["\']',
|
|
86
|
+
severity="HIGH",
|
|
87
|
+
message="Hardcoded secret detected",
|
|
88
|
+
category="security",
|
|
89
|
+
languages=["python", "py", "javascript", "js", "typescript", "ts", "csharp", "cs"]
|
|
90
|
+
),
|
|
91
|
+
GrepPattern(
|
|
92
|
+
id="SEC-003",
|
|
93
|
+
pattern=r'(AWS_ACCESS_KEY|AWS_SECRET|AKIA[0-9A-Z]{16})',
|
|
94
|
+
severity="CRITICAL",
|
|
95
|
+
message="AWS credentials detected",
|
|
96
|
+
category="security",
|
|
97
|
+
languages=["python", "py", "javascript", "js", "typescript", "ts", "csharp", "cs"]
|
|
98
|
+
),
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
def __init__(self, custom_patterns: Optional[List[GrepPattern]] = None):
|
|
102
|
+
"""
|
|
103
|
+
Initialize analyzer with patterns.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
custom_patterns: Additional patterns to use
|
|
107
|
+
"""
|
|
108
|
+
self.patterns = self.SECURITY_PATTERNS.copy()
|
|
109
|
+
if custom_patterns:
|
|
110
|
+
self.patterns.extend(custom_patterns)
|
|
111
|
+
|
|
112
|
+
def analyze_file(self, file_path: str) -> List[Dict]:
|
|
113
|
+
"""
|
|
114
|
+
Analyze single file with grep patterns.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
file_path: Path to file to analyze
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
List of violations found (as dicts for compatibility)
|
|
121
|
+
"""
|
|
122
|
+
violations = []
|
|
123
|
+
file_path_obj = Path(file_path)
|
|
124
|
+
|
|
125
|
+
if not file_path_obj.exists():
|
|
126
|
+
return violations
|
|
127
|
+
|
|
128
|
+
if not file_path_obj.is_file():
|
|
129
|
+
return violations
|
|
130
|
+
|
|
131
|
+
# Skip binary files
|
|
132
|
+
try:
|
|
133
|
+
with open(file_path_obj, 'r', encoding='utf-8') as f:
|
|
134
|
+
content = f.read()
|
|
135
|
+
except (UnicodeDecodeError, PermissionError):
|
|
136
|
+
# Binary file or permission denied - skip gracefully
|
|
137
|
+
return violations
|
|
138
|
+
|
|
139
|
+
# Apply each pattern
|
|
140
|
+
for pattern in self.patterns:
|
|
141
|
+
# Check if pattern applies to this file type
|
|
142
|
+
if pattern.languages:
|
|
143
|
+
file_ext = file_path_obj.suffix.lstrip('.')
|
|
144
|
+
if file_ext not in pattern.languages and not any(lang in file_ext for lang in pattern.languages):
|
|
145
|
+
continue
|
|
146
|
+
|
|
147
|
+
# Search for pattern in content
|
|
148
|
+
matches = re.finditer(pattern.pattern, content, re.MULTILINE | re.IGNORECASE)
|
|
149
|
+
|
|
150
|
+
for match in matches:
|
|
151
|
+
# Find line number and column
|
|
152
|
+
line_num = content[:match.start()].count('\n') + 1
|
|
153
|
+
line_start = content.rfind('\n', 0, match.start()) + 1
|
|
154
|
+
column = match.start() - line_start + 1
|
|
155
|
+
|
|
156
|
+
# Extract evidence (the matching line)
|
|
157
|
+
line_end = content.find('\n', match.start())
|
|
158
|
+
if line_end == -1:
|
|
159
|
+
line_end = len(content)
|
|
160
|
+
evidence = content[line_start:line_end].strip()
|
|
161
|
+
|
|
162
|
+
violation = Violation(
|
|
163
|
+
file=str(file_path_obj),
|
|
164
|
+
line=line_num,
|
|
165
|
+
column=column,
|
|
166
|
+
rule_id=pattern.id,
|
|
167
|
+
severity=pattern.severity,
|
|
168
|
+
message=pattern.message,
|
|
169
|
+
evidence=evidence,
|
|
170
|
+
analysis_method="grep-fallback",
|
|
171
|
+
category=pattern.category
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
violations.append(violation.to_dict())
|
|
175
|
+
|
|
176
|
+
return violations
|
|
177
|
+
|
|
178
|
+
def analyze_directory(self, directory: str,
|
|
179
|
+
category: Optional[str] = None,
|
|
180
|
+
language: Optional[str] = None) -> List[Dict]:
|
|
181
|
+
"""
|
|
182
|
+
Analyze directory recursively.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
directory: Directory to scan
|
|
186
|
+
category: Filter by category (security, anti-patterns)
|
|
187
|
+
language: Filter by language (python, typescript, csharp)
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
List of all violations found
|
|
191
|
+
"""
|
|
192
|
+
all_violations = []
|
|
193
|
+
directory_path = Path(directory)
|
|
194
|
+
|
|
195
|
+
if not directory_path.exists() or not directory_path.is_dir():
|
|
196
|
+
return all_violations
|
|
197
|
+
|
|
198
|
+
# Find all relevant files
|
|
199
|
+
if language:
|
|
200
|
+
# Map language to file extensions
|
|
201
|
+
ext_map = {
|
|
202
|
+
'python': ['.py'],
|
|
203
|
+
'typescript': ['.ts', '.tsx'],
|
|
204
|
+
'javascript': ['.js', '.jsx'],
|
|
205
|
+
'csharp': ['.cs']
|
|
206
|
+
}
|
|
207
|
+
extensions = ext_map.get(language, [])
|
|
208
|
+
else:
|
|
209
|
+
extensions = ['.py', '.ts', '.tsx', '.js', '.jsx', '.cs']
|
|
210
|
+
|
|
211
|
+
# Scan all files
|
|
212
|
+
for ext in extensions:
|
|
213
|
+
for file_path in directory_path.rglob(f'*{ext}'):
|
|
214
|
+
if file_path.is_file():
|
|
215
|
+
violations = self.analyze_file(str(file_path))
|
|
216
|
+
|
|
217
|
+
# Filter by category if specified
|
|
218
|
+
if category:
|
|
219
|
+
violations = [v for v in violations if v.get('category') == category]
|
|
220
|
+
|
|
221
|
+
all_violations.extend(violations)
|
|
222
|
+
|
|
223
|
+
return all_violations
|
|
224
|
+
|
|
225
|
+
def format_results(self, violations: List[Dict], format: str = "json") -> str:
|
|
226
|
+
"""
|
|
227
|
+
Format results for output.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
violations: List of violations
|
|
231
|
+
format: Output format (json, text, markdown)
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
Formatted output string
|
|
235
|
+
"""
|
|
236
|
+
if format == "json":
|
|
237
|
+
return self._format_json(violations)
|
|
238
|
+
elif format == "text":
|
|
239
|
+
return self._format_text(violations)
|
|
240
|
+
elif format == "markdown":
|
|
241
|
+
return self._format_markdown(violations)
|
|
242
|
+
else:
|
|
243
|
+
return self._format_json(violations)
|
|
244
|
+
|
|
245
|
+
def _format_json(self, violations: List[Dict]) -> str:
|
|
246
|
+
"""Format as JSON matching ast-grep schema."""
|
|
247
|
+
# Calculate summary
|
|
248
|
+
by_severity = {}
|
|
249
|
+
for v in violations:
|
|
250
|
+
severity = v.get('severity', 'UNKNOWN')
|
|
251
|
+
by_severity[severity] = by_severity.get(severity, 0) + 1
|
|
252
|
+
|
|
253
|
+
result = {
|
|
254
|
+
"violations": violations,
|
|
255
|
+
"analysis_method": "grep-fallback",
|
|
256
|
+
"summary": {
|
|
257
|
+
"total_violations": len(violations),
|
|
258
|
+
"by_severity": by_severity,
|
|
259
|
+
"accuracy_note": "60-75% vs 90-95% with ast-grep"
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return json.dumps(result, indent=2)
|
|
264
|
+
|
|
265
|
+
def _format_text(self, violations: List[Dict]) -> str:
|
|
266
|
+
"""Format as human-readable text."""
|
|
267
|
+
if not violations:
|
|
268
|
+
return "No violations found.\n"
|
|
269
|
+
|
|
270
|
+
lines = []
|
|
271
|
+
lines.append(f"Found {len(violations)} violation(s):\n")
|
|
272
|
+
|
|
273
|
+
for v in violations:
|
|
274
|
+
lines.append(f"{v.get('severity', 'UNKNOWN')}: {v.get('file', 'unknown')}")
|
|
275
|
+
lines.append(f" Line {v.get('line', '?')}, Column {v.get('column', '?')}")
|
|
276
|
+
lines.append(f" {v.get('message', 'No message')}")
|
|
277
|
+
lines.append(f" Evidence: {v.get('evidence', 'N/A')}")
|
|
278
|
+
lines.append("")
|
|
279
|
+
|
|
280
|
+
return "\n".join(lines)
|
|
281
|
+
|
|
282
|
+
def _format_markdown(self, violations: List[Dict]) -> str:
|
|
283
|
+
"""Format as Markdown."""
|
|
284
|
+
if not violations:
|
|
285
|
+
return "## Code Analysis Results\n\nNo violations found.\n"
|
|
286
|
+
|
|
287
|
+
lines = []
|
|
288
|
+
lines.append("## Code Analysis Results\n")
|
|
289
|
+
lines.append(f"**Total Violations:** {len(violations)}\n")
|
|
290
|
+
lines.append("**Analysis Method:** grep-fallback (60-75% accuracy)\n")
|
|
291
|
+
lines.append("### Violations\n")
|
|
292
|
+
|
|
293
|
+
for v in violations:
|
|
294
|
+
lines.append(f"#### {v.get('severity', 'UNKNOWN')}: {v.get('rule_id', 'UNKNOWN')}\n")
|
|
295
|
+
lines.append(f"**File:** `{v.get('file', 'unknown')}` ")
|
|
296
|
+
lines.append(f"**Line:** {v.get('line', '?')}, **Column:** {v.get('column', '?')}\n")
|
|
297
|
+
lines.append(f"**Message:** {v.get('message', 'No message')}\n")
|
|
298
|
+
lines.append(f"**Evidence:**\n```\n{v.get('evidence', 'N/A')}\n```\n")
|
|
299
|
+
|
|
300
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# DevForgeAI Pre-Commit Hook Installer
|
|
4
|
+
#
|
|
5
|
+
# Installs pre-commit hook that validates:
|
|
6
|
+
# - DoD completion (prevents autonomous deferrals)
|
|
7
|
+
# - Story file format
|
|
8
|
+
# - User approval markers
|
|
9
|
+
#
|
|
10
|
+
# Based on industry patterns: pre-commit.com, SpecDriven AI
|
|
11
|
+
#
|
|
12
|
+
|
|
13
|
+
set -e
|
|
14
|
+
|
|
15
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
16
|
+
echo " DevForgeAI Pre-Commit Hook Installer"
|
|
17
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
18
|
+
echo ""
|
|
19
|
+
|
|
20
|
+
# Check if in Git repository
|
|
21
|
+
if [ ! -d ".git" ]; then
|
|
22
|
+
echo "❌ Error: Not in a Git repository"
|
|
23
|
+
echo ""
|
|
24
|
+
echo "Initialize Git first:"
|
|
25
|
+
echo " git init"
|
|
26
|
+
echo " git add ."
|
|
27
|
+
echo " git commit -m 'Initial commit'"
|
|
28
|
+
echo ""
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Check if Python available
|
|
33
|
+
if ! command -v python3 &> /dev/null; then
|
|
34
|
+
echo "❌ Error: Python 3 not found"
|
|
35
|
+
echo ""
|
|
36
|
+
echo "Install Python 3.8 or higher"
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# Check if DevForgeAI CLI validators available
|
|
41
|
+
if [ ! -f ".claude/scripts/devforgeai_cli/validators/dod_validator.py" ]; then
|
|
42
|
+
echo "❌ Error: DevForgeAI CLI not found"
|
|
43
|
+
echo ""
|
|
44
|
+
echo "Expected location: .claude/scripts/devforgeai_cli/"
|
|
45
|
+
echo ""
|
|
46
|
+
echo "Install DevForgeAI CLI first:"
|
|
47
|
+
echo " pip install -e .claude/scripts/"
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
echo "Installing pre-commit hook..."
|
|
52
|
+
echo ""
|
|
53
|
+
|
|
54
|
+
# Create pre-commit hook
|
|
55
|
+
cat > .git/hooks/pre-commit <<'EOF'
|
|
56
|
+
#!/bin/bash
|
|
57
|
+
#
|
|
58
|
+
# DevForgeAI Pre-Commit Validation Hook
|
|
59
|
+
#
|
|
60
|
+
# Validates story files before commit to prevent:
|
|
61
|
+
# - Autonomous deferrals (DoD [x] but Impl [ ] without user approval)
|
|
62
|
+
# - Missing Implementation Notes
|
|
63
|
+
# - Invalid deferral justifications
|
|
64
|
+
#
|
|
65
|
+
# To bypass (NOT RECOMMENDED): git commit --no-verify
|
|
66
|
+
#
|
|
67
|
+
|
|
68
|
+
echo ""
|
|
69
|
+
echo "🔍 DevForgeAI Validators Running..."
|
|
70
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
71
|
+
|
|
72
|
+
VALIDATION_FAILED=0
|
|
73
|
+
|
|
74
|
+
# ============================================================================
|
|
75
|
+
# Registry Drift Detection (STORY-109)
|
|
76
|
+
# ============================================================================
|
|
77
|
+
AGENT_FILES=$(git diff --cached --name-only --diff-filter=d | grep '^\.claude/agents/.*\.md$' || true)
|
|
78
|
+
if [ -n "$AGENT_FILES" ]; then
|
|
79
|
+
echo " 📋 Checking subagent registry..."
|
|
80
|
+
if [ -f "scripts/generate-subagent-registry.sh" ]; then
|
|
81
|
+
if ! bash scripts/generate-subagent-registry.sh --check 2>/dev/null; then
|
|
82
|
+
echo " ❌ Registry out of date"
|
|
83
|
+
echo " Run: bash scripts/generate-subagent-registry.sh"
|
|
84
|
+
echo " Then: git add CLAUDE.md"
|
|
85
|
+
VALIDATION_FAILED=1
|
|
86
|
+
else
|
|
87
|
+
echo " ✅ Registry up to date"
|
|
88
|
+
fi
|
|
89
|
+
fi
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
# ============================================================================
|
|
93
|
+
# Story-Scoped Validation (STORY-121)
|
|
94
|
+
# Set DEVFORGEAI_STORY=STORY-NNN to validate only that story
|
|
95
|
+
# ============================================================================
|
|
96
|
+
|
|
97
|
+
if [ -n "$DEVFORGEAI_STORY" ]; then
|
|
98
|
+
# Validate format: STORY-NNN (3+ digits, uppercase only)
|
|
99
|
+
if ! echo "$DEVFORGEAI_STORY" | grep -qE '^STORY-[0-9]{3,}$'; then
|
|
100
|
+
echo " WARNING: Invalid DEVFORGEAI_STORY format: $DEVFORGEAI_STORY"
|
|
101
|
+
echo " Expected: STORY-NNN (e.g., STORY-120)"
|
|
102
|
+
echo " Falling back to unscoped validation..."
|
|
103
|
+
DEVFORGEAI_STORY=""
|
|
104
|
+
fi
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
if [ -n "$DEVFORGEAI_STORY" ]; then
|
|
108
|
+
# Scoped validation - only validate specific story
|
|
109
|
+
STORY_FILES=$(git diff --cached --name-only --diff-filter=d | grep "${DEVFORGEAI_STORY}" | grep -v '^tests/' || true)
|
|
110
|
+
echo " Scoped to: $DEVFORGEAI_STORY"
|
|
111
|
+
else
|
|
112
|
+
# Default behavior - validate all staged story files
|
|
113
|
+
STORY_FILES=$(git diff --cached --name-only --diff-filter=d | grep '\.story\.md$' | grep -v '^tests/' || true)
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
if [ -z "$STORY_FILES" ]; then
|
|
117
|
+
echo " No story files to validate"
|
|
118
|
+
echo "✅ Pre-commit validation passed"
|
|
119
|
+
echo ""
|
|
120
|
+
exit 0
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
# Validate each story file
|
|
124
|
+
VALIDATION_FAILED=0
|
|
125
|
+
|
|
126
|
+
for file in $STORY_FILES; do
|
|
127
|
+
# Skip if file doesn't exist (shouldn't happen with --diff-filter=d, but safety check)
|
|
128
|
+
if [ ! -f "$file" ]; then
|
|
129
|
+
echo " ⚠️ Skipping (file not found): $file"
|
|
130
|
+
continue
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
echo " 📋 Validating: $file"
|
|
134
|
+
|
|
135
|
+
# Run DoD validator with PYTHONPATH set (fixes relative import issue)
|
|
136
|
+
# This allows the validator to import from parent package without pip install
|
|
137
|
+
if PYTHONPATH=".claude/scripts:$PYTHONPATH" python3 -m devforgeai_cli.validators.dod_validator "$file" --project-root .; then
|
|
138
|
+
echo " ✅ Passed"
|
|
139
|
+
else
|
|
140
|
+
echo " ❌ Failed"
|
|
141
|
+
VALIDATION_FAILED=1
|
|
142
|
+
fi
|
|
143
|
+
done
|
|
144
|
+
|
|
145
|
+
echo ""
|
|
146
|
+
|
|
147
|
+
if [ $VALIDATION_FAILED -eq 1 ]; then
|
|
148
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
149
|
+
echo "❌ COMMIT BLOCKED - Fix violations"
|
|
150
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
151
|
+
echo ""
|
|
152
|
+
echo "One or more story files have validation errors."
|
|
153
|
+
echo "Fix the violations shown above before committing."
|
|
154
|
+
echo ""
|
|
155
|
+
echo "To bypass validation (NOT RECOMMENDED):"
|
|
156
|
+
echo " git commit --no-verify"
|
|
157
|
+
echo ""
|
|
158
|
+
exit 1
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
echo "✅ All validators passed - commit allowed"
|
|
162
|
+
echo ""
|
|
163
|
+
exit 0
|
|
164
|
+
EOF
|
|
165
|
+
|
|
166
|
+
# Make hook executable
|
|
167
|
+
chmod +x .git/hooks/pre-commit
|
|
168
|
+
|
|
169
|
+
echo "✅ Pre-commit hook installed successfully"
|
|
170
|
+
echo ""
|
|
171
|
+
echo "Location: .git/hooks/pre-commit"
|
|
172
|
+
echo ""
|
|
173
|
+
echo "The hook will automatically run on 'git commit' and validate:"
|
|
174
|
+
echo " • DoD completion status"
|
|
175
|
+
echo " • Autonomous deferral detection"
|
|
176
|
+
echo " • User approval markers for deferrals"
|
|
177
|
+
echo " • Story/ADR reference validation"
|
|
178
|
+
echo ""
|
|
179
|
+
echo "To test the hook:"
|
|
180
|
+
echo " git add devforgeai/specs/Stories/STORY-XXX.story.md"
|
|
181
|
+
echo " git commit -m 'Test commit'"
|
|
182
|
+
echo ""
|
|
183
|
+
echo "To bypass validation (not recommended):"
|
|
184
|
+
echo " git commit --no-verify"
|
|
185
|
+
echo ""
|
|
186
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Reusable helper function for feedback hook integration
|
|
3
|
+
# Used by: /ideate, /create-story, /create-epic, /create-sprint, /create-context, /dev
|
|
4
|
+
# Pattern: STORY-023 pilot, implemented across STORY-027-031
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
# Usage: invoke_feedback_hooks.sh <operation> <status> [additional-args...]
|
|
9
|
+
# Examples:
|
|
10
|
+
# invoke_feedback_hooks.sh ideate completed --artifacts='[epic1.md]' --complexity-score=42
|
|
11
|
+
# invoke_feedback_hooks.sh dev completed --story=STORY-001
|
|
12
|
+
# invoke_feedback_hooks.sh create-context completed
|
|
13
|
+
|
|
14
|
+
OPERATION="${1:-}"
|
|
15
|
+
STATUS="${2:-completed}"
|
|
16
|
+
shift 2 || true # Remove first 2 args, keep remaining as passthrough
|
|
17
|
+
|
|
18
|
+
# Validate required arguments
|
|
19
|
+
if [ -z "$OPERATION" ]; then
|
|
20
|
+
echo "❌ ERROR: Operation required" >&2
|
|
21
|
+
echo "Usage: invoke_feedback_hooks.sh <operation> <status> [additional-args...]" >&2
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# Phase 1: Check Hook Eligibility
|
|
26
|
+
echo "Checking hook eligibility for operation: $OPERATION..."
|
|
27
|
+
|
|
28
|
+
devforgeai check-hooks --operation="$OPERATION" --status="$STATUS" 2>/dev/null
|
|
29
|
+
CHECK_EXIT=$?
|
|
30
|
+
|
|
31
|
+
# Interpret exit code
|
|
32
|
+
case $CHECK_EXIT in
|
|
33
|
+
0)
|
|
34
|
+
# Eligible - proceed to invocation
|
|
35
|
+
echo "✓ Hooks eligible for $OPERATION"
|
|
36
|
+
;;
|
|
37
|
+
1)
|
|
38
|
+
# Not eligible (disabled/rate-limited) - silent exit
|
|
39
|
+
exit 0
|
|
40
|
+
;;
|
|
41
|
+
*)
|
|
42
|
+
# Unexpected error - warn and continue
|
|
43
|
+
echo "⚠️ Hook eligibility check failed (exit code: $CHECK_EXIT), continuing..." >&2
|
|
44
|
+
exit 0
|
|
45
|
+
;;
|
|
46
|
+
esac
|
|
47
|
+
|
|
48
|
+
# Phase 2: Invoke Feedback Hooks
|
|
49
|
+
echo "Invoking feedback hooks for $OPERATION..."
|
|
50
|
+
|
|
51
|
+
devforgeai invoke-hooks --operation="$OPERATION" "$@" 2>/dev/null || {
|
|
52
|
+
# Hook invocation failed - non-blocking
|
|
53
|
+
echo "⚠️ Post-$OPERATION feedback skipped (hook system unavailable)" >&2
|
|
54
|
+
exit 0
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Phase 3: Display Success
|
|
58
|
+
echo "✓ Post-$OPERATION feedback initiated"
|
|
59
|
+
exit 0
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Migrate story template v2.0 → v2.1 (remove AC header checkboxes)
|
|
3
|
+
# Usage: migrate-ac-headers.sh <story-file-or-directory>
|
|
4
|
+
|
|
5
|
+
TARGET="$1"
|
|
6
|
+
|
|
7
|
+
if [[ -z "$TARGET" ]]; then
|
|
8
|
+
echo "Usage: migrate-ac-headers.sh <story-file-or-directory>"
|
|
9
|
+
echo ""
|
|
10
|
+
echo "Examples:"
|
|
11
|
+
echo " migrate-ac-headers.sh devforgeai/specs/Stories/STORY-052.story.md"
|
|
12
|
+
echo " migrate-ac-headers.sh devforgeai/specs/Stories/"
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# Track if any file migration failed
|
|
17
|
+
MIGRATION_FAILED=0
|
|
18
|
+
|
|
19
|
+
# ============================================================================
|
|
20
|
+
# Helper: update_format_version(file)
|
|
21
|
+
# Updates format_version from 2.0 to 2.1 in YAML frontmatter
|
|
22
|
+
# Handles three quote formats: double quotes, single quotes, and unquoted
|
|
23
|
+
# ============================================================================
|
|
24
|
+
update_format_version() {
|
|
25
|
+
local file="$1"
|
|
26
|
+
|
|
27
|
+
# Handle double quotes: format_version: "2.0"
|
|
28
|
+
sed -i 's/format_version: "2.0"/format_version: "2.1"/' "$file"
|
|
29
|
+
|
|
30
|
+
# Handle single quotes: format_version: '2.0'
|
|
31
|
+
sed -i "s/format_version: '2.0'/format_version: \"2.1\"/" "$file"
|
|
32
|
+
|
|
33
|
+
# Handle unquoted: format_version: 2.0
|
|
34
|
+
sed -i 's/format_version: 2.0$/format_version: "2.1"/' "$file"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# ============================================================================
|
|
38
|
+
# Helper: migrate_ac_headers(file)
|
|
39
|
+
# Converts AC header format from v2.0 to v2.1
|
|
40
|
+
# Changes: ### N. [ ] Title → ### AC#N: Title
|
|
41
|
+
# ============================================================================
|
|
42
|
+
migrate_ac_headers() {
|
|
43
|
+
local file="$1"
|
|
44
|
+
|
|
45
|
+
sed -i 's/^### \([0-9]\+\)\. \[ \] /### AC#\1: /' "$file"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# ============================================================================
|
|
49
|
+
# Helper: backup_file(file)
|
|
50
|
+
# Creates backup copy of file with .backup extension
|
|
51
|
+
# ============================================================================
|
|
52
|
+
backup_file() {
|
|
53
|
+
local file="$1"
|
|
54
|
+
|
|
55
|
+
cp "$file" "$file.backup"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# ============================================================================
|
|
59
|
+
# Function: migrate_file(file)
|
|
60
|
+
# Main entry point for migrating a single story file
|
|
61
|
+
# Performs: backup → migrate AC headers → update format version
|
|
62
|
+
# ============================================================================
|
|
63
|
+
migrate_file() {
|
|
64
|
+
local file="$1"
|
|
65
|
+
|
|
66
|
+
if [[ ! -f "$file" ]]; then
|
|
67
|
+
echo "Error: File not found: $file"
|
|
68
|
+
return 1
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
backup_file "$file"
|
|
72
|
+
migrate_ac_headers "$file"
|
|
73
|
+
update_format_version "$file"
|
|
74
|
+
|
|
75
|
+
echo "Migrated: $file"
|
|
76
|
+
echo " Backup: $file.backup"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# ============================================================================
|
|
80
|
+
# Main Logic: Process TARGET (file or directory)
|
|
81
|
+
# ============================================================================
|
|
82
|
+
|
|
83
|
+
if [[ -d "$TARGET" ]]; then
|
|
84
|
+
# Directory mode - migrate all story files
|
|
85
|
+
# Tracks failures and reports summary
|
|
86
|
+
FILES_PROCESSED=0
|
|
87
|
+
FILES_FAILED=0
|
|
88
|
+
|
|
89
|
+
for file in "$TARGET"/*.story.md; do
|
|
90
|
+
if [[ -f "$file" ]]; then
|
|
91
|
+
FILES_PROCESSED=$((FILES_PROCESSED + 1))
|
|
92
|
+
if ! migrate_file "$file"; then
|
|
93
|
+
FILES_FAILED=$((FILES_FAILED + 1))
|
|
94
|
+
MIGRATION_FAILED=1
|
|
95
|
+
fi
|
|
96
|
+
fi
|
|
97
|
+
done
|
|
98
|
+
|
|
99
|
+
# Report directory migration summary
|
|
100
|
+
if [[ $FILES_PROCESSED -gt 0 ]]; then
|
|
101
|
+
echo ""
|
|
102
|
+
echo "Directory migration complete:"
|
|
103
|
+
echo " Files processed: $FILES_PROCESSED"
|
|
104
|
+
if [[ $FILES_FAILED -gt 0 ]]; then
|
|
105
|
+
echo " Files failed: $FILES_FAILED"
|
|
106
|
+
fi
|
|
107
|
+
fi
|
|
108
|
+
else
|
|
109
|
+
# Single file mode
|
|
110
|
+
if ! migrate_file "$TARGET"; then
|
|
111
|
+
MIGRATION_FAILED=1
|
|
112
|
+
fi
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
echo ""
|
|
116
|
+
echo "Migration complete!"
|
|
117
|
+
echo "To undo, restore from .backup files"
|
|
118
|
+
|
|
119
|
+
# Exit with error if migration failed
|
|
120
|
+
if [[ $MIGRATION_FAILED -eq 1 ]]; then
|
|
121
|
+
exit 1
|
|
122
|
+
fi
|