devforgeai 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) 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/devforgeai-qa/SKILL.md +1 -1
  127. package/src/claude/skills/researching-market/SKILL.md +2 -1
  128. package/src/cli/lib/copier.js +13 -1
  129. package/src/claude/skills/designing-systems/scripts/__pycache__/detect_anti_patterns.cpython-312.pyc +0 -0
  130. package/src/claude/skills/designing-systems/scripts/__pycache__/validate_all_context.cpython-312.pyc +0 -0
  131. package/src/claude/skills/designing-systems/scripts/__pycache__/validate_architecture.cpython-312.pyc +0 -0
  132. package/src/claude/skills/designing-systems/scripts/__pycache__/validate_dependencies.cpython-312.pyc +0 -0
  133. package/src/claude/skills/devforgeai-story-creation/scripts/__pycache__/migrate_story_v1_to_v2.cpython-312.pyc +0 -0
  134. package/src/claude/skills/devforgeai-story-creation/scripts/tests/__pycache__/measure_accuracy.cpython-312.pyc +0 -0
@@ -0,0 +1,851 @@
1
+ """
2
+ Test Suite for Session Checkpoint Protocol (STORY-120)
3
+
4
+ This test suite validates the checkpoint system that enables resuming development
5
+ sessions when context window fills. Tests follow AAA pattern (Arrange, Act, Assert)
6
+ and are designed to FAIL (RED phase) since checkpoint.py doesn't exist yet.
7
+
8
+ Test framework: pytest
9
+ Coverage target: 95% for business logic
10
+ Test pyramid: 70% unit, 20% integration, 10% E2E
11
+ """
12
+
13
+ import json
14
+ import tempfile
15
+ from pathlib import Path
16
+ from datetime import datetime
17
+ from typing import Dict, Optional
18
+ import shutil
19
+
20
+ import pytest
21
+
22
+ # These imports will FAIL (RED phase) - checkpoint module doesn't exist yet
23
+ # This is intentional for TDD
24
+ try:
25
+ from src.claude.scriptsdevforgeai_cli.session.checkpoint import (
26
+ write_checkpoint,
27
+ read_checkpoint,
28
+ delete_checkpoint,
29
+ )
30
+ except ImportError:
31
+ pytest.skip("checkpoint module not yet implemented", allow_module_level=True)
32
+
33
+
34
+ # ============================================================================
35
+ # FIXTURES
36
+ # ============================================================================
37
+
38
+ @pytest.fixture
39
+ def temp_session_dir():
40
+ """Create temporary session directory for testing."""
41
+ temp_dir = tempfile.mkdtemp(prefix="devforgeai_sessions_")
42
+ yield temp_dir
43
+ # Cleanup
44
+ shutil.rmtree(temp_dir, ignore_errors=True)
45
+
46
+
47
+ @pytest.fixture
48
+ def valid_checkpoint_data() -> Dict:
49
+ """Valid checkpoint data matching AC#2 requirements."""
50
+ return {
51
+ "story_id": "STORY-120",
52
+ "phase": 3,
53
+ "phase_name": "Refactor",
54
+ "timestamp": "2025-12-21T15:30:00Z",
55
+ "progress_percentage": 67,
56
+ "dod_completion": {
57
+ "implementation": [5, 8],
58
+ "quality": [2, 6],
59
+ "testing": [3, 5],
60
+ "documentation": [1, 4]
61
+ },
62
+ "last_action": "code-reviewer subagent completed",
63
+ "next_action": "Phase 4: Integration Testing"
64
+ }
65
+
66
+
67
+ @pytest.fixture
68
+ def valid_checkpoint_json_file(temp_session_dir) -> Path:
69
+ """Create a valid checkpoint.json file in temp directory."""
70
+ session_dir = Path(temp_session_dir) / "STORY-120"
71
+ session_dir.mkdir(parents=True, exist_ok=True)
72
+
73
+ checkpoint_file = session_dir / "checkpoint.json"
74
+ valid_data = {
75
+ "story_id": "STORY-120",
76
+ "phase": 2,
77
+ "phase_name": "Green",
78
+ "timestamp": "2025-12-21T14:00:00Z",
79
+ "progress_percentage": 45,
80
+ "dod_completion": {
81
+ "implementation": [3, 8],
82
+ "quality": [1, 6],
83
+ "testing": [2, 5],
84
+ "documentation": [0, 4]
85
+ },
86
+ "last_action": "backend-architect implementation complete",
87
+ "next_action": "Phase 3: Refactoring"
88
+ }
89
+
90
+ checkpoint_file.write_text(json.dumps(valid_data, indent=2))
91
+ return checkpoint_file
92
+
93
+
94
+ @pytest.fixture
95
+ def corrupted_checkpoint_json_file(temp_session_dir) -> Path:
96
+ """Create a corrupted checkpoint.json file (invalid JSON)."""
97
+ session_dir = Path(temp_session_dir) / "STORY-120"
98
+ session_dir.mkdir(parents=True, exist_ok=True)
99
+
100
+ checkpoint_file = session_dir / "checkpoint.json"
101
+ # Write invalid JSON
102
+ checkpoint_file.write_text("{invalid json content }")
103
+ return checkpoint_file
104
+
105
+
106
+ @pytest.fixture
107
+ def missing_fields_checkpoint_json_file(temp_session_dir) -> Path:
108
+ """Create a checkpoint.json file with missing required fields."""
109
+ session_dir = Path(temp_session_dir) / "STORY-120"
110
+ session_dir.mkdir(parents=True, exist_ok=True)
111
+
112
+ checkpoint_file = session_dir / "checkpoint.json"
113
+ incomplete_data = {
114
+ "story_id": "STORY-120",
115
+ "phase": 2,
116
+ # Missing: phase_name, timestamp, progress_percentage, dod_completion, next_action
117
+ }
118
+
119
+ checkpoint_file.write_text(json.dumps(incomplete_data, indent=2))
120
+ return checkpoint_file
121
+
122
+
123
+ @pytest.fixture
124
+ def monkeypatch_sessions_dir(temp_session_dir, monkeypatch):
125
+ """Monkeypatch the sessions directory to use temp directory."""
126
+ # This fixture patches the module-level SESSIONS_DIR if it exists
127
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", temp_session_dir)
128
+ yield temp_session_dir
129
+
130
+
131
+ # ============================================================================
132
+ # UNIT TESTS: write_checkpoint()
133
+ # ============================================================================
134
+
135
+ class TestWriteCheckpointUnit:
136
+ """Unit tests for write_checkpoint() function."""
137
+
138
+ def test_write_checkpoint_creates_directory_when_missing(
139
+ self,
140
+ temp_session_dir,
141
+ valid_checkpoint_data,
142
+ monkeypatch
143
+ ):
144
+ """
145
+ AC#1: Checkpoint file written at phase completion
146
+
147
+ Given: devforgeai/sessions/{STORY-ID}/ directory does not exist
148
+ When: write_checkpoint(story_id='STORY-120', phase=3, progress={...}) called
149
+ Then: Directory is created at devforgeai/sessions/STORY-120/
150
+ """
151
+ # Arrange
152
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", temp_session_dir)
153
+ story_id = "STORY-120"
154
+ phase = 3
155
+ progress = valid_checkpoint_data
156
+
157
+ expected_dir = Path(temp_session_dir) / story_id
158
+ assert not expected_dir.exists(), "Directory should not exist before call"
159
+
160
+ # Act
161
+ result = write_checkpoint(story_id, phase, progress)
162
+
163
+ # Assert
164
+ assert result is True, "write_checkpoint should return True on success"
165
+ assert expected_dir.exists(), f"Directory should exist: {expected_dir}"
166
+ assert expected_dir.is_dir(), f"Should be directory: {expected_dir}"
167
+
168
+
169
+ def test_write_checkpoint_creates_valid_json_file(
170
+ self,
171
+ temp_session_dir,
172
+ valid_checkpoint_data,
173
+ monkeypatch
174
+ ):
175
+ """
176
+ AC#2: Checkpoint includes required fields
177
+
178
+ Given: Valid checkpoint data provided
179
+ When: write_checkpoint() called
180
+ Then: Valid JSON file created with all required fields
181
+ """
182
+ # Arrange
183
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", temp_session_dir)
184
+ story_id = "STORY-120"
185
+ phase = 3
186
+ progress = valid_checkpoint_data
187
+
188
+ checkpoint_file = Path(temp_session_dir) / story_id / "checkpoint.json"
189
+
190
+ # Act
191
+ result = write_checkpoint(story_id, phase, progress)
192
+
193
+ # Assert
194
+ assert result is True, "Should return True"
195
+ assert checkpoint_file.exists(), f"Checkpoint file should exist: {checkpoint_file}"
196
+
197
+ # Verify JSON is valid and contains all required fields
198
+ with open(checkpoint_file) as f:
199
+ data = json.load(f)
200
+
201
+ required_fields = ["story_id", "phase", "phase_name", "timestamp",
202
+ "progress_percentage", "dod_completion", "next_action"]
203
+ for field in required_fields:
204
+ assert field in data, f"Required field missing: {field}"
205
+
206
+
207
+ def test_write_checkpoint_overwrites_existing_file(
208
+ self,
209
+ temp_session_dir,
210
+ valid_checkpoint_data,
211
+ monkeypatch
212
+ ):
213
+ """
214
+ Given: Checkpoint file already exists
215
+ When: write_checkpoint() called with new data
216
+ Then: Existing checkpoint is overwritten with new data
217
+ """
218
+ # Arrange
219
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", temp_session_dir)
220
+ story_id = "STORY-120"
221
+
222
+ # Write initial checkpoint
223
+ initial_data = valid_checkpoint_data.copy()
224
+ initial_data["phase"] = 1
225
+ write_checkpoint(story_id, 1, initial_data)
226
+
227
+ # Write new checkpoint
228
+ new_data = valid_checkpoint_data.copy()
229
+ new_data["phase"] = 3
230
+
231
+ # Act
232
+ result = write_checkpoint(story_id, 3, new_data)
233
+
234
+ # Assert
235
+ assert result is True, "Should return True"
236
+
237
+ checkpoint_file = Path(temp_session_dir) / story_id / "checkpoint.json"
238
+ with open(checkpoint_file) as f:
239
+ data = json.load(f)
240
+
241
+ assert data["phase"] == 3, "Should have new phase number"
242
+
243
+
244
+ def test_write_checkpoint_validates_phase_range(
245
+ self,
246
+ temp_session_dir,
247
+ valid_checkpoint_data,
248
+ monkeypatch
249
+ ):
250
+ """
251
+ Given: Invalid phase number (not in 0-7 range)
252
+ When: write_checkpoint() called
253
+ Then: Should raise ValueError or return False
254
+ """
255
+ # Arrange
256
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", temp_session_dir)
257
+ story_id = "STORY-120"
258
+ invalid_phase = 9 # Outside 0-7 range
259
+ progress = valid_checkpoint_data.copy()
260
+
261
+ # Act & Assert
262
+ try:
263
+ result = write_checkpoint(story_id, invalid_phase, progress)
264
+ assert result is False, "Should return False for invalid phase"
265
+ except (ValueError, AssertionError):
266
+ pass # Either exception or False return is acceptable
267
+
268
+
269
+ def test_write_checkpoint_validates_story_id_format(
270
+ self,
271
+ temp_session_dir,
272
+ valid_checkpoint_data,
273
+ monkeypatch
274
+ ):
275
+ """
276
+ AC#2: story_id validation
277
+
278
+ Given: Invalid story_id format (not STORY-NNN)
279
+ When: write_checkpoint() called
280
+ Then: Should raise ValueError or return False
281
+ """
282
+ # Arrange
283
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", temp_session_dir)
284
+ invalid_story_id = "invalid-id"
285
+ phase = 3
286
+ progress = valid_checkpoint_data.copy()
287
+
288
+ # Act & Assert
289
+ try:
290
+ result = write_checkpoint(invalid_story_id, phase, progress)
291
+ assert result is False, "Should return False for invalid story_id format"
292
+ except (ValueError, AssertionError):
293
+ pass # Either exception or False return is acceptable
294
+
295
+
296
+ def test_write_checkpoint_stores_all_required_fields(
297
+ self,
298
+ temp_session_dir,
299
+ valid_checkpoint_data,
300
+ monkeypatch
301
+ ):
302
+ """
303
+ AC#2: All required fields in checkpoint
304
+
305
+ Given: Complete checkpoint data
306
+ When: write_checkpoint() called
307
+ Then: File contains: story_id, phase, phase_name, timestamp,
308
+ progress_percentage, dod_completion, next_action
309
+ """
310
+ # Arrange
311
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", temp_session_dir)
312
+ story_id = "STORY-120"
313
+ phase = 3
314
+ progress = valid_checkpoint_data
315
+
316
+ # Act
317
+ write_checkpoint(story_id, phase, progress)
318
+
319
+ # Assert
320
+ checkpoint_file = Path(temp_session_dir) / story_id / "checkpoint.json"
321
+ with open(checkpoint_file) as f:
322
+ data = json.load(f)
323
+
324
+ assert data["story_id"] == "STORY-120"
325
+ assert data["phase"] == 3
326
+ assert data["phase_name"] == "Refactor"
327
+ assert data["timestamp"].endswith("Z"), "Timestamp should be ISO 8601"
328
+ assert isinstance(data["progress_percentage"], int)
329
+ assert 0 <= data["progress_percentage"] <= 100, "Progress should be 0-100"
330
+ assert isinstance(data["dod_completion"], dict)
331
+ assert all(k in data["dod_completion"] for k in
332
+ ["implementation", "quality", "testing", "documentation"])
333
+ assert data["next_action"] is not None
334
+
335
+
336
+ # ============================================================================
337
+ # UNIT TESTS: read_checkpoint()
338
+ # ============================================================================
339
+
340
+ class TestReadCheckpointUnit:
341
+ """Unit tests for read_checkpoint() function."""
342
+
343
+ def test_read_checkpoint_returns_dict_when_file_exists(
344
+ self,
345
+ valid_checkpoint_json_file,
346
+ monkeypatch
347
+ ):
348
+ """
349
+ AC#2 & AC#3: read_checkpoint returns valid data
350
+
351
+ Given: Valid checkpoint.json file exists
352
+ When: read_checkpoint(story_id='STORY-120') called
353
+ Then: Returns dict with checkpoint data
354
+ """
355
+ # Arrange
356
+ sessions_dir = valid_checkpoint_json_file.parent.parent
357
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", str(sessions_dir))
358
+ story_id = "STORY-120"
359
+
360
+ # Act
361
+ result = read_checkpoint(story_id)
362
+
363
+ # Assert
364
+ assert result is not None, "Should return dict, not None"
365
+ assert isinstance(result, dict), "Should return dictionary"
366
+ assert result["story_id"] == "STORY-120"
367
+ assert result["phase"] == 2
368
+
369
+
370
+ def test_read_checkpoint_returns_none_when_file_missing(
371
+ self,
372
+ temp_session_dir,
373
+ monkeypatch
374
+ ):
375
+ """
376
+ AC#5: Graceful fallback if checkpoint missing
377
+
378
+ Given: Checkpoint file does not exist for story
379
+ When: read_checkpoint(story_id='STORY-999') called
380
+ Then: Returns None (not error)
381
+ """
382
+ # Arrange
383
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", temp_session_dir)
384
+ story_id = "STORY-999"
385
+
386
+ # Act
387
+ result = read_checkpoint(story_id)
388
+
389
+ # Assert
390
+ assert result is None, "Should return None when checkpoint missing"
391
+
392
+
393
+ def test_read_checkpoint_handles_corrupted_json(
394
+ self,
395
+ corrupted_checkpoint_json_file,
396
+ monkeypatch
397
+ ):
398
+ """
399
+ AC#5: Graceful fallback if checkpoint corrupted
400
+
401
+ Given: checkpoint.json contains invalid JSON
402
+ When: read_checkpoint() called
403
+ Then: Returns None (not error)
404
+ """
405
+ # Arrange
406
+ sessions_dir = corrupted_checkpoint_json_file.parent.parent
407
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", str(sessions_dir))
408
+ story_id = "STORY-120"
409
+
410
+ # Act
411
+ result = read_checkpoint(story_id)
412
+
413
+ # Assert
414
+ assert result is None, "Should return None for corrupted JSON"
415
+
416
+
417
+ def test_read_checkpoint_validates_schema(
418
+ self,
419
+ missing_fields_checkpoint_json_file,
420
+ monkeypatch
421
+ ):
422
+ """
423
+ AC#2: Checkpoint schema validation
424
+
425
+ Given: checkpoint.json missing required fields
426
+ When: read_checkpoint() called
427
+ Then: Returns None (schema validation failed)
428
+ """
429
+ # Arrange
430
+ sessions_dir = missing_fields_checkpoint_json_file.parent.parent
431
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", str(sessions_dir))
432
+ story_id = "STORY-120"
433
+
434
+ # Act
435
+ result = read_checkpoint(story_id)
436
+
437
+ # Assert
438
+ assert result is None, "Should return None for invalid schema"
439
+
440
+
441
+ def test_read_checkpoint_returns_all_required_fields(
442
+ self,
443
+ valid_checkpoint_json_file,
444
+ monkeypatch
445
+ ):
446
+ """
447
+ AC#2: Checkpoint includes all required fields
448
+
449
+ Given: Valid checkpoint file with all fields
450
+ When: read_checkpoint() called
451
+ Then: Returned dict has all required fields
452
+ """
453
+ # Arrange
454
+ sessions_dir = valid_checkpoint_json_file.parent.parent
455
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", str(sessions_dir))
456
+ story_id = "STORY-120"
457
+
458
+ # Act
459
+ result = read_checkpoint(story_id)
460
+
461
+ # Assert
462
+ assert result is not None
463
+ required_fields = ["story_id", "phase", "phase_name", "timestamp",
464
+ "progress_percentage", "dod_completion", "next_action"]
465
+ for field in required_fields:
466
+ assert field in result, f"Missing required field: {field}"
467
+
468
+
469
+ # ============================================================================
470
+ # UNIT TESTS: delete_checkpoint()
471
+ # ============================================================================
472
+
473
+ class TestDeleteCheckpointUnit:
474
+ """Unit tests for delete_checkpoint() function."""
475
+
476
+ def test_delete_checkpoint_removes_file(
477
+ self,
478
+ valid_checkpoint_json_file,
479
+ monkeypatch
480
+ ):
481
+ """
482
+ AC#4: Checkpoint cleaned up on story completion
483
+
484
+ Given: checkpoint.json file exists
485
+ When: delete_checkpoint(story_id='STORY-120') called
486
+ Then: Checkpoint file is deleted
487
+ """
488
+ # Arrange
489
+ sessions_dir = valid_checkpoint_json_file.parent.parent
490
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", str(sessions_dir))
491
+ story_id = "STORY-120"
492
+ checkpoint_file = valid_checkpoint_json_file
493
+
494
+ assert checkpoint_file.exists(), "Checkpoint should exist before delete"
495
+
496
+ # Act
497
+ result = delete_checkpoint(story_id)
498
+
499
+ # Assert
500
+ assert result is True, "Should return True on success"
501
+ assert not checkpoint_file.exists(), "Checkpoint file should be deleted"
502
+
503
+
504
+ def test_delete_checkpoint_removes_empty_directory(
505
+ self,
506
+ valid_checkpoint_json_file,
507
+ monkeypatch
508
+ ):
509
+ """
510
+ AC#4: Cleanup removes empty session directory
511
+
512
+ Given: Session directory contains only checkpoint.json
513
+ When: delete_checkpoint() called
514
+ Then: Empty directory is also removed
515
+ """
516
+ # Arrange
517
+ sessions_dir = valid_checkpoint_json_file.parent.parent
518
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", str(sessions_dir))
519
+ story_id = "STORY-120"
520
+ session_dir = valid_checkpoint_json_file.parent
521
+
522
+ assert session_dir.exists(), "Session dir should exist"
523
+
524
+ # Act
525
+ result = delete_checkpoint(story_id)
526
+
527
+ # Assert
528
+ assert result is True, "Should return True"
529
+ assert not session_dir.exists(), "Empty session directory should be removed"
530
+
531
+
532
+ def test_delete_checkpoint_handles_missing_file(
533
+ self,
534
+ temp_session_dir,
535
+ monkeypatch
536
+ ):
537
+ """
538
+ AC#5: Graceful handling of missing checkpoint
539
+
540
+ Given: Checkpoint file does not exist
541
+ When: delete_checkpoint(story_id='STORY-999') called
542
+ Then: Returns True (not error)
543
+ """
544
+ # Arrange
545
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", temp_session_dir)
546
+ story_id = "STORY-999"
547
+
548
+ # Act
549
+ result = delete_checkpoint(story_id)
550
+
551
+ # Assert
552
+ assert result is True, "Should return True even if checkpoint missing"
553
+
554
+
555
+ # ============================================================================
556
+ # INTEGRATION TESTS: Round-trip operations
557
+ # ============================================================================
558
+
559
+ class TestCheckpointIntegration:
560
+ """Integration tests for checkpoint round-trip operations."""
561
+
562
+ def test_checkpoint_round_trip_write_read_delete(
563
+ self,
564
+ temp_session_dir,
565
+ valid_checkpoint_data,
566
+ monkeypatch
567
+ ):
568
+ """
569
+ AC#1-4: Complete checkpoint lifecycle
570
+
571
+ Given: No checkpoint exists
572
+ When: write -> read -> delete sequence executed
573
+ Then: Data persists and is properly cleaned up
574
+ """
575
+ # Arrange
576
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", temp_session_dir)
577
+ story_id = "STORY-120"
578
+ phase = 3
579
+
580
+ # Act - Write
581
+ write_result = write_checkpoint(story_id, phase, valid_checkpoint_data)
582
+ assert write_result is True, "Write should succeed"
583
+
584
+ # Act - Read
585
+ read_data = read_checkpoint(story_id)
586
+
587
+ # Assert - Read returns data
588
+ assert read_data is not None, "Should read written data"
589
+ assert read_data["phase"] == 3, "Should contain correct phase"
590
+ assert read_data["story_id"] == "STORY-120", "Should contain correct story_id"
591
+
592
+ # Act - Delete
593
+ delete_result = delete_checkpoint(story_id)
594
+ assert delete_result is True, "Delete should succeed"
595
+
596
+ # Assert - Data gone
597
+ read_after_delete = read_checkpoint(story_id)
598
+ assert read_after_delete is None, "Should not read data after delete"
599
+
600
+
601
+ def test_checkpoint_concurrent_access_safety(
602
+ self,
603
+ temp_session_dir,
604
+ valid_checkpoint_data,
605
+ monkeypatch
606
+ ):
607
+ """
608
+ AC#4: Concurrent checkpoint writes handled safely
609
+
610
+ Given: Multiple rapid write operations (simulating phase completion)
611
+ When: write_checkpoint called 3 times in sequence (phase 1, 2, 3)
612
+ Then: Final checkpoint reflects last write, no corruption
613
+ """
614
+ # Arrange
615
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", temp_session_dir)
616
+ story_id = "STORY-120"
617
+
618
+ phases = [
619
+ (1, "Red", 30),
620
+ (2, "Green", 50),
621
+ (3, "Refactor", 70)
622
+ ]
623
+
624
+ # Act - Write multiple phases
625
+ for phase, phase_name, progress_pct in phases:
626
+ data = valid_checkpoint_data.copy()
627
+ data["phase"] = phase
628
+ data["phase_name"] = phase_name
629
+ data["progress_percentage"] = progress_pct
630
+
631
+ result = write_checkpoint(story_id, phase, data)
632
+ assert result is True, f"Write phase {phase} should succeed"
633
+
634
+ # Assert - Final read shows phase 3
635
+ final_data = read_checkpoint(story_id)
636
+ assert final_data is not None, "Should read final checkpoint"
637
+ assert final_data["phase"] == 3, "Should have final phase number"
638
+ assert final_data["phase_name"] == "Refactor", "Should have final phase name"
639
+ assert final_data["progress_percentage"] == 70, "Should have final progress"
640
+
641
+
642
+ # ============================================================================
643
+ # EDGE CASE TESTS
644
+ # ============================================================================
645
+
646
+ class TestCheckpointEdgeCases:
647
+ """Edge case tests for checkpoint operations."""
648
+
649
+ def test_checkpoint_with_unicode_characters(
650
+ self,
651
+ temp_session_dir,
652
+ monkeypatch
653
+ ):
654
+ """
655
+ Given: Checkpoint with unicode characters in last_action
656
+ When: write_checkpoint called
657
+ Then: Unicode is preserved and readable
658
+ """
659
+ # Arrange
660
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", temp_session_dir)
661
+ story_id = "STORY-120"
662
+ phase = 3
663
+
664
+ data = {
665
+ "story_id": "STORY-120",
666
+ "phase": 3,
667
+ "phase_name": "Refactor",
668
+ "timestamp": datetime.utcnow().isoformat() + "Z",
669
+ "progress_percentage": 70,
670
+ "dod_completion": {
671
+ "implementation": [5, 8],
672
+ "quality": [2, 6],
673
+ "testing": [3, 5],
674
+ "documentation": [1, 4]
675
+ },
676
+ "last_action": "code-reviewer: Fixed 🐛 in parsing logic",
677
+ "next_action": "Phase 4: Integration Testing"
678
+ }
679
+
680
+ # Act
681
+ write_result = write_checkpoint(story_id, phase, data)
682
+ read_data = read_checkpoint(story_id)
683
+
684
+ # Assert
685
+ assert write_result is True
686
+ assert read_data is not None
687
+ assert "🐛" in read_data["last_action"], "Unicode should be preserved"
688
+
689
+
690
+ def test_checkpoint_timestamp_is_iso8601(
691
+ self,
692
+ temp_session_dir,
693
+ valid_checkpoint_data,
694
+ monkeypatch
695
+ ):
696
+ """
697
+ AC#2: Timestamp validation (ISO 8601 format)
698
+
699
+ Given: Valid checkpoint data with ISO 8601 timestamp
700
+ When: write_checkpoint called
701
+ Then: Timestamp format is maintained
702
+ """
703
+ # Arrange
704
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", temp_session_dir)
705
+ story_id = "STORY-120"
706
+ phase = 3
707
+ data = valid_checkpoint_data.copy()
708
+
709
+ # Act
710
+ write_checkpoint(story_id, phase, data)
711
+ read_data = read_checkpoint(story_id)
712
+
713
+ # Assert
714
+ timestamp = read_data["timestamp"]
715
+ assert timestamp.endswith("Z"), "Should be ISO 8601 UTC (ends with Z)"
716
+ # Should be parseable as ISO 8601
717
+ try:
718
+ datetime.fromisoformat(timestamp.replace("Z", "+00:00"))
719
+ except ValueError:
720
+ pytest.fail(f"Timestamp not valid ISO 8601: {timestamp}")
721
+
722
+
723
+ def test_checkpoint_progress_percentage_boundary_values(
724
+ self,
725
+ temp_session_dir,
726
+ monkeypatch
727
+ ):
728
+ """
729
+ AC#2: Progress percentage validation (0-100)
730
+
731
+ Given: Progress percentage boundary values
732
+ When: write_checkpoint called with progress 0, 50, 100
733
+ Then: All values accepted and preserved
734
+ """
735
+ # Arrange
736
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", temp_session_dir)
737
+
738
+ for progress_pct in [0, 50, 100]:
739
+ story_id = f"STORY-{120 + progress_pct}"
740
+ phase = 3
741
+ data = {
742
+ "story_id": story_id,
743
+ "phase": phase,
744
+ "phase_name": "Refactor",
745
+ "timestamp": datetime.utcnow().isoformat() + "Z",
746
+ "progress_percentage": progress_pct,
747
+ "dod_completion": {
748
+ "implementation": [0, 8],
749
+ "quality": [0, 6],
750
+ "testing": [0, 5],
751
+ "documentation": [0, 4]
752
+ },
753
+ "next_action": "Continue"
754
+ }
755
+
756
+ # Act
757
+ write_result = write_checkpoint(story_id, phase, data)
758
+ read_data = read_checkpoint(story_id)
759
+
760
+ # Assert
761
+ assert write_result is True
762
+ assert read_data["progress_percentage"] == progress_pct
763
+
764
+
765
+ # ============================================================================
766
+ # RESUME-DEV AUTO-DETECTION TESTS
767
+ # ============================================================================
768
+
769
+ class TestResumeDevAutoDetection:
770
+ """Tests for /resume-dev auto-detection from checkpoint (AC#3)."""
771
+
772
+ def test_resume_dev_detects_last_completed_phase(
773
+ self,
774
+ temp_session_dir,
775
+ valid_checkpoint_data,
776
+ monkeypatch
777
+ ):
778
+ """
779
+ AC#3: /resume-dev auto-detects from checkpoint
780
+
781
+ Given: Checkpoint shows phase 3 completed
782
+ When: read_checkpoint(story_id) called (simulating /resume-dev)
783
+ Then: Detected phase is 3 (should resume at phase 4)
784
+ """
785
+ # Arrange
786
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", temp_session_dir)
787
+ story_id = "STORY-120"
788
+
789
+ # Write checkpoint showing phase 3 complete
790
+ data = valid_checkpoint_data.copy()
791
+ data["phase"] = 3
792
+ data["phase_name"] = "Refactor"
793
+ write_checkpoint(story_id, 3, data)
794
+
795
+ # Act
796
+ checkpoint = read_checkpoint(story_id)
797
+
798
+ # Assert
799
+ assert checkpoint is not None
800
+ assert checkpoint["phase"] == 3, "Should detect phase 3 completed"
801
+ # /resume-dev would use this to determine next phase: 3 + 1 = phase 4
802
+
803
+
804
+ def test_resume_dev_no_prompting_needed_when_checkpoint_exists(
805
+ self,
806
+ valid_checkpoint_json_file,
807
+ monkeypatch
808
+ ):
809
+ """
810
+ AC#3: No prompting required with checkpoint
811
+
812
+ Given: Valid checkpoint file exists
813
+ When: /resume-dev STORY-120 (no phase arg)
814
+ Then: Auto-detection works without asking user for phase
815
+ """
816
+ # Arrange
817
+ sessions_dir = valid_checkpoint_json_file.parent.parent
818
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", str(sessions_dir))
819
+ story_id = "STORY-120"
820
+
821
+ # Act
822
+ checkpoint = read_checkpoint(story_id)
823
+
824
+ # Assert
825
+ assert checkpoint is not None, "Should find checkpoint automatically"
826
+ assert checkpoint["phase"] is not None, "Should have phase data"
827
+ # In /resume-dev, this would allow auto-detection without user input
828
+
829
+
830
+ def test_resume_dev_falls_back_to_phase_0_if_no_checkpoint(
831
+ self,
832
+ temp_session_dir,
833
+ monkeypatch
834
+ ):
835
+ """
836
+ AC#5: Graceful fallback if checkpoint missing
837
+
838
+ Given: No checkpoint exists for story
839
+ When: /resume-dev STORY-999 called
840
+ Then: Falls back to Phase 0 with warning message
841
+ """
842
+ # Arrange
843
+ monkeypatch.setenv("DEVFORGEAI_SESSIONS_DIR", temp_session_dir)
844
+ story_id = "STORY-999"
845
+
846
+ # Act
847
+ checkpoint = read_checkpoint(story_id)
848
+
849
+ # Assert
850
+ assert checkpoint is None, "Checkpoint should be None"
851
+ # /resume-dev would show: "Checkpoint not found. Starting from Phase 0."