@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.
- package/Dev/i18n/de/Common/commands/ralph-run.md +6 -0
- package/Dev/i18n/en/Common/commands/ralph-run.md +6 -0
- package/Dev/i18n/es/Common/commands/ralph-run.md +6 -0
- package/Dev/i18n/fr/Common/commands/ralph-run.md +6 -0
- package/Dev/i18n/pt/Common/commands/ralph-run.md +6 -0
- 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 +166 -85
- 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 +88 -64
- 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 +107 -33
- package/Tools/i18n/ralph/en.sh +74 -0
- package/Tools/i18n/ralph/es.sh +151 -77
- package/Tools/i18n/ralph/fr.sh +176 -102
- package/Tools/i18n/ralph/pt.sh +169 -95
- package/package.json +1 -1
|
@@ -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 -
|
|
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 -
|
|
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 -
|
|
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 -
|
|
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 -
|
|
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 -
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
148
|
-
|
|
149
|
-
|
|
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 "$
|
|
152
|
-
# New session ID
|
|
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[@]}" --
|
|
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[@]}" --
|
|
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
|
-
|
|
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
|
-
#
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
107
|
-
git log --oneline -5 2>/dev/null || echo "No commits
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
183
|
-
echo "
|
|
186
|
+
echo "**${MSG_SECTION_TASK_LABEL:-Task}**: $current_task"
|
|
187
|
+
echo "**${MSG_SPRINT_PHASE:-Phase}**: ${phase:-unknown}"
|
|
184
188
|
echo ""
|
|
185
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
221
|
+
local context
|
|
222
|
+
context=$(reconstruct_context "$previous_session")
|
|
235
223
|
|
|
236
|
-
|
|
237
|
-
|
|
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)
|