@the-bearded-bear/claude-craft 8.26.0-next.2a54921 → 8.26.0-next.36e6dd8

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.
@@ -51,6 +51,12 @@ Führt Claude in einer kontinuierlichen Schleife aus, bis die Aufgabe abgeschlos
51
51
  - Maximale Iterationen, Timeouts, DoD-Kriterien festlegen
52
52
  - Hooks initialisieren, falls aktiviert
53
53
 
54
+ #### Isolierung von Sessions und parallelen Workern
55
+
56
+ - **Explizite Claude-Session**: Jede Ralph-Session legt ihre eigene Claude-Konversations-ID fest — `claude --session-id <uuid>` in der ersten Iteration, danach `claude --resume <uuid>`. Die UUID wird in `.ralph/sessions/<session>/claude_session_id` gespeichert, sodass `--continue=<session>` die richtige Konversation fortsetzt. Ralph verwendet `claude --continue` nicht mehr, das „die neueste Konversation im Verzeichnis“ fortsetzte (geteilt zwischen Workern oder sogar mit der interaktiven Session des Benutzers).
57
+ - **Ein Git-Worktree pro Worker** (paralleler Modus, `lib/parallel-manager.sh`): Jede Story läuft in einem eigenen Worktree `.ralph/parallel/worktrees/<session>` auf dem Branch `ralph/<session>`, mit ihrem Ralph-Zustand unter `.ralph/parallel/sessions/<session>/ralph/`. Am Ende des Laufs werden verbleibende Änderungen als `wip(ralph): <story>` auf diesem Branch committet, danach wird der Worktree entfernt (`git worktree remove`). Schlägt dieser Commit fehl, bleibt der Worktree erhalten (kein Datenverlust) und wird in `output.log` gemeldet. Die Arbeit wird durch Mergen der `ralph/*`-Branches übernommen.
58
+ - **Außerhalb eines Git-Repositorys**: kein Worktree; der Worker läuft im aktuellen Verzeichnis (eine Warnung wird ausgegeben), die Isolierung der Claude-Sessions bleibt bestehen.
59
+
54
60
  ### 2. Hauptschleife mit Dashboard
55
61
 
