agentic-loop 3.19.0 → 3.22.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/commands/tour.md +11 -7
- package/.claude/commands/vibe-help.md +5 -2
- package/.claude/commands/vibe-list.md +17 -2
- package/.claude/skills/prd/SKILL.md +21 -6
- package/.claude/skills/setup-review/SKILL.md +56 -0
- package/.claude/skills/tour/SKILL.md +11 -7
- package/.claude/skills/vibe-help/SKILL.md +2 -1
- package/.claude/skills/vibe-list/SKILL.md +5 -2
- package/.pre-commit-hooks.yaml +8 -0
- package/README.md +4 -0
- package/bin/agentic-loop.sh +7 -0
- package/bin/ralph.sh +29 -0
- package/dist/checks/check-signs-secrets.d.ts +9 -0
- package/dist/checks/check-signs-secrets.d.ts.map +1 -0
- package/dist/checks/check-signs-secrets.js +57 -0
- package/dist/checks/check-signs-secrets.js.map +1 -0
- package/dist/checks/index.d.ts +2 -5
- package/dist/checks/index.d.ts.map +1 -1
- package/dist/checks/index.js +4 -9
- package/dist/checks/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/ralph/hooks/common.sh +47 -0
- package/ralph/hooks/warn-debug.sh +12 -26
- package/ralph/hooks/warn-empty-catch.sh +21 -34
- package/ralph/hooks/warn-secrets.sh +39 -52
- package/ralph/hooks/warn-urls.sh +25 -45
- package/ralph/init.sh +58 -82
- package/ralph/loop.sh +506 -53
- package/ralph/prd-check.sh +177 -236
- package/ralph/prd.sh +5 -2
- package/ralph/setup/quick-setup.sh +2 -16
- package/ralph/setup.sh +68 -80
- package/ralph/signs.sh +8 -0
- package/ralph/uat.sh +2664 -0
- package/ralph/utils.sh +213 -70
- package/ralph/verify/tests.sh +65 -10
- package/templates/PROMPT.md +10 -4
- package/templates/UAT-PROMPT.md +197 -0
- package/templates/config/elixir.json +0 -2
- package/templates/config/fastmcp.json +0 -2
- package/templates/config/fullstack.json +2 -4
- package/templates/config/go.json +0 -2
- package/templates/config/minimal.json +0 -2
- package/templates/config/node.json +0 -2
- package/templates/config/python.json +0 -2
- package/templates/config/rust.json +0 -2
- package/templates/prd-example.json +6 -8
package/ralph/hooks/common.sh
CHANGED
|
@@ -87,3 +87,50 @@ hook_block() {
|
|
|
87
87
|
"message": $msg
|
|
88
88
|
}'
|
|
89
89
|
}
|
|
90
|
+
|
|
91
|
+
# Run a warn-* hook with shared boilerplate
|
|
92
|
+
# Usage: run_warn_hook <extensions> <check_fn> [--skip-tests] [--block]
|
|
93
|
+
# extensions: comma-separated file extensions to match (e.g., "ts,tsx,js,jsx,py")
|
|
94
|
+
# check_fn: function name that sets WARNINGS variable
|
|
95
|
+
# --skip-tests: skip test files
|
|
96
|
+
# --block: use hook_block instead of hook_warn on warning
|
|
97
|
+
run_warn_hook() {
|
|
98
|
+
local extensions="$1"
|
|
99
|
+
local check_fn="$2"
|
|
100
|
+
shift 2
|
|
101
|
+
|
|
102
|
+
local skip_tests=false
|
|
103
|
+
local block=false
|
|
104
|
+
while [[ $# -gt 0 ]]; do
|
|
105
|
+
case "$1" in
|
|
106
|
+
--skip-tests) skip_tests=true ;;
|
|
107
|
+
--block) block=true ;;
|
|
108
|
+
esac
|
|
109
|
+
shift
|
|
110
|
+
done
|
|
111
|
+
|
|
112
|
+
parse_hook_input
|
|
113
|
+
|
|
114
|
+
if [[ "$skip_tests" == "true" ]] && is_test_file; then
|
|
115
|
+
hook_allow
|
|
116
|
+
exit 0
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
if ! is_code_file "$extensions"; then
|
|
120
|
+
hook_allow
|
|
121
|
+
exit 0
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
WARNINGS=""
|
|
125
|
+
"$check_fn"
|
|
126
|
+
|
|
127
|
+
if [[ -n "$WARNINGS" ]]; then
|
|
128
|
+
if [[ "$block" == "true" ]]; then
|
|
129
|
+
hook_block "$WARNINGS"
|
|
130
|
+
else
|
|
131
|
+
hook_warn "$WARNINGS"
|
|
132
|
+
fi
|
|
133
|
+
else
|
|
134
|
+
hook_allow
|
|
135
|
+
fi
|
|
136
|
+
}
|
|
@@ -5,32 +5,18 @@
|
|
|
5
5
|
|
|
6
6
|
source "$(dirname "$0")/common.sh"
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
_check_debug() {
|
|
9
|
+
if echo "$NEW_CONTENT" | grep -qE 'console\.(log|debug|info|warn|error)[[:space:]]*\('; then
|
|
10
|
+
WARNINGS="⚠️ Debug statement detected: console.log/debug. Remove before commit."
|
|
11
|
+
fi
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
exit 0
|
|
14
|
-
fi
|
|
13
|
+
if echo "$NEW_CONTENT" | grep -qE '^[[:space:]]*debugger[[:space:]]*;?[[:space:]]*$'; then
|
|
14
|
+
WARNINGS="${WARNINGS}${WARNINGS:+\\n}⚠️ Debugger statement detected. Remove before commit."
|
|
15
|
+
fi
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
WARNINGS=""
|
|
17
|
+
if echo "$NEW_CONTENT" | grep -qE '^[[:space:]]*print[[:space:]]*\('; then
|
|
18
|
+
WARNINGS="${WARNINGS}${WARNINGS:+\\n}⚠️ Print statement detected. Remove before commit."
|
|
19
|
+
fi
|
|
20
|
+
}
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
WARNINGS="⚠️ Debug statement detected: console.log/debug. Remove before commit."
|
|
21
|
-
fi
|
|
22
|
-
|
|
23
|
-
if echo "$NEW_CONTENT" | grep -qE '^[[:space:]]*debugger[[:space:]]*;?[[:space:]]*$'; then
|
|
24
|
-
WARNINGS="${WARNINGS}${WARNINGS:+\\n}⚠️ Debugger statement detected. Remove before commit."
|
|
25
|
-
fi
|
|
26
|
-
|
|
27
|
-
if echo "$NEW_CONTENT" | grep -qE '^[[:space:]]*print[[:space:]]*\('; then
|
|
28
|
-
WARNINGS="${WARNINGS}${WARNINGS:+\\n}⚠️ Print statement detected. Remove before commit."
|
|
29
|
-
fi
|
|
30
|
-
|
|
31
|
-
# Output warning or allow
|
|
32
|
-
if [[ -n "$WARNINGS" ]]; then
|
|
33
|
-
hook_warn "$WARNINGS"
|
|
34
|
-
else
|
|
35
|
-
hook_allow
|
|
36
|
-
fi
|
|
22
|
+
run_warn_hook "ts,tsx,js,jsx,py,go,rs" _check_debug
|
|
@@ -5,43 +5,30 @@
|
|
|
5
5
|
|
|
6
6
|
source "$(dirname "$0")/common.sh"
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
exit 0
|
|
14
|
-
fi
|
|
15
|
-
|
|
16
|
-
WARNINGS=""
|
|
17
|
-
|
|
18
|
-
# JavaScript/TypeScript: catch (e) { } or catch { }
|
|
19
|
-
if echo "$NEW_CONTENT" | grep -qE 'catch[[:space:]]*\([^)]*\)[[:space:]]*\{[[:space:]]*\}'; then
|
|
20
|
-
WARNINGS="⚠️ Empty catch block detected. Handle the error or add a comment explaining why it's ignored."
|
|
21
|
-
fi
|
|
8
|
+
_check_empty_catch() {
|
|
9
|
+
# JavaScript/TypeScript: catch (e) { } or catch { }
|
|
10
|
+
if echo "$NEW_CONTENT" | grep -qE 'catch[[:space:]]*\([^)]*\)[[:space:]]*\{[[:space:]]*\}'; then
|
|
11
|
+
WARNINGS="⚠️ Empty catch block detected. Handle the error or add a comment explaining why it's ignored."
|
|
12
|
+
fi
|
|
22
13
|
|
|
23
|
-
# Also check for catch with only a comment (no actual handling)
|
|
24
|
-
if echo "$NEW_CONTENT" | grep -qE 'catch[[:space:]]*\([^)]*\)[[:space:]]*\{[[:space:]]*(//[^}]*)?\}'; then
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
# Also check for catch with only a comment (no actual handling)
|
|
15
|
+
if echo "$NEW_CONTENT" | grep -qE 'catch[[:space:]]*\([^)]*\)[[:space:]]*\{[[:space:]]*(//[^}]*)?\}'; then
|
|
16
|
+
if [[ -z "$WARNINGS" ]]; then
|
|
17
|
+
WARNINGS="⚠️ Catch block with no error handling. Consider logging or rethrowing the error."
|
|
18
|
+
fi
|
|
27
19
|
fi
|
|
28
|
-
fi
|
|
29
20
|
|
|
30
|
-
# Python: except: pass or except Exception: pass
|
|
31
|
-
if echo "$NEW_CONTENT" | grep -qE 'except.*:[[:space:]]*pass[[:space:]]*$'; then
|
|
32
|
-
|
|
33
|
-
fi
|
|
21
|
+
# Python: except: pass or except Exception: pass
|
|
22
|
+
if echo "$NEW_CONTENT" | grep -qE 'except.*:[[:space:]]*pass[[:space:]]*$'; then
|
|
23
|
+
WARNINGS="⚠️ Empty except block (pass). Handle the exception or add logging."
|
|
24
|
+
fi
|
|
34
25
|
|
|
35
|
-
# Python: bare except with just pass on next line
|
|
36
|
-
if echo "$NEW_CONTENT" | grep -qE 'except.*:[[:space:]]*$' && echo "$NEW_CONTENT" | grep -qE '^[[:space:]]*pass[[:space:]]*$'; then
|
|
37
|
-
|
|
38
|
-
|
|
26
|
+
# Python: bare except with just pass on next line
|
|
27
|
+
if echo "$NEW_CONTENT" | grep -qE 'except.*:[[:space:]]*$' && echo "$NEW_CONTENT" | grep -qE '^[[:space:]]*pass[[:space:]]*$'; then
|
|
28
|
+
if [[ -z "$WARNINGS" ]]; then
|
|
29
|
+
WARNINGS="⚠️ Except block with only 'pass'. Consider logging or reraising the exception."
|
|
30
|
+
fi
|
|
39
31
|
fi
|
|
40
|
-
|
|
32
|
+
}
|
|
41
33
|
|
|
42
|
-
|
|
43
|
-
if [[ -n "$WARNINGS" ]]; then
|
|
44
|
-
hook_block "$WARNINGS"
|
|
45
|
-
else
|
|
46
|
-
hook_allow
|
|
47
|
-
fi
|
|
34
|
+
run_warn_hook "ts,tsx,js,jsx,py" _check_empty_catch --block
|
|
@@ -7,65 +7,52 @@
|
|
|
7
7
|
|
|
8
8
|
source "$(dirname "$0")/common.sh"
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
exit 0
|
|
16
|
-
fi
|
|
17
|
-
|
|
18
|
-
WARNINGS=""
|
|
19
|
-
|
|
20
|
-
# AWS Access Key (AKIA followed by 16 alphanumeric chars)
|
|
21
|
-
if echo "$NEW_CONTENT" | grep -qE 'AKIA[0-9A-Z]{16}'; then
|
|
22
|
-
WARNINGS="🚨 SECURITY: Possible AWS Access Key detected! Use environment variables."
|
|
23
|
-
fi
|
|
10
|
+
_check_secrets() {
|
|
11
|
+
# AWS Access Key (AKIA followed by 16 alphanumeric chars)
|
|
12
|
+
if echo "$NEW_CONTENT" | grep -qE 'AKIA[0-9A-Z]{16}'; then
|
|
13
|
+
WARNINGS="🚨 SECURITY: Possible AWS Access Key detected! Use environment variables."
|
|
14
|
+
fi
|
|
24
15
|
|
|
25
|
-
# Stripe keys (sk_live_* or sk_test_*)
|
|
26
|
-
if echo "$NEW_CONTENT" | grep -qE 'sk_(live|test)_[0-9a-zA-Z]{24,}'; then
|
|
27
|
-
|
|
28
|
-
fi
|
|
16
|
+
# Stripe keys (sk_live_* or sk_test_*)
|
|
17
|
+
if echo "$NEW_CONTENT" | grep -qE 'sk_(live|test)_[0-9a-zA-Z]{24,}'; then
|
|
18
|
+
WARNINGS="${WARNINGS}${WARNINGS:+\\n}🚨 SECURITY: Stripe API key detected! Use environment variables."
|
|
19
|
+
fi
|
|
29
20
|
|
|
30
|
-
# GitHub tokens (ghp_, gho_, ghu_, ghs_, ghr_)
|
|
31
|
-
if echo "$NEW_CONTENT" | grep -qE 'gh[pousr]_[A-Za-z0-9_]{36,}'; then
|
|
32
|
-
|
|
33
|
-
fi
|
|
21
|
+
# GitHub tokens (ghp_, gho_, ghu_, ghs_, ghr_)
|
|
22
|
+
if echo "$NEW_CONTENT" | grep -qE 'gh[pousr]_[A-Za-z0-9_]{36,}'; then
|
|
23
|
+
WARNINGS="${WARNINGS}${WARNINGS:+\\n}🚨 SECURITY: GitHub token detected! Use environment variables."
|
|
24
|
+
fi
|
|
34
25
|
|
|
35
|
-
# Slack tokens (xoxb-, xoxp-, xoxa-, xoxr-, xoxs-)
|
|
36
|
-
if echo "$NEW_CONTENT" | grep -qE 'xox[baprs]-[0-9]{10,}-[0-9a-zA-Z]{24,}'; then
|
|
37
|
-
|
|
38
|
-
fi
|
|
26
|
+
# Slack tokens (xoxb-, xoxp-, xoxa-, xoxr-, xoxs-)
|
|
27
|
+
if echo "$NEW_CONTENT" | grep -qE 'xox[baprs]-[0-9]{10,}-[0-9a-zA-Z]{24,}'; then
|
|
28
|
+
WARNINGS="${WARNINGS}${WARNINGS:+\\n}🚨 SECURITY: Slack token detected! Use environment variables."
|
|
29
|
+
fi
|
|
39
30
|
|
|
40
|
-
# SendGrid keys (SG.*)
|
|
41
|
-
if echo "$NEW_CONTENT" | grep -qE 'SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}'; then
|
|
42
|
-
|
|
43
|
-
fi
|
|
31
|
+
# SendGrid keys (SG.*)
|
|
32
|
+
if echo "$NEW_CONTENT" | grep -qE 'SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}'; then
|
|
33
|
+
WARNINGS="${WARNINGS}${WARNINGS:+\\n}🚨 SECURITY: SendGrid API key detected! Use environment variables."
|
|
34
|
+
fi
|
|
44
35
|
|
|
45
|
-
# Private keys
|
|
46
|
-
if echo "$NEW_CONTENT" | grep -q -- '-----BEGIN.*PRIVATE KEY-----'; then
|
|
47
|
-
|
|
48
|
-
fi
|
|
36
|
+
# Private keys
|
|
37
|
+
if echo "$NEW_CONTENT" | grep -q -- '-----BEGIN.*PRIVATE KEY-----'; then
|
|
38
|
+
WARNINGS="${WARNINGS}${WARNINGS:+\\n}🚨 SECURITY: Private key detected! Never commit private keys."
|
|
39
|
+
fi
|
|
49
40
|
|
|
50
|
-
# Generic API key patterns (api_key = "...", apikey: "...", etc.)
|
|
51
|
-
if echo "$NEW_CONTENT" | grep -qiE '(api[_-]?key|api[_-]?secret)[[:space:]]*[:=][[:space:]]*['"'"'"][a-zA-Z0-9_-]{20,}['"'"'"]'; then
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
41
|
+
# Generic API key patterns (api_key = "...", apikey: "...", etc.)
|
|
42
|
+
if echo "$NEW_CONTENT" | grep -qiE '(api[_-]?key|api[_-]?secret)[[:space:]]*[:=][[:space:]]*['"'"'"][a-zA-Z0-9_-]{20,}['"'"'"]'; then
|
|
43
|
+
# Check it's not a placeholder
|
|
44
|
+
if ! echo "$NEW_CONTENT" | grep -qiE '(example|placeholder|your[_-]?key|xxx|test|dummy|fake|sample|demo)'; then
|
|
45
|
+
WARNINGS="${WARNINGS}${WARNINGS:+\\n}⚠️ Possible hardcoded API key - use environment variables."
|
|
46
|
+
fi
|
|
55
47
|
fi
|
|
56
|
-
fi
|
|
57
48
|
|
|
58
|
-
# Generic secrets (password = "...", token = "...", etc.)
|
|
59
|
-
if echo "$NEW_CONTENT" | grep -qiE '(password|passwd|pwd|secret|token)[[:space:]]*[:=][[:space:]]*['"'"'"][^'"'"'"]{8,}['"'"'"]'; then
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
49
|
+
# Generic secrets (password = "...", token = "...", etc.)
|
|
50
|
+
if echo "$NEW_CONTENT" | grep -qiE '(password|passwd|pwd|secret|token)[[:space:]]*[:=][[:space:]]*['"'"'"][^'"'"'"]{8,}['"'"'"]'; then
|
|
51
|
+
# Check it's not a placeholder or type annotation
|
|
52
|
+
if ! echo "$NEW_CONTENT" | grep -qiE '(example|placeholder|xxx|test|dummy|type|interface|password:)'; then
|
|
53
|
+
WARNINGS="${WARNINGS}${WARNINGS:+\\n}⚠️ Possible hardcoded secret - use environment variables."
|
|
54
|
+
fi
|
|
63
55
|
fi
|
|
64
|
-
|
|
56
|
+
}
|
|
65
57
|
|
|
66
|
-
|
|
67
|
-
if [[ -n "$WARNINGS" ]]; then
|
|
68
|
-
hook_warn "$WARNINGS"
|
|
69
|
-
else
|
|
70
|
-
hook_allow
|
|
71
|
-
fi
|
|
58
|
+
run_warn_hook "ts,tsx,js,jsx,py,json,yaml,yml,env,env.local,env.development,env.production" _check_secrets
|
package/ralph/hooks/warn-urls.sh
CHANGED
|
@@ -7,55 +7,35 @@
|
|
|
7
7
|
|
|
8
8
|
source "$(dirname "$0")/common.sh"
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
# Skip test files
|
|
13
|
-
if is_test_file; then
|
|
14
|
-
hook_allow
|
|
15
|
-
exit 0
|
|
16
|
-
fi
|
|
17
|
-
|
|
18
|
-
# Only check code files
|
|
19
|
-
if ! is_code_file "ts,tsx,js,jsx,py"; then
|
|
20
|
-
hook_allow
|
|
21
|
-
exit 0
|
|
22
|
-
fi
|
|
23
|
-
|
|
24
|
-
WARNINGS=""
|
|
10
|
+
SAFE_DOMAINS="cdn.jsdelivr.net|cdnjs.cloudflare.com|unpkg.com|fonts.googleapis.com|fonts.gstatic.com|api.github.com|raw.githubusercontent.com|registry.npmjs.org|schema.org|www.w3.org|example.com|example.org"
|
|
25
11
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
12
|
+
_check_urls() {
|
|
13
|
+
# Check for localhost URLs
|
|
14
|
+
if echo "$NEW_CONTENT" | grep -qE 'https?://localhost(:[0-9]+)?'; then
|
|
15
|
+
if ! echo "$NEW_CONTENT" | grep -qE '(\|\||\?\?|default|fallback).*localhost'; then
|
|
16
|
+
WARNINGS="⚠️ Hardcoded localhost URL detected - use environment variable (e.g., process.env.API_URL)"
|
|
17
|
+
fi
|
|
31
18
|
fi
|
|
32
|
-
fi
|
|
33
19
|
|
|
34
|
-
# Check for 127.0.0.1 URLs
|
|
35
|
-
if echo "$NEW_CONTENT" | grep -qE 'https?://127\.0\.0\.1(:[0-9]+)?'; then
|
|
36
|
-
|
|
37
|
-
|
|
20
|
+
# Check for 127.0.0.1 URLs
|
|
21
|
+
if echo "$NEW_CONTENT" | grep -qE 'https?://127\.0\.0\.1(:[0-9]+)?'; then
|
|
22
|
+
if ! echo "$NEW_CONTENT" | grep -qE '(\|\||\?\?|default|fallback).*127\.0\.0\.1'; then
|
|
23
|
+
WARNINGS="${WARNINGS}${WARNINGS:+\\n}⚠️ Hardcoded 127.0.0.1 URL detected - use environment variable"
|
|
24
|
+
fi
|
|
38
25
|
fi
|
|
39
|
-
fi
|
|
40
|
-
|
|
41
|
-
# Check for hardcoded production-looking URLs (skip CDNs and common safe domains)
|
|
42
|
-
SAFE_DOMAINS="cdn.jsdelivr.net|cdnjs.cloudflare.com|unpkg.com|fonts.googleapis.com|fonts.gstatic.com|api.github.com|raw.githubusercontent.com|registry.npmjs.org|schema.org|www.w3.org|example.com|example.org"
|
|
43
|
-
|
|
44
|
-
# Look for https:// URLs that aren't safe domains
|
|
45
|
-
PROD_URLS=$(echo "$NEW_CONTENT" | grep -oE 'https://[a-zA-Z0-9][a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' | grep -vE "$SAFE_DOMAINS" || true)
|
|
46
26
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
27
|
+
# Look for https:// URLs that aren't safe domains
|
|
28
|
+
local prod_urls
|
|
29
|
+
prod_urls=$(echo "$NEW_CONTENT" | grep -oE 'https://[a-zA-Z0-9][a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' | grep -vE "$SAFE_DOMAINS" || true)
|
|
30
|
+
|
|
31
|
+
if [[ -n "$prod_urls" ]]; then
|
|
32
|
+
prod_urls=$(echo "$prod_urls" | grep -v -E '(example|placeholder|test|mock)' || true)
|
|
33
|
+
if [[ -n "$prod_urls" ]]; then
|
|
34
|
+
local first_url
|
|
35
|
+
first_url=$(echo "$prod_urls" | head -1)
|
|
36
|
+
WARNINGS="${WARNINGS}${WARNINGS:+\\n}⚠️ Hardcoded URL ($first_url) - consider using environment variable"
|
|
37
|
+
fi
|
|
53
38
|
fi
|
|
54
|
-
|
|
39
|
+
}
|
|
55
40
|
|
|
56
|
-
|
|
57
|
-
if [[ -n "$WARNINGS" ]]; then
|
|
58
|
-
hook_warn "$WARNINGS"
|
|
59
|
-
else
|
|
60
|
-
hook_allow
|
|
61
|
-
fi
|
|
41
|
+
run_warn_hook "ts,tsx,js,jsx,py" _check_urls --skip-tests
|
package/ralph/init.sh
CHANGED
|
@@ -84,7 +84,7 @@ configure_test_auth() {
|
|
|
84
84
|
print_info "=== Test Authentication Setup ==="
|
|
85
85
|
echo ""
|
|
86
86
|
echo "Ralph needs test credentials to verify authenticated endpoints."
|
|
87
|
-
echo "(You can skip this and
|
|
87
|
+
echo "(You can skip this and add to .env later)"
|
|
88
88
|
echo ""
|
|
89
89
|
|
|
90
90
|
# Ask if they want to configure auth
|
|
@@ -92,7 +92,7 @@ configure_test_auth() {
|
|
|
92
92
|
echo ""
|
|
93
93
|
|
|
94
94
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
95
|
-
print_info "Skipped.
|
|
95
|
+
print_info "Skipped. Add RALPH_TEST_USER and RALPH_TEST_PASSWORD to .env when needed."
|
|
96
96
|
return 0
|
|
97
97
|
fi
|
|
98
98
|
|
|
@@ -103,84 +103,31 @@ configure_test_auth() {
|
|
|
103
103
|
|
|
104
104
|
if [[ -z "$test_user" || -z "$test_password" ]]; then
|
|
105
105
|
print_warning "Credentials not provided."
|
|
106
|
-
echo "
|
|
107
|
-
echo "
|
|
108
|
-
echo "
|
|
106
|
+
echo " Add to .env later:"
|
|
107
|
+
echo " RALPH_TEST_USER=your-email"
|
|
108
|
+
echo " RALPH_TEST_PASSWORD=your-password"
|
|
109
109
|
return 0
|
|
110
110
|
fi
|
|
111
111
|
|
|
112
|
-
#
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
# Save credentials to .env (gitignored, never committed)
|
|
113
|
+
if [[ ! -f ".env" ]]; then
|
|
114
|
+
touch ".env"
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
# Remove old entries if present
|
|
118
|
+
if grep -q "^RALPH_TEST_USER=" .env 2>/dev/null; then
|
|
115
119
|
local tmpfile
|
|
116
120
|
tmpfile=$(mktemp)
|
|
117
|
-
|
|
118
|
-
'.auth.testUser = $user | .auth.testPassword = $pass' \
|
|
119
|
-
"$config" > "$tmpfile" 2>/dev/null; then
|
|
120
|
-
mv "$tmpfile" "$config"
|
|
121
|
-
print_success "Test credentials saved to .ralph/config.json"
|
|
122
|
-
print_warning "Note: Credentials stored in plain text. Consider using env vars instead:"
|
|
123
|
-
echo " export RALPH_TEST_USER='$test_user'"
|
|
124
|
-
echo " export RALPH_TEST_PASSWORD='****'"
|
|
125
|
-
else
|
|
126
|
-
rm -f "$tmpfile"
|
|
127
|
-
print_warning "Failed to update config. Edit .ralph/config.json manually."
|
|
128
|
-
fi
|
|
121
|
+
grep -v "^RALPH_TEST_USER=\|^RALPH_TEST_PASSWORD=" .env > "$tmpfile" && mv "$tmpfile" .env
|
|
129
122
|
fi
|
|
130
|
-
}
|
|
131
123
|
|
|
132
|
-
#
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if [[ -d "frontend" && -d "core" ]]; then
|
|
138
|
-
project_type="fullstack"
|
|
139
|
-
elif [[ -d "frontend" && -d "backend" ]]; then
|
|
140
|
-
project_type="fullstack"
|
|
141
|
-
elif [[ -d "apps" ]]; then
|
|
142
|
-
project_type="fullstack" # Monorepo
|
|
143
|
-
# Then check for single-language projects
|
|
144
|
-
elif [[ -f "Cargo.toml" ]]; then
|
|
145
|
-
project_type="rust"
|
|
146
|
-
elif [[ -f "go.mod" ]]; then
|
|
147
|
-
project_type="go"
|
|
148
|
-
elif [[ -f "mix.exs" ]]; then
|
|
149
|
-
project_type="elixir"
|
|
150
|
-
# Check for Python framework variants (more specific first)
|
|
151
|
-
elif [[ -f "pyproject.toml" ]]; then
|
|
152
|
-
# FastMCP detection (check for fastmcp in any quote style)
|
|
153
|
-
if grep -qiE "(fastmcp|\"fastmcp\"|'fastmcp')" pyproject.toml 2>/dev/null; then
|
|
154
|
-
project_type="fastmcp"
|
|
155
|
-
# Django detection
|
|
156
|
-
elif grep -qiE "(django|\"django\"|'django')" pyproject.toml 2>/dev/null || [[ -f "manage.py" ]]; then
|
|
157
|
-
project_type="django"
|
|
158
|
-
# FastAPI detection
|
|
159
|
-
elif grep -qiE "(fastapi|\"fastapi\"|'fastapi')" pyproject.toml 2>/dev/null; then
|
|
160
|
-
project_type="fastapi"
|
|
161
|
-
else
|
|
162
|
-
project_type="python"
|
|
163
|
-
fi
|
|
164
|
-
elif [[ -f "requirements.txt" || -f "setup.py" ]]; then
|
|
165
|
-
# Check requirements.txt for frameworks
|
|
166
|
-
if [[ -f "requirements.txt" ]]; then
|
|
167
|
-
if grep -qi 'fastmcp' requirements.txt 2>/dev/null; then
|
|
168
|
-
project_type="fastmcp"
|
|
169
|
-
elif grep -qi 'django' requirements.txt 2>/dev/null || [[ -f "manage.py" ]]; then
|
|
170
|
-
project_type="django"
|
|
171
|
-
elif grep -qi 'fastapi' requirements.txt 2>/dev/null; then
|
|
172
|
-
project_type="fastapi"
|
|
173
|
-
else
|
|
174
|
-
project_type="python"
|
|
175
|
-
fi
|
|
176
|
-
else
|
|
177
|
-
project_type="python"
|
|
178
|
-
fi
|
|
179
|
-
elif [[ -f "package.json" ]]; then
|
|
180
|
-
project_type="node"
|
|
181
|
-
fi
|
|
124
|
+
# Append credentials
|
|
125
|
+
echo "" >> .env
|
|
126
|
+
echo "# Test credentials for browser automation (auto-added by ralph init)" >> .env
|
|
127
|
+
printf 'RALPH_TEST_USER=%s\n' "$test_user" >> .env
|
|
128
|
+
printf 'RALPH_TEST_PASSWORD=%s\n' "$test_password" >> .env
|
|
182
129
|
|
|
183
|
-
|
|
130
|
+
print_success "Test credentials saved to .env (gitignored — never committed)"
|
|
184
131
|
}
|
|
185
132
|
|
|
186
133
|
# Auto-detect and configure project-specific settings
|
|
@@ -215,7 +162,7 @@ auto_configure_project() {
|
|
|
215
162
|
fi
|
|
216
163
|
fi
|
|
217
164
|
|
|
218
|
-
# 2. Detect
|
|
165
|
+
# 2. Detect urls.frontend by parsing actual config files for port values
|
|
219
166
|
local base_url=""
|
|
220
167
|
local web_port=""
|
|
221
168
|
|
|
@@ -281,11 +228,11 @@ auto_configure_project() {
|
|
|
281
228
|
fi
|
|
282
229
|
|
|
283
230
|
if [[ -n "$base_url" ]]; then
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
jq --arg url "$base_url" '.
|
|
288
|
-
echo " Auto-detected
|
|
231
|
+
local existing_frontend
|
|
232
|
+
existing_frontend=$(jq -r '.urls.frontend // empty' "$tmpfile" 2>/dev/null)
|
|
233
|
+
if [[ -z "$existing_frontend" ]]; then
|
|
234
|
+
jq --arg url "$base_url" '.urls.frontend = $url' "$tmpfile" > "${tmpfile}.new" && mv "${tmpfile}.new" "$tmpfile"
|
|
235
|
+
echo " Auto-detected urls.frontend: $base_url"
|
|
289
236
|
updated=true
|
|
290
237
|
fi
|
|
291
238
|
fi
|
|
@@ -448,7 +395,7 @@ auto_configure_project() {
|
|
|
448
395
|
if ! jq -e '.mcp.serverModule' "$tmpfile" >/dev/null 2>&1 || [[ "$(jq -r '.mcp.serverModule' "$tmpfile")" == "" ]]; then
|
|
449
396
|
jq --arg mod "$mcp_module" '.mcp.serverModule = $mod' "$tmpfile" > "${tmpfile}.new" && mv "${tmpfile}.new" "$tmpfile"
|
|
450
397
|
# Also update the dev command
|
|
451
|
-
jq --arg cmd "
|
|
398
|
+
jq --arg cmd "python3 -m ${mcp_module}.server" '.commands.dev = $cmd' "$tmpfile" > "${tmpfile}.new" && mv "${tmpfile}.new" "$tmpfile"
|
|
452
399
|
echo " Auto-detected mcp.serverModule: $mcp_module"
|
|
453
400
|
updated=true
|
|
454
401
|
fi
|
|
@@ -586,11 +533,13 @@ auto_configure_project() {
|
|
|
586
533
|
updated=true
|
|
587
534
|
fi
|
|
588
535
|
else
|
|
589
|
-
# No tests found - check if warning
|
|
536
|
+
# No tests found by auto-detection - check if already configured or warning suppressed
|
|
590
537
|
local require_tests
|
|
591
538
|
require_tests=$(jq -r '.checks.requireTests // true' "$tmpfile" 2>/dev/null)
|
|
539
|
+
local existing_test_dir
|
|
540
|
+
existing_test_dir=$(jq -r '.tests.directory // empty' "$tmpfile" 2>/dev/null)
|
|
592
541
|
|
|
593
|
-
if [[ "$require_tests" == "true" ]]; then
|
|
542
|
+
if [[ "$require_tests" == "true" && -z "$existing_test_dir" ]]; then
|
|
594
543
|
echo ""
|
|
595
544
|
print_warning "No test directory or test files found."
|
|
596
545
|
echo " Without tests, Ralph relies on lint, type-checking, and PRD test steps."
|
|
@@ -630,6 +579,20 @@ auto_configure_project() {
|
|
|
630
579
|
echo " Auto-updated commands.dev: $new_dev"
|
|
631
580
|
updated=true
|
|
632
581
|
fi
|
|
582
|
+
else
|
|
583
|
+
# No runner detected — convert bare 'python' to 'python3' for macOS compat
|
|
584
|
+
local current_dev
|
|
585
|
+
current_dev=$(jq -r '.commands.dev // ""' "$tmpfile" 2>/dev/null)
|
|
586
|
+
if [[ "$current_dev" == "python "* ]]; then
|
|
587
|
+
local new_dev="python3 ${current_dev#python }"
|
|
588
|
+
jq --arg dev "$new_dev" '.commands.dev = $dev' "$tmpfile" > "${tmpfile}.new" && mv "${tmpfile}.new" "$tmpfile"
|
|
589
|
+
echo " Auto-updated commands.dev: $new_dev (macOS compat)"
|
|
590
|
+
updated=true
|
|
591
|
+
fi
|
|
592
|
+
echo ""
|
|
593
|
+
echo " Tip: No Python package manager detected. Consider uv for faster deps & consistent environments:"
|
|
594
|
+
echo " curl -LsSf https://astral.sh/uv/install.sh | sh"
|
|
595
|
+
echo " https://docs.astral.sh/uv/"
|
|
633
596
|
fi
|
|
634
597
|
fi
|
|
635
598
|
|
|
@@ -742,13 +705,22 @@ Commands:
|
|
|
742
705
|
prd <notes> Generate PRD interactively (quick mode)
|
|
743
706
|
prd --file <file> Generate PRD from file
|
|
744
707
|
run Run autonomous loop until all stories pass
|
|
745
|
-
run --max <n>
|
|
708
|
+
run --max <n> Limit to n iterations (no limit by default)
|
|
746
709
|
run --fast Skip code review for faster iterations
|
|
710
|
+
run --quiet Suppress the live activity feed
|
|
747
711
|
stop Stop loop after current story finishes
|
|
748
712
|
skip Skip the current story, move to next
|
|
749
713
|
status Show current feature and story status
|
|
750
714
|
check Run verification checks only
|
|
751
715
|
verify <story-id> Verify a specific story
|
|
716
|
+
uat Run UAT loop (team explores, tests, fixes bugs)
|
|
717
|
+
uat --plan-only Generate test plan without executing
|
|
718
|
+
uat --focus <id|cat> Run specific test case or category
|
|
719
|
+
uat --no-fix Write tests but don't fix app bugs
|
|
720
|
+
uat --review Force review of existing plan
|
|
721
|
+
chaos-agent Run Chaos Agent (adversarial red team testing)
|
|
722
|
+
chaos-agent --plan-only Generate chaos plan without executing
|
|
723
|
+
chaos-agent --no-fix Find vulnerabilities without fixing
|
|
752
724
|
sign <pattern> [cat] Add a learned pattern (sign)
|
|
753
725
|
signs List all learned patterns
|
|
754
726
|
backup Backup detected databases to .backups/
|
|
@@ -766,6 +738,10 @@ Examples:
|
|
|
766
738
|
npx agentic-loop prd "Add a contact form"
|
|
767
739
|
npx agentic-loop run
|
|
768
740
|
npx agentic-loop run --max 10
|
|
741
|
+
npx agentic-loop uat
|
|
742
|
+
npx agentic-loop uat --focus auth
|
|
743
|
+
npx agentic-loop chaos-agent
|
|
744
|
+
npx agentic-loop chaos-agent --no-fix
|
|
769
745
|
npx agentic-loop status
|
|
770
746
|
npx agentic-loop sign "Always use camelCase" frontend
|
|
771
747
|
|