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,697 @@
1
+ """
2
+ Tests for STORY-254: Update phase_commands.py Import.
3
+
4
+ TDD Red Phase: These tests verify the import refactoring from sys.path
5
+ manipulation to relative imports (from ..phase_state import PhaseState).
6
+
7
+ Acceptance Criteria:
8
+ - AC#1: Relative import replaces sys.path manipulation
9
+ - AC#2: Function behavior remains unchanged
10
+ - AC#3: All phase commands still work correctly
11
+
12
+ Technical Specification Requirements:
13
+ - SVC-001: Replace sys.path manipulation with relative import (Critical)
14
+ - SVC-002: Function returns PhaseState instance unchanged (Critical)
15
+ - SVC-003: All 6 phase commands work after refactor (Critical)
16
+ - SVC-004: Remove unused sys import if applicable (Low)
17
+ - BR-001: Import path must be `from ..phase_state` (Critical)
18
+ - NFR-001: Import latency < 5ms (Medium)
19
+ - NFR-002: Function reduced from 8 lines to 3 lines (High)
20
+
21
+ Test Framework: pytest (per tech-stack.md)
22
+ Test Naming: test_<function>_<scenario>_<expected>
23
+ """
24
+
25
+ import ast
26
+ import inspect
27
+ import json
28
+ import re
29
+ import sys
30
+ import tempfile
31
+ import time
32
+ from pathlib import Path
33
+ from typing import Callable, List
34
+ from unittest.mock import patch, MagicMock
35
+
36
+ import pytest
37
+
38
+
39
+ # =============================================================================
40
+ # Test Fixtures
41
+ # =============================================================================
42
+
43
+
44
+ @pytest.fixture
45
+ def phase_commands_source() -> str:
46
+ """Read the source code of phase_commands.py."""
47
+ source_path = Path(__file__).parent.parent / "commands" / "phase_commands.py"
48
+ return source_path.read_text()
49
+
50
+
51
+ @pytest.fixture
52
+ def get_phase_state_function_source(phase_commands_source: str) -> str:
53
+ """Extract the _get_phase_state function source code."""
54
+ # Parse the AST to find the function
55
+ tree = ast.parse(phase_commands_source)
56
+
57
+ for node in ast.walk(tree):
58
+ if isinstance(node, ast.FunctionDef) and node.name == "_get_phase_state":
59
+ # Get the line range
60
+ start_line = node.lineno
61
+ end_line = node.end_lineno
62
+
63
+ # Extract lines
64
+ lines = phase_commands_source.split('\n')
65
+ function_lines = lines[start_line - 1:end_line]
66
+ return '\n'.join(function_lines)
67
+
68
+ raise ValueError("_get_phase_state function not found in phase_commands.py")
69
+
70
+
71
+ @pytest.fixture
72
+ def temp_project_dir():
73
+ """Create a temporary project directory with required structure."""
74
+ with tempfile.TemporaryDirectory() as tmpdir:
75
+ project_root = Path(tmpdir)
76
+ # Create devforgeai/workflows directory
77
+ workflows_dir = project_root / "devforgeai" / "workflows"
78
+ workflows_dir.mkdir(parents=True, exist_ok=True)
79
+ yield project_root
80
+
81
+
82
+ @pytest.fixture
83
+ def existing_state(temp_project_dir):
84
+ """Create an existing phase state file for testing commands."""
85
+ state = {
86
+ "story_id": "STORY-001",
87
+ "workflow_started": "2026-01-12T10:00:00Z",
88
+ "current_phase": "02",
89
+ "blocking_status": False,
90
+ "phases": {
91
+ "01": {
92
+ "status": "completed",
93
+ "started_at": "2026-01-12T10:00:00Z",
94
+ "completed_at": "2026-01-12T10:05:00Z",
95
+ "subagents_required": ["git-validator", "tech-stack-detector"],
96
+ "subagents_invoked": ["git-validator", "tech-stack-detector"],
97
+ "checkpoint_passed": True
98
+ },
99
+ "02": {
100
+ "status": "pending",
101
+ "subagents_required": ["test-automator"],
102
+ "subagents_invoked": []
103
+ },
104
+ **{f"{i:02d}": {"status": "pending", "subagents_required": [], "subagents_invoked": []}
105
+ for i in range(3, 11)}
106
+ },
107
+ "validation_errors": [],
108
+ "observations": []
109
+ }
110
+
111
+ state_file = temp_project_dir / "devforgeai" / "workflows" / "STORY-001-phase-state.json"
112
+ state_file.write_text(json.dumps(state, indent=2))
113
+ return state_file
114
+
115
+
116
+ # =============================================================================
117
+ # AC#1: Relative import replaces sys.path manipulation
118
+ # =============================================================================
119
+
120
+
121
+ class TestAC1_RelativeImport:
122
+ """
123
+ AC#1: Relative import replaces sys.path manipulation
124
+
125
+ Given: STORY-253 has been completed and PhaseState is available
126
+ When: The _get_phase_state() function in phase_commands.py is updated
127
+ Then: The function uses `from ..phase_state import PhaseState` instead of
128
+ sys.path.insert() and `from installer.phase_state import PhaseState`
129
+ """
130
+
131
+ def test_get_phase_state_uses_relative_import_statement(
132
+ self,
133
+ get_phase_state_function_source: str
134
+ ):
135
+ """
136
+ SVC-001 / BR-001: Import uses `from ..phase_state` relative path.
137
+
138
+ The function should contain:
139
+ from ..phase_state import PhaseState
140
+
141
+ NOT:
142
+ from installer.phase_state import PhaseState
143
+ """
144
+ # Check for relative import pattern
145
+ relative_import_pattern = r'from\s+\.\.phase_state\s+import\s+PhaseState'
146
+ has_relative_import = bool(
147
+ re.search(relative_import_pattern, get_phase_state_function_source)
148
+ )
149
+
150
+ assert has_relative_import, (
151
+ "FAIL (TDD Red): _get_phase_state() should use relative import "
152
+ "'from ..phase_state import PhaseState' but does not.\n"
153
+ f"Current function:\n{get_phase_state_function_source}"
154
+ )
155
+
156
+ def test_get_phase_state_no_sys_path_insert(
157
+ self,
158
+ get_phase_state_function_source: str
159
+ ):
160
+ """
161
+ SVC-001: No sys.path.insert() calls in the function.
162
+
163
+ The function should NOT contain:
164
+ sys.path.insert(...)
165
+ """
166
+ sys_path_pattern = r'sys\.path\.insert'
167
+ has_sys_path_insert = bool(
168
+ re.search(sys_path_pattern, get_phase_state_function_source)
169
+ )
170
+
171
+ assert not has_sys_path_insert, (
172
+ "FAIL (TDD Red): _get_phase_state() should NOT use sys.path.insert() "
173
+ "but it does.\n"
174
+ f"Current function:\n{get_phase_state_function_source}"
175
+ )
176
+
177
+ def test_get_phase_state_no_installer_import_path(
178
+ self,
179
+ get_phase_state_function_source: str
180
+ ):
181
+ """
182
+ SVC-001: No 'from installer.phase_state' import path.
183
+
184
+ The function should NOT contain:
185
+ from installer.phase_state import PhaseState
186
+ """
187
+ installer_import_pattern = r'from\s+installer\.phase_state\s+import'
188
+ has_installer_import = bool(
189
+ re.search(installer_import_pattern, get_phase_state_function_source)
190
+ )
191
+
192
+ assert not has_installer_import, (
193
+ "FAIL (TDD Red): _get_phase_state() should NOT use "
194
+ "'from installer.phase_state import PhaseState' "
195
+ "but should use relative import instead.\n"
196
+ f"Current function:\n{get_phase_state_function_source}"
197
+ )
198
+
199
+ def test_get_phase_state_no_installer_path_construction(
200
+ self,
201
+ get_phase_state_function_source: str
202
+ ):
203
+ """
204
+ SVC-001: No installer path construction (no Path(project_root) / "installer").
205
+
206
+ The function should NOT contain:
207
+ installer_path = Path(project_root) / "installer"
208
+ """
209
+ path_construction_pattern = r'installer_path\s*=.*["\']installer["\']'
210
+ has_path_construction = bool(
211
+ re.search(path_construction_pattern, get_phase_state_function_source)
212
+ )
213
+
214
+ assert not has_path_construction, (
215
+ "FAIL (TDD Red): _get_phase_state() should NOT construct installer path "
216
+ "but it does.\n"
217
+ f"Current function:\n{get_phase_state_function_source}"
218
+ )
219
+
220
+
221
+ # =============================================================================
222
+ # AC#2: Function behavior remains unchanged
223
+ # =============================================================================
224
+
225
+
226
+ class TestAC2_FunctionBehaviorUnchanged:
227
+ """
228
+ AC#2: Function behavior remains unchanged
229
+
230
+ Given: The refactored _get_phase_state() function
231
+ When: Called with a valid project_root parameter
232
+ Then: It returns a PhaseState instance with the same behavior as before
233
+ """
234
+
235
+ def test_get_phase_state_returns_phase_state_instance(self, temp_project_dir):
236
+ """
237
+ SVC-002: Function returns PhaseState instance unchanged.
238
+
239
+ _get_phase_state(project_root) should return a PhaseState instance.
240
+ """
241
+ # Import the module to test
242
+ from devforgeai_cli.commands.phase_commands import _get_phase_state
243
+
244
+ result = _get_phase_state(str(temp_project_dir))
245
+
246
+ # The result should be a PhaseState instance
247
+ # We check the class name since import paths may differ
248
+ assert result.__class__.__name__ == "PhaseState", (
249
+ f"FAIL (TDD Red): _get_phase_state() should return PhaseState instance, "
250
+ f"got {type(result).__name__}"
251
+ )
252
+
253
+ def test_get_phase_state_sets_project_root(self, temp_project_dir):
254
+ """
255
+ SVC-002: PhaseState instance has correct project_root.
256
+ """
257
+ from devforgeai_cli.commands.phase_commands import _get_phase_state
258
+
259
+ result = _get_phase_state(str(temp_project_dir))
260
+
261
+ # PhaseState should have project_root attribute
262
+ assert hasattr(result, 'project_root'), (
263
+ "FAIL (TDD Red): PhaseState instance should have project_root attribute"
264
+ )
265
+ assert result.project_root == Path(temp_project_dir), (
266
+ f"FAIL (TDD Red): PhaseState.project_root should be {temp_project_dir}, "
267
+ f"got {result.project_root}"
268
+ )
269
+
270
+ def test_get_phase_state_can_create_workflow_state(self, temp_project_dir):
271
+ """
272
+ SVC-002: Returned PhaseState can create workflow state files.
273
+ """
274
+ from devforgeai_cli.commands.phase_commands import _get_phase_state
275
+
276
+ ps = _get_phase_state(str(temp_project_dir))
277
+
278
+ # Should be able to create a state
279
+ state = ps.create("STORY-099")
280
+
281
+ assert state is not None
282
+ assert state["story_id"] == "STORY-099"
283
+ assert state["current_phase"] == "01"
284
+
285
+
286
+ # =============================================================================
287
+ # AC#3: All phase commands still work correctly
288
+ # =============================================================================
289
+
290
+
291
+ class TestAC3_AllPhaseCommandsWork:
292
+ """
293
+ AC#3: All phase commands still work correctly
294
+
295
+ Given: The updated import in _get_phase_state()
296
+ When: All phase command functions are invoked
297
+ Then: All commands execute without import errors and maintain existing functionality
298
+
299
+ Commands tested:
300
+ - phase_init_command
301
+ - phase_check_command
302
+ - phase_complete_command
303
+ - phase_status_command
304
+ - phase_record_command
305
+ - phase_observe_command
306
+ """
307
+
308
+ def test_phase_init_command_works_after_import_refactor(self, temp_project_dir):
309
+ """
310
+ SVC-003: phase_init command works with relative import.
311
+ """
312
+ from devforgeai_cli.commands.phase_commands import phase_init_command
313
+
314
+ # Should not raise ImportError
315
+ exit_code = phase_init_command(
316
+ story_id="STORY-100",
317
+ project_root=str(temp_project_dir),
318
+ format="text"
319
+ )
320
+
321
+ assert exit_code == 0, (
322
+ f"FAIL (TDD Red): phase_init_command should return 0, got {exit_code}"
323
+ )
324
+
325
+ # State file should be created
326
+ state_file = temp_project_dir / "devforgeai" / "workflows" / "STORY-100-phase-state.json"
327
+ assert state_file.exists(), "State file should be created"
328
+
329
+ def test_phase_check_command_works_after_import_refactor(
330
+ self,
331
+ temp_project_dir,
332
+ existing_state
333
+ ):
334
+ """
335
+ SVC-003: phase_check command works with relative import.
336
+ """
337
+ from devforgeai_cli.commands.phase_commands import phase_check_command
338
+
339
+ # Should not raise ImportError
340
+ exit_code = phase_check_command(
341
+ story_id="STORY-001",
342
+ from_phase="01",
343
+ to_phase="02",
344
+ project_root=str(temp_project_dir),
345
+ format="text"
346
+ )
347
+
348
+ assert exit_code == 0, (
349
+ f"FAIL (TDD Red): phase_check_command should return 0, got {exit_code}"
350
+ )
351
+
352
+ def test_phase_complete_command_works_after_import_refactor(
353
+ self,
354
+ temp_project_dir,
355
+ existing_state
356
+ ):
357
+ """
358
+ SVC-003: phase_complete command works with relative import.
359
+ """
360
+ from devforgeai_cli.commands.phase_commands import phase_complete_command
361
+
362
+ # Should not raise ImportError
363
+ exit_code = phase_complete_command(
364
+ story_id="STORY-001",
365
+ phase="02",
366
+ checkpoint_passed=True,
367
+ project_root=str(temp_project_dir),
368
+ format="text"
369
+ )
370
+
371
+ assert exit_code == 0, (
372
+ f"FAIL (TDD Red): phase_complete_command should return 0, got {exit_code}"
373
+ )
374
+
375
+ def test_phase_status_command_works_after_import_refactor(
376
+ self,
377
+ temp_project_dir,
378
+ existing_state
379
+ ):
380
+ """
381
+ SVC-003: phase_status command works with relative import.
382
+ """
383
+ from devforgeai_cli.commands.phase_commands import phase_status_command
384
+
385
+ # Should not raise ImportError
386
+ exit_code = phase_status_command(
387
+ story_id="STORY-001",
388
+ project_root=str(temp_project_dir),
389
+ format="text"
390
+ )
391
+
392
+ assert exit_code == 0, (
393
+ f"FAIL (TDD Red): phase_status_command should return 0, got {exit_code}"
394
+ )
395
+
396
+ def test_phase_record_command_works_after_import_refactor(
397
+ self,
398
+ temp_project_dir,
399
+ existing_state
400
+ ):
401
+ """
402
+ SVC-003: phase_record command works with relative import.
403
+ """
404
+ from devforgeai_cli.commands.phase_commands import phase_record_command
405
+
406
+ # Should not raise ImportError
407
+ exit_code = phase_record_command(
408
+ story_id="STORY-001",
409
+ phase="02",
410
+ subagent="test-automator",
411
+ project_root=str(temp_project_dir),
412
+ format="text"
413
+ )
414
+
415
+ assert exit_code == 0, (
416
+ f"FAIL (TDD Red): phase_record_command should return 0, got {exit_code}"
417
+ )
418
+
419
+ def test_phase_observe_command_works_after_import_refactor(
420
+ self,
421
+ temp_project_dir,
422
+ existing_state
423
+ ):
424
+ """
425
+ SVC-003: phase_observe command works with relative import.
426
+ """
427
+ from devforgeai_cli.commands.phase_commands import phase_observe_command
428
+
429
+ # Should not raise ImportError
430
+ exit_code = phase_observe_command(
431
+ story_id="STORY-001",
432
+ phase="02",
433
+ category="success",
434
+ note="Test observation",
435
+ severity="medium",
436
+ project_root=str(temp_project_dir),
437
+ format="text"
438
+ )
439
+
440
+ assert exit_code == 0, (
441
+ f"FAIL (TDD Red): phase_observe_command should return 0, got {exit_code}"
442
+ )
443
+
444
+
445
+ # =============================================================================
446
+ # Technical Specification: Non-Functional Requirements
447
+ # =============================================================================
448
+
449
+
450
+ class TestNFR_NonFunctionalRequirements:
451
+ """
452
+ Non-Functional Requirements from Technical Specification.
453
+ """
454
+
455
+ def test_get_phase_state_function_line_count_reduced(
456
+ self,
457
+ get_phase_state_function_source: str
458
+ ):
459
+ """
460
+ NFR-002: Function reduced from original sys.path approach.
461
+
462
+ STORY-254: Reduced from 8 lines (sys.path) to 3 lines (relative import)
463
+ STORY-255: Added graceful error handling (~20 additional lines for
464
+ helpful diagnostic message when PhaseState module missing)
465
+
466
+ The refactored function should have:
467
+ - Relative import (from ..phase_state) - STORY-254
468
+ - try/except error handling with helpful message - STORY-255
469
+ - Comprehensive docstring documenting the ImportError behavior
470
+ """
471
+ # Count non-empty, non-comment lines
472
+ lines = get_phase_state_function_source.split('\n')
473
+ code_lines = [
474
+ line for line in lines
475
+ if line.strip() and not line.strip().startswith('#')
476
+ and not line.strip().startswith('"""')
477
+ ]
478
+
479
+ # STORY-255 adds error handling which increases line count legitimately:
480
+ # - ~18 lines for docstring
481
+ # - ~4 lines for try block (def, try, import, return)
482
+ # - ~15 lines for except block (error message)
483
+ # Total: ~35-40 lines maximum
484
+ max_expected_lines = 40 # With error handling from STORY-255
485
+ min_expected_lines = 15 # Minimum for proper error handling
486
+
487
+ actual_line_count = len(code_lines)
488
+
489
+ # Verify function has error handling (STORY-255) but isn't bloated
490
+ assert actual_line_count <= max_expected_lines, (
491
+ f"_get_phase_state() should have ≤{max_expected_lines} lines "
492
+ f"(with STORY-255 error handling), but has {actual_line_count}.\n"
493
+ f"Current function:\n{get_phase_state_function_source}"
494
+ )
495
+
496
+ # Verify function has minimum content for error handling
497
+ assert actual_line_count >= min_expected_lines, (
498
+ f"_get_phase_state() should have ≥{min_expected_lines} lines "
499
+ f"(must include STORY-255 error handling), but has {actual_line_count}.\n"
500
+ f"Verify try/except block and helpful error message are present."
501
+ )
502
+
503
+ def test_get_phase_state_import_latency(self, temp_project_dir):
504
+ """
505
+ NFR-001: Import latency < 5ms per import.
506
+
507
+ The function should execute quickly without sys.path overhead.
508
+ """
509
+ from devforgeai_cli.commands.phase_commands import _get_phase_state
510
+
511
+ # Warm up (first import may be slower)
512
+ _get_phase_state(str(temp_project_dir))
513
+
514
+ # Measure subsequent calls
515
+ iterations = 10
516
+ start_time = time.perf_counter()
517
+
518
+ for _ in range(iterations):
519
+ _get_phase_state(str(temp_project_dir))
520
+
521
+ elapsed_ms = ((time.perf_counter() - start_time) / iterations) * 1000
522
+
523
+ # Allow 5ms per call
524
+ assert elapsed_ms < 5.0, (
525
+ f"FAIL: _get_phase_state() took {elapsed_ms:.2f}ms per call, "
526
+ f"expected < 5ms"
527
+ )
528
+
529
+
530
+ # =============================================================================
531
+ # Technical Specification: SVC-004 Sys Import Removal
532
+ # =============================================================================
533
+
534
+
535
+ class TestSVC004_SysImportRemoval:
536
+ """
537
+ SVC-004: Remove unused sys import if applicable (Low priority).
538
+
539
+ If sys is only used for sys.path.insert() in _get_phase_state(),
540
+ and that usage is removed, check if sys import can be removed.
541
+ """
542
+
543
+ def test_sys_import_usage_check(self, phase_commands_source: str):
544
+ """
545
+ SVC-004: Check if sys is still needed after refactoring.
546
+
547
+ After removing sys.path.insert(), verify sys usage:
548
+ - If sys is NOT used anywhere else, import should be removed
549
+ - If sys IS used elsewhere, import should remain
550
+ """
551
+ # Count sys usages (excluding the import statement itself)
552
+ # Pattern matches sys.something usage
553
+ sys_usage_pattern = r'(?<!import\s)sys\.\w+'
554
+ sys_usages = re.findall(sys_usage_pattern, phase_commands_source)
555
+
556
+ # Import statement pattern
557
+ sys_import_pattern = r'^import\s+sys\s*$|^from\s+sys\s+import'
558
+ has_sys_import = bool(
559
+ re.search(sys_import_pattern, phase_commands_source, re.MULTILINE)
560
+ )
561
+
562
+ # If there are no sys usages (after refactoring), import should be removed
563
+ # For TDD Red, we expect sys.path.insert to still exist
564
+ if len(sys_usages) == 0 and has_sys_import:
565
+ pytest.fail(
566
+ "FAIL (TDD Red): sys is imported but not used. "
567
+ "Remove 'import sys' if sys.path.insert() has been removed."
568
+ )
569
+
570
+ # Note: This test passes if either:
571
+ # 1. sys IS used (current state before refactoring)
572
+ # 2. sys import is removed (after refactoring if not needed)
573
+
574
+
575
+ # =============================================================================
576
+ # Integration Test: Import Chain Verification
577
+ # =============================================================================
578
+
579
+
580
+ class TestImportChainVerification:
581
+ """
582
+ Verify the import chain works correctly after refactoring.
583
+ """
584
+
585
+ def test_phase_state_module_importable_from_parent_package(self):
586
+ """
587
+ Verify PhaseState can be imported via relative path from commands module.
588
+
589
+ This tests that the package structure supports:
590
+ from ..phase_state import PhaseState
591
+
592
+ When executed from .claude/scripts/devforgeai_cli/commands/phase_commands.py
593
+ """
594
+ # Test file path: .claude/scripts/devforgeai_cli/tests/test_phase_commands_import.py
595
+ # devforgeai_cli path: .claude/scripts/devforgeai_cli/
596
+ # phase_state.py should be at: .claude/scripts/devforgeai_cli/phase_state.py
597
+
598
+ # The package structure should be:
599
+ # devforgeai_cli/
600
+ # __init__.py
601
+ # phase_state.py <- PhaseState lives here (STORY-253 creates this)
602
+ # commands/
603
+ # __init__.py
604
+ # phase_commands.py <- imports from ..phase_state
605
+ # tests/
606
+ # test_phase_commands_import.py <- This file
607
+
608
+ # Check that phase_state.py exists at the correct location
609
+ # Path(__file__).parent = tests/
610
+ # Path(__file__).parent.parent = devforgeai_cli/
611
+ phase_state_path = Path(__file__).parent.parent / "phase_state.py"
612
+
613
+ assert phase_state_path.exists(), (
614
+ f"FAIL (TDD Red): PhaseState module not found at expected location.\n"
615
+ f"Expected: {phase_state_path}\n"
616
+ "The package structure must support: from ..phase_state import PhaseState\n"
617
+ "STORY-253 must be completed first to create phase_state.py in devforgeai_cli/"
618
+ )
619
+
620
+ def test_package_init_files_exist(self):
621
+ """
622
+ Verify __init__.py files exist for proper package structure.
623
+ """
624
+ # Path(__file__).parent = tests/
625
+ # Path(__file__).parent.parent = devforgeai_cli/
626
+ devforgeai_cli_path = Path(__file__).parent.parent
627
+
628
+ required_init_files = [
629
+ devforgeai_cli_path / "__init__.py", # devforgeai_cli/__init__.py
630
+ devforgeai_cli_path / "commands" / "__init__.py", # devforgeai_cli/commands/__init__.py
631
+ ]
632
+
633
+ missing = []
634
+ for init_file in required_init_files:
635
+ if not init_file.exists():
636
+ missing.append(str(init_file))
637
+
638
+ assert not missing, (
639
+ f"FAIL: Missing __init__.py files for package structure:\n"
640
+ + "\n".join(missing)
641
+ )
642
+
643
+
644
+ # =============================================================================
645
+ # Error Handling Tests
646
+ # =============================================================================
647
+
648
+
649
+ class TestImportErrorHandling:
650
+ """
651
+ Test error handling when import fails.
652
+ """
653
+
654
+ def test_import_error_is_clear_when_phase_state_missing(self):
655
+ """
656
+ When PhaseState module is not found, error should be clear.
657
+
658
+ This test documents expected behavior - if the relative import
659
+ fails, Python should raise ImportError with a clear message.
660
+ """
661
+ # This test validates that if phase_state.py doesn't exist,
662
+ # the import will fail with ImportError, not some obscure error
663
+ # from sys.path manipulation
664
+
665
+ # We can't easily test this without mocking, but we document
666
+ # the expected behavior: ImportError raised immediately (fail-fast)
667
+ pass # Documentation-only test
668
+
669
+
670
+ # =============================================================================
671
+ # Code Style Tests
672
+ # =============================================================================
673
+
674
+
675
+ class TestCodeStyle:
676
+ """
677
+ Verify code follows Python best practices (PEP 328 for imports).
678
+ """
679
+
680
+ def test_no_magic_path_strings(self, get_phase_state_function_source: str):
681
+ """
682
+ Function should not contain hardcoded path strings like "installer".
683
+ """
684
+ # Magic strings that indicate old implementation
685
+ magic_strings = ['installer', 'phase_state.py']
686
+
687
+ found = []
688
+ for magic in magic_strings:
689
+ if f'"{magic}"' in get_phase_state_function_source:
690
+ found.append(magic)
691
+ if f"'{magic}'" in get_phase_state_function_source:
692
+ found.append(magic)
693
+
694
+ assert not found, (
695
+ f"FAIL (TDD Red): _get_phase_state() contains magic path strings: {found}\n"
696
+ "Use relative imports instead of path string manipulation."
697
+ )