devforgeai 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +120 -0
- package/package.json +9 -1
- package/src/CLAUDE.md +699 -0
- package/src/claude/scripts/README.md +396 -0
- package/src/claude/scripts/audit-command-skill-overlap.sh +67 -0
- package/src/claude/scripts/check-hooks-fast.sh +70 -0
- package/src/claude/scripts/devforgeai-validate +6 -0
- package/src/claude/scripts/devforgeai_cli/README.md +531 -0
- package/src/claude/scripts/devforgeai_cli/__init__.py +12 -0
- package/src/claude/scripts/devforgeai_cli/cli.py +716 -0
- package/src/claude/scripts/devforgeai_cli/commands/__init__.py +1 -0
- package/src/claude/scripts/devforgeai_cli/commands/check_hooks.py +384 -0
- package/src/claude/scripts/devforgeai_cli/commands/invoke_hooks.py +149 -0
- package/src/claude/scripts/devforgeai_cli/commands/phase_commands.py +731 -0
- package/src/claude/scripts/devforgeai_cli/commands/validate_installation.py +412 -0
- package/src/claude/scripts/devforgeai_cli/context_extraction.py +426 -0
- package/src/claude/scripts/devforgeai_cli/feedback/AC_TO_TEST_MAPPING.md +636 -0
- package/src/claude/scripts/devforgeai_cli/feedback/DELIVERY_SUMMARY.txt +329 -0
- package/src/claude/scripts/devforgeai_cli/feedback/README_TEST_SPECS.md +486 -0
- package/src/claude/scripts/devforgeai_cli/feedback/TEST_IMPLEMENTATION_GUIDE.md +529 -0
- package/src/claude/scripts/devforgeai_cli/feedback/TEST_SPECIFICATIONS.md +2652 -0
- package/src/claude/scripts/devforgeai_cli/feedback/TEST_SPECS_INDEX.md +398 -0
- package/src/claude/scripts/devforgeai_cli/feedback/__init__.py +34 -0
- package/src/claude/scripts/devforgeai_cli/feedback/adaptive_questioning_engine.py +581 -0
- package/src/claude/scripts/devforgeai_cli/feedback/aggregation.py +179 -0
- package/src/claude/scripts/devforgeai_cli/feedback/commands.py +535 -0
- package/src/claude/scripts/devforgeai_cli/feedback/config_defaults.py +58 -0
- package/src/claude/scripts/devforgeai_cli/feedback/config_manager.py +423 -0
- package/src/claude/scripts/devforgeai_cli/feedback/config_models.py +192 -0
- package/src/claude/scripts/devforgeai_cli/feedback/config_schema.py +140 -0
- package/src/claude/scripts/devforgeai_cli/feedback/coverage.json +1 -0
- package/src/claude/scripts/devforgeai_cli/feedback/feature_flag.py +152 -0
- package/src/claude/scripts/devforgeai_cli/feedback/feedback_indexer.py +394 -0
- package/src/claude/scripts/devforgeai_cli/feedback/hot_reload.py +226 -0
- package/src/claude/scripts/devforgeai_cli/feedback/longitudinal.py +115 -0
- package/src/claude/scripts/devforgeai_cli/feedback/models.py +67 -0
- package/src/claude/scripts/devforgeai_cli/feedback/question_router.py +236 -0
- package/src/claude/scripts/devforgeai_cli/feedback/retrospective.py +233 -0
- package/src/claude/scripts/devforgeai_cli/feedback/skip_tracker.py +177 -0
- package/src/claude/scripts/devforgeai_cli/feedback/skip_tracking.py +221 -0
- package/src/claude/scripts/devforgeai_cli/feedback/template_engine.py +549 -0
- package/src/claude/scripts/devforgeai_cli/feedback/validation.py +163 -0
- package/src/claude/scripts/devforgeai_cli/headless/__init__.py +30 -0
- package/src/claude/scripts/devforgeai_cli/headless/answer_models.py +206 -0
- package/src/claude/scripts/devforgeai_cli/headless/answer_resolver.py +204 -0
- package/src/claude/scripts/devforgeai_cli/headless/exceptions.py +36 -0
- package/src/claude/scripts/devforgeai_cli/headless/pattern_matcher.py +156 -0
- package/src/claude/scripts/devforgeai_cli/hooks.py +313 -0
- package/src/claude/scripts/devforgeai_cli/metrics/__init__.py +46 -0
- package/src/claude/scripts/devforgeai_cli/metrics/command_metrics.py +142 -0
- package/src/claude/scripts/devforgeai_cli/metrics/failure_modes.py +152 -0
- package/src/claude/scripts/devforgeai_cli/metrics/story_segmentation.py +181 -0
- package/src/claude/scripts/devforgeai_cli/orchestrate_hooks.py +780 -0
- package/src/claude/scripts/devforgeai_cli/phase_state.py +1229 -0
- package/src/claude/scripts/devforgeai_cli/session/__init__.py +30 -0
- package/src/claude/scripts/devforgeai_cli/session/checkpoint.py +268 -0
- package/src/claude/scripts/devforgeai_cli/tests/__init__.py +1 -0
- package/src/claude/scripts/devforgeai_cli/tests/conftest.py +29 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/TEST_EXECUTION_GUIDE.md +298 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/__init__.py +3 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_adaptive_questioning_engine.py +2171 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_aggregation.py +476 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_defaults.py +133 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_manager.py +592 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_models.py +373 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_schema.py +130 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_configuration_management.py +1355 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_edge_cases.py +308 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_feature_flag.py +307 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_feedback_indexer.py +384 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_hot_reload.py +580 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_integration.py +402 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_models.py +105 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_question_routing.py +262 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_retrospective.py +333 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracker.py +410 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracking.py +159 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracking_integration.py +1155 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_template_engine.py +1389 -0
- package/src/claude/scripts/devforgeai_cli/tests/feedback/test_validation_comprehensive.py +210 -0
- package/src/claude/scripts/devforgeai_cli/tests/fixtures/autonomous-deferral-story.md +46 -0
- package/src/claude/scripts/devforgeai_cli/tests/fixtures/missing-impl-notes.md +31 -0
- package/src/claude/scripts/devforgeai_cli/tests/fixtures/valid-deferral-story.md +46 -0
- package/src/claude/scripts/devforgeai_cli/tests/fixtures/valid-story-complete.md +48 -0
- package/src/claude/scripts/devforgeai_cli/tests/manual_test_invoke_hooks.sh +200 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/DELIVERABLES.md +518 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/TEST_SUMMARY.md +468 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/__init__.py +6 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/corrupted-checkpoint.json +1 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/missing-fields-checkpoint.json +4 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/valid-checkpoint.json +15 -0
- package/src/claude/scripts/devforgeai_cli/tests/session/test_checkpoint.py +851 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_check_hooks.py +1886 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_depends_on_normalizer.py +171 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_dod_validator.py +97 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_invoke_hooks.py +1902 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands.py +320 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands_error_handling.py +1021 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands_import.py +697 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_phase_state.py +2187 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_skip_tracking.py +2141 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_skip_tracking_coverage_gap.py +195 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_subagent_enforcement.py +539 -0
- package/src/claude/scripts/devforgeai_cli/tests/test_validate_installation.py +361 -0
- package/src/claude/scripts/devforgeai_cli/utils/__init__.py +11 -0
- package/src/claude/scripts/devforgeai_cli/utils/depends_on_normalizer.py +149 -0
- package/src/claude/scripts/devforgeai_cli/utils/markdown_parser.py +219 -0
- package/src/claude/scripts/devforgeai_cli/utils/story_analyzer.py +249 -0
- package/src/claude/scripts/devforgeai_cli/utils/yaml_parser.py +152 -0
- package/src/claude/scripts/devforgeai_cli/validators/__init__.py +27 -0
- package/src/claude/scripts/devforgeai_cli/validators/ast_grep_validator.py +373 -0
- package/src/claude/scripts/devforgeai_cli/validators/context_validator.py +180 -0
- package/src/claude/scripts/devforgeai_cli/validators/dod_validator.py +309 -0
- package/src/claude/scripts/devforgeai_cli/validators/git_validator.py +107 -0
- package/src/claude/scripts/devforgeai_cli/validators/grep_fallback.py +300 -0
- package/src/claude/scripts/install_hooks.sh +186 -0
- package/src/claude/scripts/invoke_feedback_hooks.sh +59 -0
- package/src/claude/scripts/migrate-ac-headers.sh +122 -0
- package/src/claude/scripts/plan_file_kb.sh +704 -0
- package/src/claude/scripts/requirements.txt +8 -0
- package/src/claude/scripts/session_catalog.sh +543 -0
- package/src/claude/scripts/setup.py +55 -0
- package/src/claude/scripts/start-devforgeai.sh +16 -0
- package/src/claude/scripts/statusline.sh +27 -0
- package/src/claude/scripts/validate_deferrals.py +344 -0
- package/src/claude/skills/devforgeai-qa/SKILL.md +1 -1
- package/src/claude/skills/researching-market/SKILL.md +2 -1
- package/src/cli/lib/copier.js +13 -1
- package/src/claude/skills/designing-systems/scripts/__pycache__/detect_anti_patterns.cpython-312.pyc +0 -0
- package/src/claude/skills/designing-systems/scripts/__pycache__/validate_all_context.cpython-312.pyc +0 -0
- package/src/claude/skills/designing-systems/scripts/__pycache__/validate_architecture.cpython-312.pyc +0 -0
- package/src/claude/skills/designing-systems/scripts/__pycache__/validate_dependencies.cpython-312.pyc +0 -0
- package/src/claude/skills/devforgeai-story-creation/scripts/__pycache__/migrate_story_v1_to_v2.cpython-312.pyc +0 -0
- package/src/claude/skills/devforgeai-story-creation/scripts/tests/__pycache__/measure_accuracy.cpython-312.pyc +0 -0
|
@@ -0,0 +1,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."
|