@the-bearded-bear/claude-craft 8.26.0 → 8.27.0
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/Dev/i18n/base/Common/commands/aliases.md +48 -48
- package/Dev/i18n/base/Common/skills/sub-agents-patterns/SKILL.md +1 -0
- package/Dev/i18n/de/Common/agents/devops-engineer.md +19 -14
- package/Dev/i18n/de/Common/agents/migration-specialist.md +10 -15
- package/Dev/i18n/de/Common/agents/mlops-engineer.md +19 -15
- package/Dev/i18n/de/Common/agents/ralph-conductor.md +4 -15
- package/Dev/i18n/de/Common/agents/refactoring-specialist.md +0 -9
- package/Dev/i18n/de/Common/agents/tdd-coach.md +0 -9
- package/Dev/i18n/de/Common/agents/uiux-orchestrator.md +0 -9
- package/Dev/i18n/de/Common/commands/ralph-run.md +52 -0
- package/Dev/i18n/de/Common/commands/research-context7.md +5 -253
- package/Dev/i18n/de/Common/rules/12-context-management.md +10 -8
- package/Dev/i18n/de/Common/templates/settings.json.template +4 -0
- package/Dev/i18n/de/Python/skills/coding-standards/SKILL.md +1 -1
- package/Dev/i18n/de/Python/skills/testing-python/SKILL.md +1 -1
- package/Dev/i18n/de/Python/skills/tooling/SKILL.md +1 -1
- package/Dev/i18n/de/Symfony/commands/check-security.md +20 -20
- package/Dev/i18n/de/Symfony/skills/aggregates/SKILL.md +1 -1
- package/Dev/i18n/de/Symfony/skills/async/SKILL.md +1 -1
- package/Dev/i18n/de/Symfony/skills/cqrs/SKILL.md +1 -1
- package/Dev/i18n/de/Symfony/skills/domain-events/SKILL.md +1 -1
- package/Dev/i18n/de/Symfony/skills/i18n/SKILL.md +1 -1
- package/Dev/i18n/de/Symfony/skills/multitenant/SKILL.md +1 -1
- package/Dev/i18n/de/Symfony/skills/value-objects/SKILL.md +1 -1
- package/Dev/i18n/de/Team/commands/audit.md +29 -4
- package/Dev/i18n/de/Team/commands/delivery.md +29 -4
- package/Dev/i18n/de/Team/commands/security.md +31 -4
- package/Dev/i18n/de/Team/commands/sprint.md +30 -5
- package/Dev/i18n/de/UIUX/commands/audit.md +1 -30
- package/Dev/i18n/de/UIUX/commands/component-spec.md +1 -5
- package/Dev/i18n/de/UIUX/commands/orchestrator.md +5 -98
- package/Dev/i18n/de/Workflow/commands/auto-sprint.md +7 -4
- package/Dev/i18n/de/Workflow/commands/plan.md +1 -2
- package/Dev/i18n/en/Common/agents/devops-engineer.md +19 -14
- package/Dev/i18n/en/Common/agents/migration-specialist.md +10 -15
- package/Dev/i18n/en/Common/agents/mlops-engineer.md +19 -15
- package/Dev/i18n/en/Common/agents/ralph-conductor.md +4 -15
- package/Dev/i18n/en/Common/agents/refactoring-specialist.md +0 -9
- package/Dev/i18n/en/Common/agents/tdd-coach.md +0 -9
- package/Dev/i18n/en/Common/agents/uiux-orchestrator.md +0 -9
- package/Dev/i18n/en/Common/commands/ralph-run.md +52 -0
- package/Dev/i18n/en/Common/commands/research-context7.md +5 -253
- package/Dev/i18n/en/Common/rules/12-context-management.md +12 -10
- package/Dev/i18n/en/Common/templates/settings.json.template +4 -0
- package/Dev/i18n/en/Python/skills/coding-standards/SKILL.md +1 -1
- package/Dev/i18n/en/Python/skills/testing-python/SKILL.md +1 -1
- package/Dev/i18n/en/Python/skills/tooling/SKILL.md +1 -1
- package/Dev/i18n/en/Symfony/commands/check-security.md +20 -20
- package/Dev/i18n/en/Symfony/skills/aggregates/SKILL.md +1 -1
- package/Dev/i18n/en/Symfony/skills/async/SKILL.md +1 -1
- package/Dev/i18n/en/Symfony/skills/cqrs/SKILL.md +1 -1
- package/Dev/i18n/en/Symfony/skills/domain-events/SKILL.md +1 -1
- package/Dev/i18n/en/Symfony/skills/i18n/SKILL.md +1 -1
- package/Dev/i18n/en/Symfony/skills/multitenant/SKILL.md +1 -1
- package/Dev/i18n/en/Symfony/skills/value-objects/SKILL.md +1 -1
- package/Dev/i18n/en/Team/commands/audit.md +29 -4
- package/Dev/i18n/en/Team/commands/delivery.md +29 -4
- package/Dev/i18n/en/Team/commands/security.md +31 -4
- package/Dev/i18n/en/Team/commands/sprint.md +30 -5
- package/Dev/i18n/en/UIUX/commands/audit.md +1 -30
- package/Dev/i18n/en/UIUX/commands/component-spec.md +1 -5
- package/Dev/i18n/en/UIUX/commands/orchestrator.md +5 -98
- package/Dev/i18n/en/Workflow/commands/auto-sprint.md +7 -4
- package/Dev/i18n/en/Workflow/commands/plan.md +1 -2
- package/Dev/i18n/es/Common/agents/devops-engineer.md +19 -14
- package/Dev/i18n/es/Common/agents/migration-specialist.md +10 -15
- package/Dev/i18n/es/Common/agents/mlops-engineer.md +19 -15
- package/Dev/i18n/es/Common/agents/ralph-conductor.md +4 -15
- package/Dev/i18n/es/Common/agents/refactoring-specialist.md +0 -9
- package/Dev/i18n/es/Common/agents/tdd-coach.md +0 -9
- package/Dev/i18n/es/Common/agents/uiux-orchestrator.md +0 -9
- package/Dev/i18n/es/Common/commands/ralph-run.md +52 -0
- package/Dev/i18n/es/Common/commands/research-context7.md +5 -253
- package/Dev/i18n/es/Common/rules/12-context-management.md +9 -7
- package/Dev/i18n/es/Common/templates/settings.json.template +4 -0
- package/Dev/i18n/es/Python/skills/coding-standards/SKILL.md +1 -1
- package/Dev/i18n/es/Python/skills/testing-python/SKILL.md +1 -1
- package/Dev/i18n/es/Python/skills/tooling/SKILL.md +1 -1
- package/Dev/i18n/es/Symfony/commands/check-security.md +20 -20
- package/Dev/i18n/es/Symfony/skills/aggregates/SKILL.md +1 -1
- package/Dev/i18n/es/Symfony/skills/async/SKILL.md +1 -1
- package/Dev/i18n/es/Symfony/skills/cqrs/SKILL.md +1 -1
- package/Dev/i18n/es/Symfony/skills/domain-events/SKILL.md +1 -1
- package/Dev/i18n/es/Symfony/skills/i18n/SKILL.md +1 -1
- package/Dev/i18n/es/Symfony/skills/multitenant/SKILL.md +1 -1
- package/Dev/i18n/es/Symfony/skills/value-objects/SKILL.md +1 -1
- package/Dev/i18n/es/Team/commands/audit.md +29 -4
- package/Dev/i18n/es/Team/commands/delivery.md +29 -4
- package/Dev/i18n/es/Team/commands/security.md +31 -4
- package/Dev/i18n/es/Team/commands/sprint.md +30 -5
- package/Dev/i18n/es/UIUX/commands/audit.md +1 -30
- package/Dev/i18n/es/UIUX/commands/component-spec.md +1 -5
- package/Dev/i18n/es/UIUX/commands/orchestrator.md +5 -98
- package/Dev/i18n/es/Workflow/commands/auto-sprint.md +7 -4
- package/Dev/i18n/es/Workflow/commands/plan.md +1 -2
- package/Dev/i18n/fr/Common/agents/devops-engineer.md +19 -14
- package/Dev/i18n/fr/Common/agents/migration-specialist.md +10 -15
- package/Dev/i18n/fr/Common/agents/mlops-engineer.md +19 -15
- package/Dev/i18n/fr/Common/agents/ralph-conductor.md +4 -15
- package/Dev/i18n/fr/Common/agents/refactoring-specialist.md +0 -9
- package/Dev/i18n/fr/Common/agents/tdd-coach.md +0 -9
- package/Dev/i18n/fr/Common/agents/uiux-orchestrator.md +0 -9
- package/Dev/i18n/fr/Common/commands/ralph-run.md +52 -0
- package/Dev/i18n/fr/Common/commands/research-context7.md +5 -253
- package/Dev/i18n/fr/Common/rules/12-context-management.md +10 -8
- package/Dev/i18n/fr/Common/templates/settings.json.template +4 -0
- package/Dev/i18n/fr/React/commands/check-compliance.md +1 -1
- package/Dev/i18n/fr/React/commands/check-security.md +2 -2
- package/Dev/i18n/fr/Symfony/commands/check-security.md +20 -20
- package/Dev/i18n/fr/Symfony/skills/aggregates/SKILL.md +1 -1
- package/Dev/i18n/fr/Symfony/skills/async/SKILL.md +1 -1
- package/Dev/i18n/fr/Symfony/skills/cqrs/SKILL.md +1 -1
- package/Dev/i18n/fr/Symfony/skills/domain-events/SKILL.md +1 -1
- package/Dev/i18n/fr/Symfony/skills/i18n/SKILL.md +1 -1
- package/Dev/i18n/fr/Symfony/skills/multitenant/SKILL.md +1 -1
- package/Dev/i18n/fr/Symfony/skills/value-objects/SKILL.md +1 -1
- package/Dev/i18n/fr/Team/commands/audit.md +29 -4
- package/Dev/i18n/fr/Team/commands/delivery.md +29 -4
- package/Dev/i18n/fr/Team/commands/security.md +31 -4
- package/Dev/i18n/fr/Team/commands/sprint.md +30 -5
- package/Dev/i18n/fr/UIUX/commands/audit.md +1 -30
- package/Dev/i18n/fr/UIUX/commands/component-spec.md +1 -5
- package/Dev/i18n/fr/UIUX/commands/orchestrator.md +5 -98
- package/Dev/i18n/fr/Workflow/commands/auto-sprint.md +7 -4
- package/Dev/i18n/fr/Workflow/commands/plan.md +1 -2
- package/Dev/i18n/pt/Common/agents/devops-engineer.md +19 -14
- package/Dev/i18n/pt/Common/agents/migration-specialist.md +10 -15
- package/Dev/i18n/pt/Common/agents/mlops-engineer.md +19 -15
- package/Dev/i18n/pt/Common/agents/ralph-conductor.md +4 -15
- package/Dev/i18n/pt/Common/agents/refactoring-specialist.md +0 -9
- package/Dev/i18n/pt/Common/agents/tdd-coach.md +0 -9
- package/Dev/i18n/pt/Common/agents/uiux-orchestrator.md +0 -9
- package/Dev/i18n/pt/Common/commands/ralph-run.md +52 -0
- package/Dev/i18n/pt/Common/commands/research-context7.md +5 -253
- package/Dev/i18n/pt/Common/rules/12-context-management.md +9 -7
- package/Dev/i18n/pt/Common/templates/settings.json.template +4 -0
- package/Dev/i18n/pt/Python/skills/coding-standards/SKILL.md +1 -1
- package/Dev/i18n/pt/Python/skills/testing-python/SKILL.md +1 -1
- package/Dev/i18n/pt/Python/skills/tooling/SKILL.md +1 -1
- package/Dev/i18n/pt/Symfony/commands/check-security.md +20 -20
- package/Dev/i18n/pt/Symfony/skills/aggregates/SKILL.md +1 -1
- package/Dev/i18n/pt/Symfony/skills/async/SKILL.md +1 -1
- package/Dev/i18n/pt/Symfony/skills/cqrs/SKILL.md +1 -1
- package/Dev/i18n/pt/Symfony/skills/domain-events/SKILL.md +1 -1
- package/Dev/i18n/pt/Symfony/skills/i18n/SKILL.md +1 -1
- package/Dev/i18n/pt/Symfony/skills/multitenant/SKILL.md +1 -1
- package/Dev/i18n/pt/Symfony/skills/value-objects/SKILL.md +1 -1
- package/Dev/i18n/pt/Team/commands/audit.md +29 -4
- package/Dev/i18n/pt/Team/commands/delivery.md +29 -4
- package/Dev/i18n/pt/Team/commands/security.md +31 -4
- package/Dev/i18n/pt/Team/commands/sprint.md +30 -5
- package/Dev/i18n/pt/UIUX/commands/audit.md +1 -30
- package/Dev/i18n/pt/UIUX/commands/component-spec.md +1 -5
- package/Dev/i18n/pt/UIUX/commands/orchestrator.md +5 -98
- package/Dev/i18n/pt/Workflow/commands/auto-sprint.md +7 -4
- package/Dev/i18n/pt/Workflow/commands/plan.md +1 -2
- package/Dev/scripts/install-common-rules.sh +8 -0
- package/README.md +12 -8
- package/Tools/Ralph/lib/checkpoint.sh +3 -3
- package/Tools/Ralph/lib/circuit-breaker.sh +10 -10
- package/Tools/Ralph/lib/context-manager.sh +56 -65
- package/Tools/Ralph/lib/context-reconstruction.sh +46 -79
- package/Tools/Ralph/lib/dod-templates.sh +2 -2
- package/Tools/Ralph/lib/dod-validator.sh +18 -18
- package/Tools/Ralph/lib/escalation-service.sh +9 -7
- package/Tools/Ralph/lib/hooks-generator.sh +2 -1
- package/Tools/Ralph/lib/loop-finalizer.sh +1 -1
- package/Tools/Ralph/lib/loop.sh +44 -10
- package/Tools/Ralph/lib/metrics-exporter.sh +1 -1
- package/Tools/Ralph/lib/parallel-manager.sh +173 -88
- package/Tools/Ralph/lib/project-detector.sh +3 -3
- package/Tools/Ralph/lib/recovery-engine.sh +8 -8
- package/Tools/Ralph/lib/session.sh +2 -2
- package/Tools/Ralph/lib/sprint-conductor.sh +167 -124
- package/Tools/Ralph/lib/sprint-progress.sh +9 -9
- package/Tools/Ralph/ralph.sh +66 -38
- package/Tools/StatusLine/statusline.sh +0 -0
- package/Tools/i18n/ralph/de.sh +165 -33
- package/Tools/i18n/ralph/en.sh +132 -0
- package/Tools/i18n/ralph/es.sh +209 -77
- package/Tools/i18n/ralph/fr.sh +234 -102
- package/Tools/i18n/ralph/pt.sh +227 -95
- package/bundles/cursor/.cursorrules +3 -3
- package/bundles/windsurf/.windsurfrules +3 -3
- package/cli/lib/doctor.js +1 -1
- package/config/removed-commands.yaml +61 -1
- package/package.json +1 -1
|
@@ -17,11 +17,19 @@ PARALLEL_RESOURCE_LIMIT_CPU="${PARALLEL_RESOURCE_LIMIT_CPU:-80}"
|
|
|
17
17
|
PARALLEL_RESOURCE_LIMIT_MEM="${PARALLEL_RESOURCE_LIMIT_MEM:-80}"
|
|
18
18
|
|
|
19
19
|
# Session tracking
|
|
20
|
-
|
|
21
|
-
declare -
|
|
20
|
+
# `=()`: a declared-but-unassigned array is "unbound" for `${#arr[@]}` under set -u
|
|
21
|
+
declare -gA PARALLEL_SESSIONS=()
|
|
22
|
+
declare -gA PARALLEL_PIDS=()
|
|
22
23
|
PARALLEL_SESSION_COUNT=0
|
|
23
24
|
PARALLEL_COMPLETED_COUNT=0
|
|
24
25
|
PARALLEL_FAILED_COUNT=0
|
|
26
|
+
# Results returned through globals: spawn_session and collect_completed_sessions
|
|
27
|
+
# must run in the leader shell, never in `$(...)` (the worker would hold the
|
|
28
|
+
# substitution pipe open, and the tracking arrays would be lost).
|
|
29
|
+
PARALLEL_SPAWNED_SESSION_ID=""
|
|
30
|
+
# One story id per line (ids may contain spaces).
|
|
31
|
+
PARALLEL_COLLECTED_STORIES=""
|
|
32
|
+
PARALLEL_FAILED_STORIES=""
|
|
25
33
|
|
|
26
34
|
# Paths
|
|
27
35
|
PARALLEL_BASE_DIR=""
|
|
@@ -36,6 +44,8 @@ PARALLEL_WORKER_DIR=""
|
|
|
36
44
|
init_parallel_manager() {
|
|
37
45
|
PARALLEL_BASE_DIR="${RALPH_SESSION_BASE:-.ralph}/parallel"
|
|
38
46
|
mkdir -p "$PARALLEL_BASE_DIR"
|
|
47
|
+
# Absolute: workers cd into their own worktree
|
|
48
|
+
PARALLEL_BASE_DIR="$(cd "$PARALLEL_BASE_DIR" && pwd)"
|
|
39
49
|
|
|
40
50
|
PARALLEL_STATE_FILE="$PARALLEL_BASE_DIR/state.yaml"
|
|
41
51
|
PARALLEL_DEPENDENCY_GRAPH="$PARALLEL_BASE_DIR/dependencies.json"
|
|
@@ -98,32 +108,41 @@ EOF
|
|
|
98
108
|
|
|
99
109
|
# Build dependency graph from stories
|
|
100
110
|
build_dependency_graph() {
|
|
101
|
-
local stories="$1" #
|
|
111
|
+
local stories="$1" # Story IDs, one per line
|
|
102
112
|
local bmad_dir="${BMAD_DIR:-.bmad}"
|
|
103
113
|
local status_file="$bmad_dir/sprint-status.yaml"
|
|
104
114
|
|
|
115
|
+
# Without a status file (or yq), all stories are independent nodes: the
|
|
116
|
+
# graph must still list them, get_ready_stories reads it.
|
|
117
|
+
local with_deps=true
|
|
105
118
|
if [[ ! -f "$status_file" ]] || ! command -v yq &>/dev/null; then
|
|
106
|
-
|
|
107
|
-
echo '{"nodes":{},"edges":[]}'
|
|
108
|
-
return
|
|
119
|
+
with_deps=false
|
|
109
120
|
fi
|
|
110
121
|
|
|
111
122
|
local nodes="{}"
|
|
112
123
|
local edges="[]"
|
|
113
124
|
|
|
114
|
-
|
|
125
|
+
local -a story_list=()
|
|
126
|
+
mapfile -t story_list <<< "$stories"
|
|
127
|
+
for story_id in "${story_list[@]}"; do
|
|
128
|
+
[[ -z "$story_id" ]] && continue
|
|
115
129
|
# Get story dependencies
|
|
116
|
-
local deps
|
|
117
|
-
|
|
130
|
+
local deps="[]"
|
|
131
|
+
if [[ "$with_deps" == "true" ]]; then
|
|
132
|
+
deps=$(STORY_ID="$story_id" yq '.stories[strenv(STORY_ID)].blocked_by // []' "$status_file" 2>/dev/null) || deps="[]"
|
|
133
|
+
fi
|
|
118
134
|
|
|
119
|
-
# Add node
|
|
120
|
-
|
|
135
|
+
# Add node (on jq failure, keep the previous value: `next` buffers it)
|
|
136
|
+
local next
|
|
137
|
+
next=$(echo "$nodes" | jq --arg id "$story_id" '. + {($id): {"status": "pending", "deps": []}}') && nodes="$next"
|
|
121
138
|
|
|
122
139
|
# Add edges for dependencies
|
|
123
140
|
if [[ "$deps" != "[]" && "$deps" != "null" ]]; then
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
141
|
+
local -a dep_list=()
|
|
142
|
+
mapfile -t dep_list < <(echo "$deps" | jq -r '.[]' 2>/dev/null || true)
|
|
143
|
+
for dep in "${dep_list[@]}"; do
|
|
144
|
+
next=$(echo "$edges" | jq --arg from "$dep" --arg to "$story_id" '. + [{"from": $from, "to": $to}]') && edges="$next"
|
|
145
|
+
next=$(echo "$nodes" | jq --arg id "$story_id" --arg dep "$dep" '.[$id].deps += [$dep]') && nodes="$next"
|
|
127
146
|
done
|
|
128
147
|
fi
|
|
129
148
|
done
|
|
@@ -144,18 +163,20 @@ get_ready_stories() {
|
|
|
144
163
|
fi
|
|
145
164
|
|
|
146
165
|
# Find nodes with status=pending and all deps completed
|
|
166
|
+
# ($root: jq has no `$.` root accessor — that syntax failed to compile)
|
|
147
167
|
jq -r '
|
|
168
|
+
. as $root |
|
|
148
169
|
.nodes | to_entries[] |
|
|
149
170
|
select(.value.status == "pending") |
|
|
150
171
|
select(
|
|
151
172
|
(.value.deps | length) == 0 or
|
|
152
173
|
(
|
|
153
174
|
.value.deps as $deps |
|
|
154
|
-
($deps | map(. as $d |
|
|
175
|
+
($deps | map(. as $d | $root.nodes[$d].status == "completed") | all)
|
|
155
176
|
)
|
|
156
177
|
) |
|
|
157
178
|
.key
|
|
158
|
-
' "$PARALLEL_DEPENDENCY_GRAPH" 2>/dev/null
|
|
179
|
+
' "$PARALLEL_DEPENDENCY_GRAPH" 2>/dev/null || true
|
|
159
180
|
}
|
|
160
181
|
|
|
161
182
|
# Mark story as completed in dependency graph
|
|
@@ -171,6 +192,19 @@ mark_story_completed() {
|
|
|
171
192
|
echo "$updated" > "$PARALLEL_DEPENDENCY_GRAPH"
|
|
172
193
|
}
|
|
173
194
|
|
|
195
|
+
# Mark story as running in dependency graph (leader only, at spawn time)
|
|
196
|
+
mark_story_running() {
|
|
197
|
+
local story_id="$1"
|
|
198
|
+
|
|
199
|
+
if [[ ! -f "$PARALLEL_DEPENDENCY_GRAPH" ]] || ! command -v jq &>/dev/null; then
|
|
200
|
+
return 0
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
local updated
|
|
204
|
+
updated=$(jq --arg id "$story_id" '.nodes[$id].status = "running"' "$PARALLEL_DEPENDENCY_GRAPH")
|
|
205
|
+
echo "$updated" > "$PARALLEL_DEPENDENCY_GRAPH"
|
|
206
|
+
}
|
|
207
|
+
|
|
174
208
|
# Mark story as failed in dependency graph
|
|
175
209
|
mark_story_failed() {
|
|
176
210
|
local story_id="$1"
|
|
@@ -211,11 +245,11 @@ check_resource_availability() {
|
|
|
211
245
|
mem_usage=$(free | grep Mem | awk '{print int($3/$2 * 100)}' 2>/dev/null || echo "0")
|
|
212
246
|
elif [[ "$(uname)" == "Darwin" ]]; then
|
|
213
247
|
local pages_free pages_active pages_inactive pages_speculative pages_wired
|
|
214
|
-
pages_free=$(vm_stat | grep "Pages free" | awk '{print $3}' | tr -d '.')
|
|
215
|
-
pages_active=$(vm_stat | grep "Pages active" | awk '{print $3}' | tr -d '.')
|
|
216
|
-
pages_inactive=$(vm_stat | grep "Pages inactive" | awk '{print $3}' | tr -d '.')
|
|
217
|
-
pages_speculative=$(vm_stat | grep "Pages speculative" | awk '{print $3}' | tr -d '.')
|
|
218
|
-
pages_wired=$(vm_stat | grep "Pages wired" | awk '{print $4}' | tr -d '.')
|
|
248
|
+
pages_free=$(vm_stat | grep "Pages free" | awk '{print $3}' | tr -d '.') || pages_free=0
|
|
249
|
+
pages_active=$(vm_stat | grep "Pages active" | awk '{print $3}' | tr -d '.') || pages_active=0
|
|
250
|
+
pages_inactive=$(vm_stat | grep "Pages inactive" | awk '{print $3}' | tr -d '.') || pages_inactive=0
|
|
251
|
+
pages_speculative=$(vm_stat | grep "Pages speculative" | awk '{print $3}' | tr -d '.') || pages_speculative=0
|
|
252
|
+
pages_wired=$(vm_stat | grep "Pages wired" | awk '{print $4}' | tr -d '.') || pages_wired=0
|
|
219
253
|
local total=$((pages_free + pages_active + pages_inactive + pages_speculative + pages_wired))
|
|
220
254
|
local used=$((pages_active + pages_wired))
|
|
221
255
|
mem_usage=$((used * 100 / total))
|
|
@@ -232,9 +266,7 @@ check_resource_availability() {
|
|
|
232
266
|
# Get number of available slots
|
|
233
267
|
get_available_slots() {
|
|
234
268
|
local active_count=0
|
|
235
|
-
|
|
236
|
-
active_count=${#PARALLEL_PIDS[@]}
|
|
237
|
-
fi
|
|
269
|
+
active_count=${#PARALLEL_PIDS[@]}
|
|
238
270
|
local available=$((PARALLEL_MAX_CONCURRENT - active_count))
|
|
239
271
|
|
|
240
272
|
# Further limit if resources are constrained
|
|
@@ -250,14 +282,18 @@ get_available_slots() {
|
|
|
250
282
|
# Session Spawning
|
|
251
283
|
# =============================================================================
|
|
252
284
|
|
|
253
|
-
# Spawn a new Ralph session for a story
|
|
285
|
+
# Spawn a new Ralph session for a story (id in PARALLEL_SPAWNED_SESSION_ID)
|
|
254
286
|
spawn_session() {
|
|
255
287
|
local story_id="$1"
|
|
256
288
|
local prompt="$2"
|
|
257
289
|
local config="${3:-}"
|
|
258
290
|
|
|
259
|
-
# Generate session ID
|
|
260
|
-
|
|
291
|
+
# Generate session ID. It names a directory and a git branch: the story id
|
|
292
|
+
# is reduced to [A-Za-z0-9_-] (no `/`, `..` nor space), and the spawn
|
|
293
|
+
# counter keeps ids that sanitize alike ("US 1", "US/1") apart.
|
|
294
|
+
local session_id
|
|
295
|
+
session_id="PAR-${story_id//[^A-Za-z0-9_-]/-}-$(date +%s)-${PARALLEL_SESSION_COUNT}"
|
|
296
|
+
PARALLEL_SPAWNED_SESSION_ID=""
|
|
261
297
|
|
|
262
298
|
print_info "Spawning parallel session: $session_id for $story_id"
|
|
263
299
|
|
|
@@ -265,21 +301,22 @@ spawn_session() {
|
|
|
265
301
|
local session_dir="$PARALLEL_BASE_DIR/sessions/$session_id"
|
|
266
302
|
mkdir -p "$session_dir"
|
|
267
303
|
|
|
268
|
-
# Write session info
|
|
269
|
-
|
|
270
|
-
session_id
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
304
|
+
# Write session info (ids through strenv(): a `"` must not break the YAML)
|
|
305
|
+
if command -v yq &>/dev/null; then
|
|
306
|
+
SESSION_ID_ARG="$session_id" STORY_ID="$story_id" TS="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" yq -n '
|
|
307
|
+
.session_id = strenv(SESSION_ID_ARG) |
|
|
308
|
+
.story_id = strenv(STORY_ID) |
|
|
309
|
+
.status = "running" |
|
|
310
|
+
.started_at = strenv(TS) |
|
|
311
|
+
.pid = "" |
|
|
312
|
+
.exit_code = ""' > "$session_dir/info.yaml"
|
|
313
|
+
fi
|
|
277
314
|
|
|
278
315
|
# Find Ralph script
|
|
279
316
|
local ralph_script=""
|
|
280
317
|
for path in "${SCRIPT_DIR}/ralph.sh" "Tools/Ralph/ralph.sh" "$(dirname "${BASH_SOURCE[0]}")/../ralph.sh"; do
|
|
281
318
|
if [[ -f "$path" ]]; then
|
|
282
|
-
ralph_script="$path"
|
|
319
|
+
ralph_script="$(cd "$(dirname "$path")" && pwd)/$(basename "$path")"
|
|
283
320
|
break
|
|
284
321
|
fi
|
|
285
322
|
done
|
|
@@ -290,19 +327,37 @@ EOF
|
|
|
290
327
|
fi
|
|
291
328
|
|
|
292
329
|
# Build command
|
|
293
|
-
local cmd="$ralph_script"
|
|
294
|
-
[[ -n "$
|
|
330
|
+
local -a cmd=("$ralph_script")
|
|
331
|
+
[[ -n "${LANG_ARG:-}" ]] && cmd+=("--lang=$LANG_ARG")
|
|
332
|
+
[[ -n "$config" ]] && cmd+=("--config=$config")
|
|
333
|
+
|
|
334
|
+
# Isolate the worker's working tree (one git worktree per session)
|
|
335
|
+
local worktree_dir=""
|
|
336
|
+
if ! worktree_dir=$(create_worker_worktree "$session_id"); then
|
|
337
|
+
worktree_dir=""
|
|
338
|
+
print_warning "No git worktree for $session_id: worker shares the current directory"
|
|
339
|
+
fi
|
|
295
340
|
|
|
296
341
|
# Spawn in background
|
|
297
342
|
# Workers write to individual temp files (single-writer pattern).
|
|
298
343
|
# The leader merges results after all workers complete.
|
|
299
|
-
|
|
344
|
+
# Derived from the (sanitized) session id: it names the report file
|
|
345
|
+
local worker_id="${session_id}-$$"
|
|
300
346
|
(
|
|
301
|
-
|
|
347
|
+
if [[ -n "$worktree_dir" ]]; then
|
|
348
|
+
cd "$worktree_dir" || exit 1
|
|
349
|
+
fi
|
|
350
|
+
# Per-worker Ralph state (incl. the Claude session UUID) under the
|
|
351
|
+
# leader's .ralph/, so it survives the worktree removal.
|
|
352
|
+
export RALPH_SESSION_BASE="$session_dir/ralph"
|
|
302
353
|
|
|
303
354
|
local output
|
|
304
|
-
|
|
305
|
-
|
|
355
|
+
local status=0
|
|
356
|
+
output=$("${cmd[@]}" "$prompt" 2>&1) || status=$?
|
|
357
|
+
|
|
358
|
+
if [[ -n "$worktree_dir" ]] && ! cleanup_worker_worktree "$worktree_dir" "$story_id"; then
|
|
359
|
+
output+=$'\n'"[WARN] Worktree kept: $worktree_dir"
|
|
360
|
+
fi
|
|
306
361
|
|
|
307
362
|
# Save output (per-session dir, no conflict)
|
|
308
363
|
echo "$output" > "$session_dir/output.log"
|
|
@@ -337,7 +392,31 @@ EOF
|
|
|
337
392
|
# Update state file
|
|
338
393
|
update_parallel_state "spawn" "$session_id" "$story_id"
|
|
339
394
|
|
|
340
|
-
|
|
395
|
+
PARALLEL_SPAWNED_SESSION_ID="$session_id"
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
# Create a dedicated git worktree on branch ralph/<session_id> so parallel
|
|
399
|
+
# workers never share a working tree. Prints its path; fails outside git.
|
|
400
|
+
create_worker_worktree() {
|
|
401
|
+
local session_id="$1"
|
|
402
|
+
local worktree_dir="$PARALLEL_BASE_DIR/worktrees/$session_id"
|
|
403
|
+
|
|
404
|
+
git rev-parse --is-inside-work-tree &> /dev/null || return 1
|
|
405
|
+
git worktree add -q -b "ralph/$session_id" "$worktree_dir" HEAD &> /dev/null || return 1
|
|
406
|
+
echo "$worktree_dir"
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
# Commit leftover changes as WIP on the worker branch, then remove the
|
|
410
|
+
# worktree. If the commit fails, the dirty worktree is kept (no data loss).
|
|
411
|
+
cleanup_worker_worktree() {
|
|
412
|
+
local worktree_dir="$1"
|
|
413
|
+
local story_id="$2"
|
|
414
|
+
|
|
415
|
+
if [[ -n "$(git -C "$worktree_dir" status --porcelain)" ]]; then
|
|
416
|
+
git -C "$worktree_dir" add -A
|
|
417
|
+
git -C "$worktree_dir" commit -q -m "wip(ralph): $story_id" || return 1
|
|
418
|
+
fi
|
|
419
|
+
git -C "$worktree_dir" worktree remove "$worktree_dir"
|
|
341
420
|
}
|
|
342
421
|
|
|
343
422
|
# =============================================================================
|
|
@@ -347,6 +426,8 @@ EOF
|
|
|
347
426
|
# Check and collect completed sessions.
|
|
348
427
|
# In the single-writer pattern, this function collects finished PIDs
|
|
349
428
|
# and then merges worker reports written by the background processes.
|
|
429
|
+
# Completed (failed) stories are also appended, one per line, to
|
|
430
|
+
# PARALLEL_COLLECTED_STORIES (PARALLEL_FAILED_STORIES).
|
|
350
431
|
collect_completed_sessions() {
|
|
351
432
|
local completed=()
|
|
352
433
|
local failed=()
|
|
@@ -359,16 +440,18 @@ collect_completed_sessions() {
|
|
|
359
440
|
local session_id="${PARALLEL_PIDS[$pid]}"
|
|
360
441
|
local story_id="${PARALLEL_SESSIONS[$session_id]}"
|
|
361
442
|
|
|
362
|
-
# Check exit status
|
|
363
|
-
|
|
364
|
-
|
|
443
|
+
# Check exit status (a failed worker must not kill the leader)
|
|
444
|
+
local exit_code=0
|
|
445
|
+
wait "$pid" 2>/dev/null || exit_code=$?
|
|
365
446
|
|
|
366
447
|
if [[ $exit_code -eq 0 ]]; then
|
|
367
448
|
completed+=("$story_id")
|
|
449
|
+
PARALLEL_COLLECTED_STORIES+="$story_id"$'\n'
|
|
368
450
|
PARALLEL_COMPLETED_COUNT=$((PARALLEL_COMPLETED_COUNT + 1))
|
|
369
451
|
print_success "Session completed: $session_id ($story_id)"
|
|
370
452
|
else
|
|
371
453
|
failed+=("$story_id")
|
|
454
|
+
PARALLEL_FAILED_STORIES+="$story_id"$'\n'
|
|
372
455
|
PARALLEL_FAILED_COUNT=$((PARALLEL_FAILED_COUNT + 1))
|
|
373
456
|
print_error "Session failed: $session_id ($story_id)"
|
|
374
457
|
fi
|
|
@@ -395,9 +478,7 @@ wait_all_sessions() {
|
|
|
395
478
|
start_time=$(date +%s)
|
|
396
479
|
|
|
397
480
|
local active_count=0
|
|
398
|
-
|
|
399
|
-
active_count=${#PARALLEL_PIDS[@]}
|
|
400
|
-
fi
|
|
481
|
+
active_count=${#PARALLEL_PIDS[@]}
|
|
401
482
|
|
|
402
483
|
while [[ $active_count -gt 0 ]]; do
|
|
403
484
|
collect_completed_sessions > /dev/null
|
|
@@ -415,9 +496,7 @@ wait_all_sessions() {
|
|
|
415
496
|
|
|
416
497
|
# Update active count
|
|
417
498
|
active_count=0
|
|
418
|
-
|
|
419
|
-
active_count=${#PARALLEL_PIDS[@]}
|
|
420
|
-
fi
|
|
499
|
+
active_count=${#PARALLEL_PIDS[@]}
|
|
421
500
|
done
|
|
422
501
|
|
|
423
502
|
return 0
|
|
@@ -426,9 +505,7 @@ wait_all_sessions() {
|
|
|
426
505
|
# Get active session count
|
|
427
506
|
get_active_count() {
|
|
428
507
|
local count=0
|
|
429
|
-
|
|
430
|
-
count=${#PARALLEL_PIDS[@]}
|
|
431
|
-
fi
|
|
508
|
+
count=${#PARALLEL_PIDS[@]}
|
|
432
509
|
echo "$count"
|
|
433
510
|
}
|
|
434
511
|
|
|
@@ -450,12 +527,12 @@ process_stories_parallel() {
|
|
|
450
527
|
print_info "Stories: $(echo "$stories" | wc -w | tr -d ' ')"
|
|
451
528
|
print_info "Max concurrent: $PARALLEL_MAX_CONCURRENT"
|
|
452
529
|
|
|
453
|
-
# Build dependency graph
|
|
454
|
-
build_dependency_graph "$stories" > /dev/null
|
|
530
|
+
# Build dependency graph (it reads one id per line)
|
|
531
|
+
build_dependency_graph "$(tr -s ' ' '\n' <<< "$stories")" > /dev/null
|
|
455
532
|
|
|
456
533
|
local processed=0
|
|
457
534
|
local total
|
|
458
|
-
total=$(echo "$stories" | wc -w | tr -d ' ')
|
|
535
|
+
total=$(echo "$stories" | wc -w | tr -d ' ') || total=0
|
|
459
536
|
|
|
460
537
|
while [[ $processed -lt $total ]]; do
|
|
461
538
|
# Collect completed sessions
|
|
@@ -473,9 +550,6 @@ process_stories_parallel() {
|
|
|
473
550
|
for story_id in $ready; do
|
|
474
551
|
[[ $slots -le 0 ]] && break
|
|
475
552
|
|
|
476
|
-
# Skip if already processed
|
|
477
|
-
[[ -n "${PARALLEL_SESSIONS[$story_id]:-}" ]] && continue
|
|
478
|
-
|
|
479
553
|
# Build prompt
|
|
480
554
|
local prompt="$prompt_template $story_id"
|
|
481
555
|
|
|
@@ -484,14 +558,19 @@ process_stories_parallel() {
|
|
|
484
558
|
local status_file="$bmad_dir/sprint-status.yaml"
|
|
485
559
|
if [[ -f "$status_file" ]] && command -v yq &>/dev/null; then
|
|
486
560
|
local title
|
|
487
|
-
title=$(yq
|
|
561
|
+
title=$(STORY_ID="$story_id" yq '.stories[strenv(STORY_ID)].title' "$status_file" 2>/dev/null)
|
|
488
562
|
prompt="Implement user story $story_id: $title. Follow TDD approach."
|
|
489
563
|
fi
|
|
490
564
|
|
|
491
|
-
# Spawn session
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
565
|
+
# Spawn session. "running" keeps get_ready_stories (pending
|
|
566
|
+
# only) from re-spawning it before its report is merged.
|
|
567
|
+
if spawn_session "$story_id" "$prompt"; then
|
|
568
|
+
mark_story_running "$story_id"
|
|
569
|
+
else
|
|
570
|
+
mark_story_failed "$story_id"
|
|
571
|
+
fi
|
|
572
|
+
slots=$((slots - 1))
|
|
573
|
+
processed=$((processed + 1))
|
|
495
574
|
done
|
|
496
575
|
fi
|
|
497
576
|
|
|
@@ -503,6 +582,9 @@ process_stories_parallel() {
|
|
|
503
582
|
print_info "Waiting for remaining sessions to complete..."
|
|
504
583
|
wait_all_sessions
|
|
505
584
|
|
|
585
|
+
# Drop stale worktree metadata (e.g. a worker killed before its cleanup)
|
|
586
|
+
git worktree prune &> /dev/null || true
|
|
587
|
+
|
|
506
588
|
# Print summary
|
|
507
589
|
print_parallel_summary
|
|
508
590
|
}
|
|
@@ -520,16 +602,14 @@ write_worker_report() {
|
|
|
520
602
|
local status="$4" # "completed" or "failed"
|
|
521
603
|
|
|
522
604
|
local report_file="${PARALLEL_WORKER_DIR}/worker-${worker_id}.yaml"
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
reported_at: "${ts}"
|
|
532
|
-
EOF
|
|
605
|
+
# Values through strenv(): a `"` in an id must not break the YAML
|
|
606
|
+
WORKER_ID="$worker_id" SESSION_ID_ARG="$session_id" STORY_ID="$story_id" \
|
|
607
|
+
STATUS="$status" TS="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" yq -n '
|
|
608
|
+
.worker_id = strenv(WORKER_ID) |
|
|
609
|
+
.session_id = strenv(SESSION_ID_ARG) |
|
|
610
|
+
.story_id = strenv(STORY_ID) |
|
|
611
|
+
.status = strenv(STATUS) |
|
|
612
|
+
.reported_at = strenv(TS)' > "$report_file"
|
|
533
613
|
}
|
|
534
614
|
|
|
535
615
|
# Merge all worker reports into the shared state file (leader only).
|
|
@@ -567,18 +647,18 @@ merge_worker_reports() {
|
|
|
567
647
|
if [[ "$dry_run" == "true" ]]; then
|
|
568
648
|
print_info "DRY RUN - Would merge: session=$session_id story=$story_id status=$status" 2>/dev/null \
|
|
569
649
|
|| echo "[INFO] DRY RUN - Would merge: session=$session_id story=$story_id status=$status"
|
|
570
|
-
((merged_count
|
|
650
|
+
merged_count=$((merged_count + 1))
|
|
571
651
|
continue
|
|
572
652
|
fi
|
|
573
653
|
|
|
574
654
|
# Update state file (single writer - no race condition)
|
|
575
655
|
if [[ -f "$PARALLEL_STATE_FILE" ]]; then
|
|
576
|
-
yq -i
|
|
656
|
+
SESSION_ID_ARG="$session_id" yq -i '.sessions.active -= [strenv(SESSION_ID_ARG)]' "$PARALLEL_STATE_FILE"
|
|
577
657
|
if [[ "$status" == "completed" ]]; then
|
|
578
|
-
yq -i
|
|
658
|
+
SESSION_ID_ARG="$session_id" yq -i '.sessions.completed += [strenv(SESSION_ID_ARG)]' "$PARALLEL_STATE_FILE"
|
|
579
659
|
yq -i ".metrics.total_completed += 1" "$PARALLEL_STATE_FILE"
|
|
580
660
|
else
|
|
581
|
-
yq -i
|
|
661
|
+
SESSION_ID_ARG="$session_id" yq -i '.sessions.failed += [strenv(SESSION_ID_ARG)]' "$PARALLEL_STATE_FILE"
|
|
582
662
|
yq -i ".metrics.total_failed += 1" "$PARALLEL_STATE_FILE"
|
|
583
663
|
fi
|
|
584
664
|
fi
|
|
@@ -591,7 +671,7 @@ merge_worker_reports() {
|
|
|
591
671
|
fi
|
|
592
672
|
|
|
593
673
|
rm -f "$report_file"
|
|
594
|
-
((merged_count
|
|
674
|
+
merged_count=$((merged_count + 1))
|
|
595
675
|
done
|
|
596
676
|
|
|
597
677
|
# Update efficiency after merge
|
|
@@ -640,19 +720,19 @@ update_parallel_state() {
|
|
|
640
720
|
case "$action" in
|
|
641
721
|
spawn)
|
|
642
722
|
# Spawn is always called from the leader, safe to write directly
|
|
643
|
-
yq -i
|
|
723
|
+
SESSION_ID_ARG="$session_id" yq -i '.sessions.active += [strenv(SESSION_ID_ARG)]' "$PARALLEL_STATE_FILE"
|
|
644
724
|
yq -i ".metrics.total_spawned += 1" "$PARALLEL_STATE_FILE"
|
|
645
725
|
;;
|
|
646
726
|
complete|fail)
|
|
647
727
|
# DEPRECATED for parallel mode: use write_worker_report() + merge_worker_reports()
|
|
648
728
|
# Kept for backward compatibility with sequential mode
|
|
649
729
|
if [[ "$action" == "complete" ]]; then
|
|
650
|
-
yq -i
|
|
651
|
-
yq -i
|
|
730
|
+
SESSION_ID_ARG="$session_id" yq -i '.sessions.active -= [strenv(SESSION_ID_ARG)]' "$PARALLEL_STATE_FILE"
|
|
731
|
+
SESSION_ID_ARG="$session_id" yq -i '.sessions.completed += [strenv(SESSION_ID_ARG)]' "$PARALLEL_STATE_FILE"
|
|
652
732
|
yq -i ".metrics.total_completed += 1" "$PARALLEL_STATE_FILE"
|
|
653
733
|
else
|
|
654
|
-
yq -i
|
|
655
|
-
yq -i
|
|
734
|
+
SESSION_ID_ARG="$session_id" yq -i '.sessions.active -= [strenv(SESSION_ID_ARG)]' "$PARALLEL_STATE_FILE"
|
|
735
|
+
SESSION_ID_ARG="$session_id" yq -i '.sessions.failed += [strenv(SESSION_ID_ARG)]' "$PARALLEL_STATE_FILE"
|
|
656
736
|
yq -i ".metrics.total_failed += 1" "$PARALLEL_STATE_FILE"
|
|
657
737
|
fi
|
|
658
738
|
;;
|
|
@@ -746,7 +826,7 @@ cmd_parallel_manager() {
|
|
|
746
826
|
echo "Usage: parallel-manager.sh spawn <story-id> [prompt]"
|
|
747
827
|
return 1
|
|
748
828
|
fi
|
|
749
|
-
spawn_session "$story_id" "$prompt"
|
|
829
|
+
spawn_session "$story_id" "$prompt" && echo "$PARALLEL_SPAWNED_SESSION_ID"
|
|
750
830
|
;;
|
|
751
831
|
wait)
|
|
752
832
|
wait_all_sessions "${1:-0}"
|
|
@@ -781,6 +861,11 @@ cmd_parallel_manager() {
|
|
|
781
861
|
echo "Single-Writer Pattern:"
|
|
782
862
|
echo " Background workers write to .ralph/parallel/workers/worker-{id}.yaml"
|
|
783
863
|
echo " Only the leader merges results into shared state files"
|
|
864
|
+
echo ""
|
|
865
|
+
echo "Isolation:"
|
|
866
|
+
echo " Each worker runs in its own git worktree (branch ralph/<session>),"
|
|
867
|
+
echo " removed at the end of the run (leftover changes committed as WIP)."
|
|
868
|
+
echo " Each Ralph session pins its own Claude session (--session-id, then --resume)."
|
|
784
869
|
;;
|
|
785
870
|
*)
|
|
786
871
|
echo "Usage: parallel-manager.sh <command> [args]"
|
|
@@ -72,7 +72,7 @@ detect_symfony() {
|
|
|
72
72
|
|
|
73
73
|
# Detect Symfony version
|
|
74
74
|
local version
|
|
75
|
-
version=$(grep -o '"symfony/framework-bundle"[[:space:]]*:[[:space:]]*"[^"]*"' "$composer_file" | grep -o '[0-9]\+\.[0-9]\+' | head -1)
|
|
75
|
+
version=$(grep -o '"symfony/framework-bundle"[[:space:]]*:[[:space:]]*"[^"]*"' "$composer_file" | grep -o '[0-9]\+\.[0-9]\+' | head -1) || true
|
|
76
76
|
if [[ -n "$version" ]]; then
|
|
77
77
|
DETECTED_PROJECT_TYPE="symfony-$version"
|
|
78
78
|
fi
|
|
@@ -325,7 +325,7 @@ detect_dotnet() {
|
|
|
325
325
|
|
|
326
326
|
# Look for .csproj files
|
|
327
327
|
local csproj_count
|
|
328
|
-
csproj_count=$(find "$project_dir" -maxdepth 3 -name "*.csproj" 2>/dev/null | wc -l)
|
|
328
|
+
csproj_count=$(find "$project_dir" -maxdepth 3 -name "*.csproj" 2>/dev/null | wc -l) || csproj_count=0
|
|
329
329
|
|
|
330
330
|
if [[ $csproj_count -gt 0 ]]; then
|
|
331
331
|
DETECTED_PROJECT_TYPE="dotnet"
|
|
@@ -340,7 +340,7 @@ detect_dotnet() {
|
|
|
340
340
|
|
|
341
341
|
# Detect specific framework
|
|
342
342
|
local csproj_file
|
|
343
|
-
csproj_file=$(find "$project_dir" -maxdepth 3 -name "*.csproj" 2>/dev/null | head -1)
|
|
343
|
+
csproj_file=$(find "$project_dir" -maxdepth 3 -name "*.csproj" 2>/dev/null | head -1) || true
|
|
344
344
|
if [[ -f "$csproj_file" ]]; then
|
|
345
345
|
if grep -q 'Microsoft.NET.Sdk.Web' "$csproj_file" 2>/dev/null; then
|
|
346
346
|
DETECTED_PROJECT_TYPE="dotnet-web"
|
|
@@ -17,7 +17,7 @@ RECOVERY_AUTO_FIX_TESTS="${RECOVERY_AUTO_FIX_TESTS:-retry_tdd}"
|
|
|
17
17
|
RECOVERY_AUTO_RETRY_TRANSIENT="${RECOVERY_AUTO_RETRY_TRANSIENT:-true}"
|
|
18
18
|
|
|
19
19
|
# Error classification levels
|
|
20
|
-
declare -
|
|
20
|
+
declare -gA RECOVERY_LEVELS=(
|
|
21
21
|
[0]="transient" # Auto-retry immediately
|
|
22
22
|
[1]="recoverable" # Auto-fix possible
|
|
23
23
|
[2]="degraded" # Continue with warning
|
|
@@ -25,14 +25,14 @@ declare -A RECOVERY_LEVELS=(
|
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
# Error patterns for classification
|
|
28
|
-
declare -
|
|
28
|
+
declare -gA ERROR_PATTERNS_TRANSIENT=(
|
|
29
29
|
["timeout"]="timeout|timed out|ETIMEDOUT"
|
|
30
30
|
["rate_limit"]="rate limit|429|too many requests"
|
|
31
31
|
["network"]="ECONNRESET|ECONNREFUSED|network error"
|
|
32
32
|
["temporary"]="temporary failure|try again|service unavailable|503"
|
|
33
33
|
)
|
|
34
34
|
|
|
35
|
-
declare -
|
|
35
|
+
declare -gA ERROR_PATTERNS_RECOVERABLE=(
|
|
36
36
|
["lint"]="lint error|eslint|phpcs|flake8|prettier|stylelint"
|
|
37
37
|
["test_fail"]="test failed|assertion failed|tests? (failed|failing)"
|
|
38
38
|
["deps"]="dependency|package not found|module not found|could not resolve"
|
|
@@ -40,14 +40,14 @@ declare -A ERROR_PATTERNS_RECOVERABLE=(
|
|
|
40
40
|
["type_error"]="type error|typescript error|type mismatch"
|
|
41
41
|
)
|
|
42
42
|
|
|
43
|
-
declare -
|
|
43
|
+
declare -gA ERROR_PATTERNS_DEGRADED=(
|
|
44
44
|
["docs"]="documentation|readme|changelog"
|
|
45
45
|
["optional_gate"]="optional.*gate|non-blocking"
|
|
46
46
|
["coverage"]="coverage below|coverage threshold"
|
|
47
47
|
["warning"]="warning:|deprecated"
|
|
48
48
|
)
|
|
49
49
|
|
|
50
|
-
declare -
|
|
50
|
+
declare -gA ERROR_PATTERNS_BLOCKED=(
|
|
51
51
|
["security"]="security vulnerability|CVE-|critical security|injection"
|
|
52
52
|
["architecture"]="architecture violation|circular dependency|breaking change"
|
|
53
53
|
["auth"]="authentication failed|unauthorized|permission denied"
|
|
@@ -55,7 +55,7 @@ declare -A ERROR_PATTERNS_BLOCKED=(
|
|
|
55
55
|
)
|
|
56
56
|
|
|
57
57
|
# Recovery strategies
|
|
58
|
-
declare -
|
|
58
|
+
declare -gA RECOVERY_STRATEGIES=(
|
|
59
59
|
["lint"]="run_auto_lint_fix"
|
|
60
60
|
["test_fail"]="run_tdd_retry"
|
|
61
61
|
["deps"]="run_deps_install"
|
|
@@ -124,7 +124,7 @@ init_recovery_engine() {
|
|
|
124
124
|
classify_error() {
|
|
125
125
|
local error_output="$1"
|
|
126
126
|
local error_lower
|
|
127
|
-
error_lower=$(echo "$error_output" | tr '[:upper:]' '[:lower:]')
|
|
127
|
+
error_lower=$(echo "$error_output" | tr '[:upper:]' '[:lower:]') || true
|
|
128
128
|
|
|
129
129
|
# Check Level 3 - Blocked (security, architecture)
|
|
130
130
|
for pattern_name in "${!ERROR_PATTERNS_BLOCKED[@]}"; do
|
|
@@ -415,7 +415,7 @@ run_tdd_retry() {
|
|
|
415
415
|
|
|
416
416
|
# Try to parse test output
|
|
417
417
|
if echo "$error_output" | grep -qE "FAIL|FAILED|Error:"; then
|
|
418
|
-
failing_tests=$(echo "$error_output" | grep -E "FAIL|FAILED|Error:" | head -5)
|
|
418
|
+
failing_tests=$(echo "$error_output" | grep -E "FAIL|FAILED|Error:" | head -5) || true
|
|
419
419
|
fi
|
|
420
420
|
|
|
421
421
|
# Update sprint progress with TDD phase
|
|
@@ -23,10 +23,10 @@ generate_session_id() {
|
|
|
23
23
|
elif command -v xxd &>/dev/null; then
|
|
24
24
|
random=$(head -c 4 /dev/urandom | xxd -p)
|
|
25
25
|
elif command -v od &>/dev/null; then
|
|
26
|
-
random=$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' \n')
|
|
26
|
+
random=$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' \n') || true
|
|
27
27
|
else
|
|
28
28
|
# Fallback: use $RANDOM (less entropy but portable)
|
|
29
|
-
random=$(printf '%04x%04x' $RANDOM $RANDOM | head -c 8)
|
|
29
|
+
random=$(printf '%04x%04x' $RANDOM $RANDOM | head -c 8) || true
|
|
30
30
|
fi
|
|
31
31
|
|
|
32
32
|
echo "ralph-${timestamp}-${random}"
|