devforgeai 1.0.5 → 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/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,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"
|