devforgeai 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/CLAUDE.md +120 -0
  2. package/bin/devforgeai.js +0 -0
  3. package/package.json +9 -1
  4. package/src/CLAUDE.md +699 -0
  5. package/src/claude/hooks/phase-completion-gate.sh +0 -0
  6. package/src/claude/scripts/README.md +396 -0
  7. package/src/claude/scripts/audit-command-skill-overlap.sh +67 -0
  8. package/src/claude/scripts/check-hooks-fast.sh +70 -0
  9. package/src/claude/scripts/devforgeai-validate +6 -0
  10. package/src/claude/scripts/devforgeai_cli/README.md +531 -0
  11. package/src/claude/scripts/devforgeai_cli/__init__.py +12 -0
  12. package/src/claude/scripts/devforgeai_cli/cli.py +716 -0
  13. package/src/claude/scripts/devforgeai_cli/commands/__init__.py +1 -0
  14. package/src/claude/scripts/devforgeai_cli/commands/check_hooks.py +384 -0
  15. package/src/claude/scripts/devforgeai_cli/commands/invoke_hooks.py +149 -0
  16. package/src/claude/scripts/devforgeai_cli/commands/phase_commands.py +731 -0
  17. package/src/claude/scripts/devforgeai_cli/commands/validate_installation.py +412 -0
  18. package/src/claude/scripts/devforgeai_cli/context_extraction.py +426 -0
  19. package/src/claude/scripts/devforgeai_cli/feedback/AC_TO_TEST_MAPPING.md +636 -0
  20. package/src/claude/scripts/devforgeai_cli/feedback/DELIVERY_SUMMARY.txt +329 -0
  21. package/src/claude/scripts/devforgeai_cli/feedback/README_TEST_SPECS.md +486 -0
  22. package/src/claude/scripts/devforgeai_cli/feedback/TEST_IMPLEMENTATION_GUIDE.md +529 -0
  23. package/src/claude/scripts/devforgeai_cli/feedback/TEST_SPECIFICATIONS.md +2652 -0
  24. package/src/claude/scripts/devforgeai_cli/feedback/TEST_SPECS_INDEX.md +398 -0
  25. package/src/claude/scripts/devforgeai_cli/feedback/__init__.py +34 -0
  26. package/src/claude/scripts/devforgeai_cli/feedback/adaptive_questioning_engine.py +581 -0
  27. package/src/claude/scripts/devforgeai_cli/feedback/aggregation.py +179 -0
  28. package/src/claude/scripts/devforgeai_cli/feedback/commands.py +535 -0
  29. package/src/claude/scripts/devforgeai_cli/feedback/config_defaults.py +58 -0
  30. package/src/claude/scripts/devforgeai_cli/feedback/config_manager.py +423 -0
  31. package/src/claude/scripts/devforgeai_cli/feedback/config_models.py +192 -0
  32. package/src/claude/scripts/devforgeai_cli/feedback/config_schema.py +140 -0
  33. package/src/claude/scripts/devforgeai_cli/feedback/coverage.json +1 -0
  34. package/src/claude/scripts/devforgeai_cli/feedback/feature_flag.py +152 -0
  35. package/src/claude/scripts/devforgeai_cli/feedback/feedback_indexer.py +394 -0
  36. package/src/claude/scripts/devforgeai_cli/feedback/hot_reload.py +226 -0
  37. package/src/claude/scripts/devforgeai_cli/feedback/longitudinal.py +115 -0
  38. package/src/claude/scripts/devforgeai_cli/feedback/models.py +67 -0
  39. package/src/claude/scripts/devforgeai_cli/feedback/question_router.py +236 -0
  40. package/src/claude/scripts/devforgeai_cli/feedback/retrospective.py +233 -0
  41. package/src/claude/scripts/devforgeai_cli/feedback/skip_tracker.py +177 -0
  42. package/src/claude/scripts/devforgeai_cli/feedback/skip_tracking.py +221 -0
  43. package/src/claude/scripts/devforgeai_cli/feedback/template_engine.py +549 -0
  44. package/src/claude/scripts/devforgeai_cli/feedback/validation.py +163 -0
  45. package/src/claude/scripts/devforgeai_cli/headless/__init__.py +30 -0
  46. package/src/claude/scripts/devforgeai_cli/headless/answer_models.py +206 -0
  47. package/src/claude/scripts/devforgeai_cli/headless/answer_resolver.py +204 -0
  48. package/src/claude/scripts/devforgeai_cli/headless/exceptions.py +36 -0
  49. package/src/claude/scripts/devforgeai_cli/headless/pattern_matcher.py +156 -0
  50. package/src/claude/scripts/devforgeai_cli/hooks.py +313 -0
  51. package/src/claude/scripts/devforgeai_cli/metrics/__init__.py +46 -0
  52. package/src/claude/scripts/devforgeai_cli/metrics/command_metrics.py +142 -0
  53. package/src/claude/scripts/devforgeai_cli/metrics/failure_modes.py +152 -0
  54. package/src/claude/scripts/devforgeai_cli/metrics/story_segmentation.py +181 -0
  55. package/src/claude/scripts/devforgeai_cli/orchestrate_hooks.py +780 -0
  56. package/src/claude/scripts/devforgeai_cli/phase_state.py +1229 -0
  57. package/src/claude/scripts/devforgeai_cli/session/__init__.py +30 -0
  58. package/src/claude/scripts/devforgeai_cli/session/checkpoint.py +268 -0
  59. package/src/claude/scripts/devforgeai_cli/tests/__init__.py +1 -0
  60. package/src/claude/scripts/devforgeai_cli/tests/conftest.py +29 -0
  61. package/src/claude/scripts/devforgeai_cli/tests/feedback/TEST_EXECUTION_GUIDE.md +298 -0
  62. package/src/claude/scripts/devforgeai_cli/tests/feedback/__init__.py +3 -0
  63. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_adaptive_questioning_engine.py +2171 -0
  64. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_aggregation.py +476 -0
  65. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_defaults.py +133 -0
  66. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_manager.py +592 -0
  67. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_models.py +373 -0
  68. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_config_schema.py +130 -0
  69. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_configuration_management.py +1355 -0
  70. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_edge_cases.py +308 -0
  71. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_feature_flag.py +307 -0
  72. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_feedback_indexer.py +384 -0
  73. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_hot_reload.py +580 -0
  74. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_integration.py +402 -0
  75. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_models.py +105 -0
  76. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_question_routing.py +262 -0
  77. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_retrospective.py +333 -0
  78. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracker.py +410 -0
  79. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracking.py +159 -0
  80. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_skip_tracking_integration.py +1155 -0
  81. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_template_engine.py +1389 -0
  82. package/src/claude/scripts/devforgeai_cli/tests/feedback/test_validation_comprehensive.py +210 -0
  83. package/src/claude/scripts/devforgeai_cli/tests/fixtures/autonomous-deferral-story.md +46 -0
  84. package/src/claude/scripts/devforgeai_cli/tests/fixtures/missing-impl-notes.md +31 -0
  85. package/src/claude/scripts/devforgeai_cli/tests/fixtures/valid-deferral-story.md +46 -0
  86. package/src/claude/scripts/devforgeai_cli/tests/fixtures/valid-story-complete.md +48 -0
  87. package/src/claude/scripts/devforgeai_cli/tests/manual_test_invoke_hooks.sh +200 -0
  88. package/src/claude/scripts/devforgeai_cli/tests/session/DELIVERABLES.md +518 -0
  89. package/src/claude/scripts/devforgeai_cli/tests/session/TEST_SUMMARY.md +468 -0
  90. package/src/claude/scripts/devforgeai_cli/tests/session/__init__.py +6 -0
  91. package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/corrupted-checkpoint.json +1 -0
  92. package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/missing-fields-checkpoint.json +4 -0
  93. package/src/claude/scripts/devforgeai_cli/tests/session/fixtures/valid-checkpoint.json +15 -0
  94. package/src/claude/scripts/devforgeai_cli/tests/session/test_checkpoint.py +851 -0
  95. package/src/claude/scripts/devforgeai_cli/tests/test_check_hooks.py +1886 -0
  96. package/src/claude/scripts/devforgeai_cli/tests/test_depends_on_normalizer.py +171 -0
  97. package/src/claude/scripts/devforgeai_cli/tests/test_dod_validator.py +97 -0
  98. package/src/claude/scripts/devforgeai_cli/tests/test_invoke_hooks.py +1902 -0
  99. package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands.py +320 -0
  100. package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands_error_handling.py +1021 -0
  101. package/src/claude/scripts/devforgeai_cli/tests/test_phase_commands_import.py +697 -0
  102. package/src/claude/scripts/devforgeai_cli/tests/test_phase_state.py +2187 -0
  103. package/src/claude/scripts/devforgeai_cli/tests/test_skip_tracking.py +2141 -0
  104. package/src/claude/scripts/devforgeai_cli/tests/test_skip_tracking_coverage_gap.py +195 -0
  105. package/src/claude/scripts/devforgeai_cli/tests/test_subagent_enforcement.py +539 -0
  106. package/src/claude/scripts/devforgeai_cli/tests/test_validate_installation.py +361 -0
  107. package/src/claude/scripts/devforgeai_cli/utils/__init__.py +11 -0
  108. package/src/claude/scripts/devforgeai_cli/utils/depends_on_normalizer.py +149 -0
  109. package/src/claude/scripts/devforgeai_cli/utils/markdown_parser.py +219 -0
  110. package/src/claude/scripts/devforgeai_cli/utils/story_analyzer.py +249 -0
  111. package/src/claude/scripts/devforgeai_cli/utils/yaml_parser.py +152 -0
  112. package/src/claude/scripts/devforgeai_cli/validators/__init__.py +27 -0
  113. package/src/claude/scripts/devforgeai_cli/validators/ast_grep_validator.py +373 -0
  114. package/src/claude/scripts/devforgeai_cli/validators/context_validator.py +180 -0
  115. package/src/claude/scripts/devforgeai_cli/validators/dod_validator.py +309 -0
  116. package/src/claude/scripts/devforgeai_cli/validators/git_validator.py +107 -0
  117. package/src/claude/scripts/devforgeai_cli/validators/grep_fallback.py +300 -0
  118. package/src/claude/scripts/install_hooks.sh +186 -0
  119. package/src/claude/scripts/invoke_feedback_hooks.sh +59 -0
  120. package/src/claude/scripts/migrate-ac-headers.sh +122 -0
  121. package/src/claude/scripts/plan_file_kb.sh +704 -0
  122. package/src/claude/scripts/requirements.txt +8 -0
  123. package/src/claude/scripts/session_catalog.sh +543 -0
  124. package/src/claude/scripts/setup.py +55 -0
  125. package/src/claude/scripts/start-devforgeai.sh +16 -0
  126. package/src/claude/scripts/statusline.sh +27 -0
  127. package/src/claude/scripts/validate_deferrals.py +344 -0
  128. package/src/claude/skills/designing-systems/scripts/__pycache__/detect_anti_patterns.cpython-312.pyc +0 -0
  129. package/src/claude/skills/designing-systems/scripts/__pycache__/validate_all_context.cpython-312.pyc +0 -0
  130. package/src/claude/skills/designing-systems/scripts/__pycache__/validate_architecture.cpython-312.pyc +0 -0
  131. package/src/claude/skills/designing-systems/scripts/__pycache__/validate_dependencies.cpython-312.pyc +0 -0
  132. package/src/claude/skills/devforgeai-story-creation/scripts/__pycache__/migrate_story_v1_to_v2.cpython-312.pyc +0 -0
  133. package/src/claude/skills/devforgeai-story-creation/scripts/tests/__pycache__/measure_accuracy.cpython-312.pyc +0 -0
