@the-bearded-bear/claude-craft 8.26.0-next.2a54921 → 8.26.0-next.4097a70

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"
@@ -349,30 +369,11 @@ handle_max_compacts_reached() {
349
369
  create_continuation_session() {
350
370
  local previous_session="$1"
351
371
 
352
- # Generate new session ID with portable random suffix generation
353
- local timestamp=$(date +%Y%m%d_%H%M%S)
354
- local random_suffix
355
-
356
- # Use utils function if available, otherwise fallback
357
- if type generate_random_suffix &>/dev/null; then
358
- random_suffix=$(generate_random_suffix 4)
359
- elif command -v xxd &>/dev/null; then
360
- random_suffix=$(head -c 4 /dev/urandom | xxd -p)
361
- elif command -v od &>/dev/null; then
362
- random_suffix=$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' \n')
363
- else
364
- # Fallback: use $RANDOM (less entropy but portable)
365
- random_suffix=$(printf '%04x%04x' $RANDOM $RANDOM | head -c 8)
366
- fi
367
-
368
- local new_session_id="ralph-cont-${timestamp}-${random_suffix}"
369
-
370
- # Create session directory if create_session is available
371
- if type create_session &>/dev/null; then
372
- if ! create_session "$new_session_id" 2>/dev/null; then
373
- print_warning "Failed to create session directory for $new_session_id"
374
- fi
375
- fi
372
+ # create_session takes a prompt and prints the id it created (with its
373
+ # state.json and session.log): the id must come from it, or the session
374
+ # directory is an orphan and the continuation id has no state.
375
+ local new_session_id
376
+ new_session_id=$(create_session "Continuation of $previous_session") || return 1
376
377
 
377
378
  # Link sessions in state files (with error handling)
378
379
  if type update_session_state &>/dev/null; then
@@ -385,10 +386,6 @@ create_continuation_session() {
385
386
  link_session_to_progress "$new_session_id" || print_verbose "Failed to link session to progress"
386
387
  fi
387
388
 
388
- # Reset context manager for new session
389
- CONTEXT_COMPACT_COUNT=0
390
- CONTEXT_CONTINUATION_COUNT=$((CONTEXT_CONTINUATION_COUNT)) # Keep continuation count
391
-
392
389
  echo "$new_session_id"
393
390
  }
394
391
 
@@ -404,26 +401,28 @@ check_preventive_compact() {
404
401
  return 1
405
402
  fi
406
403
 
404
+ # Nothing to measure before the first Claude call of this session.
405
+ local claude_uuid
406
+ claude_uuid=$(get_claude_session_uuid "$session_id")
407
+ [[ -z "$claude_uuid" ]] && return 1
408
+
407
409
  # Try to get context usage from Claude CLI
408
410
  # Note: This is a best-effort check, Claude may not always report usage
409
411
  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
412
+ context_info=$(probe_context_usage "$claude_uuid")
415
413
 
416
414
  # Try to extract percentage from output
417
415
  local usage
418
- usage=$(echo "$context_info" | grep -oP '\d+(?=%)' | head -1)
416
+ usage=$(echo "$context_info" | grep -oP '\d+(?=%)' | head -1 || true)
419
417
 
420
418
  if [[ -n "$usage" && "$usage" -ge "$CONTEXT_PREVENTIVE_THRESHOLD" ]]; then
421
- print_warning "${MSG_CONTEXT_PREVENTIVE:-Context at ${usage}% - running preventive compact}"
419
+ local msg="${MSG_CONTEXT_PREVENTIVE:-Context at ${usage}% - running preventive compact}"
420
+ print_warning "${msg//\{0\}/$usage}"
422
421
  log_session "$session_id" "INFO" "Preventive compact triggered at ${usage}%"
423
422
 
424
423
  # Save progress before preventive compact
425
424
  if type save_sprint_progress &>/dev/null; then
426
- save_sprint_progress "$session_id"
425
+ save_sprint_progress "$session_id" || print_verbose "No sprint progress file to save"
427
426
  fi
428
427
 
429
428
  run_auto_compact "$session_id"
@@ -437,23 +436,15 @@ check_preventive_compact() {
437
436
  # Context Continuation Prompt
438
437
  # =============================================================================
439
438
 
439
+ # Print the continuation prompt stored by a real compaction, if any.
440
+ # Callers using `$(...)` must reset CONTEXT_CONTINUATION_PROMPT themselves.
440
441
  get_context_continuation_prompt() {
441
- # Return the stored continuation prompt if available
442
442
  if [[ -n "$CONTEXT_CONTINUATION_PROMPT" ]]; then
443
443
  echo "$CONTEXT_CONTINUATION_PROMPT"
444
- # Clear after use
445
444
  CONTEXT_CONTINUATION_PROMPT=""
446
445
  return 0
447
446
  fi
448
447
 
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
448
  return 1
458
449
  }
459
450
 
@@ -56,71 +56,75 @@ reconstruct_context() {
56
56
  # Section Builders
57
57
  # =============================================================================
58
58
 
59
+ # Headings and sentences come from Tools/i18n/ralph/<lang>.sh (MSG_SECTION_*):
60
+ # these sections are sent to Claude and must follow --lang.
61
+
59
62
  build_progress_section() {
60
- echo "## Sprint Progress"
63
+ echo "${MSG_SECTION_PROGRESS:-## Sprint Progress}"
61
64
  echo ""
62
65
 
63
66
  if [[ -f "$SPRINT_PROGRESS_FILE" ]]; then
64
67
  # Extract key sections only (not the entire file)
65
- echo "### Current State"
66
- grep -A4 "^## Current State" "$SPRINT_PROGRESS_FILE" 2>/dev/null | tail -n +2 || echo "- Unknown"
68
+ echo "${MSG_SECTION_CURRENT_STATE:-### Current State}"
69
+ grep -A4 "^## Current State" "$SPRINT_PROGRESS_FILE" 2>/dev/null | tail -n +2 || echo "- ${MSG_SECTION_UNKNOWN:-Unknown}"
67
70
 
68
71
  echo ""
69
- echo "### Completed Tasks"
72
+ echo "${MSG_SECTION_COMPLETED_TASKS:-### Completed Tasks}"
70
73
  # Show last 5 completed tasks
71
- grep '^\- \[x\]' "$SPRINT_PROGRESS_FILE" 2>/dev/null | tail -5 || echo "- None yet"
74
+ grep '^\- \[x\]' "$SPRINT_PROGRESS_FILE" 2>/dev/null | tail -5 || echo "- ${MSG_SECTION_NONE_YET:-None yet}"
72
75
 
73
76
  echo ""
74
- echo "### Key Decisions"
75
- grep -A10 "^## Key Decisions" "$SPRINT_PROGRESS_FILE" 2>/dev/null | grep '^-' | head -5 || echo "- None recorded"
77
+ echo "${MSG_SECTION_KEY_DECISIONS:-### Key Decisions}"
78
+ grep -A10 "^## Key Decisions" "$SPRINT_PROGRESS_FILE" 2>/dev/null | grep '^-' | head -5 || echo "- ${MSG_SECTION_NONE_RECORDED:-None recorded}"
76
79
  else
77
- echo "No sprint progress file found."
80
+ echo "${MSG_SECTION_NO_PROGRESS_FILE:-No sprint progress file found.}"
78
81
  fi
79
82
  }
80
83
 
81
84
  build_git_section() {
82
- echo "## Recent Changes"
85
+ echo "${MSG_SECTION_GIT:-## Recent Changes}"
83
86
  echo ""
84
87
 
85
88
  # Check if we're in a git repository
86
89
  if ! git rev-parse --git-dir > /dev/null 2>&1; then
87
- echo "Not in a git repository."
90
+ echo "${MSG_SECTION_NOT_GIT:-Not in a git repository.}"
88
91
  return
89
92
  fi
90
93
 
91
94
  # Get summary of changes - check commit count first
92
- echo "### Modified Files (recent commits)"
95
+ echo "${MSG_SECTION_MODIFIED_FILES:-### Modified Files (recent commits)}"
93
96
  local commit_count
94
97
  commit_count=$(git rev-list --count HEAD 2>/dev/null || echo "0")
95
98
 
99
+ local no_changes="${MSG_SECTION_NO_RECENT_CHANGES:-No recent changes}"
96
100
  if [[ "$commit_count" -ge 5 ]]; then
97
- git diff --stat HEAD~5 2>/dev/null | tail -15 || echo "No recent changes"
101
+ git diff --stat HEAD~5 2>/dev/null | tail -15 || echo "$no_changes"
98
102
  elif [[ "$commit_count" -gt 0 ]]; then
99
103
  # Show diff from initial commit if less than 5 commits
100
- git diff --stat "$(git rev-list --max-parents=0 HEAD 2>/dev/null)" 2>/dev/null | tail -15 || echo "No recent changes"
104
+ git diff --stat "$(git rev-list --max-parents=0 HEAD 2>/dev/null)" 2>/dev/null | tail -15 || echo "$no_changes"
101
105
  else
102
- echo "No commits yet"
106
+ echo "${MSG_SECTION_NO_COMMITS:-No commits yet}"
103
107
  fi
104
108
 
105
109
  echo ""
106
- echo "### Recent Commits"
107
- git log --oneline -5 2>/dev/null || echo "No commits found"
110
+ echo "${MSG_SECTION_RECENT_COMMITS:-### Recent Commits}"
111
+ git log --oneline -5 2>/dev/null || echo "${MSG_SECTION_NO_COMMITS:-No commits yet}"
108
112
 
109
113
  echo ""
110
- echo "### Uncommitted Changes"
114
+ echo "${MSG_SECTION_UNCOMMITTED:-### Uncommitted Changes}"
111
115
  local status
112
- status=$(git status --porcelain 2>/dev/null | head -10)
116
+ status=$(git status --porcelain 2>/dev/null | head -10) || true
113
117
  if [[ -n "$status" ]]; then
114
118
  echo "\`\`\`"
115
119
  echo "$status"
116
120
  echo "\`\`\`"
117
121
  else
118
- echo "No uncommitted changes."
122
+ echo "${MSG_SECTION_NO_UNCOMMITTED:-No uncommitted changes.}"
119
123
  fi
120
124
  }
121
125
 
122
126
  build_test_section() {
123
- echo "## Test Status"
127
+ echo "${MSG_SECTION_TESTS:-## Test Status}"
124
128
  echo ""
125
129
 
126
130
  # Try to detect test framework
@@ -149,7 +153,7 @@ build_test_section() {
149
153
  fi
150
154
 
151
155
  if [[ "$framework_detected" == "false" ]]; then
152
- echo "- Framework: Unknown"
156
+ echo "- Framework: ${MSG_SECTION_UNKNOWN:-Unknown}"
153
157
  fi
154
158
 
155
159
  # Check sprint progress for recorded test status
@@ -158,13 +162,13 @@ build_test_section() {
158
162
  unit_status=$(grep -oP 'Unit: \K.*' "$SPRINT_PROGRESS_FILE" 2>/dev/null || echo "unknown")
159
163
  local integration_status
160
164
  integration_status=$(grep -oP 'Integration: \K.*' "$SPRINT_PROGRESS_FILE" 2>/dev/null || echo "unknown")
161
- echo "- Unit Tests: $unit_status"
162
- echo "- Integration Tests: $integration_status"
165
+ echo "- ${MSG_SECTION_UNIT_TESTS:-Unit Tests}: $unit_status"
166
+ echo "- ${MSG_SECTION_INTEGRATION_TESTS:-Integration Tests}: $integration_status"
163
167
  fi
164
168
  }
165
169
 
166
170
  build_current_task_section() {
167
- echo "## Current Task"
171
+ echo "${MSG_SECTION_TASK:-## Current Task}"
168
172
  echo ""
169
173
 
170
174
  # Check for current task file
@@ -179,15 +183,16 @@ build_current_task_section() {
179
183
  local phase=$(grep -oP '\*\*Phase\*\*: \K.*' "$SPRINT_PROGRESS_FILE" 2>/dev/null)
180
184
 
181
185
  if [[ -n "$current_task" && "$current_task" != "none" ]]; then
182
- echo "**Task**: $current_task"
183
- echo "**Phase**: ${phase:-unknown}"
186
+ echo "**${MSG_SECTION_TASK_LABEL:-Task}**: $current_task"
187
+ echo "**${MSG_SPRINT_PHASE:-Phase}**: ${phase:-unknown}"
184
188
  echo ""
185
- echo "Continue from where you left off in the $phase phase."
189
+ local msg="${MSG_SECTION_CONTINUE_PHASE:-"Continue from where you left off in the {0} phase."}"
190
+ echo "${msg//\{0\}/"$phase"}"
186
191
  else
187
- echo "No current task in progress."
192
+ echo "${MSG_SECTION_NO_TASK:-No current task in progress.}"
188
193
  fi
189
194
  else
190
- echo "No current task information available."
195
+ echo "${MSG_SECTION_NO_TASK_INFO:-No current task information available.}"
191
196
  fi
192
197
  }
193
198
 
@@ -195,34 +200,16 @@ build_current_task_section() {
195
200
  # Continuation Prompt Generation
196
201
  # =============================================================================
197
202
 
203
+ # Templates come from Tools/i18n/ralph/<lang>.sh (MSG_PROMPT_*), so the
204
+ # prompt sent to Claude follows --lang. Replacements are quoted: bash 5.2
205
+ # would otherwise expand `&` in the context to the matched placeholder.
198
206
  generate_continuation_prompt() {
199
207
  local session_id="${SESSION_ID:-unknown}"
200
- local context=$(reconstruct_context "$session_id")
201
-
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
208
+ local context
209
+ context=$(reconstruct_context "$session_id")
209
210
 
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
211
+ local prompt="${MSG_PROMPT_CONTINUATION:-"{0}"}"
212
+ echo "${prompt//\{0\}/"$context"}"
226
213
  }
227
214
 
228
215
  # =============================================================================
@@ -231,32 +218,12 @@ EOF
231
218
 
232
219
  generate_session_handoff_prompt() {
233
220
  local previous_session="$1"
234
- local context=$(reconstruct_context "$previous_session")
221
+ local context
222
+ context=$(reconstruct_context "$previous_session")
235
223
 
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
224
+ local prompt="${MSG_PROMPT_HANDOFF:-"{1}"}"
225
+ prompt="${prompt//\{0\}/"$previous_session"}"
226
+ echo "${prompt//\{1\}/"$context"}"
260
227
  }
261
228
 
262
229
  # =============================================================================
@@ -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)