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,309 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Definition of Done (DoD) Validator
|
|
4
|
+
|
|
5
|
+
Prevents autonomous deferrals by validating user approval markers.
|
|
6
|
+
|
|
7
|
+
Based on research patterns:
|
|
8
|
+
- SpecDriven AI: spec_validator.py (spec-test traceability)
|
|
9
|
+
- GitHub DoD Checker: checkbox status validation
|
|
10
|
+
- Industry traceability: explicit approval markers
|
|
11
|
+
|
|
12
|
+
This validator catches the exact issue from tmp/output.md where Claude
|
|
13
|
+
marked DoD item as [x] but deferred implementation without user approval.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Dict, List, Tuple
|
|
19
|
+
|
|
20
|
+
from ..utils.story_analyzer import (
|
|
21
|
+
load_story_file,
|
|
22
|
+
extract_dod_items,
|
|
23
|
+
extract_impl_notes_items,
|
|
24
|
+
find_dod_impl_mismatch,
|
|
25
|
+
check_user_approval_marker,
|
|
26
|
+
has_implementation_notes,
|
|
27
|
+
extract_story_references,
|
|
28
|
+
extract_adr_references
|
|
29
|
+
)
|
|
30
|
+
from ..utils.markdown_parser import extract_item_justification, extract_section
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class DoDValidator:
|
|
34
|
+
"""Validates Definition of Done completion and deferral justifications."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, project_root: str = "."):
|
|
37
|
+
self.project_root = Path(project_root)
|
|
38
|
+
|
|
39
|
+
def validate(self, story_file: str) -> Tuple[bool, List[Dict]]:
|
|
40
|
+
"""
|
|
41
|
+
Validate DoD completion for a story file.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
story_file: Path to story file (relative to project root or absolute)
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Tuple of (is_valid, violations)
|
|
48
|
+
|
|
49
|
+
Violations include:
|
|
50
|
+
- CRITICAL: Autonomous deferrals (no user approval)
|
|
51
|
+
- CRITICAL: DoD [x] but missing from Implementation Notes
|
|
52
|
+
- HIGH: Referenced stories don't exist
|
|
53
|
+
- HIGH: Missing Implementation Notes section
|
|
54
|
+
- MEDIUM: Deferred items with incomplete justifications
|
|
55
|
+
"""
|
|
56
|
+
violations = []
|
|
57
|
+
|
|
58
|
+
# Resolve story file path relative to project root
|
|
59
|
+
story_path = Path(story_file)
|
|
60
|
+
if not story_path.is_absolute():
|
|
61
|
+
story_path = self.project_root / story_path
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
frontmatter, content = load_story_file(str(story_path))
|
|
65
|
+
except FileNotFoundError as e:
|
|
66
|
+
return False, [{'severity': 'CRITICAL', 'error': str(e)}]
|
|
67
|
+
|
|
68
|
+
story_id = frontmatter.get('id', 'UNKNOWN') if frontmatter else 'UNKNOWN'
|
|
69
|
+
|
|
70
|
+
# Check 1: Implementation Notes section must exist
|
|
71
|
+
if not has_implementation_notes(content):
|
|
72
|
+
violations.append({
|
|
73
|
+
'story_id': story_id,
|
|
74
|
+
'severity': 'HIGH',
|
|
75
|
+
'error': 'Implementation Notes section missing',
|
|
76
|
+
'fix': 'Add ## Implementation Notes section to story file'
|
|
77
|
+
})
|
|
78
|
+
# Can't continue validation without Implementation Notes
|
|
79
|
+
return False, violations
|
|
80
|
+
|
|
81
|
+
# Check 2: Find DoD vs Implementation mismatches
|
|
82
|
+
mismatches = find_dod_impl_mismatch(content)
|
|
83
|
+
|
|
84
|
+
for mismatch in mismatches:
|
|
85
|
+
item_text = mismatch['item']
|
|
86
|
+
|
|
87
|
+
if not mismatch['impl_found']:
|
|
88
|
+
# DoD item not in Implementation Notes
|
|
89
|
+
violations.append({
|
|
90
|
+
'story_id': story_id,
|
|
91
|
+
'item': item_text,
|
|
92
|
+
'severity': 'CRITICAL',
|
|
93
|
+
'error': 'DoD item marked [x] but missing from Implementation Notes',
|
|
94
|
+
'dod_status': '[x]' if mismatch['dod_checked'] else '[ ]',
|
|
95
|
+
'impl_status': 'NOT FOUND',
|
|
96
|
+
'fix': f'Add "- [x] {item_text} - Completed: ..." to Implementation Notes'
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
elif mismatch['dod_checked'] and not mismatch['impl_checked']:
|
|
100
|
+
# AUTONOMOUS DEFERRAL: DoD [x] but Impl [ ]
|
|
101
|
+
# This is the exact issue from tmp/output.md
|
|
102
|
+
|
|
103
|
+
impl_section = extract_section(content, "Implementation Notes")
|
|
104
|
+
justification = extract_item_justification(impl_section, item_text)
|
|
105
|
+
|
|
106
|
+
# Check for user approval marker
|
|
107
|
+
has_approval, marker_type = check_user_approval_marker(justification)
|
|
108
|
+
|
|
109
|
+
if not has_approval:
|
|
110
|
+
violations.append({
|
|
111
|
+
'story_id': story_id,
|
|
112
|
+
'item': item_text,
|
|
113
|
+
'severity': 'CRITICAL',
|
|
114
|
+
'error': 'AUTONOMOUS DEFERRAL DETECTED - DoD marked [x] but deferred without user approval',
|
|
115
|
+
'dod_status': '[x]',
|
|
116
|
+
'impl_status': '[ ]',
|
|
117
|
+
'justification': justification[:200] if justification else None,
|
|
118
|
+
'fix': 'Add user approval marker: "User approved: YES" OR STORY-XXX/ADR-XXX reference',
|
|
119
|
+
'violation_type': 'autonomous_deferral'
|
|
120
|
+
})
|
|
121
|
+
else:
|
|
122
|
+
# Has approval marker, validate references exist
|
|
123
|
+
violations.extend(self._validate_references(
|
|
124
|
+
story_id, item_text, justification, marker_type
|
|
125
|
+
))
|
|
126
|
+
|
|
127
|
+
# Check 3: Validate all deferred items have justifications
|
|
128
|
+
impl_items = extract_impl_notes_items(content)
|
|
129
|
+
|
|
130
|
+
for impl_item in impl_items:
|
|
131
|
+
if not impl_item['checked']: # Deferred item
|
|
132
|
+
item_text = impl_item['text'].split(' - ')[0].strip()
|
|
133
|
+
|
|
134
|
+
impl_section = extract_section(content, "Implementation Notes")
|
|
135
|
+
justification = extract_item_justification(impl_section, item_text)
|
|
136
|
+
|
|
137
|
+
if not justification or len(justification.strip()) < 10:
|
|
138
|
+
violations.append({
|
|
139
|
+
'story_id': story_id,
|
|
140
|
+
'item': item_text,
|
|
141
|
+
'severity': 'MEDIUM',
|
|
142
|
+
'error': 'Deferred item has insufficient justification',
|
|
143
|
+
'justification': justification if justification else '(none)',
|
|
144
|
+
'fix': 'Add detailed justification explaining deferral reason'
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
is_valid = len([v for v in violations if v['severity'] in ['CRITICAL', 'HIGH']]) == 0
|
|
148
|
+
|
|
149
|
+
return is_valid, violations
|
|
150
|
+
|
|
151
|
+
def _validate_references(self, story_id: str, item_text: str, justification: str, marker_type: str) -> List[Dict]:
|
|
152
|
+
"""
|
|
153
|
+
Validate that referenced stories/ADRs exist.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
story_id: Current story ID
|
|
157
|
+
item_text: DoD item text
|
|
158
|
+
justification: Justification text
|
|
159
|
+
marker_type: Type of approval marker found
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
List of violations if references don't exist
|
|
163
|
+
"""
|
|
164
|
+
violations = []
|
|
165
|
+
|
|
166
|
+
if marker_type == 'story_reference':
|
|
167
|
+
story_refs = extract_story_references(justification)
|
|
168
|
+
|
|
169
|
+
for ref in story_refs:
|
|
170
|
+
if not self._story_exists(ref):
|
|
171
|
+
violations.append({
|
|
172
|
+
'story_id': story_id,
|
|
173
|
+
'item': item_text,
|
|
174
|
+
'severity': 'HIGH',
|
|
175
|
+
'error': f'Referenced story {ref} does not exist',
|
|
176
|
+
'fix': f'Create {ref} or update reference to existing story'
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
if marker_type == 'adr_reference':
|
|
180
|
+
adr_refs = extract_adr_references(justification)
|
|
181
|
+
|
|
182
|
+
for ref in adr_refs:
|
|
183
|
+
if not self._adr_exists(ref):
|
|
184
|
+
violations.append({
|
|
185
|
+
'story_id': story_id,
|
|
186
|
+
'item': item_text,
|
|
187
|
+
'severity': 'HIGH',
|
|
188
|
+
'error': f'Referenced ADR {ref} does not exist',
|
|
189
|
+
'fix': f'Create {ref} or update reference to existing ADR'
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
return violations
|
|
193
|
+
|
|
194
|
+
def _story_exists(self, story_id: str) -> bool:
|
|
195
|
+
"""Check if story file exists in project."""
|
|
196
|
+
story_files = list(self.project_root.glob(f"devforgeai/specs/Stories/{story_id}*.story.md"))
|
|
197
|
+
return len(story_files) > 0
|
|
198
|
+
|
|
199
|
+
def _adr_exists(self, adr_id: str) -> bool:
|
|
200
|
+
"""Check if ADR file exists in project."""
|
|
201
|
+
adr_files = list(self.project_root.glob(f"devforgeai/specs/adrs/{adr_id}*.md"))
|
|
202
|
+
return len(adr_files) > 0
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def validate_dod(story_file: str, output_format: str = 'text', project_root: str = '.') -> int:
|
|
206
|
+
"""
|
|
207
|
+
Main validator entry point.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
story_file: Path to story file
|
|
211
|
+
output_format: 'text' or 'json'
|
|
212
|
+
project_root: Project root directory
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Exit code: 0 = valid, 1 = violations found, 2 = error
|
|
216
|
+
"""
|
|
217
|
+
validator = DoDValidator(project_root)
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
is_valid, violations = validator.validate(story_file)
|
|
221
|
+
|
|
222
|
+
if output_format == 'json':
|
|
223
|
+
import json
|
|
224
|
+
result = {
|
|
225
|
+
'valid': is_valid,
|
|
226
|
+
'violations': violations,
|
|
227
|
+
'story_file': story_file
|
|
228
|
+
}
|
|
229
|
+
print(json.dumps(result, indent=2))
|
|
230
|
+
|
|
231
|
+
else:
|
|
232
|
+
# Text output
|
|
233
|
+
if is_valid:
|
|
234
|
+
print(f"✅ {Path(story_file).name}: All DoD items validated")
|
|
235
|
+
return 0
|
|
236
|
+
else:
|
|
237
|
+
print(f"❌ VALIDATION FAILED: {Path(story_file).name}\n")
|
|
238
|
+
|
|
239
|
+
# Group by severity
|
|
240
|
+
critical = [v for v in violations if v['severity'] == 'CRITICAL']
|
|
241
|
+
high = [v for v in violations if v['severity'] == 'HIGH']
|
|
242
|
+
medium = [v for v in violations if v['severity'] == 'MEDIUM']
|
|
243
|
+
|
|
244
|
+
if critical:
|
|
245
|
+
print("CRITICAL VIOLATIONS:")
|
|
246
|
+
for v in critical:
|
|
247
|
+
print(f" • {v.get('item', v.get('story_id', 'N/A'))}")
|
|
248
|
+
print(f" Error: {v['error']}")
|
|
249
|
+
if 'dod_status' in v:
|
|
250
|
+
print(f" DoD: {v['dod_status']} | Impl: {v['impl_status']}")
|
|
251
|
+
if 'justification' in v and v['justification']:
|
|
252
|
+
print(f" Found: {v['justification']}")
|
|
253
|
+
if 'fix' in v:
|
|
254
|
+
print(f" Fix: {v['fix']}")
|
|
255
|
+
print()
|
|
256
|
+
|
|
257
|
+
if high:
|
|
258
|
+
print("HIGH VIOLATIONS:")
|
|
259
|
+
for v in high:
|
|
260
|
+
print(f" • {v.get('item', 'N/A')}")
|
|
261
|
+
print(f" Error: {v['error']}")
|
|
262
|
+
print(f" Fix: {v['fix']}")
|
|
263
|
+
print()
|
|
264
|
+
|
|
265
|
+
if medium:
|
|
266
|
+
print("MEDIUM VIOLATIONS:")
|
|
267
|
+
for v in medium:
|
|
268
|
+
print(f" • {v.get('item', v.get('story_id', 'N/A'))}")
|
|
269
|
+
print(f" Error: {v['error']}")
|
|
270
|
+
if 'fix' in v:
|
|
271
|
+
print(f" Fix: {v['fix']}")
|
|
272
|
+
print()
|
|
273
|
+
|
|
274
|
+
print("=" * 80)
|
|
275
|
+
print("GIT COMMIT BLOCKED - Fix violations before committing")
|
|
276
|
+
print("=" * 80)
|
|
277
|
+
print("\nRequired for all deferred DoD items:")
|
|
278
|
+
print(" 1. Reference to follow-up story (STORY-XXX), OR")
|
|
279
|
+
print(" 2. Reference to scope change ADR (ADR-XXX), OR")
|
|
280
|
+
print(" 3. External blocker: 'Blocked by: ... (external)', OR")
|
|
281
|
+
print(" 4. Explicit marker: 'User approved: [reason]'")
|
|
282
|
+
print("\nTo fix:")
|
|
283
|
+
print(" 1. Re-run /dev command and answer AskUserQuestion for each deferral, OR")
|
|
284
|
+
print(" 2. Manually add approval marker to Implementation Notes")
|
|
285
|
+
print()
|
|
286
|
+
|
|
287
|
+
return 1
|
|
288
|
+
|
|
289
|
+
except Exception as e:
|
|
290
|
+
print(f"ERROR: Validation failed with exception: {e}", file=sys.stderr)
|
|
291
|
+
return 2
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
if __name__ == '__main__':
|
|
295
|
+
import argparse
|
|
296
|
+
|
|
297
|
+
parser = argparse.ArgumentParser(
|
|
298
|
+
description='Validate Definition of Done completion and detect autonomous deferrals'
|
|
299
|
+
)
|
|
300
|
+
parser.add_argument('story_file', help='Path to story file (.story.md)')
|
|
301
|
+
parser.add_argument('--format', choices=['text', 'json'], default='text',
|
|
302
|
+
help='Output format (default: text)')
|
|
303
|
+
parser.add_argument('--project-root', default='.',
|
|
304
|
+
help='Project root directory (default: current directory)')
|
|
305
|
+
|
|
306
|
+
args = parser.parse_args()
|
|
307
|
+
|
|
308
|
+
exit_code = validate_dod(args.story_file, args.format, args.project_root)
|
|
309
|
+
sys.exit(exit_code)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Git Availability Validator
|
|
4
|
+
|
|
5
|
+
Checks if directory is a Git repository.
|
|
6
|
+
Prevents RCA-006 errors by validating Git before workflow commands execute.
|
|
7
|
+
|
|
8
|
+
Uses proven pattern: git rev-parse --is-inside-work-tree
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Tuple
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def check_git(directory: str = ".") -> Tuple[bool, str]:
|
|
18
|
+
"""
|
|
19
|
+
Check if directory is a Git repository.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
directory: Directory to check (default: current directory)
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Tuple of (is_git_repo, message)
|
|
26
|
+
|
|
27
|
+
Uses the proven pattern: git rev-parse --is-inside-work-tree
|
|
28
|
+
This is the same pattern used in implementing-stories skill Phase 0.
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
result = subprocess.run(
|
|
32
|
+
["git", "rev-parse", "--is-inside-work-tree"],
|
|
33
|
+
cwd=directory,
|
|
34
|
+
capture_output=True,
|
|
35
|
+
text=True,
|
|
36
|
+
timeout=5
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
if result.returncode == 0 and result.stdout.strip() == "true":
|
|
40
|
+
return True, f"✅ Git repository detected: {Path(directory).resolve()}"
|
|
41
|
+
else:
|
|
42
|
+
return False, f"❌ Not a Git repository: {Path(directory).resolve()}"
|
|
43
|
+
|
|
44
|
+
except subprocess.TimeoutExpired:
|
|
45
|
+
return False, "❌ Git command timed out"
|
|
46
|
+
|
|
47
|
+
except FileNotFoundError:
|
|
48
|
+
return False, "❌ Git not installed or not in PATH"
|
|
49
|
+
|
|
50
|
+
except Exception as e:
|
|
51
|
+
return False, f"❌ Error checking Git: {e}"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def validate_git(directory: str = ".", output_format: str = 'text') -> int:
|
|
55
|
+
"""
|
|
56
|
+
Main validator entry point.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
directory: Directory to check
|
|
60
|
+
output_format: 'text' or 'json'
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Exit code: 0 = Git available, 1 = Git not available, 2 = error
|
|
64
|
+
"""
|
|
65
|
+
is_git, message = check_git(directory)
|
|
66
|
+
|
|
67
|
+
if output_format == 'json':
|
|
68
|
+
import json
|
|
69
|
+
result = {
|
|
70
|
+
'git_available': is_git,
|
|
71
|
+
'message': message,
|
|
72
|
+
'directory': str(Path(directory).resolve())
|
|
73
|
+
}
|
|
74
|
+
print(json.dumps(result, indent=2))
|
|
75
|
+
else:
|
|
76
|
+
print(message)
|
|
77
|
+
|
|
78
|
+
if not is_git:
|
|
79
|
+
print("\nDevForgeAI workflows require Git:")
|
|
80
|
+
print(" - /dev (TDD development)")
|
|
81
|
+
print(" - /qa (quality validation)")
|
|
82
|
+
print(" - /release (deployment)")
|
|
83
|
+
print(" - /orchestrate (full lifecycle)")
|
|
84
|
+
print("\nTo initialize Git:")
|
|
85
|
+
print(" git init")
|
|
86
|
+
print(" git add .")
|
|
87
|
+
print(" git commit -m 'Initial commit'")
|
|
88
|
+
print()
|
|
89
|
+
|
|
90
|
+
return 0 if is_git else 1
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if __name__ == '__main__':
|
|
94
|
+
import argparse
|
|
95
|
+
|
|
96
|
+
parser = argparse.ArgumentParser(
|
|
97
|
+
description='Check if directory is a Git repository'
|
|
98
|
+
)
|
|
99
|
+
parser.add_argument('--directory', default='.',
|
|
100
|
+
help='Directory to check (default: current directory)')
|
|
101
|
+
parser.add_argument('--format', choices=['text', 'json'], default='text',
|
|
102
|
+
help='Output format (default: text)')
|
|
103
|
+
|
|
104
|
+
args = parser.parse_args()
|
|
105
|
+
|
|
106
|
+
exit_code = validate_git(args.directory, args.format)
|
|
107
|
+
sys.exit(exit_code)
|