@@ -0,0 +1,8 @@
1
+ # DevForgeAI CLI Requirements
2
+
3
+ # Core dependencies
4
+ PyYAML>=6.0
5
+
6
+ # Development dependencies (optional)
7
+ pytest>=7.0 # For running tests
8
+ pytest-cov>=4.0 # For coverage reports
@@ -0,0 +1,543 @@
1
+ #!/bin/bash
2
+ #
3
+ # Session Catalog Functions for STORY-223
4
+ # Provides session file cataloging, dependency graph building, and session chain tracking
5
+ #
6
+ # Usage: source .claude/scripts/session_catalog.sh
7
+ #
8
+ # Functions:
9
+ # catalog_session_files(directory) - Map plans to stories to artifacts (AC#1)
10
+ # build_dependency_graph(directory) - Build file dependency graph (AC#2)
11
+ # track_session_chains(directory) - Track session continuity chains (AC#3)
12
+ #
13
+ # Refactored: Extracted helper functions, reduced complexity, improved performance
14
+ #
15
+
16
+ # ============================================================================
17
+ # Helper Functions (DRY - extracted repeated patterns)
18
+ # ============================================================================
19
+
20
+ # Find files matching a pattern and store in array variable
21
+ # Usage: find_files_into_array "/path" "*.md" result_array
22
+ find_files_into_array() {
23
+ local directory="$1"
24
+ local pattern="$2"
25
+ local -n _result_array="$3"
26
+ _result_array=()
27
+ while IFS= read -r -d '' file; do
28
+ _result_array+=("$file")
29
+ done < <(find "$directory" -name "$pattern" -type f -print0 2>/dev/null)
30
+ }
31
+
32
+ # Classify file type based on path
33
+ # Usage: file_type=$(classify_file_type "/path/to/file")
34
+ classify_file_type() {
35
+ local file="$1"
36
+ case "$file" in
37
+ */plans/*) echo "plan" ;;
38
+ */sessions/*) echo "session" ;;
39
+ */artifacts/*) echo "artifact" ;;
40
+ */Stories/*|*/stories/*) echo "story" ;;
41
+ *) echo "other" ;;
42
+ esac
43
+ }
44
+
45
+ # Build JSON array from bash array
46
+ # Usage: json=$(build_json_array "${items[@]}")
47
+ build_json_array() {
48
+ local items=("$@")
49
+ local result=""
50
+ for item in "${items[@]}"; do
51
+ # Escape special JSON characters
52
+ local escaped="${item//\\/\\\\}"
53
+ escaped="${escaped//\"/\\\"}"
54
+ [[ -n "$result" ]] && result+=","
55
+ result+="\"$escaped\""
56
+ done
57
+ echo "[$result]"
58
+ }
59
+
60
+ # ============================================================================
61
+ # AC#1: Catalog Session Files
62
+ # Maps plan files -> story references -> associated artifacts
63
+ # ============================================================================
64
+
65
+ # Extract story references from plan file YAML frontmatter
66
+ # Usage: extract_story_refs "/path/to/plan.md" result_array
67
+ _extract_story_refs() {
68
+ local plan_file="$1"
69
+ local -n _refs="$2"
70
+ _refs=()
71
+ while IFS= read -r line; do
72
+ if [[ "$line" =~ ^[[:space:]]*-[[:space:]]*(STORY-[0-9]+) ]]; then
73
+ _refs+=("${BASH_REMATCH[1]}")
74
+ fi
75
+ done < <(sed -n '/^related_stories:/,/^[^[:space:]-]/p' "$plan_file" 2>/dev/null)
76
+ }
77
+
78
+ # Process plan files and populate mappings
79
+ # Uses associative arrays for O(1) lookups, then converts to JSON at end
80
+ _process_plan_files() {
81
+ local directory="$1"
82
+ local -n _p2s_map="$2"
83
+ local -n _s2p_map="$3"
84
+ local -n _files_json="$4"
85
+
86
+ local plan_files=()
87
+ while IFS= read -r -d '' file; do
88
+ plan_files+=("$file")
89
+ done < <(find "$directory/plans" -name "*.md" -type f -print0 2>/dev/null)
90
+
91
+ for plan_file in "${plan_files[@]}"; do
92
+ local plan_id
93
+ plan_id=$(basename "$plan_file" .md)
94
+
95
+ local story_refs=()
96
+ _extract_story_refs "$plan_file" story_refs
97
+
98
+ if [[ ${#story_refs[@]} -gt 0 ]]; then
99
+ # Store as comma-separated for later conversion
100
+ _p2s_map["$plan_id"]="${story_refs[*]}"
101
+
102
+ # Build reverse mapping
103
+ for story in "${story_refs[@]}"; do
104
+ if [[ -n "${_s2p_map[$story]:-}" ]]; then
105
+ _s2p_map["$story"]+=" $plan_id"
106
+ else
107
+ _s2p_map["$story"]="$plan_id"
108
+ fi
109
+ done
110
+ fi
111
+
112
+ # Collect file entry
113
+ _files_json+=("{\"path\":\"$plan_file\",\"type\":\"plan\"}")
114
+ done
115
+ }
116
+
117
+ # Process artifact directories and populate mappings
118
+ _process_artifacts() {
119
+ local directory="$1"
120
+ local -n _s2a_map="$2"
121
+ local -n _files_json="$3"
122
+
123
+ local artifact_dirs=()
124
+ while IFS= read -r -d '' dir; do
125
+ artifact_dirs+=("$dir")
126
+ done < <(find "$directory/artifacts" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null)
127
+
128
+ for artifact_dir in "${artifact_dirs[@]}"; do
129
+ local story_id
130
+ story_id=$(basename "$artifact_dir")
131
+ local artifact_names=()
132
+
133
+ while IFS= read -r -d '' file; do
134
+ local filename
135
+ filename=$(basename "$file")
136
+ artifact_names+=("$filename")
137
+ _files_json+=("{\"path\":\"$file\",\"type\":\"artifact\"}")
138
+ done < <(find "$artifact_dir" -type f -print0 2>/dev/null)
139
+
140
+ if [[ ${#artifact_names[@]} -gt 0 ]]; then
141
+ _s2a_map["$story_id"]="${artifact_names[*]}"
142
+ fi
143
+ done
144
+ }
145
+
146
+ # Convert associative array to JSON object with array values
147
+ # Usage: _assoc_to_json_object assoc_array -> {"key":["val1","val2"]}
148
+ _assoc_to_json_object() {
149
+ local -n _assoc="$1"
150
+ local result="{"
151
+ local first=true
152
+
153
+ for key in "${!_assoc[@]}"; do
154
+ [[ "$first" == "true" ]] || result+=","
155
+ first=false
156
+
157
+ # Convert space-separated values to JSON array
158
+ local vals="${_assoc[$key]}"
159
+ local arr_json=""
160
+ for val in $vals; do
161
+ [[ -n "$arr_json" ]] && arr_json+=","
162
+ arr_json+="\"$val\""
163
+ done
164
+ result+="\"$key\":[$arr_json]"
165
+ done
166
+
167
+ echo "$result}"
168
+ }
169
+
170
+ catalog_session_files() {
171
+ local directory="${1:-.}"
172
+
173
+ # Use associative arrays for O(1) lookups during processing
174
+ declare -A plan_to_story_map
175
+ declare -A story_to_artifacts_map
176
+ declare -A story_to_plans_map
177
+ local files_entries=()
178
+
179
+ # Process all plan files
180
+ _process_plan_files "$directory" plan_to_story_map story_to_plans_map files_entries
181
+
182
+ # Process all artifacts
183
+ _process_artifacts "$directory" story_to_artifacts_map files_entries
184
+
185
+ # Process session files
186
+ while IFS= read -r -d '' file; do
187
+ files_entries+=("{\"path\":\"$file\",\"type\":\"session\"}")
188
+ done < <(find "$directory/sessions" -name "*.json" -type f -print0 2>/dev/null)
189
+
190
+ # Convert to JSON (single jq call for final assembly)
191
+ local p2s_json s2a_json s2p_json files_json
192
+ p2s_json=$(_assoc_to_json_object plan_to_story_map)
193
+ s2a_json=$(_assoc_to_json_object story_to_artifacts_map)
194
+ s2p_json=$(_assoc_to_json_object story_to_plans_map)
195
+
196
+ # Build files array
197
+ if [[ ${#files_entries[@]} -eq 0 ]]; then
198
+ files_json="[]"
199
+ else
200
+ files_json="[$(IFS=,; echo "${files_entries[*]}")]"
201
+ fi
202
+
203
+ # Single jq call to assemble final output (performance optimization)
204
+ jq -n -c \
205
+ --argjson plan_to_story "$p2s_json" \
206
+ --argjson story_to_artifacts "$s2a_json" \
207
+ --argjson story_to_plans "$s2p_json" \
208
+ --argjson files "$files_json" \
209
+ '{
210
+ "plan_to_story_map": $plan_to_story,
211
+ "story_to_artifacts_map": $story_to_artifacts,
212
+ "story_to_plans_map": $story_to_plans,
213
+ "files": $files
214
+ }'
215
+ }
216
+
217
+ # ============================================================================
218
+ # AC#2: Build Dependency Graph
219
+ # Analyzes file references and builds dependency graph
220
+ # Performance: O(n*m) where n=files, m=average file size for grep
221
+ # Optimized: Uses associative arrays for O(1) lookups in cycle detection
222
+ # ============================================================================
223
+
224
+ # Classify dependency type based on source and target paths
225
+ # Usage: dep_type=$(_classify_dependency_type "/source/path" "/target/path")
226
+ _classify_dependency_type() {
227
+ local source_path="$1"
228
+ local target_path="$2"
229
+
230
+ if [[ "$source_path" == */plans/* && "$target_path" == */Stories/* ]]; then
231
+ echo "plan_to_story"
232
+ elif [[ "$source_path" == */sessions/* && "$target_path" == */sessions/* ]]; then
233
+ echo "session_chain"
234
+ else
235
+ echo "reference"
236
+ fi
237
+ }
238
+
239
+ # Build nodes map from file list (batch operation)
240
+ # Usage: nodes_json=$(_build_nodes_map "${files[@]}")
241
+ _build_nodes_map() {
242
+ local files=("$@")
243
+ local result="{"
244
+ local first=true
245
+
246
+ for file in "${files[@]}"; do
247
+ local file_id file_type
248
+ file_id=$(basename "$file")
249
+ file_type=$(classify_file_type "$file")
250
+
251
+ [[ "$first" == "true" ]] || result+=","
252
+ first=false
253
+ result+="\"$file_id\":{\"path\":\"$file\",\"type\":\"$file_type\"}"
254
+ done
255
+
256
+ echo "$result}"
257
+ }
258
+
259
+ # Find file references using optimized grep (single pass per file)
260
+ # Populates associative array with source|target pairs
261
+ _find_file_references() {
262
+ local -n _dep_entries="$1"
263
+ shift
264
+ local files=("$@")
265
+
266
+ # Build lookup sets for O(1) target matching
267
+ declare -A file_ids
268
+ declare -A file_basenames
269
+ declare -A file_paths
270
+ for file in "${files[@]}"; do
271
+ local fid fbase
272
+ fid=$(basename "$file")
273
+ fbase="${fid%.*}"
274
+ file_ids["$fid"]=1
275
+ file_basenames["$fbase"]="$fid"
276
+ file_paths["$fid"]="$file"
277
+ done
278
+
279
+ # Process each source file
280
+ for source_file in "${files[@]}"; do
281
+ local source_id
282
+ source_id=$(basename "$source_file")
283
+
284
+ # Single grep to get all potential references
285
+ local file_content
286
+ file_content=$(cat "$source_file" 2>/dev/null) || continue
287
+
288
+ # Check each potential target
289
+ for target_id in "${!file_ids[@]}"; do
290
+ [[ "$source_id" == "$target_id" ]] && continue
291
+
292
+ local target_basename="${target_id%.*}"
293
+
294
+ # Check if file content references target
295
+ if [[ "$file_content" == *"$target_id"* ]] || [[ "$file_content" == *"$target_basename"* ]]; then
296
+ local target_file="${file_paths[$target_id]}"
297
+ local dep_type
298
+ dep_type=$(_classify_dependency_type "$source_file" "$target_file")
299
+ _dep_entries+=("{\"source\":\"$source_id\",\"target\":\"$target_id\",\"type\":\"$dep_type\"}")
300
+ fi
301
+ done
302
+ done
303
+ }
304
+
305
+ # Detect circular dependencies using associative array for O(n) performance
306
+ # Usage: circular_json=$(_detect_circular_deps "${dep_entries[@]}")
307
+ _detect_circular_deps() {
308
+ local dep_entries=("$@")
309
+
310
+ # Build edge set for O(1) reverse lookup
311
+ declare -A edge_set
312
+ declare -A seen_cycles
313
+ local circular_entries=()
314
+
315
+ for entry in "${dep_entries[@]}"; do
316
+ # Extract source and target from JSON entry
317
+ local source target
318
+ source=$(echo "$entry" | grep -o '"source":"[^"]*"' | cut -d'"' -f4)
319
+ target=$(echo "$entry" | grep -o '"target":"[^"]*"' | cut -d'"' -f4)
320
+ edge_set["$source|$target"]=1
321
+ done
322
+
323
+ # Check for reverse edges (cycles)
324
+ for edge in "${!edge_set[@]}"; do
325
+ IFS='|' read -r source target <<< "$edge"
326
+ local reverse_edge="$target|$source"
327
+
328
+ if [[ -n "${edge_set[$reverse_edge]:-}" ]]; then
329
+ # Normalize cycle representation (alphabetical order)
330
+ local cycle_key
331
+ if [[ "$source" < "$target" ]]; then
332
+ cycle_key="$source|$target"
333
+ else
334
+ cycle_key="$target|$source"
335
+ fi
336
+
337
+ # Only add if not already seen
338
+ if [[ -z "${seen_cycles[$cycle_key]:-}" ]]; then
339
+ seen_cycles["$cycle_key"]=1
340
+ if [[ "$source" < "$target" ]]; then
341
+ circular_entries+=("[\"$source\",\"$target\"]")
342
+ else
343
+ circular_entries+=("[\"$target\",\"$source\"]")
344
+ fi
345
+ fi
346
+ fi
347
+ done
348
+
349
+ # Build JSON array
350
+ if [[ ${#circular_entries[@]} -eq 0 ]]; then
351
+ echo "[]"
352
+ else
353
+ echo "[$(IFS=,; echo "${circular_entries[*]}")]"
354
+ fi
355
+ }
356
+
357
+ build_dependency_graph() {
358
+ local directory="${1:-.}"
359
+
360
+ # Find all relevant files
361
+ local all_files=()
362
+ while IFS= read -r -d '' file; do
363
+ all_files+=("$file")
364
+ done < <(find "$directory" \( -name "*.md" -o -name "*.json" -o -name "*.sh" \) -type f -print0 2>/dev/null)
365
+
366
+ # Build nodes map (batch operation - no jq calls in loop)
367
+ local nodes_json
368
+ nodes_json=$(_build_nodes_map "${all_files[@]}")
369
+
370
+ # Find all dependencies
371
+ local dep_entries=()
372
+ _find_file_references dep_entries "${all_files[@]}"
373
+
374
+ # Detect circular dependencies (O(n) with hash lookup)
375
+ local circular_json
376
+ circular_json=$(_detect_circular_deps "${dep_entries[@]}")
377
+
378
+ # Build dependencies JSON array
379
+ local deps_json
380
+ if [[ ${#dep_entries[@]} -eq 0 ]]; then
381
+ deps_json="[]"
382
+ else
383
+ deps_json="[$(IFS=,; echo "${dep_entries[*]}")]"
384
+ fi
385
+
386
+ # Single jq call for final assembly
387
+ jq -n -c \
388
+ --argjson deps "$deps_json" \
389
+ --argjson nodes "$nodes_json" \
390
+ --argjson circular "$circular_json" \
391
+ '{
392
+ "dependencies": $deps,
393
+ "nodes": $nodes,
394
+ "circular_dependencies": $circular
395
+ }'
396
+ }
397
+
398
+ # ============================================================================
399
+ # AC#3: Track Session Chains
400
+ # Identifies parent-child relationships via parentUuid
401
+ # Optimized: Reduced jq calls, uses pure bash for chain building
402
+ # ============================================================================
403
+
404
+ # Extract uuid and parentUuid from session file (single jq call)
405
+ # Usage: read uuid parent_uuid < <(_extract_session_uuids "$file")
406
+ _extract_session_uuids() {
407
+ local file="$1"
408
+ jq -r '[.uuid // "", .parentUuid // ""] | @tsv' "$file" 2>/dev/null
409
+ }
410
+
411
+ # Build session chain using BFS (pure bash, no jq in loop)
412
+ # Usage: chain_json=$(_build_session_chain "$root_uuid" children_map visited_set)
413
+ _build_session_chain() {
414
+ local root_uuid="$1"
415
+ local -n _children="$2"
416
+ local -n _visited="$3"
417
+
418
+ local depth=0
419
+ local node_list=("$root_uuid")
420
+ local queue=("$root_uuid")
421
+ _visited["$root_uuid"]=1
422
+
423
+ while [[ ${#queue[@]} -gt 0 ]]; do
424
+ local current="${queue[0]}"
425
+ queue=("${queue[@]:1}")
426
+
427
+ local children="${_children[$current]:-}"
428
+ for child in $children; do
429
+ if [[ -z "${_visited[$child]:-}" ]]; then
430
+ _visited["$child"]=1
431
+ queue+=("$child")
432
+ node_list+=("$child")
433
+ ((depth++))
434
+ fi
435
+ done
436
+ done
437
+
438
+ # Build JSON without jq
439
+ local nodes_json
440
+ nodes_json=$(build_json_array "${node_list[@]}")
441
+ echo "{\"root\":\"$root_uuid\",\"nodes\":$nodes_json,\"depth\":$depth}"
442
+ }
443
+
444
+ track_session_chains() {
445
+ local session_directory="${1:-.}"
446
+
447
+ # Find all session files
448
+ local session_files=()
449
+ while IFS= read -r -d '' file; do
450
+ session_files+=("$file")
451
+ done < <(find "$session_directory" -name "*.json" -type f -print0 2>/dev/null)
452
+
453
+ # Build session maps: uuid -> file, uuid -> parent, parent -> children
454
+ declare -A session_map
455
+ declare -A parent_map
456
+ declare -A children_map
457
+
458
+ for file in "${session_files[@]}"; do
459
+ local uuid parent_uuid
460
+ read -r uuid parent_uuid < <(_extract_session_uuids "$file")
461
+
462
+ [[ -z "$uuid" ]] && continue
463
+
464
+ session_map["$uuid"]="$file"
465
+
466
+ if [[ -n "$parent_uuid" ]]; then
467
+ parent_map["$uuid"]="$parent_uuid"
468
+ children_map["$parent_uuid"]+="$uuid "
469
+ fi
470
+ done
471
+
472
+ # Categorize sessions: roots (no parent) and orphans (missing parent)
473
+ local root_uuids=()
474
+ local orphan_uuids=()
475
+
476
+ for uuid in "${!session_map[@]}"; do
477
+ local parent="${parent_map[$uuid]:-}"
478
+
479
+ if [[ -z "$parent" ]]; then
480
+ root_uuids+=("$uuid")
481
+ elif [[ -z "${session_map[$parent]:-}" ]]; then
482
+ orphan_uuids+=("$uuid")
483
+ fi
484
+ done
485
+
486
+ # Build chains from each root (pure bash)
487
+ local chain_entries=()
488
+ for root in "${root_uuids[@]}"; do
489
+ declare -A visited
490
+ local chain_json
491
+ chain_json=$(_build_session_chain "$root" children_map visited)
492
+ chain_entries+=("$chain_json")
493
+ unset visited
494
+ done
495
+
496
+ # Build output JSON arrays without intermediate jq calls
497
+ local roots_json orphans_json chains_json
498
+ roots_json=$(build_json_array "${root_uuids[@]}")
499
+ orphans_json=$(build_json_array "${orphan_uuids[@]}")
500
+
501
+ if [[ ${#chain_entries[@]} -eq 0 ]]; then
502
+ chains_json="[]"
503
+ else
504
+ chains_json="[$(IFS=,; echo "${chain_entries[*]}")]"
505
+ fi
506
+
507
+ # Single jq call for final assembly
508
+ jq -n -c \
509
+ --argjson chains "$chains_json" \
510
+ --argjson roots "$roots_json" \
511
+ --argjson orphans "$orphans_json" \
512
+ '{
513
+ "session_chains": $chains,
514
+ "root_sessions": $roots,
515
+ "orphan_sessions": $orphans
516
+ }'
517
+ }
518
+
519
+ # ============================================================================
520
+ # Export functions
521
+ # ============================================================================
522
+
523
+ # Primary API functions
524
+ export -f catalog_session_files
525
+ export -f build_dependency_graph
526
+ export -f track_session_chains
527
+
528
+ # Helper functions (also exported for testing/reuse)
529
+ export -f find_files_into_array
530
+ export -f classify_file_type
531
+ export -f build_json_array
532
+
533
+ # Internal helper functions (exported for subshell compatibility)
534
+ export -f _extract_story_refs
535
+ export -f _process_plan_files
536
+ export -f _process_artifacts
537
+ export -f _assoc_to_json_object
538
+ export -f _classify_dependency_type
539
+ export -f _build_nodes_map
540
+ export -f _find_file_references
541
+ export -f _detect_circular_deps
542
+ export -f _extract_session_uuids
543
+ export -f _build_session_chain
@@ -0,0 +1,55 @@
1
+ """
2
+ DevForgeAI CLI Setup
3
+
4
+ Installation:
5
+ pip install -e .claude/scripts/
6
+
7
+ Usage:
8
+ devforgeai validate-dod devforgeai/specs/Stories/STORY-001.story.md
9
+ devforgeai check-git
10
+ devforgeai validate-context
11
+ """
12
+
13
+ from setuptools import setup, find_packages
14
+ from pathlib import Path
15
+
16
+ # Read version from __init__.py
17
+ init_file = Path(__file__).parent / 'devforgeai_cli' / '__init__.py'
18
+ version = '0.1.0'
19
+ for line in init_file.read_text().splitlines():
20
+ if line.startswith('__version__'):
21
+ version = line.split('=')[1].strip(' \'"')
22
+ break
23
+
24
+ setup(
25
+ name='devforgeai-cli',
26
+ version=version,
27
+ description='DevForgeAI workflow validators for spec-driven development',
28
+ long_description='Automated validation tools that prevent autonomous deferrals, '
29
+ 'validate Git availability, and enforce workflow quality gates.',
30
+ author='DevForgeAI Contributors',
31
+ python_requires='>=3.8',
32
+ packages=find_packages(),
33
+ install_requires=[
34
+ 'PyYAML>=6.0',
35
+ ],
36
+ entry_points={
37
+ 'console_scripts': [
38
+ # Renamed from 'devforgeai' to avoid conflict with npm global CLI
39
+ # The npm CLI (devforgeai) is user-facing for installation
40
+ # This Python CLI is internal for framework validation
41
+ 'devforgeai-validate=devforgeai_cli.cli:main',
42
+ ],
43
+ },
44
+ classifiers=[
45
+ 'Development Status :: 3 - Alpha',
46
+ 'Intended Audience :: Developers',
47
+ 'Topic :: Software Development :: Quality Assurance',
48
+ 'Programming Language :: Python :: 3',
49
+ 'Programming Language :: Python :: 3.8',
50
+ 'Programming Language :: Python :: 3.9',
51
+ 'Programming Language :: Python :: 3.10',
52
+ 'Programming Language :: Python :: 3.11',
53
+ 'Programming Language :: Python :: 3.12',
54
+ ],
55
+ )
@@ -0,0 +1,16 @@
1
+ #!/bin/bash
2
+ # DevForgeAI Claude Launcher
3
+ # Usage: ./start-devforgeai.sh [claude args...]
4
+ #
5
+ # Appends DevForgeAI core directives to Claude's system prompt
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
+ SYSTEM_PROMPT_FILE="$SCRIPT_DIR/../system-prompt-core.md"
9
+
10
+ if [ ! -f "$SYSTEM_PROMPT_FILE" ]; then
11
+ echo "Error: system-prompt-core.md not found at $SYSTEM_PROMPT_FILE"
12
+ exit 1
13
+ fi
14
+
15
+ # Launch Claude with appended system prompt
16
+ claude --append-system-prompt "$(cat "$SYSTEM_PROMPT_FILE")" "$@"
@@ -0,0 +1,27 @@
1
+ #!/bin/bash
2
+ input=$(cat)
3
+
4
+ MODEL=$(echo "$input" | jq -r '.model.display_name')
5
+ DIR=$(echo "$input" | jq -r '.workspace.current_dir')
6
+ COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
7
+ PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
8
+ DURATION_MS=$(echo "$input" | jq -r '.cost.total_duration_ms // 0')
9
+
10
+ CYAN='\033[36m'; GREEN='\033[32m'; YELLOW='\033[33m'; RED='\033[31m'; RESET='\033[0m'
11
+
12
+ # Pick bar color based on context usage
13
+ if [ "$PCT" -ge 90 ]; then BAR_COLOR="$RED"
14
+ elif [ "$PCT" -ge 70 ]; then BAR_COLOR="$YELLOW"
15
+ else BAR_COLOR="$GREEN"; fi
16
+
17
+ FILLED=$((PCT / 10)); EMPTY=$((10 - FILLED))
18
+ BAR=$(printf "%${FILLED}s" | tr ' ' '█')$(printf "%${EMPTY}s" | tr ' ' '░')
19
+
20
+ MINS=$((DURATION_MS / 60000)); SECS=$(((DURATION_MS % 60000) / 1000))
21
+
22
+ BRANCH=""
23
+ git rev-parse --git-dir > /dev/null 2>&1 && BRANCH=" | 🌿 $(git branch --show-current 2>/dev/null)"
24
+
25
+ echo -e "${CYAN}[$MODEL]${RESET} 📁 ${DIR##*/}$BRANCH"
26
+ COST_FMT=$(printf '$%.2f' "$COST")
27
+ echo -e "${BAR_COLOR}${BAR}${RESET} ${PCT}% | ${YELLOW}${COST_FMT}${RESET} | ⏱️ ${MINS}m ${SECS}s"