56
62
  ```
@@ -51,6 +51,12 @@ Execute Claude in a continuous loop until the task is complete or the Definition
51
51
  - Set max iterations, timeouts, DoD criteria
52
52
  - Initialize hooks if enabled
53
53
 
54
+ #### Session and parallel worker isolation
55
+
56
+ - **Explicit Claude session**: each Ralph session pins its own Claude conversation ID — `claude --session-id <uuid>` on the first iteration, then `claude --resume <uuid>`. The UUID is stored in `.ralph/sessions/<session>/claude_session_id`, so `--continue=<session>` resumes the right conversation. Ralph no longer uses `claude --continue`, which resumed "the most recent conversation in the directory" (shared between workers, or even with the user's interactive session).
57
+ - **One git worktree per worker** (parallel mode, `lib/parallel-manager.sh`): each story runs in a dedicated worktree `.ralph/parallel/worktrees/<session>` on branch `ralph/<session>`, with its Ralph state under `.ralph/parallel/sessions/<session>/ralph/`. At the end of the run, leftover changes are committed as `wip(ralph): <story>` on that branch, then the worktree is removed (`git worktree remove`). If that commit fails, the worktree is kept (no data loss) and reported in `output.log`. Collect the work by merging the `ralph/*` branches.
58
+ - **Outside a git repository**: no worktree; the worker runs in the current directory (a warning is printed), Claude session isolation still applies.
59
+
54
60
  ### 2. Main Loop with Dashboard
55
61
 
56
62
  ```
@@ -51,6 +51,12 @@ Ejecuta Claude en un bucle continuo hasta que la tarea esté completa o se cumpl
51
51
  - Establecer máximo de iteraciones, timeouts y criterios DoD
52
52
  - Inicializar hooks si están habilitados
53
53
 
54
+ #### Aislamiento de sesiones y workers paralelos
55
+
56
+ - **Sesión de Claude explícita**: cada sesión de Ralph fija su propio identificador de conversación de Claude — `claude --session-id <uuid>` en la primera iteración y luego `claude --resume <uuid>`. El UUID se guarda en `.ralph/sessions/<session>/claude_session_id`, de modo que `--continue=<session>` reanuda la conversación correcta. Ralph ya no usa `claude --continue`, que reanudaba «la conversación más reciente del directorio» (compartida entre workers, o incluso con la sesión interactiva del usuario).
57
+ - **Un worktree de git por worker** (modo paralelo, `lib/parallel-manager.sh`): cada historia se ejecuta en un worktree dedicado `.ralph/parallel/worktrees/<session>` sobre la rama `ralph/<session>`, con su estado de Ralph en `.ralph/parallel/sessions/<session>/ralph/`. Al final de la ejecución, los cambios pendientes se confirman como `wip(ralph): <story>` en esa rama y después se elimina el worktree (`git worktree remove`). Si ese commit falla, el worktree se conserva (sin pérdida de datos) y se indica en `output.log`. El trabajo se recupera fusionando las ramas `ralph/*`.
58
+ - **Fuera de un repositorio git**: sin worktree; el worker se ejecuta en el directorio actual (se muestra un aviso) y el aislamiento de sesiones de Claude se mantiene.
59
+
54
60
  ### 2. Bucle Principal con Dashboard
55
61
 
56
62
  ```
@@ -51,6 +51,12 @@ Exécuter Claude en boucle continue jusqu'à ce que la tâche soit terminée ou
51
51
  - Définir les itérations max, les timeouts, les critères DoD
52
52
  - Initialiser les hooks si activés
53
53
 
54
+ #### Isolation des sessions et des workers parallèles
55
+
56
+ - **Session Claude explicite** : chaque session Ralph fixe son propre identifiant de conversation Claude — `claude --session-id <uuid>` à la première itération, puis `claude --resume <uuid>`. L'UUID est stocké dans `.ralph/sessions/<session>/claude_session_id`, ce qui permet à `--continue=<session>` de reprendre la bonne conversation. Ralph n'utilise plus `claude --continue`, qui reprenait « la conversation la plus récente du répertoire » (partagée entre workers, voire avec une session interactive de l'utilisateur).
57
+ - **Worktree git par worker** (mode parallèle, `lib/parallel-manager.sh`) : chaque story s'exécute dans un worktree dédié `.ralph/parallel/worktrees/<session>` sur la branche `ralph/<session>`, avec son état Ralph sous `.ralph/parallel/sessions/<session>/ralph/`. En fin de run, les modifications restantes sont commitées en `wip(ralph): <story>` sur cette branche, puis le worktree est retiré (`git worktree remove`). Si ce commit échoue, le worktree est conservé (aucune perte) et signalé dans `output.log`. Le travail se récupère en fusionnant les branches `ralph/*`.
58
+ - **Hors dépôt git** : pas de worktree ; le worker s'exécute dans le répertoire courant (avertissement affiché), l'isolation des sessions Claude restant assurée.
59
+
54
60
  ### 2. Boucle principale avec Dashboard
55
61
 
56
62
  ```
@@ -51,6 +51,12 @@ Executa o Claude em loop contínuo até que a tarefa seja concluída ou os crit
51
51
  - Definir máximo de iterações, timeouts e critérios de DoD
52
52
  - Inicializar hooks se habilitados
53
53
 
54
+ #### Isolamento de sessões e workers paralelos
55
+
56
+ - **Sessão Claude explícita**: cada sessão Ralph fixa seu próprio identificador de conversa Claude — `claude --session-id <uuid>` na primeira iteração e depois `claude --resume <uuid>`. O UUID é armazenado em `.ralph/sessions/<session>/claude_session_id`, para que `--continue=<session>` retome a conversa correta. O Ralph não usa mais `claude --continue`, que retomava "a conversa mais recente do diretório" (compartilhada entre workers, ou até com a sessão interativa do usuário).
57
+ - **Um worktree git por worker** (modo paralelo, `lib/parallel-manager.sh`): cada história é executada em um worktree dedicado `.ralph/parallel/worktrees/<session>` na branch `ralph/<session>`, com seu estado Ralph em `.ralph/parallel/sessions/<session>/ralph/`. No fim da execução, as alterações restantes são commitadas como `wip(ralph): <story>` nessa branch e o worktree é removido (`git worktree remove`). Se esse commit falhar, o worktree é mantido (sem perda de dados) e sinalizado em `output.log`. O trabalho é recuperado fazendo merge das branches `ralph/*`.
58
+ - **Fora de um repositório git**: sem worktree; o worker é executado no diretório atual (um aviso é exibido) e o isolamento das sessões Claude continua garantido.
59
+
54
60
  ### 2. Loop Principal com Painel
55
61
 
56
62
  ```
@@ -207,12 +207,12 @@ list_checkpoints() {
207
207
 
208
208
  # Find commits matching the checkpoint pattern
209
209
  local commits
210
- commits=$(git log --oneline --grep="checkpoint: Ralph" 2>/dev/null | head -20)
210
+ commits=$(git log --oneline --grep="checkpoint: Ralph" 2>/dev/null | head -20) || true
211
211
 
212
212
  if [[ -n "$commits" ]]; then
213
213
  echo "$commits" | while read -r line; do
214
- local hash=$(echo "$line" | cut -d' ' -f1)
215
- local msg=$(echo "$line" | cut -d' ' -f2-)
214
+ local hash=$(echo "$line" | cut -d' ' -f1) || true
215
+ local msg=$(echo "$line" | cut -d' ' -f2-) || true
216
216
  echo "{\"hash\":\"$hash\",\"message\":\"$msg\"}"
217
217
  done | jq -s '.'
218
218
  else
@@ -30,35 +30,35 @@ CB_LEARNING_MIN_SAMPLES=5
30
30
  # =============================================================================
31
31
  # Each profile defines thresholds optimized for different task types
32
32
 
33
- declare -A CB_PROFILE_QUICK_FIX=(
33
+ declare -gA CB_PROFILE_QUICK_FIX=(
34
34
  ["no_changes"]=2
35
35
  ["errors"]=3
36
36
  ["max_iterations"]=10
37
37
  ["keywords"]="fix bug typo hotfix patch"
38
38
  )
39
39
 
40
- declare -A CB_PROFILE_SMALL_FEATURE=(
40
+ declare -gA CB_PROFILE_SMALL_FEATURE=(
41
41
  ["no_changes"]=3
42
42
  ["errors"]=4
43
43
  ["max_iterations"]=15
44
44
  ["keywords"]="add implement small simple"
45
45
  )
46
46
 
47
- declare -A CB_PROFILE_MEDIUM_FEATURE=(
47
+ declare -gA CB_PROFILE_MEDIUM_FEATURE=(
48
48
  ["no_changes"]=4
49
49
  ["errors"]=6
50
50
  ["max_iterations"]=25
51
51
  ["keywords"]="feature create with tests"
52
52
  )
53
53
 
54
- declare -A CB_PROFILE_LARGE_FEATURE=(
54
+ declare -gA CB_PROFILE_LARGE_FEATURE=(
55
55
  ["no_changes"]=5
56
56
  ["errors"]=8
57
57
  ["max_iterations"]=50
58
58
  ["keywords"]="refactor migrate complex large"
59
59
  )
60
60
 
61
- declare -A CB_PROFILE_EXPLORATION=(
61
+ declare -gA CB_PROFILE_EXPLORATION=(
62
62
  ["no_changes"]=10
63
63
  ["errors"]=15
64
64
  ["max_iterations"]=100
@@ -66,7 +66,7 @@ declare -A CB_PROFILE_EXPLORATION=(
66
66
  )
67
67
 
68
68
  # Autonomous profile for overnight/unattended runs
69
- declare -A CB_PROFILE_AUTONOMOUS=(
69
+ declare -gA CB_PROFILE_AUTONOMOUS=(
70
70
  ["no_changes"]=5
71
71
  ["errors"]=8
72
72
  ["max_iterations"]=50
@@ -162,7 +162,7 @@ init_circuit_breaker() {
162
162
  detect_task_profile() {
163
163
  local prompt="$1"
164
164
  local prompt_lower
165
- prompt_lower=$(echo "$prompt" | tr '[:upper:]' '[:lower:]')
165
+ prompt_lower=$(echo "$prompt" | tr '[:upper:]' '[:lower:]') || true
166
166
 
167
167
  # Check each profile's keywords
168
168
  local profiles=("quick_fix" "small_feature" "medium_feature" "large_feature" "exploration" "autonomous")
@@ -265,7 +265,7 @@ apply_learned_adjustments() {
265
265
  ' "$history_file" 2>/dev/null)
266
266
 
267
267
  local samples
268
- samples=$(echo "$profile_data" | jq -r '.samples // 0')
268
+ samples=$(echo "$profile_data" | jq -r '.samples // 0') || samples=0
269
269
 
270
270
  if [[ $samples -lt $CB_LEARNING_MIN_SAMPLES ]]; then
271
271
  print_verbose "Not enough samples for learning ($samples < $CB_LEARNING_MIN_SAMPLES)"
@@ -274,10 +274,10 @@ apply_learned_adjustments() {
274
274
 
275
275
  # Apply adjustments based on historical success rate
276
276
  local success_rate
277
- success_rate=$(echo "$profile_data" | jq -r '.success_rate // 0')
277
+ success_rate=$(echo "$profile_data" | jq -r '.success_rate // 0') || success_rate=0
278
278
 
279
279
  local avg_iterations
280
- avg_iterations=$(echo "$profile_data" | jq -r '.avg_iterations // 0')
280
+ avg_iterations=$(echo "$profile_data" | jq -r '.avg_iterations // 0') || avg_iterations=0
281
281
 
282
282
  # If success rate is low, increase thresholds
283
283
  if [[ $success_rate -lt 50 ]]; then
@@ -32,6 +32,9 @@ IFS=' ' read -r -a CLAUDE_CMD <<< "$CLAUDE_COMMAND"
32
32
  CONTEXT_COMPACT_COUNT=0
33
33
  CONTEXT_CONTINUATION_COUNT=0
34
34
  CONTEXT_CONTINUATION_PROMPT=""
35
+ # Set by run_auto_compact (return 3) / handle_max_compacts_reached: call them
36
+ # in this shell, never in `$(...)`, or the counters above are lost.
37
+ CONTEXT_NEW_SESSION_ID=""
35
38
 
36
39
  # =============================================================================
37
40
  # Context Limit Detection
@@ -106,6 +109,17 @@ init_context_manager() {
106
109
  print_verbose " - Smart reconstruction: $CONTEXT_SMART_RECONSTRUCTION"
107
110
  }
108
111
 
112
+ # Print the first lines of `/context` for a Claude session UUID.
113
+ # Best effort: prints nothing on failure, always succeeds (set -e safe).
114
+ probe_context_usage() {
115
+ local claude_uuid="$1"
116
+ if command -v timeout &> /dev/null; then
117
+ timeout 10s "${CLAUDE_CMD[@]}" --resume "$claude_uuid" -p "/context" 2>/dev/null | head -5 || true
118
+ else
119
+ "${CLAUDE_CMD[@]}" --resume "$claude_uuid" -p "/context" 2>/dev/null | head -5 || true
120
+ fi
121
+ }
122
+
109
123
  # =============================================================================
110
124
  # Auto-Compact
111
125
  # =============================================================================
@@ -120,20 +134,27 @@ run_auto_compact() {
120
134
  return 1
121
135
  fi
122
136
 
137
+ # /context and /compact must target this Ralph session's Claude session,
138
+ # never "the most recent conversation in this directory" (--continue).
139
+ local claude_uuid
140
+ claude_uuid=$(get_claude_session_uuid "$session_id")
141
+ if [[ -z "$claude_uuid" ]]; then
142
+ log_session "$session_id" "WARN" "No Claude session yet, nothing to compact"
143
+ return 1
144
+ fi
145
+
123
146
  # Check if context is above minimum threshold before compacting
124
147
  if [[ "$CONTEXT_MIN_THRESHOLD" -gt 0 ]]; then
125
148
  local context_info
126
- if command -v timeout &> /dev/null; then
127
- context_info=$(timeout 10s "${CLAUDE_CMD[@]}" -p "/context" 2>/dev/null | head -5)
128
- else
129
- context_info=$("${CLAUDE_CMD[@]}" -p "/context" 2>/dev/null | head -5)
130
- fi
149
+ context_info=$(probe_context_usage "$claude_uuid")
131
150
  local current_usage
132
- current_usage=$(echo "$context_info" | grep -oP '\d+(?=%)' | head -1)
151
+ current_usage=$(echo "$context_info" | grep -oP '\d+(?=%)' | head -1 || true)
133
152
 
134
153
  if [[ -n "$current_usage" && "$current_usage" -lt "$CONTEXT_MIN_THRESHOLD" ]]; then
135
154
  log_session "$session_id" "INFO" "Context at ${current_usage}% - below minimum threshold (${CONTEXT_MIN_THRESHOLD}%), skipping compact"
136
- print_verbose "Context at ${current_usage}% - below min threshold (${CONTEXT_MIN_THRESHOLD}%), skipping compact"
155
+ local msg="${MSG_CONTEXT_BELOW_MIN:-"Context at {0}% - below minimum threshold ({1}%), skipping compact"}"
156
+ msg="${msg//\{0\}/"$current_usage"}"
157
+ print_verbose "${msg//\{1\}/"$CONTEXT_MIN_THRESHOLD"}"
137
158
  return 1
138
159
  fi
139
160
  fi
@@ -144,13 +165,12 @@ run_auto_compact() {
144
165
  print_warning "${MSG_CONTEXT_MAX_REACHED:-Maximum compacts reached} ($CONTEXT_MAX_COMPACTS)"
145
166
 
146
167
  # Handle overflow strategy
147
- local overflow_result
148
- overflow_result=$(handle_max_compacts_reached "$session_id")
149
- local overflow_status=$?
168
+ local overflow_status=0
169
+ CONTEXT_NEW_SESSION_ID=""
170
+ handle_max_compacts_reached "$session_id" || overflow_status=$?
150
171
 
151
- if [[ $overflow_status -eq 0 && -n "$overflow_result" ]]; then
152
- # New session ID returned - signal continuation
153
- echo "$overflow_result"
172
+ if [[ $overflow_status -eq 0 && -n "$CONTEXT_NEW_SESSION_ID" ]]; then
173
+ # New session ID in CONTEXT_NEW_SESSION_ID - signal continuation
154
174
  return 3 # Special return code for session continuation
155
175
  fi
156
176
 
@@ -185,11 +205,10 @@ run_auto_compact() {
185
205
 
186
206
  # Execute /compact via Claude CLI
187
207
  local compact_output
188
- local compact_status
208
+ local compact_status=0
189
209
 
190
210
  if command -v timeout &> /dev/null; then
191
- compact_output=$(timeout 60s "${CLAUDE_CMD[@]}" --continue -p "/compact" 2>&1)
192
- compact_status=$?
211
+ compact_output=$(timeout 60s "${CLAUDE_CMD[@]}" --resume "$claude_uuid" -p "/compact" 2>&1) || compact_status=$?
193
212
 
194
213
  # Handle timeout specifically (exit code 124)
195
214
  if [[ $compact_status -eq 124 ]]; then
@@ -198,8 +217,7 @@ run_auto_compact() {
198
217
  return 1
199
218
  fi
200
219
  else
201
- compact_output=$("${CLAUDE_CMD[@]}" --continue -p "/compact" 2>&1)
202
- compact_status=$?
220
+ compact_output=$("${CLAUDE_CMD[@]}" --resume "$claude_uuid" -p "/compact" 2>&1) || compact_status=$?
203
221
  fi
204
222
 
205
223
  if [[ $compact_status -eq 0 ]]; then
@@ -313,10 +331,12 @@ handle_max_compacts_reached() {
313
331
  new_session_id=$(create_continuation_session "$session_id")
314
332
 
315
333
  if [[ -n "$new_session_id" ]]; then
334
+ # Reset here: create_continuation_session ran in a subshell
335
+ CONTEXT_COMPACT_COUNT=0
316
336
  CONTEXT_CONTINUATION_COUNT=$((CONTEXT_CONTINUATION_COUNT + 1))
317
337
  print_success "${MSG_CONTEXT_CONTINUATION_CREATED:-Continuation session created}: $new_session_id"
318
338
  log_session "$session_id" "INFO" "Created continuation session: $new_session_id"
319
- echo "$new_session_id"
339
+ CONTEXT_NEW_SESSION_ID="$new_session_id"
320
340
  return 0
321
341
  else
322
342
  print_error "Failed to create continuation session"
@@ -359,10 +379,10 @@ create_continuation_session() {
359
379
  elif command -v xxd &>/dev/null; then
360
380
  random_suffix=$(head -c 4 /dev/urandom | xxd -p)
361
381
  elif command -v od &>/dev/null; then
362
- random_suffix=$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' \n')
382
+ random_suffix=$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' \n') || true
363
383
  else
364
384
  # Fallback: use $RANDOM (less entropy but portable)
365
- random_suffix=$(printf '%04x%04x' $RANDOM $RANDOM | head -c 8)
385
+ random_suffix=$(printf '%04x%04x' $RANDOM $RANDOM | head -c 8) || true
366
386
  fi
367
387
 
368
388
  local new_session_id="ralph-cont-${timestamp}-${random_suffix}"
@@ -385,10 +405,6 @@ create_continuation_session() {
385
405
  link_session_to_progress "$new_session_id" || print_verbose "Failed to link session to progress"
386
406
  fi
387
407
 
388
- # Reset context manager for new session
389
- CONTEXT_COMPACT_COUNT=0
390
- CONTEXT_CONTINUATION_COUNT=$((CONTEXT_CONTINUATION_COUNT)) # Keep continuation count
391
-
392
408
  echo "$new_session_id"
393
409
  }
394
410
 
@@ -404,26 +420,28 @@ check_preventive_compact() {
404
420
  return 1
405
421
  fi
406
422
 
423
+ # Nothing to measure before the first Claude call of this session.
424
+ local claude_uuid
425
+ claude_uuid=$(get_claude_session_uuid "$session_id")
426
+ [[ -z "$claude_uuid" ]] && return 1
427
+
407
428
  # Try to get context usage from Claude CLI
408
429
  # Note: This is a best-effort check, Claude may not always report usage
409
430
  local context_info
410
- if command -v timeout &> /dev/null; then
411
- context_info=$(timeout 10s "${CLAUDE_CMD[@]}" -p "/context" 2>/dev/null | head -5)
412
- else
413
- context_info=$("${CLAUDE_CMD[@]}" -p "/context" 2>/dev/null | head -5)
414
- fi
431
+ context_info=$(probe_context_usage "$claude_uuid")
415
432
 
416
433
  # Try to extract percentage from output
417
434
  local usage
418
- usage=$(echo "$context_info" | grep -oP '\d+(?=%)' | head -1)
435
+ usage=$(echo "$context_info" | grep -oP '\d+(?=%)' | head -1 || true)
419
436
 
420
437
  if [[ -n "$usage" && "$usage" -ge "$CONTEXT_PREVENTIVE_THRESHOLD" ]]; then
421
- print_warning "${MSG_CONTEXT_PREVENTIVE:-Context at ${usage}% - running preventive compact}"
438
+ local msg="${MSG_CONTEXT_PREVENTIVE:-Context at ${usage}% - running preventive compact}"
439
+ print_warning "${msg//\{0\}/$usage}"
422
440
  log_session "$session_id" "INFO" "Preventive compact triggered at ${usage}%"
423
441
 
424
442
  # Save progress before preventive compact
425
443
  if type save_sprint_progress &>/dev/null; then
426
- save_sprint_progress "$session_id"
444
+ save_sprint_progress "$session_id" || print_verbose "No sprint progress file to save"
427
445
  fi
428
446
 
429
447
  run_auto_compact "$session_id"
@@ -437,23 +455,15 @@ check_preventive_compact() {
437
455
  # Context Continuation Prompt
438
456
  # =============================================================================
439
457
 
458
+ # Print the continuation prompt stored by a real compaction, if any.
459
+ # Callers using `$(...)` must reset CONTEXT_CONTINUATION_PROMPT themselves.
440
460
  get_context_continuation_prompt() {
441
- # Return the stored continuation prompt if available
442
461
  if [[ -n "$CONTEXT_CONTINUATION_PROMPT" ]]; then
443
462
  echo "$CONTEXT_CONTINUATION_PROMPT"
444
- # Clear after use
445
463
  CONTEXT_CONTINUATION_PROMPT=""
446
464
  return 0
447
465
  fi
448
466
 
449
- # Generate fresh if smart reconstruction is enabled
450
- if [[ "$CONTEXT_SMART_RECONSTRUCTION" == "true" ]]; then
451
- if type generate_continuation_prompt &>/dev/null; then
452
- generate_continuation_prompt
453
- return 0
454
- fi
455
- fi
456
-
457
467
  return 1
458
468
  }
459
469
 
@@ -109,7 +109,7 @@ build_git_section() {
109
109
  echo ""
110
110
  echo "### Uncommitted Changes"
111
111
  local status
112
- status=$(git status --porcelain 2>/dev/null | head -10)
112
+ status=$(git status --porcelain 2>/dev/null | head -10) || true
113
113
  if [[ -n "$status" ]]; then
114
114
  echo "\`\`\`"
115
115
  echo "$status"
@@ -195,34 +195,16 @@ build_current_task_section() {
195
195
  # Continuation Prompt Generation
196
196
  # =============================================================================
197
197
 
198
+ # Templates come from Tools/i18n/ralph/<lang>.sh (MSG_PROMPT_*), so the
199
+ # prompt sent to Claude follows --lang. Replacements are quoted: bash 5.2
200
+ # would otherwise expand `&` in the context to the matched placeholder.
198
201
  generate_continuation_prompt() {
199
202
  local session_id="${SESSION_ID:-unknown}"
200
- local context=$(reconstruct_context "$session_id")
203
+ local context
204
+ context=$(reconstruct_context "$session_id")
201
205
 
202
- cat << EOF
203
- # Continuation du Sprint
204
-
205
- Vous reprenez le travail sur ce sprint apres une compaction de contexte.
206
- Le fichier sprint-progress.md contient l'etat complet du travail effectue.
207
-
208
- $context
209
-
210
- ## Instructions
211
-
212
- 1. **Lisez** le progres ci-dessus pour comprendre l'etat actuel
213
- 2. **Verifiez** les fichiers modifies si necessaire
214
- 3. **Reprenez** la tache en cours la ou elle s'est arretee
215
- 4. **Continuez** le workflow TDD (RED -> GREEN -> REFACTOR)
216
- 5. **Mettez a jour** sprint-progress.md apres chaque tache terminee
217
-
218
- ## Commandes Importantes
219
-
220
- - Apres chaque tache: mettre a jour sprint-progress.md
221
- - Si blocker: noter dans la section Blockers
222
- - Si decision importante: ajouter dans Key Decisions
223
-
224
- Continuez l'implementation.
225
- EOF
206
+ local prompt="${MSG_PROMPT_CONTINUATION:-"{0}"}"
207
+ echo "${prompt//\{0\}/"$context"}"
226
208
  }
227
209
 
228
210
  # =============================================================================
@@ -231,32 +213,12 @@ EOF
231
213
 
232
214
  generate_session_handoff_prompt() {
233
215
  local previous_session="$1"
234
- local context=$(reconstruct_context "$previous_session")
216
+ local context
217
+ context=$(reconstruct_context "$previous_session")
235
218
 
236
- cat << EOF
237
- # Nouvelle Session de Continuation
238
-
239
- Cette session continue le travail de la session precedente ($previous_session).
240
- Le contexte a ete reinitialise mais le fichier sprint-progress.md contient tout le progres.
241
-
242
- $context
243
-
244
- ## Contexte de la Transition
245
-
246
- - Session precedente: $previous_session
247
- - Raison: Limite de contexte atteinte (max compacts)
248
- - Le travail a ete sauvegarde dans sprint-progress.md
249
- - Les fichiers sont dans l'etat du dernier checkpoint git
250
-
251
- ## Instructions
252
-
253
- 1. **Verifiez** l'etat actuel via git status
254
- 2. **Lisez** sprint-progress.md pour le contexte complet
255
- 3. **Identifiez** la tache en cours et son etat
256
- 4. **Continuez** exactement ou la session precedente s'est arretee
257
-
258
- Reprenez le travail.
259
- EOF
219
+ local prompt="${MSG_PROMPT_HANDOFF:-"{1}"}"
220
+ prompt="${prompt//\{0\}/"$previous_session"}"
221
+ echo "${prompt//\{1\}/"$context"}"
260
222
  }
261
223
 
262
224
  # =============================================================================
@@ -18,7 +18,7 @@ get_dod_template() {
18
18
 
19
19
  # Normalize project type (remove version suffixes)
20
20
  local base_type
21
- base_type=$(echo "$project_type" | sed 's/-[0-9.]*$//' | sed 's/-typescript$//')
21
+ base_type=$(echo "$project_type" | sed 's/-[0-9.]*$//' | sed 's/-typescript$//') || true
22
22
 
23
23
  # Map to template file
24
24
  local template_file=""
@@ -69,7 +69,7 @@ get_dod_template_path() {
69
69
  local templates_dir="${2:-$DOD_TEMPLATES_DIR}"
70
70
 
71
71
  local base_type
72
- base_type=$(echo "$project_type" | sed 's/-[0-9.]*$//' | sed 's/-typescript$//')
72
+ base_type=$(echo "$project_type" | sed 's/-[0-9.]*$//' | sed 's/-typescript$//') || true
73
73
 
74
74
  case "$base_type" in
75
75
  symfony|laravel|php)
@@ -9,7 +9,7 @@ set -euo pipefail
9
9
  DOD_COMPLETION_MARKER="${DOD_COMPLETION_MARKER:-<promise>COMPLETE</promise>}"
10
10
 
11
11
  # DoD validator types
12
- declare -A DOD_VALIDATORS=(
12
+ declare -gA DOD_VALIDATORS=(
13
13
  ["command"]="validate_command"
14
14
  ["output_contains"]="validate_output_contains"
15
15
  ["file_changed"]="validate_file_changed"
@@ -27,12 +27,12 @@ validate_dod() {
27
27
 
28
28
  # If no config, use simple completion marker check
29
29
  if [[ -z "$config_file" || ! -f "$config_file" ]]; then
30
- return $(validate_simple_completion "$output")
30
+ validate_simple_completion "$output"; return
31
31
  fi
32
32
 
33
33
  # Check if yq is available for YAML parsing
34
34
  if ! command -v yq &> /dev/null; then
35
- return $(validate_simple_completion "$output")
35
+ validate_simple_completion "$output"; return
36
36
  fi
37
37
 
38
38
  # Get DoD checklist from config
@@ -44,7 +44,7 @@ validate_dod() {
44
44
  if [[ -n "$marker" ]]; then
45
45
  DOD_COMPLETION_MARKER="$marker"
46
46
  fi
47
- return $(validate_simple_completion "$output")
47
+ validate_simple_completion "$output"; return
48
48
  fi
49
49
 
50
50
  # Process checklist items
@@ -52,14 +52,14 @@ validate_dod() {
52
52
  local results=()
53
53
 
54
54
  # Get count of checklist items
55
- local count=$(echo "$checklist" | yq e 'length' -)
55
+ local count=$(echo "$checklist" | yq e 'length' -) || true
56
56
 
57
57
  for ((i=0; i<count; i++)); do
58
58
  local item=$(yq e ".definition_of_done.checklist[$i]" "$config_file")
59
- local id=$(echo "$item" | yq e '.id' -)
60
- local name=$(echo "$item" | yq e '.name' -)
61
- local type=$(echo "$item" | yq e '.type' -)
62
- local required=$(echo "$item" | yq e '.required // true' -)
59
+ local id=$(echo "$item" | yq e '.id' -) || true
60
+ local name=$(echo "$item" | yq e '.name' -) || true
61
+ local type=$(echo "$item" | yq e '.type' -) || true
62
+ local required=$(echo "$item" | yq e '.required // true' -) || true
63
63
 
64
64
  # Validate based on type
65
65
  local passed=false
@@ -67,7 +67,7 @@ validate_dod() {
67
67
 
68
68
  case "$type" in
69
69
  command)
70
- local cmd=$(echo "$item" | yq e '.command' -)
70
+ local cmd=$(echo "$item" | yq e '.command' -) || true
71
71
  if validate_command "$cmd"; then
72
72
  passed=true
73
73
  message="Command passed"
@@ -76,7 +76,7 @@ validate_dod() {
76
76
  fi
77
77
  ;;
78
78
  output_contains)
79
- local pattern=$(echo "$item" | yq e '.pattern' -)
79
+ local pattern=$(echo "$item" | yq e '.pattern' -) || true
80
80
  if validate_output_contains "$output" "$pattern"; then
81
81
  passed=true
82
82
  message="Pattern found"
@@ -85,7 +85,7 @@ validate_dod() {
85
85
  fi
86
86
  ;;
87
87
  file_changed)
88
- local pattern=$(echo "$item" | yq e '.pattern' -)
88
+ local pattern=$(echo "$item" | yq e '.pattern' -) || true
89
89
  if validate_file_changed "$pattern"; then
90
90
  passed=true
91
91
  message="Files changed"
@@ -94,7 +94,7 @@ validate_dod() {
94
94
  fi
95
95
  ;;
96
96
  hook)
97
- local script=$(echo "$item" | yq e '.script' -)
97
+ local script=$(echo "$item" | yq e '.script' -) || true
98
98
  if validate_hook "$script" "$output"; then
99
99
  passed=true
100
100
  message="Hook passed"
@@ -103,7 +103,7 @@ validate_dod() {
103
103
  fi
104
104
  ;;
105
105
  human)
106
- local prompt=$(echo "$item" | yq e '.prompt // ""' -)
106
+ local prompt=$(echo "$item" | yq e '.prompt // ""' -) || true
107
107
  if validate_human "$name" "$prompt"; then
108
108
  passed=true
109
109
  message="Human approved"
@@ -209,13 +209,13 @@ validate_file_changed() {
209
209
 
210
210
  # Use git to check for changes matching pattern
211
211
  if command -v git &> /dev/null && git rev-parse --git-dir &> /dev/null; then
212
- local changes=$(git status --porcelain 2>/dev/null | grep -E "$pattern" | wc -l)
212
+ local changes=$(git status --porcelain 2>/dev/null | grep -E "$pattern" | wc -l) || true
213
213
  if [[ $changes -gt 0 ]]; then
214
214
  return 0
215
215
  fi
216
216
  else
217
217
  # Fallback: check for recently modified files
218
- local changes=$(find . -name "$pattern" -mmin -5 2>/dev/null | wc -l)
218
+ local changes=$(find . -name "$pattern" -mmin -5 2>/dev/null | wc -l) || true
219
219
  if [[ $changes -gt 0 ]]; then
220
220
  return 0
221
221
  fi
@@ -381,7 +381,7 @@ export_dod_status() {
381
381
 
382
382
  # Count checks
383
383
  local total
384
- total=$(echo "$checklist" | yq e 'length' -)
384
+ total=$(echo "$checklist" | yq e 'length' -) || total=0
385
385
 
386
386
  local required=0
387
387
  for ((i=0; i<total; i++)); do
@@ -424,7 +424,7 @@ get_dod_summary() {
424
424
 
425
425
  # Build summary
426
426
  local count
427
- count=$(echo "$checklist" | yq e 'length' -)
427
+ count=$(echo "$checklist" | yq e 'length' -) || count=0
428
428
 
429
429
  local checks="["
430
430
  for ((i=0; i<count; i++)); do