devforgeai 1.0.5 → 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 (131) 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/designing-systems/scripts/__pycache__/detect_anti_patterns.cpython-312.pyc +0 -0
  127. package/src/claude/skills/designing-systems/scripts/__pycache__/validate_all_context.cpython-312.pyc +0 -0
  128. package/src/claude/skills/designing-systems/scripts/__pycache__/validate_architecture.cpython-312.pyc +0 -0
  129. package/src/claude/skills/designing-systems/scripts/__pycache__/validate_dependencies.cpython-312.pyc +0 -0
  130. package/src/claude/skills/devforgeai-story-creation/scripts/__pycache__/migrate_story_v1_to_v2.cpython-312.pyc +0 -0
  131. 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)