devforgeai 1.0.5 → 1.0.7

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 (133) hide show
  1. package/CLAUDE.md +120 -0
  2. package/bin/devforgeai.js +0 -0
  3. package/package.json +9 -1
  4. package/src/CLAUDE.md +699 -0
  5. package/src/claude/hooks/phase-completion-gate.sh +0 -0
  6. package/src/claude/scripts/README.md +396 -0
  7. package/src/claude/scripts/audit-command-skill-overlap.sh +67 -0
  8. package/src/claude/scripts/check-hooks-fast.sh +70 -0
  9. package/src/claude/scripts/devforgeai-validate +6 -0
  10. package/src/claude/scripts/devforgeai_cli/README.md +531 -0
  11. package/src/claude/scripts/devforgeai_cli/__init__.py +12 -0
  12. package/src/claude/scripts/devforgeai_cli/cli.py +716 -0
  13. package/src/claude/scripts/devforgeai_cli/commands/__init__.py +1 -0
  14. package/src/claude/scripts/devforgeai_cli/commands/check_hooks.py +384 -0
  15. package/src/claude/scripts/devforgeai_cli/commands/invoke_hooks.py +149 -0
  16. package/src/claude/scripts/devforgeai_cli/commands/phase_commands.py +731 -0
  17. package/src/claude/scripts/devforgeai_cli/commands/validate_installation.py +412 -0
  18. package/src/claude/scripts/devforgeai_cli/context_extraction.py +426 -0
  19. package/src/claude/scripts/devforgeai_cli/feedback/AC_TO_TEST_MAPPING.md +636 -0
  20. package/src/claude/scripts/devforgeai_cli/feedback/DELIVERY_SUMMARY.txt +329 -0
  21. package/src/claude/scripts/devforgeai_cli/feedback/README_TEST_SPECS.md +486 -0
  22. package/src/claude/scripts/devforgeai_cli/feedback/TEST_IMPLEMENTATION_GUIDE.md +529 -0
  23. package/src/claude/scripts/devforgeai_cli/feedback/TEST_SPECIFICATIONS.md +2652 -0
  24. package/src/claude/scripts/devforgeai_cli/feedback/TEST_SPECS_INDEX.md +398 -0
  25. package/src/claude/scripts/devforgeai_cli/feedback/__init__.py +34 -0
  26. package/src/claude/scripts/devforgeai_cli/feedback/adaptive_questioning_engine.py +581 -0
  27. package/src/claude/scripts/devforgeai_cli/feedback/aggregation.py +179 -0
  28. package/src/claude/scripts/devforgeai_cli/feedback/commands.py +535 -0
  29. package/src/claude/scripts/devforgeai_cli/feedback/config_defaults.py +58 -0
  30. package/src/claude/scripts/devforgeai_cli/feedback/config_manager.py +423 -0
  31. package/src/claude/scripts/devforgeai_cli/feedback/config_models.py +192 -0
  32. package/src/claude/scripts/devforgeai_cli/feedback/config_schema.py +140 -0
  33. package/src/claude/scripts/devforgeai_cli/feedback/coverage.json +1 -0
  34. package/src/claude/scripts/devforgeai_cli/feedback/feature_flag.py +152 -0
  35. package/src/claude/scripts/devforgeai_cli/feedback/feedback_indexer.py +394 -0
  36. package/src/claude/scripts/devforgeai_cli/feedback/hot_reload.py +226 -0
  37. package/src/claude/scripts/devforgeai_cli/feedback/longitudinal.py +115 -0
  38. package/src/claude/scripts/devforgeai_cli/feedback/models.py +67 -0
  39. package/src/claude/scripts/devforgeai_cli/feedback/question_router.py +236 -0
  40. package/src/claude/scripts/devforgeai_cli/feedback/retrospective.py +233 -0
  41. package/src/claude/scripts/devforgeai_cli/feedback/skip_tracker.py +177 -0
  42. package/src/claude/scripts/devforgeai_cli/feedback/skip_tracking.py +221 -0
  43. package/src/claude/scripts/devforgeai_cli/feedback/template_engine.py +549 -0
  44. package/src/claude/scripts/devforgeai_cli/feedback/validation.py +163 -0
  45. package/src/claude/scripts/devforgeai_cli/headless/__init__.py +30 -0
  46. package/src/claude/scripts/devforgeai_cli/headless/answer_models.py +206 -0
  47. package/src/claude/scripts/devforgeai_cli/headless/answer_resolver.py +204 -0
  48. package/src/claude/scripts/devforgeai_cli/headless/exceptions.py +36 -0
  49. package/src/claude/scripts/devforgeai_cli/headless/pattern_matcher.py +156 -0
  50. package/src/claude/scripts/devforgeai_cli/hooks.py +313 -0
  51. package/src/claude/scripts/devforgeai_cli/metrics/__init__.py +46 -0
  52. package/src/claude/scripts/devforgeai_cli/metrics/command_metrics.py +142 -0
  53. package/src/claude/scripts/devforgeai_cli/metrics/failure_modes.py +152 -0
  54. package/src/claude/scripts/devforgeai_cli/metrics/story_segmentation.py +181 -0
  55. package/src/claude/scripts/devforgeai_cli/orchestrate_hooks.py +780 -0
  56. package/src/claude/scripts/devforgeai_cli/phase_state.py +1229 -0
  57. package/src/claude/scripts/devforgeai_cli/session/__init__.py +30 -0
  58. package/src/claude/scripts/devforgeai_cli/session/checkpoint.py +268 -0
  59. package/src/claude/scripts/devforgeai_cli/tests/__init__.py +1 -0
  60. package/src/claude/scripts/devforgeai_cli/tests/conftest.py +29 -0
  61. package/src/claude/scripts/devforgeai_cli/tests/feedback/TEST_EXECUTION_GUIDE.md +298 -0
  62. package/src/claude/scripts/devforgeai_cli/tests/feedback/__init__.py +3 -0
  63. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_adaptive_questioning_engine.py +2171 -0
  64. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_aggregation.py +476 -0
  65. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_defaults.py +133 -0
  66. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_manager.py +592 -0
  67. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_models.py +373 -0
  68. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_schema.py +130 -0
  69. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_configuration_management.py +1355 -0
  70. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_edge_cases.py +308 -0
  71. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_feature_flag.py +307 -0
  72. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_feedback_indexer.py +384 -0
  73. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_hot_reload.py +580 -0
  74. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_integration.py +402 -0
  75. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_models.py +105 -0
  76. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_question_routing.py +262 -0
  77. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_retrospective.py +333 -0
  78. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracker.py +410 -0
  79. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracking.py +159 -0
  80. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracking_integration.py +1155 -0
  81. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_template_engine.py +1389 -0
  82. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_validation_comprehensive.py +210 -0
  83. package/src/claude/scripts/devforgeai_cli/tests/fixtures/autonomous-deferral-story.md +46 -0
  84. package/src/claude/scripts/devforgeai_cli/tests/fixtures/missing-impl-notes.md +31 -0
  85. package/src/claude/scripts/devforgeai_cli/tests/fixtures/valid-deferral-story.md +46 -0
  86. package/src/claude/scripts/devforgeai_cli/tests/fixtures/valid-story-complete.md +48 -0
  87. package/src/claude/scripts/devforgeai_cli/tests/manual_test_invoke_hooks.sh +200 -0
  88. package/src/claude/scripts/devforgeai_cli/tests/session/DELIVERABLES.md +518 -0
  89. package/src/claude/scripts/devforgeai_cli/tests/session/TEST_SUMMARY.md +468 -0
  90. package/src/claude/scripts/devforgeai_cli/tests/session/__init__.py +6 -0
  91. package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/corrupted-checkpoint.json +1 -0
  92. package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/missing-fields-checkpoint.json +4 -0
  93. package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/valid-checkpoint.json +15 -0
  94. package/src/claude/scripts/devforgeai_cli/tests/session/test_checkpoint.py +851 -0
  95. package/src/claude/scripts/devforgeai_cli/tests/test_check_hooks.py +1886 -0
  96. package/src/claude/scripts/devforgeai_cli/tests/test_depends_on_normalizer.py +171 -0
  97. package/src/claude/scripts/devforgeai_cli/tests/test_dod_validator.py +97 -0
  98. package/src/claude/scripts/devforgeai_cli/tests/test_invoke_hooks.py +1902 -0
  99. package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands.py +320 -0
  100. package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands_error_handling.py +1021 -0
  101. package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands_import.py +697 -0
  102. package/src/claude/scripts/devforgeai_cli/tests/test_phase_state.py +2187 -0
  103. package/src/claude/scripts/devforgeai_cli/tests/test_skip_tracking.py +2141 -0
  104. package/src/claude/scripts/devforgeai_cli/tests/test_skip_tracking_coverage_gap.py +195 -0
  105. package/src/claude/scripts/devforgeai_cli/tests/test_subagent_enforcement.py +539 -0
  106. package/src/claude/scripts/devforgeai_cli/tests/test_validate_installation.py +361 -0
  107. package/src/claude/scripts/devforgeai_cli/utils/__init__.py +11 -0
  108. package/src/claude/scripts/devforgeai_cli/utils/depends_on_normalizer.py +149 -0
  109. package/src/claude/scripts/devforgeai_cli/utils/markdown_parser.py +219 -0
  110. package/src/claude/scripts/devforgeai_cli/utils/story_analyzer.py +249 -0
  111. package/src/claude/scripts/devforgeai_cli/utils/yaml_parser.py +152 -0
  112. package/src/claude/scripts/devforgeai_cli/validators/__init__.py +27 -0
  113. package/src/claude/scripts/devforgeai_cli/validators/ast_grep_validator.py +373 -0
  114. package/src/claude/scripts/devforgeai_cli/validators/context_validator.py +180 -0
  115. package/src/claude/scripts/devforgeai_cli/validators/dod_validator.py +309 -0
  116. package/src/claude/scripts/devforgeai_cli/validators/git_validator.py +107 -0
  117. package/src/claude/scripts/devforgeai_cli/validators/grep_fallback.py +300 -0
  118. package/src/claude/scripts/install_hooks.sh +186 -0
  119. package/src/claude/scripts/invoke_feedback_hooks.sh +59 -0
  120. package/src/claude/scripts/migrate-ac-headers.sh +122 -0
  121. package/src/claude/scripts/plan_file_kb.sh +704 -0
  122. package/src/claude/scripts/requirements.txt +8 -0
  123. package/src/claude/scripts/session_catalog.sh +543 -0
  124. package/src/claude/scripts/setup.py +55 -0
  125. package/src/claude/scripts/start-devforgeai.sh +16 -0
  126. package/src/claude/scripts/statusline.sh +27 -0
  127. package/src/claude/scripts/validate_deferrals.py +344 -0
  128. package/src/claude/skills/designing-systems/scripts/__pycache__/detect_anti_patterns.cpython-312.pyc +0 -0
  129. package/src/claude/skills/designing-systems/scripts/__pycache__/validate_all_context.cpython-312.pyc +0 -0
  130. package/src/claude/skills/designing-systems/scripts/__pycache__/validate_architecture.cpython-312.pyc +0 -0
  131. package/src/claude/skills/designing-systems/scripts/__pycache__/validate_dependencies.cpython-312.pyc +0 -0
  132. package/src/claude/skills/devforgeai-story-creation/scripts/__pycache__/migrate_story_v1_to_v2.cpython-312.pyc +0 -0
  133. package/src/claude/skills/devforgeai-story-creation/scripts/tests/__pycache__/measure_accuracy.cpython-312.pyc +0 -0
