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,384 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TDD Red Phase tests for feedback indexer reindex_all_feedback function.
|
|
3
|
+
|
|
4
|
+
Tests cover:
|
|
5
|
+
- Scanning ai-analysis STORY/EPIC/RCA folders for JSON and MD files
|
|
6
|
+
- Scanning code-review and code-reviews directories for markdown files
|
|
7
|
+
- Scanning root-level feedback report files
|
|
8
|
+
- Graceful handling of missing directories
|
|
9
|
+
- Unified index generation with version, source_summary, and entries
|
|
10
|
+
- Source type field population for each entry
|
|
11
|
+
- Graceful skipping of malformed JSON files
|
|
12
|
+
- Exclusion of documentation, config, index, and register files
|
|
13
|
+
|
|
14
|
+
All tests use tmp_path fixture to create isolated directory structures.
|
|
15
|
+
Each test calls reindex_all_feedback(str(tmp_path)) and reads the
|
|
16
|
+
generated devforgeai/feedback/index.json to verify entries.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
import pytest
|
|
23
|
+
|
|
24
|
+
from devforgeai_cli.feedback.feedback_indexer import reindex_all_feedback
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _read_index(tmp_path):
|
|
28
|
+
"""Read the generated index.json and return parsed dict."""
|
|
29
|
+
index_path = Path(tmp_path) / "devforgeai" / "feedback" / "index.json"
|
|
30
|
+
assert index_path.exists(), f"Index not created at {index_path}"
|
|
31
|
+
with open(index_path) as f:
|
|
32
|
+
return json.load(f)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TestFeedbackIndexer:
|
|
36
|
+
"""Unit tests for reindex_all_feedback function."""
|
|
37
|
+
|
|
38
|
+
def test_scans_ai_analysis_story_folders(self, tmp_path):
|
|
39
|
+
"""Verify that ai-analysis STORY folders are scanned and both JSON files indexed."""
|
|
40
|
+
# Arrange
|
|
41
|
+
story_dir = tmp_path / "devforgeai" / "feedback" / "ai-analysis" / "STORY-001"
|
|
42
|
+
story_dir.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
|
|
44
|
+
consolidated = {
|
|
45
|
+
"story_id": "STORY-001",
|
|
46
|
+
"workflow_type": "dev",
|
|
47
|
+
"analysis_date": "2026-01-01T00:00:00Z",
|
|
48
|
+
"what_worked_well": [],
|
|
49
|
+
"recommendations": [],
|
|
50
|
+
}
|
|
51
|
+
with open(story_dir / "consolidated-analysis.json", "w") as f:
|
|
52
|
+
json.dump(consolidated, f)
|
|
53
|
+
|
|
54
|
+
phase_data = {
|
|
55
|
+
"subagent": "test-automator",
|
|
56
|
+
"phase": "02",
|
|
57
|
+
"story_id": "STORY-001",
|
|
58
|
+
"timestamp": "2026-01-01T00:00:00Z",
|
|
59
|
+
"observations": [],
|
|
60
|
+
}
|
|
61
|
+
with open(story_dir / "phase-02-test-automator.json", "w") as f:
|
|
62
|
+
json.dump(phase_data, f)
|
|
63
|
+
|
|
64
|
+
# Act
|
|
65
|
+
result = reindex_all_feedback(str(tmp_path))
|
|
66
|
+
|
|
67
|
+
# Assert
|
|
68
|
+
assert result == 0, f"Expected exit code 0, got {result}"
|
|
69
|
+
index = _read_index(tmp_path)
|
|
70
|
+
assert len(index["feedback-sessions"]) >= 2, (
|
|
71
|
+
f"Expected at least 2 entries, got {len(index['feedback-sessions'])}"
|
|
72
|
+
)
|
|
73
|
+
file_paths = [entry["file-path"] for entry in index["feedback-sessions"]]
|
|
74
|
+
assert any("consolidated-analysis.json" in fp for fp in file_paths), (
|
|
75
|
+
"consolidated-analysis.json not found in index entries"
|
|
76
|
+
)
|
|
77
|
+
assert any("phase-02-test-automator.json" in fp for fp in file_paths), (
|
|
78
|
+
"phase-02-test-automator.json not found in index entries"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def test_scans_ai_analysis_epic_folders(self, tmp_path):
|
|
82
|
+
"""Verify that ai-analysis EPIC folders are scanned and JSON files indexed."""
|
|
83
|
+
# Arrange
|
|
84
|
+
epic_dir = tmp_path / "devforgeai" / "feedback" / "ai-analysis" / "EPIC-001"
|
|
85
|
+
epic_dir.mkdir(parents=True, exist_ok=True)
|
|
86
|
+
|
|
87
|
+
epic_data = {
|
|
88
|
+
"ai_analysis": {
|
|
89
|
+
"operation": "epic-creation",
|
|
90
|
+
"epic_id": "EPIC-001",
|
|
91
|
+
"timestamp": "2026-01-01T00:00:00Z",
|
|
92
|
+
"what_worked_well": [],
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
with open(epic_dir / "2026-01-01-ai-analysis.json", "w") as f:
|
|
96
|
+
json.dump(epic_data, f)
|
|
97
|
+
|
|
98
|
+
# Act
|
|
99
|
+
result = reindex_all_feedback(str(tmp_path))
|
|
100
|
+
|
|
101
|
+
# Assert
|
|
102
|
+
assert result == 0
|
|
103
|
+
index = _read_index(tmp_path)
|
|
104
|
+
assert len(index["feedback-sessions"]) >= 1, (
|
|
105
|
+
f"Expected at least 1 entry, got {len(index['feedback-sessions'])}"
|
|
106
|
+
)
|
|
107
|
+
file_paths = [entry["file-path"] for entry in index["feedback-sessions"]]
|
|
108
|
+
assert any("EPIC-001" in fp for fp in file_paths), (
|
|
109
|
+
"No entry with EPIC-001 found in index"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def test_scans_ai_analysis_rca_folders(self, tmp_path):
|
|
113
|
+
"""Verify that ai-analysis RCA folders are scanned and JSON files indexed."""
|
|
114
|
+
# Arrange
|
|
115
|
+
rca_dir = tmp_path / "devforgeai" / "feedback" / "ai-analysis" / "RCA-001"
|
|
116
|
+
rca_dir.mkdir(parents=True, exist_ok=True)
|
|
117
|
+
|
|
118
|
+
rca_data = {
|
|
119
|
+
"timestamp": "2026-01-01T00:00:00Z",
|
|
120
|
+
"recommendations": [],
|
|
121
|
+
}
|
|
122
|
+
with open(rca_dir / "20260101-recommendations.json", "w") as f:
|
|
123
|
+
json.dump(rca_data, f)
|
|
124
|
+
|
|
125
|
+
# Act
|
|
126
|
+
result = reindex_all_feedback(str(tmp_path))
|
|
127
|
+
|
|
128
|
+
# Assert
|
|
129
|
+
assert result == 0
|
|
130
|
+
index = _read_index(tmp_path)
|
|
131
|
+
assert len(index["feedback-sessions"]) == 1, (
|
|
132
|
+
f"Expected 1 entry, got {len(index['feedback-sessions'])}"
|
|
133
|
+
)
|
|
134
|
+
file_paths = [entry["file-path"] for entry in index["feedback-sessions"]]
|
|
135
|
+
assert any("RCA-001" in fp for fp in file_paths), (
|
|
136
|
+
"No entry with RCA-001 found in index"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def test_scans_code_review_files(self, tmp_path):
|
|
140
|
+
"""Verify both code-review/ and code-reviews/ directories are scanned."""
|
|
141
|
+
# Arrange
|
|
142
|
+
cr_dir = tmp_path / "devforgeai" / "feedback" / "code-review"
|
|
143
|
+
cr_dir.mkdir(parents=True, exist_ok=True)
|
|
144
|
+
(cr_dir / "STORY-001-code-review.md").write_text(
|
|
145
|
+
"# Code Review\n\nSTORY-001"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
crs_dir = tmp_path / "devforgeai" / "feedback" / "code-reviews"
|
|
149
|
+
crs_dir.mkdir(parents=True, exist_ok=True)
|
|
150
|
+
(crs_dir / "STORY-002-code-review.md").write_text(
|
|
151
|
+
"# Code Review\n\nSTORY-002"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Act
|
|
155
|
+
result = reindex_all_feedback(str(tmp_path))
|
|
156
|
+
|
|
157
|
+
# Assert
|
|
158
|
+
assert result == 0
|
|
159
|
+
index = _read_index(tmp_path)
|
|
160
|
+
assert len(index["feedback-sessions"]) == 2, (
|
|
161
|
+
f"Expected 2 entries, got {len(index['feedback-sessions'])}"
|
|
162
|
+
)
|
|
163
|
+
for entry in index["feedback-sessions"]:
|
|
164
|
+
assert entry["source_type"] == "code-review", (
|
|
165
|
+
f"Expected source_type 'code-review', got '{entry['source_type']}'"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def test_scans_root_report_files(self, tmp_path):
|
|
169
|
+
"""Verify root-level feedback report files are scanned with source_type 'report'."""
|
|
170
|
+
# Arrange
|
|
171
|
+
feedback_dir = tmp_path / "devforgeai" / "feedback"
|
|
172
|
+
feedback_dir.mkdir(parents=True, exist_ok=True)
|
|
173
|
+
|
|
174
|
+
(feedback_dir / "code-review-STORY-003.md").write_text("# Code Review")
|
|
175
|
+
(feedback_dir / "integration-test-report-STORY-004.md").write_text(
|
|
176
|
+
"# Integration Test"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Act
|
|
180
|
+
result = reindex_all_feedback(str(tmp_path))
|
|
181
|
+
|
|
182
|
+
# Assert
|
|
183
|
+
assert result == 0
|
|
184
|
+
index = _read_index(tmp_path)
|
|
185
|
+
report_entries = [
|
|
186
|
+
e for e in index["feedback-sessions"] if e["source_type"] == "report"
|
|
187
|
+
]
|
|
188
|
+
assert len(report_entries) == 2, (
|
|
189
|
+
f"Expected 2 report entries, got {len(report_entries)}"
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
def test_handles_missing_sessions_dir_gracefully(self, tmp_path):
|
|
193
|
+
"""Verify no exception when sessions/ directory does not exist."""
|
|
194
|
+
# Arrange - create ai-analysis but NOT sessions/
|
|
195
|
+
story_dir = tmp_path / "devforgeai" / "feedback" / "ai-analysis" / "STORY-001"
|
|
196
|
+
story_dir.mkdir(parents=True, exist_ok=True)
|
|
197
|
+
|
|
198
|
+
valid_data = {
|
|
199
|
+
"story_id": "STORY-001",
|
|
200
|
+
"timestamp": "2026-01-01T00:00:00Z",
|
|
201
|
+
}
|
|
202
|
+
with open(story_dir / "valid.json", "w") as f:
|
|
203
|
+
json.dump(valid_data, f)
|
|
204
|
+
|
|
205
|
+
# Verify sessions/ does NOT exist
|
|
206
|
+
sessions_dir = tmp_path / "devforgeai" / "feedback" / "sessions"
|
|
207
|
+
assert not sessions_dir.exists(), "sessions/ should not exist for this test"
|
|
208
|
+
|
|
209
|
+
# Act - must NOT raise exception
|
|
210
|
+
result = reindex_all_feedback(str(tmp_path))
|
|
211
|
+
|
|
212
|
+
# Assert
|
|
213
|
+
assert result == 0, f"Expected exit code 0, got {result}"
|
|
214
|
+
index = _read_index(tmp_path)
|
|
215
|
+
assert len(index["feedback-sessions"]) >= 1, (
|
|
216
|
+
"Expected at least 1 entry from ai-analysis"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
def test_mixed_sources_unified_index(self, tmp_path):
|
|
220
|
+
"""Verify mixed source types produce unified index with correct structure."""
|
|
221
|
+
# Arrange - create files from all source types
|
|
222
|
+
feedback_dir = tmp_path / "devforgeai" / "feedback"
|
|
223
|
+
|
|
224
|
+
# ai-analysis STORY
|
|
225
|
+
story_dir = feedback_dir / "ai-analysis" / "STORY-001"
|
|
226
|
+
story_dir.mkdir(parents=True, exist_ok=True)
|
|
227
|
+
with open(story_dir / "valid.json", "w") as f:
|
|
228
|
+
json.dump({"story_id": "STORY-001", "timestamp": "2026-01-01T00:00:00Z"}, f)
|
|
229
|
+
|
|
230
|
+
# ai-analysis EPIC
|
|
231
|
+
epic_dir = feedback_dir / "ai-analysis" / "EPIC-001"
|
|
232
|
+
epic_dir.mkdir(parents=True, exist_ok=True)
|
|
233
|
+
with open(epic_dir / "valid.json", "w") as f:
|
|
234
|
+
json.dump({"epic_id": "EPIC-001", "timestamp": "2026-01-01T00:00:00Z"}, f)
|
|
235
|
+
|
|
236
|
+
# code-review
|
|
237
|
+
cr_dir = feedback_dir / "code-review"
|
|
238
|
+
cr_dir.mkdir(parents=True, exist_ok=True)
|
|
239
|
+
(cr_dir / "STORY-002-code-review.md").write_text("# Code Review\n\nSTORY-002")
|
|
240
|
+
|
|
241
|
+
# root report
|
|
242
|
+
(feedback_dir / "code-review-STORY-003.md").write_text("# Code Review")
|
|
243
|
+
|
|
244
|
+
# Act
|
|
245
|
+
result = reindex_all_feedback(str(tmp_path))
|
|
246
|
+
|
|
247
|
+
# Assert
|
|
248
|
+
assert result == 0
|
|
249
|
+
index = _read_index(tmp_path)
|
|
250
|
+
assert index["version"] == "2.0", (
|
|
251
|
+
f"Expected version '2.0', got '{index['version']}'"
|
|
252
|
+
)
|
|
253
|
+
assert "source_summary" in index, "Missing 'source_summary' in index"
|
|
254
|
+
summary = index["source_summary"]
|
|
255
|
+
for key in ("ai-analysis", "session", "code-review", "report"):
|
|
256
|
+
assert key in summary, f"Missing '{key}' in source_summary"
|
|
257
|
+
assert len(index["feedback-sessions"]) == 4, (
|
|
258
|
+
f"Expected 4 total entries, got {len(index['feedback-sessions'])}"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
def test_source_type_field_populated(self, tmp_path):
|
|
262
|
+
"""Verify source_type field is present and valid for each entry type."""
|
|
263
|
+
# Arrange
|
|
264
|
+
feedback_dir = tmp_path / "devforgeai" / "feedback"
|
|
265
|
+
|
|
266
|
+
# ai-analysis JSON
|
|
267
|
+
story_dir = feedback_dir / "ai-analysis" / "STORY-001"
|
|
268
|
+
story_dir.mkdir(parents=True, exist_ok=True)
|
|
269
|
+
with open(story_dir / "analysis.json", "w") as f:
|
|
270
|
+
json.dump({"story_id": "STORY-001", "timestamp": "2026-01-01T00:00:00Z"}, f)
|
|
271
|
+
|
|
272
|
+
# code-review MD
|
|
273
|
+
cr_dir = feedback_dir / "code-review"
|
|
274
|
+
cr_dir.mkdir(parents=True, exist_ok=True)
|
|
275
|
+
(cr_dir / "review.md").write_text("# Review")
|
|
276
|
+
|
|
277
|
+
# root report MD
|
|
278
|
+
(feedback_dir / "code-review-STORY-002.md").write_text("# Report")
|
|
279
|
+
|
|
280
|
+
valid_source_types = {"ai-analysis", "session", "code-review", "report"}
|
|
281
|
+
|
|
282
|
+
# Act
|
|
283
|
+
result = reindex_all_feedback(str(tmp_path))
|
|
284
|
+
|
|
285
|
+
# Assert
|
|
286
|
+
assert result == 0
|
|
287
|
+
index = _read_index(tmp_path)
|
|
288
|
+
for entry in index["feedback-sessions"]:
|
|
289
|
+
assert "source_type" in entry, (
|
|
290
|
+
f"Missing 'source_type' in entry: {entry}"
|
|
291
|
+
)
|
|
292
|
+
assert entry["source_type"] in valid_source_types, (
|
|
293
|
+
f"Invalid source_type '{entry['source_type']}', "
|
|
294
|
+
f"expected one of {valid_source_types}"
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
def test_json_parse_errors_skip_gracefully(self, tmp_path, capsys):
|
|
298
|
+
"""Verify malformed JSON files are skipped and counted as errors."""
|
|
299
|
+
# Arrange
|
|
300
|
+
story_dir = tmp_path / "devforgeai" / "feedback" / "ai-analysis" / "STORY-001"
|
|
301
|
+
story_dir.mkdir(parents=True, exist_ok=True)
|
|
302
|
+
|
|
303
|
+
# Broken JSON file
|
|
304
|
+
(story_dir / "broken.json").write_text("{invalid json!!")
|
|
305
|
+
|
|
306
|
+
# Valid JSON file
|
|
307
|
+
with open(story_dir / "valid.json", "w") as f:
|
|
308
|
+
json.dump({"story_id": "STORY-001", "timestamp": "2026-01-01T00:00:00Z"}, f)
|
|
309
|
+
|
|
310
|
+
# Act
|
|
311
|
+
result = reindex_all_feedback(str(tmp_path), output_format="json")
|
|
312
|
+
|
|
313
|
+
# Assert - parse stdout JSON output
|
|
314
|
+
captured = capsys.readouterr()
|
|
315
|
+
stdout_data = json.loads(captured.out)
|
|
316
|
+
assert stdout_data["error_count"] >= 1, (
|
|
317
|
+
f"Expected error_count >= 1, got {stdout_data['error_count']}"
|
|
318
|
+
)
|
|
319
|
+
assert stdout_data["indexed_count"] >= 1, (
|
|
320
|
+
f"Expected indexed_count >= 1, got {stdout_data['indexed_count']}"
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
def test_ignores_documentation_files(self, tmp_path):
|
|
324
|
+
"""Verify all documentation, config, index, and register files are excluded."""
|
|
325
|
+
# Arrange
|
|
326
|
+
feedback_dir = tmp_path / "devforgeai" / "feedback"
|
|
327
|
+
feedback_dir.mkdir(parents=True, exist_ok=True)
|
|
328
|
+
|
|
329
|
+
# All excluded documentation files
|
|
330
|
+
excluded_docs = [
|
|
331
|
+
"USER-GUIDE.md",
|
|
332
|
+
"MAINTAINER-GUIDE.md",
|
|
333
|
+
"GRACEFUL-DEGRADATION.md",
|
|
334
|
+
"RETENTION-POLICY.md",
|
|
335
|
+
"IMPLEMENTATION-COMPLETE.md",
|
|
336
|
+
"QUESTION-BANK-COMPLETION-SUMMARY.md",
|
|
337
|
+
"questions.md",
|
|
338
|
+
]
|
|
339
|
+
for doc in excluded_docs:
|
|
340
|
+
(feedback_dir / doc).write_text(f"# {doc}")
|
|
341
|
+
|
|
342
|
+
# All excluded config files
|
|
343
|
+
excluded_configs = [
|
|
344
|
+
"config.yaml",
|
|
345
|
+
"schema.json",
|
|
346
|
+
"questions.yaml",
|
|
347
|
+
"question-defaults.yaml",
|
|
348
|
+
]
|
|
349
|
+
for cfg in excluded_configs:
|
|
350
|
+
(feedback_dir / cfg).write_text(f"# {cfg}")
|
|
351
|
+
|
|
352
|
+
# Excluded index files
|
|
353
|
+
(feedback_dir / "index.json").write_text("{}")
|
|
354
|
+
(feedback_dir / "feedback-index.json").write_text("{}")
|
|
355
|
+
|
|
356
|
+
# Excluded register
|
|
357
|
+
(feedback_dir / "feedback-register.md").write_text("# Register")
|
|
358
|
+
|
|
359
|
+
# ONE valid feedback file that SHOULD be indexed
|
|
360
|
+
story_dir = feedback_dir / "ai-analysis" / "STORY-001"
|
|
361
|
+
story_dir.mkdir(parents=True, exist_ok=True)
|
|
362
|
+
with open(story_dir / "valid.json", "w") as f:
|
|
363
|
+
json.dump({"story_id": "STORY-001", "timestamp": "2026-01-01T00:00:00Z"}, f)
|
|
364
|
+
|
|
365
|
+
# Act
|
|
366
|
+
result = reindex_all_feedback(str(tmp_path))
|
|
367
|
+
|
|
368
|
+
# Assert
|
|
369
|
+
assert result == 0
|
|
370
|
+
index = _read_index(tmp_path)
|
|
371
|
+
assert len(index["feedback-sessions"]) == 1, (
|
|
372
|
+
f"Expected exactly 1 entry (valid.json only), "
|
|
373
|
+
f"got {len(index['feedback-sessions'])}"
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
# Verify no excluded file appears in any entry
|
|
377
|
+
all_excluded = excluded_docs + excluded_configs + [
|
|
378
|
+
"index.json", "feedback-index.json", "feedback-register.md"
|
|
379
|
+
]
|
|
380
|
+
for entry in index["feedback-sessions"]:
|
|
381
|
+
entry_filename = Path(entry["file-path"]).name
|
|
382
|
+
assert entry_filename not in all_excluded, (
|
|
383
|
+
f"Excluded file '{entry_filename}' found in index entries"
|
|
384
|
+
)
|