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,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
+ )