@@ -0,0 +1,1021 @@
1
+ """
2
+ Tests for STORY-255: Add Graceful Error Handling for Missing PhaseState Module.
3
+
4
+ TDD Red Phase: These tests verify graceful error handling when the PhaseState
5
+ module cannot be imported, providing helpful diagnostic messages to users.
6
+
7
+ Acceptance Criteria:
8
+ - AC#1: Provide helpful error message when PhaseState import fails
9
+ - AC#2: Error message includes context about STORY-253 implementation
10
+ - AC#3: Error is raised as ImportError with cause chain
11
+ - AC#4: All phase commands handle error consistently
12
+
13
+ Technical Specification:
14
+ - Function to enhance: _get_phase_state(project_root: str)
15
+ - Location: .claude/scripts/devforgeai_cli/commands/phase_commands.py
16
+ - Current behavior: Raises bare ImportError
17
+ - Expected behavior: Raises ImportError with helpful diagnostic message
18
+
19
+ Test Framework: pytest (per tech-stack.md)
20
+ Test Naming: test_<function>_<scenario>_<expected>
21
+ """
22
+
23
+ import json
24
+ import re
25
+ import sys
26
+ import tempfile
27
+ from pathlib import Path
28
+ from typing import Optional
29
+ from unittest.mock import patch, MagicMock
30
+ import importlib
31
+
32
+ import pytest
33
+
34
+
35
+ # =============================================================================
36
+ # Test Fixtures
37
+ # =============================================================================
38
+
39
+
40
+ @pytest.fixture
41
+ def temp_project_dir():
42
+ """Create a temporary project directory with required structure."""
43
+ with tempfile.TemporaryDirectory() as tmpdir:
44
+ project_root = Path(tmpdir)
45
+ # Create devforgeai/workflows directory
46
+ workflows_dir = project_root / "devforgeai" / "workflows"
47
+ workflows_dir.mkdir(parents=True, exist_ok=True)
48
+ yield project_root
49
+
50
+
51
+ @pytest.fixture
52
+ def existing_state(temp_project_dir):
53
+ """Create an existing phase state file for testing commands."""
54
+ state = {
55
+ "story_id": "STORY-001",
56
+ "workflow_started": "2026-01-12T10:00:00Z",
57
+ "current_phase": "02",
58
+ "blocking_status": False,
59
+ "phases": {
60
+ "01": {
61
+ "status": "completed",
62
+ "started_at": "2026-01-12T10:00:00Z",
63
+ "completed_at": "2026-01-12T10:05:00Z",
64
+ "subagents_required": ["git-validator", "tech-stack-detector"],
65
+ "subagents_invoked": ["git-validator", "tech-stack-detector"],
66
+ "checkpoint_passed": True
67
+ },
68
+ "02": {
69
+ "status": "pending",
70
+ "subagents_required": ["test-automator"],
71
+ "subagents_invoked": []
72
+ },
73
+ **{f"{i:02d}": {"status": "pending", "subagents_required": [], "subagents_invoked": []}
74
+ for i in range(3, 11)}
75
+ },
76
+ "validation_errors": [],
77
+ "observations": []
78
+ }
79
+
80
+ state_file = temp_project_dir / "devforgeai" / "workflows" / "STORY-001-phase-state.json"
81
+ state_file.write_text(json.dumps(state, indent=2))
82
+ return state_file
83
+
84
+
85
+ @pytest.fixture
86
+ def mock_import_error():
87
+ """Create a mock that simulates PhaseState import failure."""
88
+ original_import = None
89
+
90
+ def import_blocker(name, *args, **kwargs):
91
+ """Block import of phase_state module to simulate missing module."""
92
+ if 'phase_state' in name or (args and 'phase_state' in str(args)):
93
+ raise ImportError("No module named 'devforgeai_cli.phase_state'")
94
+ return original_import(name, *args, **kwargs)
95
+
96
+ return import_blocker
97
+
98
+
99
+ # =============================================================================
100
+ # AC#1: Provide helpful error message when PhaseState import fails
101
+ # =============================================================================
102
+
103
+
104
+ class TestAC1_HelpfulErrorMessage:
105
+ """
106
+ AC#1: Provide helpful error message when PhaseState import fails
107
+
108
+ Given: The PhaseState module is missing or cannot be imported
109
+ When: Any phase command is executed (phase-init, phase-check, phase-complete,
110
+ phase-status, phase-record)
111
+ Then: A clear error message is displayed containing:
112
+ - What went wrong (ImportError with original error)
113
+ - Expected module location: `.claude/scripts/devforgeai_cli/phase_state.py`
114
+ - Fix instructions: `pip install -e .claude/scripts/`
115
+ - Note that /dev workflow can continue without CLI-based phase enforcement
116
+ """
117
+
118
+ def test_get_phase_state_error_contains_original_error_message(
119
+ self,
120
+ temp_project_dir
121
+ ):
122
+ """
123
+ Error message should include the original ImportError details.
124
+
125
+ Expected: "PhaseState module not found: <original error>"
126
+ """
127
+ # We need to mock the import to simulate failure
128
+ with patch.dict('sys.modules', {'devforgeai_cli.phase_state': None}):
129
+ # Force reimport to trigger the error
130
+ with patch(
131
+ 'devforgeai_cli.commands.phase_commands._get_phase_state'
132
+ ) as mock_get:
133
+ # Simulate the graceful error handling behavior we expect
134
+ original_error = ImportError("No module named 'devforgeai_cli.phase_state'")
135
+ mock_get.side_effect = ImportError(
136
+ f"PhaseState module not found: {original_error}\n\n"
137
+ "The phase_state.py module is required for phase tracking."
138
+ )
139
+
140
+ from devforgeai_cli.commands import phase_commands
141
+
142
+ with pytest.raises(ImportError) as exc_info:
143
+ mock_get(str(temp_project_dir))
144
+
145
+ error_message = str(exc_info.value)
146
+
147
+ assert "PhaseState module not found" in error_message, (
148
+ f"FAIL (TDD Red): Error message should contain 'PhaseState module not found'\n"
149
+ f"Actual message: {error_message}"
150
+ )
151
+
152
+ def test_get_phase_state_error_contains_expected_location(
153
+ self,
154
+ temp_project_dir
155
+ ):
156
+ """
157
+ Error message should include expected module location.
158
+
159
+ Expected: ".claude/scripts/devforgeai_cli/phase_state.py"
160
+ """
161
+ expected_location = ".claude/scripts/devforgeai_cli/phase_state.py"
162
+
163
+ # Simulate the error by patching _get_phase_state to raise our expected error
164
+ with patch(
165
+ 'devforgeai_cli.commands.phase_commands._get_phase_state'
166
+ ) as mock_get:
167
+ mock_get.side_effect = ImportError(
168
+ f"PhaseState module not found: test\n\n"
169
+ f"Expected location: {expected_location}"
170
+ )
171
+
172
+ with pytest.raises(ImportError) as exc_info:
173
+ mock_get(str(temp_project_dir))
174
+
175
+ error_message = str(exc_info.value)
176
+
177
+ assert expected_location in error_message, (
178
+ f"FAIL (TDD Red): Error message should contain expected location "
179
+ f"'{expected_location}'\n"
180
+ f"Actual message: {error_message}"
181
+ )
182
+
183
+ def test_get_phase_state_error_contains_fix_instructions(
184
+ self,
185
+ temp_project_dir
186
+ ):
187
+ """
188
+ Error message should include fix instructions with pip install command.
189
+
190
+ Expected: "pip install -e .claude/scripts/"
191
+ """
192
+ fix_command = "pip install -e .claude/scripts/"
193
+
194
+ with patch(
195
+ 'devforgeai_cli.commands.phase_commands._get_phase_state'
196
+ ) as mock_get:
197
+ mock_get.side_effect = ImportError(
198
+ f"PhaseState module not found: test\n\n"
199
+ f"To fix:\n 1. Ensure STORY-253 is implemented\n"
200
+ f" 2. Reinstall CLI: {fix_command}"
201
+ )
202
+
203
+ with pytest.raises(ImportError) as exc_info:
204
+ mock_get(str(temp_project_dir))
205
+
206
+ error_message = str(exc_info.value)
207
+
208
+ assert fix_command in error_message, (
209
+ f"FAIL (TDD Red): Error message should contain fix command "
210
+ f"'{fix_command}'\n"
211
+ f"Actual message: {error_message}"
212
+ )
213
+
214
+ def test_get_phase_state_error_contains_dev_workflow_note(
215
+ self,
216
+ temp_project_dir
217
+ ):
218
+ """
219
+ Error message should note that /dev workflow can continue.
220
+
221
+ Expected: Note about /dev workflow continuing without CLI-based enforcement
222
+ """
223
+ with patch(
224
+ 'devforgeai_cli.commands.phase_commands._get_phase_state'
225
+ ) as mock_get:
226
+ mock_get.side_effect = ImportError(
227
+ "PhaseState module not found: test\n\n"
228
+ "Note: The /dev workflow can continue without CLI-based phase\n"
229
+ "enforcement if this module is unavailable."
230
+ )
231
+
232
+ with pytest.raises(ImportError) as exc_info:
233
+ mock_get(str(temp_project_dir))
234
+
235
+ error_message = str(exc_info.value)
236
+
237
+ assert "/dev workflow can continue" in error_message, (
238
+ f"FAIL (TDD Red): Error message should contain note about /dev workflow\n"
239
+ f"Actual message: {error_message}"
240
+ )
241
+
242
+
243
+ # =============================================================================
244
+ # AC#2: Error message includes context about STORY-253 implementation
245
+ # =============================================================================
246
+
247
+
248
+ class TestAC2_Story253Context:
249
+ """
250
+ AC#2: Error message includes context about STORY-253 implementation
251
+
252
+ Given: The PhaseState module is not found
253
+ When: _get_phase_state() raises ImportError
254
+ Then: The error message mentions:
255
+ - STORY-253 must be implemented (PhaseState module creation)
256
+ - Installation command to reinstall the CLI
257
+ """
258
+
259
+ def test_error_message_mentions_story_253(
260
+ self,
261
+ temp_project_dir
262
+ ):
263
+ """
264
+ Error message should reference STORY-253 for PhaseState module creation.
265
+ """
266
+ with patch(
267
+ 'devforgeai_cli.commands.phase_commands._get_phase_state'
268
+ ) as mock_get:
269
+ mock_get.side_effect = ImportError(
270
+ "PhaseState module not found: test\n\n"
271
+ "To fix:\n"
272
+ " 1. Ensure STORY-253 (PhaseState module) is implemented\n"
273
+ " 2. Reinstall CLI: pip install -e .claude/scripts/"
274
+ )
275
+
276
+ with pytest.raises(ImportError) as exc_info:
277
+ mock_get(str(temp_project_dir))
278
+
279
+ error_message = str(exc_info.value)
280
+
281
+ assert "STORY-253" in error_message, (
282
+ f"FAIL (TDD Red): Error message should reference STORY-253\n"
283
+ f"Actual message: {error_message}"
284
+ )
285
+
286
+ def test_error_message_mentions_reinstall_cli(
287
+ self,
288
+ temp_project_dir
289
+ ):
290
+ """
291
+ Error message should include CLI reinstallation instruction.
292
+ """
293
+ with patch(
294
+ 'devforgeai_cli.commands.phase_commands._get_phase_state'
295
+ ) as mock_get:
296
+ mock_get.side_effect = ImportError(
297
+ "PhaseState module not found: test\n\n"
298
+ "To fix:\n"
299
+ " 1. Ensure STORY-253 is implemented\n"
300
+ " 2. Reinstall CLI: pip install -e .claude/scripts/"
301
+ )
302
+
303
+ with pytest.raises(ImportError) as exc_info:
304
+ mock_get(str(temp_project_dir))
305
+
306
+ error_message = str(exc_info.value)
307
+
308
+ # Should contain either "reinstall" or "Reinstall"
309
+ assert re.search(r'[Rr]einstall', error_message), (
310
+ f"FAIL (TDD Red): Error message should mention reinstalling CLI\n"
311
+ f"Actual message: {error_message}"
312
+ )
313
+
314
+
315
+ # =============================================================================
316
+ # AC#3: Error is raised as ImportError with cause chain
317
+ # =============================================================================
318
+
319
+
320
+ class TestAC3_ImportErrorWithCauseChain:
321
+ """
322
+ AC#3: Error is raised as ImportError with cause chain
323
+
324
+ Given: PhaseState module fails to import
325
+ When: _get_phase_state(project_root) is called
326
+ Then: An ImportError is raised:
327
+ - Original exception preserved as __cause__ (for traceback)
328
+ - Message contains all required information
329
+ - Not silently caught or transformed to different type
330
+ """
331
+
332
+ def test_error_preserves_original_exception_as_cause(
333
+ self,
334
+ temp_project_dir
335
+ ):
336
+ """
337
+ ImportError should have __cause__ set to original exception.
338
+
339
+ The error should be raised with `from e` syntax:
340
+ raise ImportError("...") from e
341
+ """
342
+ original_error = ImportError("No module named 'devforgeai_cli.phase_state'")
343
+
344
+ with patch(
345
+ 'devforgeai_cli.commands.phase_commands._get_phase_state'
346
+ ) as mock_get:
347
+ # Create an ImportError with a cause
348
+ new_error = ImportError("PhaseState module not found: test")
349
+ new_error.__cause__ = original_error
350
+ mock_get.side_effect = new_error
351
+
352
+ with pytest.raises(ImportError) as exc_info:
353
+ mock_get(str(temp_project_dir))
354
+
355
+ # Verify cause chain is preserved
356
+ assert exc_info.value.__cause__ is not None or "module not found" in str(exc_info.value).lower(), (
357
+ "FAIL (TDD Red): ImportError should have __cause__ set to original exception\n"
358
+ "This enables proper traceback for debugging."
359
+ )
360
+
361
+ def test_error_type_is_import_error_not_transformed(
362
+ self,
363
+ temp_project_dir
364
+ ):
365
+ """
366
+ Error should be ImportError, not transformed to different type.
367
+
368
+ Must NOT be:
369
+ - RuntimeError
370
+ - Exception (generic)
371
+ - SystemExit
372
+ - Custom exception type
373
+ """
374
+ with patch(
375
+ 'devforgeai_cli.commands.phase_commands._get_phase_state'
376
+ ) as mock_get:
377
+ mock_get.side_effect = ImportError("PhaseState module not found: test")
378
+
379
+ with pytest.raises(ImportError) as exc_info:
380
+ mock_get(str(temp_project_dir))
381
+
382
+ # Verify exact type
383
+ assert type(exc_info.value) is ImportError, (
384
+ f"FAIL (TDD Red): Error should be ImportError, not {type(exc_info.value).__name__}"
385
+ )
386
+
387
+ def test_error_is_not_silently_caught(
388
+ self,
389
+ temp_project_dir
390
+ ):
391
+ """
392
+ ImportError should propagate, not be silently caught.
393
+
394
+ The function should NOT:
395
+ - Return None on error
396
+ - Return a default value
397
+ - Print error and continue
398
+ """
399
+ with patch(
400
+ 'devforgeai_cli.commands.phase_commands._get_phase_state'
401
+ ) as mock_get:
402
+ mock_get.side_effect = ImportError("PhaseState module not found: test")
403
+
404
+ # This should raise, not return None or empty value
405
+ with pytest.raises(ImportError):
406
+ result = mock_get(str(temp_project_dir))
407
+
408
+ # If we reach here, error was silently caught
409
+ pytest.fail(
410
+ f"FAIL (TDD Red): ImportError should propagate, not return {result}"
411
+ )
412
+
413
+
414
+ # =============================================================================
415
+ # AC#4: All phase commands handle error consistently
416
+ # =============================================================================
417
+
418
+
419
+ class TestAC4_AllCommandsHandleErrorConsistently:
420
+ """
421
+ AC#4: All phase commands handle error consistently
422
+
423
+ Given: Any phase command invokes _get_phase_state()
424
+ When: ImportError is raised
425
+ Then: The error propagates with helpful message to CLI output
426
+ Exit code reflects failure (non-zero)
427
+ """
428
+
429
+ def test_phase_init_command_propagates_import_error(
430
+ self,
431
+ temp_project_dir,
432
+ capsys
433
+ ):
434
+ """
435
+ phase_init_command should propagate ImportError with non-zero exit.
436
+ """
437
+ with patch(
438
+ 'devforgeai_cli.commands.phase_commands._get_phase_state'
439
+ ) as mock_get:
440
+ mock_get.side_effect = ImportError("PhaseState module not found")
441
+
442
+ from devforgeai_cli.commands.phase_commands import phase_init_command
443
+
444
+ # Command should either raise or return non-zero exit code
445
+ try:
446
+ exit_code = phase_init_command(
447
+ story_id="STORY-100",
448
+ project_root=str(temp_project_dir),
449
+ format="text"
450
+ )
451
+
452
+ # If exception caught internally, exit code should be non-zero
453
+ assert exit_code != 0, (
454
+ "FAIL (TDD Red): phase_init_command should return non-zero exit code "
455
+ "when ImportError occurs"
456
+ )
457
+ except ImportError:
458
+ # Error propagated - this is acceptable behavior
459
+ pass
460
+
461
+ def test_phase_check_command_propagates_import_error(
462
+ self,
463
+ temp_project_dir,
464
+ capsys
465
+ ):
466
+ """
467
+ phase_check_command should propagate ImportError with non-zero exit.
468
+ """
469
+ with patch(
470
+ 'devforgeai_cli.commands.phase_commands._get_phase_state'
471
+ ) as mock_get:
472
+ mock_get.side_effect = ImportError("PhaseState module not found")
473
+
474
+ from devforgeai_cli.commands.phase_commands import phase_check_command
475
+
476
+ try:
477
+ exit_code = phase_check_command(
478
+ story_id="STORY-001",
479
+ from_phase="01",
480
+ to_phase="02",
481
+ project_root=str(temp_project_dir),
482
+ format="text"
483
+ )
484
+
485
+ assert exit_code != 0, (
486
+ "FAIL (TDD Red): phase_check_command should return non-zero exit code"
487
+ )
488
+ except ImportError:
489
+ pass
490
+
491
+ def test_phase_complete_command_propagates_import_error(
492
+ self,
493
+ temp_project_dir,
494
+ capsys
495
+ ):
496
+ """
497
+ phase_complete_command should propagate ImportError with non-zero exit.
498
+ """
499
+ with patch(
500
+ 'devforgeai_cli.commands.phase_commands._get_phase_state'
501
+ ) as mock_get:
502
+ mock_get.side_effect = ImportError("PhaseState module not found")
503
+
504
+ from devforgeai_cli.commands.phase_commands import phase_complete_command
505
+
506
+ try:
507
+ exit_code = phase_complete_command(
508
+ story_id="STORY-001",
509
+ phase="02",
510
+ checkpoint_passed=True,
511
+ project_root=str(temp_project_dir),
512
+ format="text"
513
+ )
514
+
515
+ assert exit_code != 0, (
516
+ "FAIL (TDD Red): phase_complete_command should return non-zero exit code"
517
+ )
518
+ except ImportError:
519
+ pass
520
+
521
+ def test_phase_status_command_propagates_import_error(
522
+ self,
523
+ temp_project_dir,
524
+ capsys
525
+ ):
526
+ """
527
+ phase_status_command should propagate ImportError with non-zero exit.
528
+ """
529
+ with patch(
530
+ 'devforgeai_cli.commands.phase_commands._get_phase_state'
531
+ ) as mock_get:
532
+ mock_get.side_effect = ImportError("PhaseState module not found")
533
+
534
+ from devforgeai_cli.commands.phase_commands import phase_status_command
535
+
536
+ try:
537
+ exit_code = phase_status_command(
538
+ story_id="STORY-001",
539
+ project_root=str(temp_project_dir),
540
+ format="text"
541
+ )
542
+
543
+ assert exit_code != 0, (
544
+ "FAIL (TDD Red): phase_status_command should return non-zero exit code"
545
+ )
546
+ except ImportError:
547
+ pass
548
+
549
+ def test_phase_record_command_propagates_import_error(
550
+ self,
551
+ temp_project_dir,
552
+ capsys
553
+ ):
554
+ """
555
+ phase_record_command should propagate ImportError with non-zero exit.
556
+ """
557
+ with patch(
558
+ 'devforgeai_cli.commands.phase_commands._get_phase_state'
559
+ ) as mock_get:
560
+ mock_get.side_effect = ImportError("PhaseState module not found")
561
+
562
+ from devforgeai_cli.commands.phase_commands import phase_record_command
563
+
564
+ try:
565
+ exit_code = phase_record_command(
566
+ story_id="STORY-001",
567
+ phase="02",
568
+ subagent="test-automator",
569
+ project_root=str(temp_project_dir),
570
+ format="text"
571
+ )
572
+
573
+ assert exit_code != 0, (
574
+ "FAIL (TDD Red): phase_record_command should return non-zero exit code"
575
+ )
576
+ except ImportError:
577
+ pass
578
+
579
+
580
+ # =============================================================================
581
+ # Technical Specification: Error Message Content Validation
582
+ # =============================================================================
583
+
584
+
585
+ class TestErrorMessageContent:
586
+ """
587
+ Validate the complete error message structure matches specification.
588
+ """
589
+
590
+ def test_error_message_complete_structure(self):
591
+ """
592
+ Validate complete error message contains all required components.
593
+
594
+ Expected structure:
595
+ ```
596
+ PhaseState module not found: {original_error}
597
+
598
+ The phase_state.py module is required for phase tracking.
599
+ Expected location: .claude/scripts/devforgeai_cli/phase_state.py
600
+
601
+ To fix:
602
+ 1. Ensure STORY-253 (PhaseState module) is implemented
603
+ 2. Reinstall CLI: pip install -e .claude/scripts/
604
+ 3. Retry your command
605
+
606
+ Note: The /dev workflow can continue without CLI-based phase
607
+ enforcement if this module is unavailable. Phase tracking is
608
+ optional and does not block story development.
609
+ ```
610
+ """
611
+ # Build expected error message components
612
+ required_components = [
613
+ "PhaseState module not found",
614
+ "phase_state.py module is required",
615
+ ".claude/scripts/devforgeai_cli/phase_state.py",
616
+ "STORY-253",
617
+ "pip install -e .claude/scripts/",
618
+ "/dev workflow can continue",
619
+ "optional",
620
+ ]
621
+
622
+ # Simulate the expected error message format
623
+ expected_error = (
624
+ "PhaseState module not found: No module named 'devforgeai_cli.phase_state'\n\n"
625
+ "The phase_state.py module is required for phase tracking.\n"
626
+ "Expected location: .claude/scripts/devforgeai_cli/phase_state.py\n\n"
627
+ "To fix:\n"
628
+ " 1. Ensure STORY-253 (PhaseState module) is implemented\n"
629
+ " 2. Reinstall CLI: pip install -e .claude/scripts/\n"
630
+ " 3. Retry your command\n\n"
631
+ "Note: The /dev workflow can continue without CLI-based phase\n"
632
+ "enforcement if this module is unavailable. Phase tracking is\n"
633
+ "optional and does not block story development."
634
+ )
635
+
636
+ # Verify all components are present
637
+ for component in required_components:
638
+ assert component in expected_error, (
639
+ f"Expected error message missing component: {component}"
640
+ )
641
+
642
+
643
+ # =============================================================================
644
+ # Integration Tests: Real Import Failure Simulation
645
+ # =============================================================================
646
+
647
+
648
+ class TestRealImportFailure:
649
+ """
650
+ Integration tests that simulate real import failure scenarios.
651
+ """
652
+
653
+ def test_get_phase_state_with_module_patch(self, temp_project_dir):
654
+ """
655
+ Test _get_phase_state behavior when phase_state module cannot be imported.
656
+
657
+ This test patches at the module level to simulate the module not existing.
658
+ """
659
+ # Store original function if it exists
660
+ from devforgeai_cli.commands import phase_commands
661
+
662
+ # Create a version of _get_phase_state that handles the error gracefully
663
+ def _get_phase_state_with_handling(project_root: str):
664
+ """Wrapper that demonstrates expected error handling."""
665
+ try:
666
+ from devforgeai_cli.phase_state import PhaseState
667
+ return PhaseState(project_root=Path(project_root))
668
+ except ImportError as e:
669
+ raise ImportError(
670
+ f"PhaseState module not found: {e}\n\n"
671
+ "The phase_state.py module is required for phase tracking.\n"
672
+ "Expected location: .claude/scripts/devforgeai_cli/phase_state.py\n\n"
673
+ "To fix:\n"
674
+ " 1. Ensure STORY-253 (PhaseState module) is implemented\n"
675
+ " 2. Reinstall CLI: pip install -e .claude/scripts/\n"
676
+ " 3. Retry your command\n\n"
677
+ "Note: The /dev workflow can continue without CLI-based phase\n"
678
+ "enforcement if this module is unavailable. Phase tracking is\n"
679
+ "optional and does not block story development."
680
+ ) from e
681
+
682
+ # Test that when phase_state doesn't exist, we get a helpful error
683
+ with patch.object(
684
+ phase_commands,
685
+ '_get_phase_state',
686
+ _get_phase_state_with_handling
687
+ ):
688
+ # This simulates what should happen after STORY-255 implementation
689
+ # Currently, the function doesn't have this error handling
690
+ pass
691
+
692
+
693
+ # =============================================================================
694
+ # TDD RED PHASE: Implementation Requirement Tests
695
+ # These tests WILL FAIL until STORY-255 is implemented
696
+ # =============================================================================
697
+
698
+
699
+ class TestTDDRed_ImplementationRequirements:
700
+ """
701
+ TDD Red Phase: Tests that verify the implementation requirements.
702
+
703
+ These tests MUST FAIL until the _get_phase_state function has proper
704
+ error handling implemented. They check the actual source code structure.
705
+ """
706
+
707
+ def test_get_phase_state_must_have_try_except_block(self):
708
+ """
709
+ TDD RED: Verify _get_phase_state HAS a try-except block.
710
+
711
+ STORY-255 REQUIREMENT: The function MUST wrap the import in try-except.
712
+
713
+ Expected implementation:
714
+ ```python
715
+ def _get_phase_state(project_root: str):
716
+ try:
717
+ from ..phase_state import PhaseState
718
+ return PhaseState(project_root=Path(project_root))
719
+ except ImportError as e:
720
+ raise ImportError("...helpful message...") from e
721
+ ```
722
+ """
723
+ import ast
724
+
725
+ # Read the source file
726
+ source_path = Path(__file__).parent.parent / "commands" / "phase_commands.py"
727
+ source = source_path.read_text()
728
+
729
+ # Parse and find _get_phase_state function
730
+ tree = ast.parse(source)
731
+
732
+ function_found = False
733
+ has_try_except = False
734
+
735
+ for node in ast.walk(tree):
736
+ if isinstance(node, ast.FunctionDef) and node.name == "_get_phase_state":
737
+ function_found = True
738
+ # Check if function body contains Try node
739
+ for child in ast.walk(node):
740
+ if isinstance(child, ast.Try):
741
+ has_try_except = True
742
+ break
743
+ break
744
+
745
+ assert function_found, (
746
+ "_get_phase_state function not found in phase_commands.py"
747
+ )
748
+
749
+ assert has_try_except, (
750
+ "FAIL (TDD Red): _get_phase_state() MUST have try-except block.\n\n"
751
+ "Current implementation lacks error handling.\n"
752
+ "STORY-255 requires wrapping the import in try-except to provide\n"
753
+ "helpful error messages when PhaseState module is missing.\n\n"
754
+ "Expected structure:\n"
755
+ " def _get_phase_state(project_root: str):\n"
756
+ " try:\n"
757
+ " from ..phase_state import PhaseState\n"
758
+ " return PhaseState(...)\n"
759
+ " except ImportError as e:\n"
760
+ " raise ImportError('...helpful message...') from e"
761
+ )
762
+
763
+ def test_get_phase_state_error_message_must_contain_story_253(self):
764
+ """
765
+ TDD RED: Error message MUST reference STORY-253.
766
+
767
+ STORY-255 AC#2 REQUIREMENT: Error message must mention STORY-253
768
+ (PhaseState module creation) for users to know what to implement.
769
+ """
770
+ # Read the source file
771
+ source_path = Path(__file__).parent.parent / "commands" / "phase_commands.py"
772
+ source = source_path.read_text()
773
+
774
+ assert "STORY-253" in source, (
775
+ "FAIL (TDD Red): phase_commands.py MUST contain 'STORY-253' reference.\n\n"
776
+ "STORY-255 AC#2 requires the error message to mention:\n"
777
+ " - STORY-253 must be implemented (PhaseState module creation)\n\n"
778
+ "Expected in error message:\n"
779
+ " '1. Ensure STORY-253 (PhaseState module) is implemented'"
780
+ )
781
+
782
+ def test_get_phase_state_error_message_must_contain_expected_location(self):
783
+ """
784
+ TDD RED: Error message MUST include expected module location.
785
+
786
+ STORY-255 AC#1 REQUIREMENT: Error message must include:
787
+ ".claude/scripts/devforgeai_cli/phase_state.py"
788
+ """
789
+ # Read the source file
790
+ source_path = Path(__file__).parent.parent / "commands" / "phase_commands.py"
791
+ source = source_path.read_text()
792
+
793
+ expected_location = ".claude/scripts/devforgeai_cli/phase_state.py"
794
+
795
+ assert expected_location in source, (
796
+ f"FAIL (TDD Red): phase_commands.py MUST contain expected location.\n\n"
797
+ f"STORY-255 AC#1 requires the error message to include:\n"
798
+ f" Expected location: {expected_location}\n\n"
799
+ f"This helps users know where to create the PhaseState module."
800
+ )
801
+
802
+ def test_get_phase_state_error_message_must_contain_pip_install(self):
803
+ """
804
+ TDD RED: Error message MUST include pip install instructions.
805
+
806
+ STORY-255 AC#1 REQUIREMENT: Error message must include:
807
+ "pip install -e .claude/scripts/"
808
+ """
809
+ # Read the source file
810
+ source_path = Path(__file__).parent.parent / "commands" / "phase_commands.py"
811
+ source = source_path.read_text()
812
+
813
+ pip_command = "pip install -e .claude/scripts/"
814
+
815
+ assert pip_command in source, (
816
+ f"FAIL (TDD Red): phase_commands.py MUST contain pip install command.\n\n"
817
+ f"STORY-255 AC#1 requires the error message to include:\n"
818
+ f" {pip_command}\n\n"
819
+ f"This helps users reinstall the CLI after implementing PhaseState."
820
+ )
821
+
822
+ def test_get_phase_state_error_message_must_contain_dev_workflow_note(self):
823
+ """
824
+ TDD RED: Error message MUST include note about /dev workflow.
825
+
826
+ STORY-255 AC#1 REQUIREMENT: Error message must note that:
827
+ "/dev workflow can continue without CLI-based phase enforcement"
828
+ """
829
+ # Read the source file
830
+ source_path = Path(__file__).parent.parent / "commands" / "phase_commands.py"
831
+ source = source_path.read_text()
832
+
833
+ # Check for key phrase (may be split across lines)
834
+ has_dev_workflow_note = (
835
+ "/dev workflow can continue" in source or
836
+ "/dev workflow" in source and "continue" in source
837
+ )
838
+
839
+ assert has_dev_workflow_note, (
840
+ "FAIL (TDD Red): phase_commands.py MUST contain /dev workflow note.\n\n"
841
+ "STORY-255 AC#1 requires the error message to include a note that:\n"
842
+ " 'The /dev workflow can continue without CLI-based phase enforcement'\n\n"
843
+ "This reassures users that story development is not blocked."
844
+ )
845
+
846
+ def test_get_phase_state_uses_raise_from_syntax(self):
847
+ """
848
+ TDD RED: Error MUST preserve cause chain using 'raise ... from e'.
849
+
850
+ STORY-255 AC#3 REQUIREMENT: Original exception preserved as __cause__
851
+ using 'raise ImportError(...) from e' syntax.
852
+ """
853
+ import ast
854
+
855
+ # Read the source file
856
+ source_path = Path(__file__).parent.parent / "commands" / "phase_commands.py"
857
+ source = source_path.read_text()
858
+
859
+ # Parse and find _get_phase_state function
860
+ tree = ast.parse(source)
861
+
862
+ has_raise_from = False
863
+
864
+ for node in ast.walk(tree):
865
+ if isinstance(node, ast.FunctionDef) and node.name == "_get_phase_state":
866
+ # Check for Raise node with cause
867
+ for child in ast.walk(node):
868
+ if isinstance(child, ast.Raise):
869
+ if child.cause is not None:
870
+ has_raise_from = True
871
+ break
872
+ break
873
+
874
+ assert has_raise_from, (
875
+ "FAIL (TDD Red): _get_phase_state() MUST use 'raise ... from e' syntax.\n\n"
876
+ "STORY-255 AC#3 requires:\n"
877
+ " - Original exception preserved as __cause__ (for traceback)\n"
878
+ " - Use: raise ImportError('...') from e\n\n"
879
+ "This enables proper traceback for debugging import failures."
880
+ )
881
+
882
+
883
+ # =============================================================================
884
+ # Edge Cases and Error Conditions
885
+ # =============================================================================
886
+
887
+
888
+ class TestEdgeCases:
889
+ """
890
+ Test edge cases and unusual error conditions.
891
+ """
892
+
893
+ def test_import_error_from_corrupted_module(self, temp_project_dir):
894
+ """
895
+ Test behavior when phase_state.py exists but has syntax errors.
896
+
897
+ The error message should still be helpful.
898
+ """
899
+ # This tests the case where the module exists but cannot be imported
900
+ # due to syntax errors or other issues
901
+ with patch(
902
+ 'devforgeai_cli.commands.phase_commands._get_phase_state'
903
+ ) as mock_get:
904
+ # Simulate a syntax error during import
905
+ syntax_error = SyntaxError("invalid syntax", ("phase_state.py", 10, 5, "def broken("))
906
+ import_error = ImportError("cannot import name 'PhaseState'")
907
+ import_error.__cause__ = syntax_error
908
+ mock_get.side_effect = import_error
909
+
910
+ with pytest.raises(ImportError):
911
+ mock_get(str(temp_project_dir))
912
+
913
+ def test_import_error_from_dependency_missing(self, temp_project_dir):
914
+ """
915
+ Test behavior when phase_state.py has missing dependencies.
916
+
917
+ Example: phase_state.py imports a module that doesn't exist.
918
+ """
919
+ with patch(
920
+ 'devforgeai_cli.commands.phase_commands._get_phase_state'
921
+ ) as mock_get:
922
+ # Simulate missing dependency
923
+ original = ImportError("No module named 'some_dependency'")
924
+ wrapper = ImportError(
925
+ f"PhaseState module not found: {original}\n\n"
926
+ "Check that all dependencies are installed."
927
+ )
928
+ wrapper.__cause__ = original
929
+ mock_get.side_effect = wrapper
930
+
931
+ with pytest.raises(ImportError) as exc_info:
932
+ mock_get(str(temp_project_dir))
933
+
934
+ assert exc_info.value.__cause__ is not None
935
+
936
+ def test_error_handling_with_json_format(self, temp_project_dir, capsys):
937
+ """
938
+ Test that error is properly formatted when using JSON output format.
939
+
940
+ Even in JSON mode, ImportError should provide helpful information.
941
+ """
942
+ with patch(
943
+ 'devforgeai_cli.commands.phase_commands._get_phase_state'
944
+ ) as mock_get:
945
+ mock_get.side_effect = ImportError("PhaseState module not found")
946
+
947
+ from devforgeai_cli.commands.phase_commands import phase_init_command
948
+
949
+ try:
950
+ exit_code = phase_init_command(
951
+ story_id="STORY-100",
952
+ project_root=str(temp_project_dir),
953
+ format="json"
954
+ )
955
+
956
+ # If caught, should have error in JSON output
957
+ output = capsys.readouterr().out
958
+ if output:
959
+ try:
960
+ result = json.loads(output)
961
+ assert "error" in result, "JSON output should contain error field"
962
+ except json.JSONDecodeError:
963
+ pass # Not JSON, error propagated differently
964
+ except ImportError:
965
+ pass # Error propagated directly
966
+
967
+
968
+ # =============================================================================
969
+ # Test Summary and Documentation
970
+ # =============================================================================
971
+
972
+
973
+ class TestDocumentation:
974
+ """
975
+ Documentation tests that verify test coverage of all acceptance criteria.
976
+ """
977
+
978
+ def test_all_acceptance_criteria_have_tests(self):
979
+ """
980
+ Meta-test: Verify all 4 acceptance criteria are covered by tests.
981
+ """
982
+ test_classes = [
983
+ TestAC1_HelpfulErrorMessage,
984
+ TestAC2_Story253Context,
985
+ TestAC3_ImportErrorWithCauseChain,
986
+ TestAC4_AllCommandsHandleErrorConsistently,
987
+ ]
988
+
989
+ # Verify each class has at least one test method
990
+ for test_class in test_classes:
991
+ test_methods = [
992
+ method for method in dir(test_class)
993
+ if method.startswith('test_')
994
+ ]
995
+ assert len(test_methods) > 0, (
996
+ f"{test_class.__name__} should have at least one test method"
997
+ )
998
+
999
+ def test_all_phase_commands_covered(self):
1000
+ """
1001
+ Meta-test: Verify all 5 phase commands are tested for error handling.
1002
+
1003
+ Commands:
1004
+ - phase_init_command
1005
+ - phase_check_command
1006
+ - phase_complete_command
1007
+ - phase_status_command
1008
+ - phase_record_command
1009
+ """
1010
+ # Note: phase_observe_command is also in phase_commands.py
1011
+ # but is tested separately as it was added in STORY-188
1012
+ commands_tested = [
1013
+ 'phase_init',
1014
+ 'phase_check',
1015
+ 'phase_complete',
1016
+ 'phase_status',
1017
+ 'phase_record',
1018
+ ]
1019
+
1020
+ # This is a documentation test - actual coverage verified by test methods
1021
+ assert len(commands_tested) == 5, "Should test all 5 phase commands"