agentic-loop 1.0.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.
Files changed (162) hide show
  1. package/.claude/commands/explain.md +114 -0
  2. package/.claude/commands/idea.md +398 -0
  3. package/.claude/commands/my-dna.md +122 -0
  4. package/.claude/commands/prd.md +286 -0
  5. package/.claude/commands/review.md +167 -0
  6. package/.claude/commands/sign.md +32 -0
  7. package/.claude/commands/styleguide.md +450 -0
  8. package/.claude/commands/tour.md +301 -0
  9. package/.claude/commands/vibe-check.md +116 -0
  10. package/.claude/commands/vibe-help.md +47 -0
  11. package/.claude/commands/vibe-list.md +203 -0
  12. package/.pre-commit-hooks.yaml +102 -0
  13. package/LICENSE +21 -0
  14. package/README.md +238 -0
  15. package/bin/agentic-loop.sh +24 -0
  16. package/bin/postinstall.sh +29 -0
  17. package/bin/ralph.sh +171 -0
  18. package/bin/vibe-check.js +19 -0
  19. package/dist/checks/check-any-types.d.ts +6 -0
  20. package/dist/checks/check-any-types.d.ts.map +1 -0
  21. package/dist/checks/check-any-types.js +73 -0
  22. package/dist/checks/check-any-types.js.map +1 -0
  23. package/dist/checks/check-commented-code.d.ts +6 -0
  24. package/dist/checks/check-commented-code.d.ts.map +1 -0
  25. package/dist/checks/check-commented-code.js +81 -0
  26. package/dist/checks/check-commented-code.js.map +1 -0
  27. package/dist/checks/check-console-error.d.ts +6 -0
  28. package/dist/checks/check-console-error.d.ts.map +1 -0
  29. package/dist/checks/check-console-error.js +41 -0
  30. package/dist/checks/check-console-error.js.map +1 -0
  31. package/dist/checks/check-debug-statements.d.ts +6 -0
  32. package/dist/checks/check-debug-statements.d.ts.map +1 -0
  33. package/dist/checks/check-debug-statements.js +120 -0
  34. package/dist/checks/check-debug-statements.js.map +1 -0
  35. package/dist/checks/check-deep-nesting.d.ts +6 -0
  36. package/dist/checks/check-deep-nesting.d.ts.map +1 -0
  37. package/dist/checks/check-deep-nesting.js +116 -0
  38. package/dist/checks/check-deep-nesting.js.map +1 -0
  39. package/dist/checks/check-docker-platform.d.ts +6 -0
  40. package/dist/checks/check-docker-platform.d.ts.map +1 -0
  41. package/dist/checks/check-docker-platform.js +42 -0
  42. package/dist/checks/check-docker-platform.js.map +1 -0
  43. package/dist/checks/check-dry-violations.d.ts +6 -0
  44. package/dist/checks/check-dry-violations.d.ts.map +1 -0
  45. package/dist/checks/check-dry-violations.js +124 -0
  46. package/dist/checks/check-dry-violations.js.map +1 -0
  47. package/dist/checks/check-empty-catch.d.ts +6 -0
  48. package/dist/checks/check-empty-catch.d.ts.map +1 -0
  49. package/dist/checks/check-empty-catch.js +111 -0
  50. package/dist/checks/check-empty-catch.js.map +1 -0
  51. package/dist/checks/check-function-length.d.ts +6 -0
  52. package/dist/checks/check-function-length.d.ts.map +1 -0
  53. package/dist/checks/check-function-length.js +152 -0
  54. package/dist/checks/check-function-length.js.map +1 -0
  55. package/dist/checks/check-hardcoded-ai-models.d.ts +10 -0
  56. package/dist/checks/check-hardcoded-ai-models.d.ts.map +1 -0
  57. package/dist/checks/check-hardcoded-ai-models.js +102 -0
  58. package/dist/checks/check-hardcoded-ai-models.js.map +1 -0
  59. package/dist/checks/check-hardcoded-urls.d.ts +6 -0
  60. package/dist/checks/check-hardcoded-urls.d.ts.map +1 -0
  61. package/dist/checks/check-hardcoded-urls.js +124 -0
  62. package/dist/checks/check-hardcoded-urls.js.map +1 -0
  63. package/dist/checks/check-magic-numbers.d.ts +6 -0
  64. package/dist/checks/check-magic-numbers.d.ts.map +1 -0
  65. package/dist/checks/check-magic-numbers.js +116 -0
  66. package/dist/checks/check-magic-numbers.js.map +1 -0
  67. package/dist/checks/check-secrets.d.ts +6 -0
  68. package/dist/checks/check-secrets.d.ts.map +1 -0
  69. package/dist/checks/check-secrets.js +138 -0
  70. package/dist/checks/check-secrets.js.map +1 -0
  71. package/dist/checks/check-snake-case-ts.d.ts +6 -0
  72. package/dist/checks/check-snake-case-ts.d.ts.map +1 -0
  73. package/dist/checks/check-snake-case-ts.js +78 -0
  74. package/dist/checks/check-snake-case-ts.js.map +1 -0
  75. package/dist/checks/check-todo-fixme.d.ts +6 -0
  76. package/dist/checks/check-todo-fixme.d.ts.map +1 -0
  77. package/dist/checks/check-todo-fixme.js +41 -0
  78. package/dist/checks/check-todo-fixme.js.map +1 -0
  79. package/dist/checks/check-unsafe-html.d.ts +6 -0
  80. package/dist/checks/check-unsafe-html.d.ts.map +1 -0
  81. package/dist/checks/check-unsafe-html.js +101 -0
  82. package/dist/checks/check-unsafe-html.js.map +1 -0
  83. package/dist/checks/index.d.ts +30 -0
  84. package/dist/checks/index.d.ts.map +1 -0
  85. package/dist/checks/index.js +57 -0
  86. package/dist/checks/index.js.map +1 -0
  87. package/dist/cli.d.ts +13 -0
  88. package/dist/cli.d.ts.map +1 -0
  89. package/dist/cli.js +208 -0
  90. package/dist/cli.js.map +1 -0
  91. package/dist/index.d.ts +9 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +10 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/utils/file-reader.d.ts +24 -0
  96. package/dist/utils/file-reader.d.ts.map +1 -0
  97. package/dist/utils/file-reader.js +146 -0
  98. package/dist/utils/file-reader.js.map +1 -0
  99. package/dist/utils/patterns.d.ts +27 -0
  100. package/dist/utils/patterns.d.ts.map +1 -0
  101. package/dist/utils/patterns.js +84 -0
  102. package/dist/utils/patterns.js.map +1 -0
  103. package/dist/utils/reporters.d.ts +21 -0
  104. package/dist/utils/reporters.d.ts.map +1 -0
  105. package/dist/utils/reporters.js +115 -0
  106. package/dist/utils/reporters.js.map +1 -0
  107. package/dist/utils/types.d.ts +71 -0
  108. package/dist/utils/types.d.ts.map +1 -0
  109. package/dist/utils/types.js +5 -0
  110. package/dist/utils/types.js.map +1 -0
  111. package/package.json +83 -0
  112. package/ralph/api.sh +216 -0
  113. package/ralph/backup.sh +838 -0
  114. package/ralph/browser-verify/README.md +135 -0
  115. package/ralph/browser-verify/verify.ts +450 -0
  116. package/ralph/checks/check-fastapi-responses.py +155 -0
  117. package/ralph/hooks/hooks-config.json +72 -0
  118. package/ralph/hooks/inject-context.sh +44 -0
  119. package/ralph/hooks/install.sh +207 -0
  120. package/ralph/hooks/log-tools.sh +45 -0
  121. package/ralph/hooks/protect-prd.sh +27 -0
  122. package/ralph/hooks/save-learnings.sh +36 -0
  123. package/ralph/hooks/warn-debug.sh +54 -0
  124. package/ralph/hooks/warn-empty-catch.sh +63 -0
  125. package/ralph/hooks/warn-secrets.sh +89 -0
  126. package/ralph/hooks/warn-urls.sh +77 -0
  127. package/ralph/init.sh +515 -0
  128. package/ralph/loop.sh +730 -0
  129. package/ralph/playwright.sh +238 -0
  130. package/ralph/prd.sh +295 -0
  131. package/ralph/setup/feature-tour.sh +155 -0
  132. package/ralph/setup/quick-setup.sh +239 -0
  133. package/ralph/setup/tutorial.sh +159 -0
  134. package/ralph/setup/ui.sh +136 -0
  135. package/ralph/setup.sh +401 -0
  136. package/ralph/signs.sh +150 -0
  137. package/ralph/utils.sh +682 -0
  138. package/ralph/verify/browser.sh +324 -0
  139. package/ralph/verify/lint.sh +363 -0
  140. package/ralph/verify/review.sh +152 -0
  141. package/ralph/verify/tests.sh +81 -0
  142. package/ralph/verify.sh +268 -0
  143. package/templates/PROMPT.md +235 -0
  144. package/templates/config/fullstack.json +86 -0
  145. package/templates/config/go.json +81 -0
  146. package/templates/config/minimal.json +76 -0
  147. package/templates/config/node.json +81 -0
  148. package/templates/config/python.json +81 -0
  149. package/templates/config/rust.json +81 -0
  150. package/templates/examples/CLAUDE-django.md +174 -0
  151. package/templates/examples/CLAUDE-fastapi.md +270 -0
  152. package/templates/examples/CLAUDE-fastmcp.md +352 -0
  153. package/templates/examples/CLAUDE-fullstack.md +256 -0
  154. package/templates/examples/CLAUDE-node.md +246 -0
  155. package/templates/examples/CLAUDE-react.md +138 -0
  156. package/templates/optional/cursorrules.template +147 -0
  157. package/templates/optional/eslint.config.js +34 -0
  158. package/templates/optional/lint-staged.config.js +34 -0
  159. package/templates/optional/ruff.toml +125 -0
  160. package/templates/optional/vibe-check.yml +116 -0
  161. package/templates/optional/vscode-settings.json +127 -0
  162. package/templates/signs.json +46 -0
