agentic-loop 3.16.2 → 3.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/prd/SKILL.md +1 -1
- package/package.json +1 -1
- package/ralph/loop.sh +233 -20
- package/ralph/prd.sh +0 -3
- package/ralph/signs.sh +9 -2
- package/ralph/utils.sh +11 -6
|
@@ -101,7 +101,7 @@ Break the idea into small, executable stories:
|
|
|
101
101
|
|
|
102
102
|
- Each story completable in one Claude session (~10-15 min)
|
|
103
103
|
- Max 3-4 acceptance criteria per story
|
|
104
|
-
-
|
|
104
|
+
- No limit on story count — generate as many stories as the idea needs
|
|
105
105
|
- If appending, start IDs from the next available number
|
|
106
106
|
- **Each story must include its own `techStack`, `constraints`, and `contextFiles`.** Include only what's relevant to that story — don't copy-paste identical context into every story.
|
|
107
107
|
|
package/package.json
CHANGED
package/ralph/loop.sh
CHANGED
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
# shellcheck shell=bash
|
|
3
3
|
# loop.sh - The autonomous development loop
|
|
4
4
|
|
|
5
|
-
# Pre-
|
|
5
|
+
# Pre-loop checks to catch common issues before wasting iterations
|
|
6
6
|
preflight_checks() {
|
|
7
|
-
echo "
|
|
7
|
+
echo ""
|
|
8
|
+
echo " ┌──────────────────────────────────┐"
|
|
9
|
+
echo " │ ✅ Pre-Loop Checks │"
|
|
10
|
+
echo " └──────────────────────────────────┘"
|
|
11
|
+
echo ""
|
|
8
12
|
local warnings=0
|
|
9
13
|
|
|
10
14
|
# Check API connectivity if configured
|
|
@@ -86,7 +90,7 @@ preflight_checks() {
|
|
|
86
90
|
|
|
87
91
|
echo ""
|
|
88
92
|
if [[ $warnings -gt 0 ]]; then
|
|
89
|
-
print_warning "$warnings pre-
|
|
93
|
+
print_warning "$warnings pre-loop warning(s) - loop may fail on connectivity issues"
|
|
90
94
|
echo ""
|
|
91
95
|
read -r -p "Continue anyway? [Y/n] " response
|
|
92
96
|
if [[ "$response" =~ ^[Nn] ]]; then
|
|
@@ -96,6 +100,214 @@ preflight_checks() {
|
|
|
96
100
|
fi
|
|
97
101
|
}
|
|
98
102
|
|
|
103
|
+
# Check if failure context is trivial (lint/format-only retries)
|
|
104
|
+
# Returns 0 (trivial) if ALL error lines match trivial patterns
|
|
105
|
+
_is_trivial_failure() {
|
|
106
|
+
local context="$1"
|
|
107
|
+
|
|
108
|
+
# Count non-empty, non-whitespace lines
|
|
109
|
+
local total_lines
|
|
110
|
+
total_lines=$(printf '%s\n' "$context" | grep -cE '\S' || echo "0")
|
|
111
|
+
|
|
112
|
+
# If very short context, consider trivial
|
|
113
|
+
[[ "$total_lines" -lt 3 ]] && return 0
|
|
114
|
+
|
|
115
|
+
# Count error/warning/fail lines that do NOT match trivial patterns
|
|
116
|
+
# Trivial patterns: auto-fix, formatting tools, style-only issues
|
|
117
|
+
local non_trivial_errors
|
|
118
|
+
non_trivial_errors=$(printf '%s\n' "$context" | grep -iE '(error|warning|fail)' | \
|
|
119
|
+
grep -cviE '(auto.?fix|prettier|eslint --fix|trailing whitespace|import order|isort|black|ruff format|ruff check.*--fix|no-unused-vars|Missing semicolon|Expected indentation)' \
|
|
120
|
+
|| echo "0")
|
|
121
|
+
|
|
122
|
+
# Trivial if no error lines survive the trivial-pattern filter
|
|
123
|
+
[[ "$non_trivial_errors" -eq 0 ]] && return 0
|
|
124
|
+
|
|
125
|
+
return 1
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# Check if a proposed sign pattern is a duplicate of existing signs
|
|
129
|
+
# Returns 0 (is duplicate) if pattern is too similar to existing
|
|
130
|
+
_sign_is_duplicate() {
|
|
131
|
+
local pattern="$1"
|
|
132
|
+
|
|
133
|
+
[[ ! -f "$RALPH_DIR/signs.json" ]] && return 1
|
|
134
|
+
|
|
135
|
+
# Normalize: lowercase, strip punctuation
|
|
136
|
+
local normalized
|
|
137
|
+
normalized=$(printf '%s\n' "$pattern" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9 ]//g' | tr -s ' ')
|
|
138
|
+
|
|
139
|
+
# Check each existing sign
|
|
140
|
+
local existing_patterns
|
|
141
|
+
existing_patterns=$(jq -r '.signs[].pattern' "$RALPH_DIR/signs.json" 2>/dev/null)
|
|
142
|
+
|
|
143
|
+
while IFS= read -r existing; do
|
|
144
|
+
[[ -z "$existing" ]] && continue
|
|
145
|
+
|
|
146
|
+
local existing_normalized
|
|
147
|
+
existing_normalized=$(printf '%s\n' "$existing" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9 ]//g' | tr -s ' ')
|
|
148
|
+
|
|
149
|
+
# Substring match in either direction (only for patterns long enough to be meaningful)
|
|
150
|
+
local shorter_len=${#normalized}
|
|
151
|
+
[[ ${#existing_normalized} -lt $shorter_len ]] && shorter_len=${#existing_normalized}
|
|
152
|
+
if [[ $shorter_len -ge 30 ]]; then
|
|
153
|
+
if [[ "$normalized" == *"$existing_normalized"* ]] || [[ "$existing_normalized" == *"$normalized"* ]]; then
|
|
154
|
+
return 0
|
|
155
|
+
fi
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# Keyword overlap: extract words 4+ chars, flag as duplicate if >60% overlap
|
|
159
|
+
local new_words existing_words
|
|
160
|
+
new_words=$(printf '%s\n' "$normalized" | tr ' ' '\n' | awk 'length >= 4' | sort -u)
|
|
161
|
+
existing_words=$(printf '%s\n' "$existing_normalized" | tr ' ' '\n' | awk 'length >= 4' | sort -u)
|
|
162
|
+
|
|
163
|
+
local new_count overlap_count
|
|
164
|
+
new_count=$(printf '%s\n' "$new_words" | grep -cE '\S' || echo "0")
|
|
165
|
+
[[ "$new_count" -eq 0 ]] && continue
|
|
166
|
+
|
|
167
|
+
# Count overlapping words (use -xF for whole-line match, not substring)
|
|
168
|
+
overlap_count=0
|
|
169
|
+
while IFS= read -r word; do
|
|
170
|
+
[[ -z "$word" ]] && continue
|
|
171
|
+
if printf '%s\n' "$existing_words" | grep -qxF "$word"; then
|
|
172
|
+
overlap_count=$((overlap_count + 1))
|
|
173
|
+
fi
|
|
174
|
+
done <<< "$new_words"
|
|
175
|
+
|
|
176
|
+
# >60% overlap = duplicate
|
|
177
|
+
if [[ $((overlap_count * 100 / new_count)) -gt 60 ]]; then
|
|
178
|
+
return 0
|
|
179
|
+
fi
|
|
180
|
+
done <<< "$existing_patterns"
|
|
181
|
+
|
|
182
|
+
return 1
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# Auto-promote a sign from retry failure context
|
|
186
|
+
# Called when a story passes after multiple retries
|
|
187
|
+
_maybe_promote_sign() {
|
|
188
|
+
local story="$1"
|
|
189
|
+
local retries="$2"
|
|
190
|
+
local config="$RALPH_DIR/config.json"
|
|
191
|
+
|
|
192
|
+
# Check config: read .autoPromoteSigns directly (avoid get_config - its // operator
|
|
193
|
+
# treats false as falsy and returns the default). Default to true if key is absent/null.
|
|
194
|
+
if [[ -f "$config" ]]; then
|
|
195
|
+
local auto_promote
|
|
196
|
+
auto_promote=$(jq -r '.autoPromoteSigns' "$config" 2>/dev/null)
|
|
197
|
+
if [[ "$auto_promote" == "false" ]]; then
|
|
198
|
+
return 0
|
|
199
|
+
fi
|
|
200
|
+
fi
|
|
201
|
+
|
|
202
|
+
# Read failure context (safety check - caller also gates on file existence)
|
|
203
|
+
local failure_context
|
|
204
|
+
if [[ ! -f "$RALPH_DIR/last_failure.txt" ]]; then
|
|
205
|
+
return 0
|
|
206
|
+
fi
|
|
207
|
+
failure_context=$(head -"$MAX_SIGN_CONTEXT_LINES" "$RALPH_DIR/last_failure.txt")
|
|
208
|
+
|
|
209
|
+
# Skip trivial failures (lint/format only)
|
|
210
|
+
if _is_trivial_failure "$failure_context"; then
|
|
211
|
+
log_progress "$story" "SIGN_AUTO" "Skipped - trivial failure (lint/format only)"
|
|
212
|
+
return 0
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
# Load existing sign patterns for dedup context
|
|
216
|
+
local existing_signs=""
|
|
217
|
+
if [[ -f "$RALPH_DIR/signs.json" ]]; then
|
|
218
|
+
existing_signs=$(jq -r '.signs[].pattern' "$RALPH_DIR/signs.json" 2>/dev/null | head -"$MAX_SIGN_DEDUP_EXISTING")
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
# Build extraction prompt
|
|
222
|
+
local prompt
|
|
223
|
+
prompt="You are analyzing a development failure that was resolved after $retries attempts.
|
|
224
|
+
|
|
225
|
+
Extract ONE reusable pattern (a \"sign\") that would prevent this failure in future stories.
|
|
226
|
+
|
|
227
|
+
## Failure Context
|
|
228
|
+
\`\`\`
|
|
229
|
+
$failure_context
|
|
230
|
+
\`\`\`
|
|
231
|
+
|
|
232
|
+
## Existing Signs (do NOT duplicate these)
|
|
233
|
+
$existing_signs
|
|
234
|
+
|
|
235
|
+
## Rules
|
|
236
|
+
- Extract a single, actionable pattern that prevents this class of failure
|
|
237
|
+
- The pattern should be general enough to apply to future stories, not specific to this one
|
|
238
|
+
- If the failure is trivial, unclear, or you can't extract a useful pattern, respond with just: NONE
|
|
239
|
+
- Category must be one of: backend, frontend, testing, general, database, security
|
|
240
|
+
|
|
241
|
+
## Good Examples
|
|
242
|
+
CATEGORY: backend
|
|
243
|
+
PATTERN: Always run database migrations before executing test suites
|
|
244
|
+
|
|
245
|
+
CATEGORY: testing
|
|
246
|
+
PATTERN: Use waitFor() instead of fixed delays when testing async UI updates
|
|
247
|
+
|
|
248
|
+
CATEGORY: frontend
|
|
249
|
+
PATTERN: Import CSS modules with .module.css extension in Next.js projects
|
|
250
|
+
|
|
251
|
+
## Bad Examples (too specific, too vague)
|
|
252
|
+
PATTERN: Fix the login button color (too specific to one story)
|
|
253
|
+
PATTERN: Write better code (too vague to be actionable)
|
|
254
|
+
PATTERN: Always check for errors (too vague)
|
|
255
|
+
|
|
256
|
+
## Response Format
|
|
257
|
+
Respond with exactly two lines (or just NONE):
|
|
258
|
+
CATEGORY: <category>
|
|
259
|
+
PATTERN: <pattern>"
|
|
260
|
+
|
|
261
|
+
# Call Claude with timeout (one-shot, non-interactive)
|
|
262
|
+
local response
|
|
263
|
+
response=$(printf '%s\n' "$prompt" | run_with_timeout "$SIGN_EXTRACTION_TIMEOUT_SECONDS" claude -p 2>/dev/null) || {
|
|
264
|
+
log_progress "$story" "SIGN_AUTO" "Skipped - Claude extraction timed out"
|
|
265
|
+
return 0
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
# Check for NONE response
|
|
269
|
+
if printf '%s\n' "$response" | grep -qi '^NONE'; then
|
|
270
|
+
log_progress "$story" "SIGN_AUTO" "Skipped - no actionable pattern found"
|
|
271
|
+
return 0
|
|
272
|
+
fi
|
|
273
|
+
|
|
274
|
+
# Parse response for CATEGORY: and PATTERN: lines (use sed, not grep -P for macOS)
|
|
275
|
+
local category pattern
|
|
276
|
+
category=$(echo "$response" | sed -n 's/^CATEGORY:[[:space:]]*//p' | head -1 | tr -d '\r')
|
|
277
|
+
pattern=$(echo "$response" | sed -n 's/^PATTERN:[[:space:]]*//p' | head -1 | tr -d '\r')
|
|
278
|
+
|
|
279
|
+
# Validate extracted values
|
|
280
|
+
if [[ -z "$category" || -z "$pattern" ]]; then
|
|
281
|
+
log_progress "$story" "SIGN_AUTO" "Skipped - could not parse Claude response"
|
|
282
|
+
return 0
|
|
283
|
+
fi
|
|
284
|
+
|
|
285
|
+
# Validate category
|
|
286
|
+
case "$category" in
|
|
287
|
+
backend|frontend|testing|general|database|security) ;;
|
|
288
|
+
*)
|
|
289
|
+
log_progress "$story" "SIGN_AUTO" "Skipped - invalid category: $category"
|
|
290
|
+
return 0
|
|
291
|
+
;;
|
|
292
|
+
esac
|
|
293
|
+
|
|
294
|
+
# Check for duplicates before adding
|
|
295
|
+
if _sign_is_duplicate "$pattern"; then
|
|
296
|
+
log_progress "$story" "SIGN_AUTO" "Skipped - duplicate of existing sign"
|
|
297
|
+
return 0
|
|
298
|
+
fi
|
|
299
|
+
|
|
300
|
+
# Add the sign (3rd arg = autoPromoted, 4th arg = learnedFrom override)
|
|
301
|
+
if ralph_sign "$pattern" "$category" "true" "$story"; then
|
|
302
|
+
log_progress "$story" "SIGN_AUTO" "Added [$category]: $pattern"
|
|
303
|
+
print_info "Auto-promoted sign: [$category] $pattern"
|
|
304
|
+
else
|
|
305
|
+
log_progress "$story" "SIGN_AUTO" "Failed to add sign"
|
|
306
|
+
fi
|
|
307
|
+
|
|
308
|
+
return 0
|
|
309
|
+
}
|
|
310
|
+
|
|
99
311
|
run_loop() {
|
|
100
312
|
local max_iterations="$DEFAULT_MAX_ITERATIONS"
|
|
101
313
|
local specific_story=""
|
|
@@ -128,7 +340,7 @@ run_loop() {
|
|
|
128
340
|
# Validate prerequisites
|
|
129
341
|
check_dependencies
|
|
130
342
|
|
|
131
|
-
# Pre-
|
|
343
|
+
# Pre-loop checks to catch issues before wasting iterations
|
|
132
344
|
preflight_checks
|
|
133
345
|
|
|
134
346
|
if [[ ! -f "$RALPH_DIR/prd.json" ]]; then
|
|
@@ -181,9 +393,9 @@ run_loop() {
|
|
|
181
393
|
local consecutive_timeouts=0
|
|
182
394
|
local max_story_retries
|
|
183
395
|
local max_timeouts=5 # Skip after 5 consecutive timeouts (likely too large/complex)
|
|
184
|
-
# Default to
|
|
185
|
-
# Override with config.json: "maxStoryRetries":
|
|
186
|
-
max_story_retries=$(get_config '.maxStoryRetries' "
|
|
396
|
+
# Default to 5 retries - enough for transient issues, stops before wasting cycles
|
|
397
|
+
# Override with config.json: "maxStoryRetries": 8
|
|
398
|
+
max_story_retries=$(get_config '.maxStoryRetries' "5")
|
|
187
399
|
local total_attempts=0
|
|
188
400
|
local skipped_stories=()
|
|
189
401
|
local start_time
|
|
@@ -264,12 +476,10 @@ run_loop() {
|
|
|
264
476
|
'(.stories[] | select(.id==$id)) |= . + {retryCount: $count}' \
|
|
265
477
|
"$RALPH_DIR/prd.json" > "$RALPH_DIR/prd.json.tmp" && mv "$RALPH_DIR/prd.json.tmp" "$RALPH_DIR/prd.json"
|
|
266
478
|
|
|
267
|
-
# Circuit breaker:
|
|
268
|
-
#
|
|
269
|
-
# If a story consistently fails after this many tries, it likely needs manual review
|
|
270
|
-
# (vague test steps, missing prerequisites, or fundamentally broken requirements).
|
|
479
|
+
# Circuit breaker: stop the loop after max retries (stories depend on each other)
|
|
480
|
+
# If a story consistently fails after this many tries, it needs manual review.
|
|
271
481
|
if [[ $consecutive_failures -gt $max_story_retries ]]; then
|
|
272
|
-
print_error "Story $story has failed $consecutive_failures times -
|
|
482
|
+
print_error "Story $story has failed $consecutive_failures times - stopping loop"
|
|
273
483
|
echo ""
|
|
274
484
|
echo " This usually means:"
|
|
275
485
|
echo " - Test steps are too vague or ambiguous"
|
|
@@ -279,20 +489,18 @@ run_loop() {
|
|
|
279
489
|
echo " Failure context saved to: $RALPH_DIR/failures/$story.txt"
|
|
280
490
|
mkdir -p "$RALPH_DIR/failures"
|
|
281
491
|
cp "$RALPH_DIR/last_failure.txt" "$RALPH_DIR/failures/$story.txt" 2>/dev/null || true
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
jq
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
492
|
+
local passed failed
|
|
493
|
+
passed=$(jq '[.stories[] | select(.passes==true)] | length' "$RALPH_DIR/prd.json" 2>/dev/null || echo "0")
|
|
494
|
+
failed=$(jq '[.stories[] | select(.passes==false)] | length' "$RALPH_DIR/prd.json" 2>/dev/null || echo "0")
|
|
495
|
+
send_notification "🛑 Ralph stopped: $story failed $consecutive_failures times. $passed passed, $failed remaining"
|
|
496
|
+
print_progress_summary "$start_time" "$total_attempts" "0"
|
|
497
|
+
return 1
|
|
288
498
|
fi
|
|
289
499
|
|
|
290
500
|
# Show retry status (but don't make it scary - retrying is normal!)
|
|
291
501
|
if [[ $consecutive_failures -gt 1 ]]; then
|
|
292
502
|
if [[ $consecutive_failures -le 3 ]]; then
|
|
293
503
|
print_info "Attempt $consecutive_failures for $story (normal - refining solution)"
|
|
294
|
-
elif [[ $consecutive_failures -le 8 ]]; then
|
|
295
|
-
print_warning "Attempt $consecutive_failures/$max_story_retries for $story"
|
|
296
504
|
else
|
|
297
505
|
print_warning "Attempt $consecutive_failures/$max_story_retries for $story (getting close to limit)"
|
|
298
506
|
fi
|
|
@@ -494,6 +702,11 @@ run_loop() {
|
|
|
494
702
|
update_json "$RALPH_DIR/prd.json" \
|
|
495
703
|
--arg id "$story" '(.stories[] | select(.id==$id)) |= . + {passes: true, retryCount: 0}'
|
|
496
704
|
|
|
705
|
+
# Auto-promote sign if story required retries
|
|
706
|
+
if [[ $consecutive_failures -gt 1 && -f "$RALPH_DIR/last_failure.txt" ]]; then
|
|
707
|
+
_maybe_promote_sign "$story" "$consecutive_failures"
|
|
708
|
+
fi
|
|
709
|
+
|
|
497
710
|
# Clear failure context on success
|
|
498
711
|
rm -f "$RALPH_DIR/last_failure.txt"
|
|
499
712
|
rm -f "$RALPH_DIR/last_verification.log"
|
package/ralph/prd.sh
CHANGED
|
@@ -158,10 +158,7 @@ ralph_prd() {
|
|
|
158
158
|
printf '%s\n' "- Context for the next story" >> "$prompt_file"
|
|
159
159
|
printf '\n%s\n' "## Context Rot Check" >> "$prompt_file"
|
|
160
160
|
printf '\n%s\n' "Before finalizing, validate:" >> "$prompt_file"
|
|
161
|
-
printf '%s\n' "- If > 10 stories, recommend splitting into phases" >> "$prompt_file"
|
|
162
|
-
printf '%s\n' '- If complexity is "high" AND > 5 stories, MUST split' >> "$prompt_file"
|
|
163
161
|
printf '%s\n' "- Each story should be completable in one Claude session (~10 min)" >> "$prompt_file"
|
|
164
|
-
printf '\n%s\n' "If splitting is needed, generate ONLY phase 1 and note deferred items." >> "$prompt_file"
|
|
165
162
|
printf '\n%s\n' "## Context in Stories - IMPORTANT" >> "$prompt_file"
|
|
166
163
|
printf '\n%s\n' "Each story must be self-contained. Include the idea file path in each story's 'contextFiles' array." >> "$prompt_file"
|
|
167
164
|
printf '%s\n' "Include 'techStack' with the relevant subset of detected technologies for that story." >> "$prompt_file"
|
package/ralph/signs.sh
CHANGED
|
@@ -16,6 +16,8 @@ ralph_sign() {
|
|
|
16
16
|
|
|
17
17
|
local pattern="$1"
|
|
18
18
|
local category="${2:-general}"
|
|
19
|
+
local auto_promoted="${3:-false}"
|
|
20
|
+
local learned_from_override="${4:-}"
|
|
19
21
|
|
|
20
22
|
# Ensure .ralph directory exists
|
|
21
23
|
if [[ ! -d "$RALPH_DIR" ]]; then
|
|
@@ -34,8 +36,11 @@ ralph_sign() {
|
|
|
34
36
|
local sign_id="sign-$(printf '%03d' $((sign_count + 1)))"
|
|
35
37
|
|
|
36
38
|
# Get current story if available (for learnedFrom field)
|
|
39
|
+
# Override can be passed as 4th arg (used by auto-promote, since story is already marked passed)
|
|
37
40
|
local learned_from=""
|
|
38
|
-
if [[ -
|
|
41
|
+
if [[ -n "$learned_from_override" ]]; then
|
|
42
|
+
learned_from="$learned_from_override"
|
|
43
|
+
elif [[ -f "$RALPH_DIR/prd.json" ]]; then
|
|
39
44
|
learned_from=$(jq -r '.stories[] | select(.passes==false) | .id' "$RALPH_DIR/prd.json" 2>/dev/null | head -1)
|
|
40
45
|
fi
|
|
41
46
|
|
|
@@ -52,11 +57,13 @@ ralph_sign() {
|
|
|
52
57
|
--arg category "$category" \
|
|
53
58
|
--arg learnedFrom "$learned_from" \
|
|
54
59
|
--arg createdAt "$timestamp" \
|
|
60
|
+
--argjson autoPromoted "$( [[ "$auto_promoted" == "true" ]] && echo "true" || echo "false" )" \
|
|
55
61
|
'.signs += [{
|
|
56
62
|
id: $id,
|
|
57
63
|
pattern: $pattern,
|
|
58
64
|
category: $category,
|
|
59
65
|
learnedFrom: (if $learnedFrom == "" then null else $learnedFrom end),
|
|
66
|
+
autoPromoted: $autoPromoted,
|
|
60
67
|
createdAt: $createdAt
|
|
61
68
|
}]' "$RALPH_DIR/signs.json" > "$tmpfile" && jq -e . "$tmpfile" >/dev/null 2>&1; then
|
|
62
69
|
mv "$tmpfile" "$RALPH_DIR/signs.json"
|
|
@@ -100,7 +107,7 @@ ralph_signs() {
|
|
|
100
107
|
[[ -z "$category" ]] && continue
|
|
101
108
|
|
|
102
109
|
echo "[$category]"
|
|
103
|
-
jq -r --arg cat "$category" '.signs[] | select(.category==$cat) | " - \(.pattern)"' "$RALPH_DIR/signs.json"
|
|
110
|
+
jq -r --arg cat "$category" '.signs[] | select(.category==$cat) | " - \(.pattern)\(if .autoPromoted == true then " (auto)" else "" end)"' "$RALPH_DIR/signs.json"
|
|
104
111
|
echo ""
|
|
105
112
|
done <<< "$categories"
|
|
106
113
|
}
|
package/ralph/utils.sh
CHANGED
|
@@ -10,6 +10,8 @@ readonly MAX_OUTPUT_PREVIEW_LINES=20
|
|
|
10
10
|
readonly MAX_ERROR_PREVIEW_LINES=40
|
|
11
11
|
readonly MAX_LINT_ERROR_LINES=20
|
|
12
12
|
readonly MAX_PROGRESS_FILE_LINES=1000
|
|
13
|
+
readonly MAX_SIGN_CONTEXT_LINES=150
|
|
14
|
+
readonly MAX_SIGN_DEDUP_EXISTING=20
|
|
13
15
|
|
|
14
16
|
# Constants - Timeouts (centralized to avoid magic numbers)
|
|
15
17
|
readonly ITERATION_DELAY_SECONDS=0
|
|
@@ -19,6 +21,7 @@ readonly CODE_REVIEW_TIMEOUT_SECONDS=120
|
|
|
19
21
|
readonly BROWSER_TIMEOUT_SECONDS=60
|
|
20
22
|
readonly BROWSER_PAGE_TIMEOUT_MS=30000
|
|
21
23
|
readonly CURL_TIMEOUT_SECONDS=10
|
|
24
|
+
readonly SIGN_EXTRACTION_TIMEOUT_SECONDS=30
|
|
22
25
|
|
|
23
26
|
# Common project directories (avoid duplication across files)
|
|
24
27
|
readonly FRONTEND_DIRS=("apps/web" "frontend" "client" "web")
|
|
@@ -598,7 +601,9 @@ fix_hardcoded_paths() {
|
|
|
598
601
|
local original_content="$prd_content"
|
|
599
602
|
|
|
600
603
|
# Check for hardcoded absolute paths (non-portable)
|
|
601
|
-
|
|
604
|
+
# Note: stderr suppressed on echo|grep -q pipes to silence "broken pipe" noise
|
|
605
|
+
# (grep -q exits early on match, closing the pipe while echo is still writing)
|
|
606
|
+
if echo "$prd_content" 2>/dev/null | grep -qE '"/Users/|"/home/|"C:\\|"/var/|"/opt/' ; then
|
|
602
607
|
echo " Removing hardcoded absolute paths..."
|
|
603
608
|
# Remove common absolute path prefixes, keep relative path
|
|
604
609
|
prd_content=$(echo "$prd_content" | sed -E 's|"/Users/[^"]*/([^"]+)"|"\1"|g')
|
|
@@ -607,7 +612,7 @@ fix_hardcoded_paths() {
|
|
|
607
612
|
fi
|
|
608
613
|
|
|
609
614
|
# Replace hardcoded backend URLs with {config.urls.backend}
|
|
610
|
-
if [[ -n "$backend_url" ]] && echo "$prd_content" | grep -qF "$backend_url" ; then
|
|
615
|
+
if [[ -n "$backend_url" ]] && echo "$prd_content" 2>/dev/null | grep -qF "$backend_url" ; then
|
|
611
616
|
echo " Replacing hardcoded backend URL with {config.urls.backend}..."
|
|
612
617
|
local escaped_url
|
|
613
618
|
escaped_url=$(_escape_sed_pattern "$backend_url")
|
|
@@ -616,7 +621,7 @@ fix_hardcoded_paths() {
|
|
|
616
621
|
fi
|
|
617
622
|
|
|
618
623
|
# Replace hardcoded frontend URLs with {config.urls.frontend}
|
|
619
|
-
if [[ -n "$frontend_url" ]] && echo "$prd_content" | grep -qF "$frontend_url" ; then
|
|
624
|
+
if [[ -n "$frontend_url" ]] && echo "$prd_content" 2>/dev/null | grep -qF "$frontend_url" ; then
|
|
620
625
|
echo " Replacing hardcoded frontend URL with {config.urls.frontend}..."
|
|
621
626
|
local escaped_url
|
|
622
627
|
escaped_url=$(_escape_sed_pattern "$frontend_url")
|
|
@@ -625,7 +630,7 @@ fix_hardcoded_paths() {
|
|
|
625
630
|
fi
|
|
626
631
|
|
|
627
632
|
# Replace hardcoded health endpoints with config placeholder
|
|
628
|
-
if echo "$prd_content" | grep -qE '/api(/v[0-9]+)?/health|/health' ; then
|
|
633
|
+
if echo "$prd_content" 2>/dev/null | grep -qE '/api(/v[0-9]+)?/health|/health' ; then
|
|
629
634
|
echo " Replacing hardcoded health endpoints with {config.api.healthEndpoint}..."
|
|
630
635
|
prd_content=$(echo "$prd_content" | sed -E 's|/api/v[0-9]+/health|{config.api.healthEndpoint}|g')
|
|
631
636
|
prd_content=$(echo "$prd_content" | sed -E 's|/api/health|{config.api.healthEndpoint}|g')
|
|
@@ -637,7 +642,7 @@ fix_hardcoded_paths() {
|
|
|
637
642
|
# Note: Use # as delimiter since | appears in regex alternation
|
|
638
643
|
if [[ -z "$backend_url" ]]; then
|
|
639
644
|
# Common backend ports: 8000, 8001, 8080, 3001, 4000, 5000
|
|
640
|
-
if echo "$prd_content" | grep -qE 'http://localhost:(8000|8001|8080|3001|4000|5000)' ; then
|
|
645
|
+
if echo "$prd_content" 2>/dev/null | grep -qE 'http://localhost:(8000|8001|8080|3001|4000|5000)' ; then
|
|
641
646
|
echo " Replacing hardcoded localhost backend URLs with {config.urls.backend}..."
|
|
642
647
|
prd_content=$(echo "$prd_content" | sed -E 's#http://localhost:(8000|8001|8080|3001|4000|5000)#{config.urls.backend}#g')
|
|
643
648
|
modified=true
|
|
@@ -646,7 +651,7 @@ fix_hardcoded_paths() {
|
|
|
646
651
|
|
|
647
652
|
if [[ -z "$frontend_url" ]]; then
|
|
648
653
|
# Common frontend ports: 3000, 5173, 4200
|
|
649
|
-
if echo "$prd_content" | grep -qE 'http://localhost:(3000|5173|4200)' ; then
|
|
654
|
+
if echo "$prd_content" 2>/dev/null | grep -qE 'http://localhost:(3000|5173|4200)' ; then
|
|
650
655
|
echo " Replacing hardcoded localhost frontend URLs with {config.urls.frontend}..."
|
|
651
656
|
prd_content=$(echo "$prd_content" | sed -E 's#http://localhost:(3000|5173|4200)#{config.urls.frontend}#g')
|
|
652
657
|
modified=true
|