package/ralph/init.sh ADDED
@@ -0,0 +1,515 @@
1
+ #!/usr/bin/env bash
2
+ # shellcheck shell=bash
3
+ # init.sh - Initialize ralph in a project
4
+
5
+ ralph_init() {
6
+ # Check if already initialized (progress.txt is created by full init)
7
+ if [[ -f "$RALPH_DIR/progress.txt" ]]; then
8
+ echo "Ralph already initialized in this directory."
9
+ echo "Use 'ralph run' to start the loop or 'ralph status' to check status."
10
+ return 0
11
+ fi
12
+
13
+ echo "Initializing ralph..."
14
+
15
+ # Create directory structure
16
+ mkdir -p "$RALPH_DIR/archive" "$RALPH_DIR/screenshots"
17
+
18
+ # Detect project type and generate appropriate config
19
+ local project_type
20
+ project_type=$(detect_project_type)
21
+ echo "Detected project type: $project_type"
22
+
23
+ # Copy config template based on project type (only if missing)
24
+ if [[ ! -f "$RALPH_DIR/config.json" ]]; then
25
+ local config_template="$RALPH_TEMPLATES/config/${project_type}.json"
26
+ if [[ -f "$config_template" ]]; then
27
+ cp "$config_template" "$RALPH_DIR/config.json"
28
+ else
29
+ # Fall back to minimal config
30
+ cp "$RALPH_TEMPLATES/config/minimal.json" "$RALPH_DIR/config.json"
31
+ fi
32
+ fi
33
+
34
+ # Create signs with defaults (only if missing)
35
+ if [[ ! -f "$RALPH_DIR/signs.json" ]]; then
36
+ if [[ -f "$RALPH_TEMPLATES/signs.json" ]]; then
37
+ cp "$RALPH_TEMPLATES/signs.json" "$RALPH_DIR/signs.json"
38
+ else
39
+ echo '{"signs": []}' > "$RALPH_DIR/signs.json"
40
+ fi
41
+ fi
42
+
43
+ # Create progress log (only if missing)
44
+ if [[ ! -f "$RALPH_DIR/progress.txt" ]]; then
45
+ local timestamp
46
+ timestamp=$(date -Iseconds 2>/dev/null || date +%Y-%m-%dT%H:%M:%S)
47
+ echo "[$timestamp] INIT Ralph initialized" > "$RALPH_DIR/progress.txt"
48
+ fi
49
+
50
+ # Copy PROMPT.md template if it doesn't exist in project
51
+ if [[ ! -f "PROMPT.md" ]]; then
52
+ cp "$RALPH_TEMPLATES/PROMPT.md" "PROMPT.md"
53
+ echo "Created PROMPT.md template"
54
+ fi
55
+
56
+ # Auto-detect and configure project-specific settings
57
+ echo ""
58
+ echo "Auto-configuring project settings..."
59
+ auto_configure_project
60
+
61
+ print_success "Ralph initialized!"
62
+ echo ""
63
+
64
+ # Prompt for test credentials
65
+ configure_test_auth
66
+
67
+ echo ""
68
+ echo "Next steps:"
69
+ echo " 1. Review .ralph/config.json (test credentials, checks, etc.)"
70
+ echo " 2. Generate PRD:"
71
+ echo " - Thorough: /idea 'feature description' (brainstorm + architecture + scalability)"
72
+ echo " - Quick: ralph prd 'feature description' (basic PRD)"
73
+ echo " 3. Start loop: ralph run"
74
+ }
75
+
76
+ # Configure test authentication credentials
77
+ configure_test_auth() {
78
+ # Skip if not running in an interactive terminal
79
+ if [[ ! -t 0 ]]; then
80
+ return 0
81
+ fi
82
+
83
+ echo ""
84
+ print_info "=== Test Authentication Setup ==="
85
+ echo ""
86
+ echo "Ralph needs test credentials to verify authenticated endpoints."
87
+ echo "(You can skip this and edit .ralph/config.json later)"
88
+ echo ""
89
+
90
+ # Ask if they want to configure auth
91
+ read -p "Configure test credentials now? [y/N] " -n 1 -r
92
+ echo ""
93
+
94
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
95
+ print_info "Skipped. Edit .ralph/config.json to add credentials later."
96
+ return 0
97
+ fi
98
+
99
+ echo ""
100
+ read -p "Test user email/username: " test_user
101
+ read -s -p "Test user password: " test_password
102
+ echo ""
103
+
104
+ if [[ -z "$test_user" || -z "$test_password" ]]; then
105
+ print_warning "Credentials not provided."
106
+ echo " Options to add them later:"
107
+ echo " 1. Edit .ralph/config.json (stored in plain text)"
108
+ echo " 2. Set RALPH_TEST_USER and RALPH_TEST_PASSWORD env vars (recommended)"
109
+ return 0
110
+ fi
111
+
112
+ # Update config.json with credentials
113
+ local config="$RALPH_DIR/config.json"
114
+ if [[ -f "$config" ]]; then
115
+ local tmpfile
116
+ tmpfile=$(mktemp)
117
+ if jq --arg user "$test_user" --arg pass "$test_password" \
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
129
+ fi
130
+ }
131
+
132
+ # Detect the type of project based on files present
133
+ detect_project_type() {
134
+ local project_type="minimal"
135
+
136
+ # Check for fullstack patterns first (more specific)
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 "pyproject.toml" || -f "requirements.txt" || -f "setup.py" ]]; then
149
+ project_type="python"
150
+ elif [[ -f "package.json" ]]; then
151
+ project_type="node"
152
+ fi
153
+
154
+ echo "$project_type"
155
+ }
156
+
157
+ # Auto-detect and configure project-specific settings
158
+ auto_configure_project() {
159
+ local config="$RALPH_DIR/config.json"
160
+ [[ ! -f "$config" ]] && return 0
161
+
162
+ local updated=false
163
+ local tmpfile
164
+ tmpfile=$(mktemp)
165
+ cp "$config" "$tmpfile"
166
+
167
+ # 1. Detect Playwright test directory
168
+ local playwright_dir=""
169
+ for dir in "tests/e2e" "e2e" "test/e2e" \
170
+ "apps/web/tests/e2e" "apps/frontend/tests/e2e" \
171
+ "frontend/tests/e2e" "frontend/e2e" \
172
+ "packages/web/tests/e2e"; do
173
+ if [[ -d "$dir" ]]; then
174
+ playwright_dir="$dir"
175
+ break
176
+ fi
177
+ done
178
+
179
+ if [[ -n "$playwright_dir" ]]; then
180
+ if jq -e '.playwright.testDir' "$tmpfile" >/dev/null 2>&1; then
181
+ : # Already set
182
+ else
183
+ jq --arg dir "$playwright_dir" '.playwright.testDir = $dir' "$tmpfile" > "${tmpfile}.new" && mv "${tmpfile}.new" "$tmpfile"
184
+ echo " Auto-detected playwright.testDir: $playwright_dir"
185
+ updated=true
186
+ fi
187
+ fi
188
+
189
+ # 2. Detect testUrlBase by parsing actual config files for port values
190
+ local base_url=""
191
+ local web_port=""
192
+
193
+ # Priority 1: docker-compose.yml - parse actual port mappings
194
+ for compose_file in "docker-compose.yml" "docker-compose.yaml" "compose.yml" "compose.yaml"; do
195
+ if [[ -f "$compose_file" && -z "$web_port" ]]; then
196
+ # Extract port from web/frontend service: "5173:5173" -> 5173
197
+ web_port=$(grep -A 20 -E '^\s*(web|frontend|client|ui):' "$compose_file" 2>/dev/null | \
198
+ grep -E '^\s*-\s*"?[0-9]+:[0-9]+"?' | head -1 | \
199
+ grep -oE '[0-9]+:' | head -1 | tr -d ':')
200
+ fi
201
+ done
202
+
203
+ # Priority 2: Vite config - parse server.port
204
+ if [[ -z "$web_port" ]]; then
205
+ for vite_config in "vite.config.ts" "vite.config.js" "apps/web/vite.config.ts" "apps/web/vite.config.js" \
206
+ "apps/frontend/vite.config.ts" "frontend/vite.config.ts"; do
207
+ if [[ -f "$vite_config" ]]; then
208
+ # Look for port: 3000 or port: '3000'
209
+ web_port=$(grep -E 'port\s*[=:]\s*[0-9]+' "$vite_config" 2>/dev/null | grep -oE '[0-9]{4}' | head -1)
210
+ [[ -z "$web_port" ]] && web_port="5173" # Vite's documented default
211
+ break
212
+ fi
213
+ done
214
+ fi
215
+
216
+ # Priority 3: Hugo config - parse server.port or use documented default
217
+ if [[ -z "$web_port" ]]; then
218
+ for hugo_config in "hugo.toml" "hugo.yaml" "hugo.json" "config.toml"; do
219
+ if [[ -f "$hugo_config" ]] && [[ -d "content" || -d "layouts" || -d "themes" ]]; then
220
+ web_port=$(grep -E 'port\s*=' "$hugo_config" 2>/dev/null | grep -oE '[0-9]+' | head -1)
221
+ [[ -z "$web_port" ]] && web_port="1313" # Hugo's documented default
222
+ break
223
+ fi
224
+ done
225
+ fi
226
+
227
+ # Priority 4: Next.js - check package.json dev script for -p flag
228
+ if [[ -z "$web_port" ]]; then
229
+ for next_config in "next.config.js" "next.config.ts" "next.config.mjs" \
230
+ "apps/web/next.config.js" "apps/web/next.config.mjs"; do
231
+ if [[ -f "$next_config" ]]; then
232
+ # Check if package.json has custom port: "dev": "next dev -p 4000"
233
+ local pkg_dir
234
+ pkg_dir=$(dirname "$next_config")
235
+ if [[ -f "$pkg_dir/package.json" ]]; then
236
+ web_port=$(grep -E '"dev".*-p\s*[0-9]+' "$pkg_dir/package.json" 2>/dev/null | grep -oE '\-p\s*[0-9]+' | grep -oE '[0-9]+')
237
+ fi
238
+ [[ -z "$web_port" ]] && web_port="3000" # Next.js documented default
239
+ break
240
+ fi
241
+ done
242
+ fi
243
+
244
+ # Priority 5: Generic package.json port detection
245
+ if [[ -z "$web_port" && -f "package.json" ]]; then
246
+ # Look for explicit port in scripts: --port 3000, -p 3000, :3000
247
+ web_port=$(grep -E '"(dev|start|serve)"' package.json 2>/dev/null | grep -oE '(--port|-p|:)[[:space:]]*[0-9]{4}' | grep -oE '[0-9]{4}' | head -1)
248
+ fi
249
+
250
+ if [[ -n "$web_port" ]]; then
251
+ base_url="http://localhost:$web_port"
252
+ fi
253
+
254
+ if [[ -n "$base_url" ]]; then
255
+ if jq -e '.testUrlBase' "$tmpfile" >/dev/null 2>&1 && [[ "$(jq -r '.testUrlBase' "$tmpfile")" != "" ]]; then
256
+ : # Already set
257
+ else
258
+ jq --arg url "$base_url" '.testUrlBase = $url' "$tmpfile" > "${tmpfile}.new" && mv "${tmpfile}.new" "$tmpfile"
259
+ echo " Auto-detected testUrlBase: $base_url"
260
+ updated=true
261
+ fi
262
+ fi
263
+
264
+ # 3. Detect frontend/backend directories for monorepos
265
+ local frontend_dir="" backend_dir=""
266
+ for dir in "apps/web" "apps/frontend" "frontend" "packages/web" "web"; do
267
+ if [[ -d "$dir" && -f "$dir/package.json" ]]; then
268
+ frontend_dir="$dir"
269
+ break
270
+ fi
271
+ done
272
+ for dir in "apps/api" "apps/backend" "backend" "api" "server"; do
273
+ if [[ -d "$dir" ]] && ([[ -f "$dir/package.json" ]] || [[ -f "$dir/pyproject.toml" ]] || [[ -f "$dir/requirements.txt" ]]); then
274
+ backend_dir="$dir"
275
+ break
276
+ fi
277
+ done
278
+
279
+ if [[ -n "$frontend_dir" ]]; then
280
+ if jq -e '.directories.frontend' "$tmpfile" >/dev/null 2>&1; then
281
+ : # Already set
282
+ else
283
+ jq --arg dir "$frontend_dir" '.directories.frontend = $dir' "$tmpfile" > "${tmpfile}.new" && mv "${tmpfile}.new" "$tmpfile"
284
+ echo " Auto-detected directories.frontend: $frontend_dir"
285
+ updated=true
286
+ fi
287
+ fi
288
+
289
+ if [[ -n "$backend_dir" ]]; then
290
+ if jq -e '.directories.backend' "$tmpfile" >/dev/null 2>&1; then
291
+ : # Already set
292
+ else
293
+ jq --arg dir "$backend_dir" '.directories.backend = $dir' "$tmpfile" > "${tmpfile}.new" && mv "${tmpfile}.new" "$tmpfile"
294
+ echo " Auto-detected directories.backend: $backend_dir"
295
+ updated=true
296
+ fi
297
+ fi
298
+
299
+ # 4. Detect API baseUrl by parsing actual config files for port values
300
+ local api_url=""
301
+ local api_port=""
302
+
303
+ # Priority 1: docker-compose.yml - parse actual port mappings
304
+ for compose_file in "docker-compose.yml" "docker-compose.yaml" "compose.yml" "compose.yaml"; do
305
+ if [[ -f "$compose_file" && -z "$api_port" ]]; then
306
+ # Extract port from api/backend service: "8000:8000" -> 8000
307
+ api_port=$(grep -A 20 -E '^\s*(api|backend|server|app):' "$compose_file" 2>/dev/null | \
308
+ grep -E '^\s*-\s*"?[0-9]+:[0-9]+"?' | head -1 | \
309
+ grep -oE '[0-9]+:' | head -1 | tr -d ':')
310
+ fi
311
+ done
312
+
313
+ # Priority 2: Dockerfile EXPOSE directive
314
+ if [[ -n "$backend_dir" && -z "$api_port" ]]; then
315
+ if [[ -f "$backend_dir/Dockerfile" ]]; then
316
+ api_port=$(grep -i "^EXPOSE" "$backend_dir/Dockerfile" 2>/dev/null | head -1 | grep -oE '[0-9]+' | head -1)
317
+ fi
318
+ fi
319
+
320
+ # Priority 3: Python - check for uvicorn/gunicorn port in scripts or pyproject.toml
321
+ if [[ -n "$backend_dir" && -z "$api_port" ]]; then
322
+ if [[ -f "$backend_dir/pyproject.toml" ]] || [[ -f "$backend_dir/requirements.txt" ]]; then
323
+ # Check pyproject.toml for port config
324
+ if [[ -f "$backend_dir/pyproject.toml" ]]; then
325
+ api_port=$(grep -E 'port\s*=' "$backend_dir/pyproject.toml" 2>/dev/null | grep -oE '[0-9]+' | head -1)
326
+ fi
327
+ # Check for uvicorn command with --port
328
+ if [[ -z "$api_port" && -f "$backend_dir/Makefile" ]]; then
329
+ api_port=$(grep -E 'uvicorn.*--port' "$backend_dir/Makefile" 2>/dev/null | grep -oE '\-\-port[[:space:]]*[0-9]+' | grep -oE '[0-9]+' | head -1)
330
+ fi
331
+ # Uvicorn/FastAPI default
332
+ [[ -z "$api_port" ]] && api_port="8000"
333
+ fi
334
+ fi
335
+
336
+ # Priority 4: Node backend - check package.json for port
337
+ if [[ -n "$backend_dir" && -z "$api_port" && -f "$backend_dir/package.json" ]]; then
338
+ api_port=$(grep -E '"(dev|start|serve)"' "$backend_dir/package.json" 2>/dev/null | grep -oE '(--port|-p|:)[[:space:]]*[0-9]{4}' | grep -oE '[0-9]{4}' | head -1)
339
+ fi
340
+
341
+ if [[ -n "$api_port" ]]; then
342
+ api_url="http://localhost:$api_port"
343
+ fi
344
+
345
+ if [[ -n "$api_url" ]]; then
346
+ if jq -e '.api.baseUrl' "$tmpfile" >/dev/null 2>&1 && [[ "$(jq -r '.api.baseUrl' "$tmpfile")" != "" ]]; then
347
+ : # Already set
348
+ else
349
+ jq --arg url "$api_url" '.api.baseUrl = $url' "$tmpfile" > "${tmpfile}.new" && mv "${tmpfile}.new" "$tmpfile"
350
+ echo " Auto-detected api.baseUrl: $api_url"
351
+ updated=true
352
+ fi
353
+ fi
354
+
355
+ # 5. Detect package manager
356
+ local pkg_manager="npm"
357
+ if [[ -f "pnpm-lock.yaml" ]]; then
358
+ pkg_manager="pnpm"
359
+ elif [[ -f "yarn.lock" ]]; then
360
+ pkg_manager="yarn"
361
+ elif [[ -f "bun.lockb" ]]; then
362
+ pkg_manager="bun"
363
+ fi
364
+
365
+ if [[ "$pkg_manager" != "npm" ]]; then
366
+ if jq -e '.packageManager' "$tmpfile" >/dev/null 2>&1; then
367
+ : # Already set
368
+ else
369
+ jq --arg pm "$pkg_manager" '.packageManager = $pm' "$tmpfile" > "${tmpfile}.new" && mv "${tmpfile}.new" "$tmpfile"
370
+ echo " Auto-detected packageManager: $pkg_manager"
371
+ updated=true
372
+ fi
373
+ fi
374
+
375
+ # Save if updated
376
+ if [[ "$updated" == "true" ]]; then
377
+ mv "$tmpfile" "$config"
378
+ else
379
+ rm -f "$tmpfile"
380
+ fi
381
+ }
382
+
383
+ # Show current ralph status
384
+ ralph_status() {
385
+ if [[ ! -d "$RALPH_DIR" ]]; then
386
+ print_error "Ralph not initialized. Run 'ralph init' first."
387
+ return 1
388
+ fi
389
+
390
+ echo ""
391
+ print_info "=== Ralph Status ==="
392
+ echo ""
393
+
394
+ # Check if PRD exists
395
+ if [[ -f "$RALPH_DIR/prd.json" ]]; then
396
+ local feature_name status
397
+ feature_name=$(jq -r '.feature.name // "Unknown"' "$RALPH_DIR/prd.json")
398
+ status=$(jq -r '.feature.status // "unknown"' "$RALPH_DIR/prd.json")
399
+
400
+ echo "Feature: $feature_name"
401
+ echo "Status: $status"
402
+ echo ""
403
+
404
+ # Show stories
405
+ echo "Stories:"
406
+ jq -r '.stories[] | " \(.id): \(.title) [\(if .passes then "DONE" else "TODO" end)]"' "$RALPH_DIR/prd.json" 2>/dev/null || echo " (none)"
407
+
408
+ # Count pass/fail
409
+ local total passed failed
410
+ total=$(jq '.stories | length' "$RALPH_DIR/prd.json")
411
+ passed=$(jq '[.stories[] | select(.passes == true)] | length' "$RALPH_DIR/prd.json")
412
+ failed=$((total - passed))
413
+ echo ""
414
+ echo "Progress: $passed/$total passed ($failed remaining)"
415
+ else
416
+ # Check for misplaced PRD in subdirectories
417
+ local found_prd
418
+ found_prd=$(find . -path "./.ralph" -prune -o -name "prd.json" -path "*/.ralph/prd.json" -print 2>/dev/null | head -1)
419
+
420
+ if [[ -n "$found_prd" ]]; then
421
+ print_warning "PRD found in wrong location: $found_prd"
422
+ echo ""
423
+ echo "Move it to root with:"
424
+ echo " mv $found_prd .ralph/prd.json"
425
+ else
426
+ echo "No active PRD. Generate one with: ralph prd 'your feature notes...'"
427
+ fi
428
+ fi
429
+
430
+ echo ""
431
+
432
+ # Show recent progress
433
+ if [[ -f "$RALPH_DIR/progress.txt" ]]; then
434
+ echo "Recent activity:"
435
+ tail -5 "$RALPH_DIR/progress.txt" | sed 's/^/ /'
436
+ fi
437
+
438
+ echo ""
439
+ }
440
+
441
+ # Show help
442
+ ralph_help() {
443
+ cat <<'EOF'
444
+ agentic-loop - Tools to thrive with agentic coding
445
+
446
+ What is this?
447
+ agentic-loop helps you build features using two terminals working together.
448
+
449
+ Terminal 1 - Claude Code (your AI pair programmer):
450
+ claude --dangerously-skip-permissions
451
+
452
+ The --dangerously-skip-permissions flag lets Claude edit files and run
453
+ commands without asking permission each time. This enables fluid,
454
+ uninterrupted collaboration while you brainstorm and refine ideas.
455
+
456
+ Use /idea to brainstorm big features. Claude saves your ideas to
457
+ docs/ideas/, then breaks them into small, executable PRDs.
458
+
459
+ Terminal 2 - Ralph (autonomous execution):
460
+ npx agentic-loop run
461
+
462
+ Ralph picks up the PRDs you created and builds them autonomously.
463
+ It writes code, runs tests, and iterates until each feature works.
464
+ Ralph runs separately so you can keep brainstorming in Claude.
465
+
466
+ Quick Start:
467
+ 1. npx agentic-loop setup
468
+ 2. Terminal 1: claude --dangerously-skip-permissions
469
+ 3. In Claude: /idea "your feature description"
470
+ 4. Terminal 2: npx agentic-loop run
471
+ 5. Monitor Terminal 2 - it should be autonomous. If issues come up,
472
+ stop the loop (Ctrl+C) and paste the errors into Terminal 1.
473
+
474
+ Usage:
475
+ npx agentic-loop <command> [options]
476
+
477
+ Commands:
478
+ setup Set up project (hooks, config, CLAUDE.md)
479
+ init Initialize Ralph in current directory
480
+ config Re-detect and update project config
481
+ prd <notes> Generate PRD interactively (quick mode)
482
+ prd --file <file> Generate PRD from file
483
+ run Run autonomous loop until all stories pass
484
+ run --max <n> Run with max iterations (default: 20)
485
+ run --fast Skip code review for faster iterations
486
+ status Show current feature and story status
487
+ check Run verification checks only
488
+ verify <story-id> Verify a specific story
489
+ sign <pattern> [cat] Add a learned pattern (sign)
490
+ signs List all learned patterns
491
+ backup Backup detected databases to .backups/
492
+ backups List available database backups
493
+ restore <path> Restore database from backup
494
+ help Show this help message
495
+
496
+ PRD Generation:
497
+ /idea <description> Thorough brainstorm (in Claude Code)
498
+ npx agentic-loop prd <notes> Quick PRD generation
499
+
500
+ Examples:
501
+ npm install agentic-loop && npx agentic-loop setup
502
+ /idea "Add user authentication with OAuth"
503
+ npx agentic-loop prd "Add a contact form"
504
+ npx agentic-loop run
505
+ npx agentic-loop run --max 10
506
+ npx agentic-loop status
507
+ npx agentic-loop sign "Always use camelCase" frontend
508
+
509
+ Environment:
510
+ RALPH_DIR Override .ralph directory location (default: .ralph)
511
+ PROMPT_FILE Override PROMPT.md location (default: PROMPT.md)
512
+
513
+ For more information, see: https://github.com/allthriveai/agentic-loop
514
+ EOF
515
+ }