agentic-loop 3.5.1 → 3.6.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/README.md +19 -1
- package/bin/ralph.sh +37 -3
- package/package.json +1 -1
- package/ralph/init.sh +124 -2
- package/ralph/setup.sh +131 -8
- package/templates/config/fastmcp.json +94 -0
package/README.md
CHANGED
|
@@ -4,7 +4,25 @@
|
|
|
4
4
|
|
|
5
5
|
You describe what you want to build. Claude Code writes a PRD (Product Requirements Document) with small, testable stories. Ralph executes each story automatically - coding, testing, and committing in a loop until everything passes.
|
|
6
6
|
|
|
7
|
-
> **Optimized for:** Python, TypeScript, React, Go/Hugo, and Docker projects.
|
|
7
|
+
> **Optimized for:** Python, TypeScript, React, Go/Hugo, FastMCP, and Docker projects.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Supported Project Types
|
|
12
|
+
|
|
13
|
+
Ralph auto-detects your project type and configures itself accordingly:
|
|
14
|
+
|
|
15
|
+
| Type | Detection | Auto-Configured |
|
|
16
|
+
|------|-----------|-----------------|
|
|
17
|
+
| **FastMCP** | `fastmcp` in pyproject.toml | Server module, MCP port, transport, subprojects |
|
|
18
|
+
| **FastAPI** | `fastapi` in pyproject.toml | uvicorn dev server, pytest, ruff |
|
|
19
|
+
| **Django** | `django` in pyproject.toml or manage.py | migrations, pytest, ruff |
|
|
20
|
+
| **Python** | pyproject.toml or requirements.txt | pytest, ruff, uv/poetry detection |
|
|
21
|
+
| **Node.js** | package.json | npm/yarn/pnpm, vitest/jest, eslint |
|
|
22
|
+
| **React** | `react` in package.json | Vite/Next.js, TypeScript, Tailwind |
|
|
23
|
+
| **Go/Hugo** | go.mod or hugo.toml | Hugo server, Go build |
|
|
24
|
+
| **Rust** | Cargo.toml | cargo build/test/clippy |
|
|
25
|
+
| **Fullstack** | frontend + backend directories | Monorepo support, separate configs |
|
|
8
26
|
|
|
9
27
|
---
|
|
10
28
|
|
package/bin/ralph.sh
CHANGED
|
@@ -37,10 +37,44 @@ export RALPH_DIR PROMPT_FILE RALPH_LIB RALPH_TEMPLATES
|
|
|
37
37
|
if [[ ! -d ".ralph" ]]; then
|
|
38
38
|
mkdir -p ".ralph/archive" ".ralph/screenshots"
|
|
39
39
|
fi
|
|
40
|
-
#
|
|
40
|
+
# Check if config.json needs to be created (but don't create it yet - let init copy the template)
|
|
41
41
|
if [[ ! -f ".ralph/config.json" ]]; then
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
# Copy config template based on detected project type
|
|
43
|
+
_project_type="minimal"
|
|
44
|
+
if [[ -f "pyproject.toml" ]]; then
|
|
45
|
+
if grep -qE "(fastmcp|\"fastmcp\"|'fastmcp')" pyproject.toml 2>/dev/null; then
|
|
46
|
+
_project_type="fastmcp"
|
|
47
|
+
elif grep -qE "(fastapi|\"fastapi\"|'fastapi')" pyproject.toml 2>/dev/null; then
|
|
48
|
+
_project_type="fastapi"
|
|
49
|
+
elif grep -qE "(django|\"django\"|'django')" pyproject.toml 2>/dev/null || [[ -f "manage.py" ]]; then
|
|
50
|
+
_project_type="django"
|
|
51
|
+
else
|
|
52
|
+
_project_type="python"
|
|
53
|
+
fi
|
|
54
|
+
elif [[ -f "go.mod" ]]; then
|
|
55
|
+
_project_type="go"
|
|
56
|
+
elif [[ -f "Cargo.toml" ]]; then
|
|
57
|
+
_project_type="rust"
|
|
58
|
+
elif [[ -d "frontend" ]] && [[ -d "backend" || -d "core" || -d "apps" ]]; then
|
|
59
|
+
_project_type="fullstack"
|
|
60
|
+
elif [[ -f "package.json" ]]; then
|
|
61
|
+
_project_type="node"
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
_config_template="$RALPH_TEMPLATES/config/${_project_type}.json"
|
|
65
|
+
if [[ -f "$_config_template" ]]; then
|
|
66
|
+
cp "$_config_template" ".ralph/config.json"
|
|
67
|
+
else
|
|
68
|
+
# Fall back to minimal config if template doesn't exist
|
|
69
|
+
_config_template="$RALPH_TEMPLATES/config/minimal.json"
|
|
70
|
+
if [[ -f "$_config_template" ]]; then
|
|
71
|
+
cp "$_config_template" ".ralph/config.json"
|
|
72
|
+
else
|
|
73
|
+
echo '{"projectType": "unknown"}' > ".ralph/config.json"
|
|
74
|
+
fi
|
|
75
|
+
fi
|
|
76
|
+
unset _project_type _config_template
|
|
77
|
+
# Run auto-detection to fill in project-specific values
|
|
44
78
|
_ralph_needs_autoconfig=true
|
|
45
79
|
fi
|
|
46
80
|
[[ ! -f ".ralph/signs.json" ]] && echo '{"signs": []}' > ".ralph/signs.json"
|
package/package.json
CHANGED
package/ralph/init.sh
CHANGED
|
@@ -145,8 +145,35 @@ detect_project_type() {
|
|
|
145
145
|
project_type="rust"
|
|
146
146
|
elif [[ -f "go.mod" ]]; then
|
|
147
147
|
project_type="go"
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
# Check for Python framework variants (more specific first)
|
|
149
|
+
elif [[ -f "pyproject.toml" ]]; then
|
|
150
|
+
# FastMCP detection (check for fastmcp in any quote style)
|
|
151
|
+
if grep -qiE "(fastmcp|\"fastmcp\"|'fastmcp')" pyproject.toml 2>/dev/null; then
|
|
152
|
+
project_type="fastmcp"
|
|
153
|
+
# Django detection
|
|
154
|
+
elif grep -qiE "(django|\"django\"|'django')" pyproject.toml 2>/dev/null || [[ -f "manage.py" ]]; then
|
|
155
|
+
project_type="django"
|
|
156
|
+
# FastAPI detection
|
|
157
|
+
elif grep -qiE "(fastapi|\"fastapi\"|'fastapi')" pyproject.toml 2>/dev/null; then
|
|
158
|
+
project_type="fastapi"
|
|
159
|
+
else
|
|
160
|
+
project_type="python"
|
|
161
|
+
fi
|
|
162
|
+
elif [[ -f "requirements.txt" || -f "setup.py" ]]; then
|
|
163
|
+
# Check requirements.txt for frameworks
|
|
164
|
+
if [[ -f "requirements.txt" ]]; then
|
|
165
|
+
if grep -qi 'fastmcp' requirements.txt 2>/dev/null; then
|
|
166
|
+
project_type="fastmcp"
|
|
167
|
+
elif grep -qi 'django' requirements.txt 2>/dev/null || [[ -f "manage.py" ]]; then
|
|
168
|
+
project_type="django"
|
|
169
|
+
elif grep -qi 'fastapi' requirements.txt 2>/dev/null; then
|
|
170
|
+
project_type="fastapi"
|
|
171
|
+
else
|
|
172
|
+
project_type="python"
|
|
173
|
+
fi
|
|
174
|
+
else
|
|
175
|
+
project_type="python"
|
|
176
|
+
fi
|
|
150
177
|
elif [[ -f "package.json" ]]; then
|
|
151
178
|
project_type="node"
|
|
152
179
|
fi
|
|
@@ -372,6 +399,101 @@ auto_configure_project() {
|
|
|
372
399
|
fi
|
|
373
400
|
fi
|
|
374
401
|
|
|
402
|
+
# 6. FastMCP-specific detection
|
|
403
|
+
if [[ -f "pyproject.toml" ]] && grep -qiE "(fastmcp|\"fastmcp\"|'fastmcp')" pyproject.toml 2>/dev/null; then
|
|
404
|
+
# Detect MCP server module from entry points
|
|
405
|
+
local mcp_module=""
|
|
406
|
+
# Look for [project.scripts] section with pattern: name = "module.server:main"
|
|
407
|
+
mcp_module=$(grep -A 20 '^\[project\.scripts\]' pyproject.toml 2>/dev/null | \
|
|
408
|
+
grep -E '^\w+\s*=\s*"[^"]+\.server:' | head -1 | \
|
|
409
|
+
sed -E 's/.*"([^"]+)\.server:.*/\1/' || true)
|
|
410
|
+
|
|
411
|
+
# Fallback: detect from src directory structure
|
|
412
|
+
if [[ -z "$mcp_module" && -d "src" ]]; then
|
|
413
|
+
for dir in src/*/; do
|
|
414
|
+
if [[ -f "${dir}server.py" ]]; then
|
|
415
|
+
mcp_module=$(basename "${dir%/}")
|
|
416
|
+
break
|
|
417
|
+
fi
|
|
418
|
+
done
|
|
419
|
+
fi
|
|
420
|
+
|
|
421
|
+
if [[ -n "$mcp_module" ]]; then
|
|
422
|
+
if ! jq -e '.mcp.serverModule' "$tmpfile" >/dev/null 2>&1 || [[ "$(jq -r '.mcp.serverModule' "$tmpfile")" == "" ]]; then
|
|
423
|
+
jq --arg mod "$mcp_module" '.mcp.serverModule = $mod' "$tmpfile" > "${tmpfile}.new" && mv "${tmpfile}.new" "$tmpfile"
|
|
424
|
+
# Also update the dev command
|
|
425
|
+
jq --arg cmd "python -m ${mcp_module}.server" '.commands.dev = $cmd' "$tmpfile" > "${tmpfile}.new" && mv "${tmpfile}.new" "$tmpfile"
|
|
426
|
+
echo " Auto-detected mcp.serverModule: $mcp_module"
|
|
427
|
+
updated=true
|
|
428
|
+
fi
|
|
429
|
+
fi
|
|
430
|
+
|
|
431
|
+
# Detect MCP port from .env or docker-compose
|
|
432
|
+
local mcp_port=""
|
|
433
|
+
if [[ -f ".env" ]]; then
|
|
434
|
+
mcp_port=$(grep -E '^[A-Z_]*PORT=' .env 2>/dev/null | grep -v '#' | head -1 | grep -oE '[0-9]+' || true)
|
|
435
|
+
fi
|
|
436
|
+
if [[ -z "$mcp_port" ]]; then
|
|
437
|
+
for compose_file in "docker-compose.yml" "docker-compose.yaml"; do
|
|
438
|
+
if [[ -f "$compose_file" ]]; then
|
|
439
|
+
# Look for port in main app service
|
|
440
|
+
mcp_port=$(grep -A 20 -E '^\s*(app|gopa|mcp|server):' "$compose_file" 2>/dev/null | \
|
|
441
|
+
grep -E '^\s*-\s*"?[0-9]+:[0-9]+"?' | head -1 | \
|
|
442
|
+
grep -oE '[0-9]+:' | head -1 | tr -d ':' || true)
|
|
443
|
+
[[ -n "$mcp_port" ]] && break
|
|
444
|
+
fi
|
|
445
|
+
done
|
|
446
|
+
fi
|
|
447
|
+
|
|
448
|
+
if [[ -n "$mcp_port" ]]; then
|
|
449
|
+
if ! jq -e '.api.baseUrl' "$tmpfile" >/dev/null 2>&1 || [[ "$(jq -r '.api.baseUrl' "$tmpfile")" == "http://localhost:8000" ]]; then
|
|
450
|
+
jq --arg url "http://localhost:$mcp_port" '.api.baseUrl = $url | .urls.app = $url' "$tmpfile" > "${tmpfile}.new" && mv "${tmpfile}.new" "$tmpfile"
|
|
451
|
+
echo " Auto-detected MCP port: $mcp_port"
|
|
452
|
+
updated=true
|
|
453
|
+
fi
|
|
454
|
+
fi
|
|
455
|
+
|
|
456
|
+
# Detect MCP transport from .env
|
|
457
|
+
if [[ -f ".env" ]]; then
|
|
458
|
+
local transport=""
|
|
459
|
+
transport=$(grep -E '^[A-Z_]*TRANSPORT=' .env 2>/dev/null | grep -v '#' | head -1 | cut -d'=' -f2 | tr -d '"'"'" || true)
|
|
460
|
+
if [[ -n "$transport" ]]; then
|
|
461
|
+
jq --arg t "$transport" '.mcp.transport = $t' "$tmpfile" > "${tmpfile}.new" && mv "${tmpfile}.new" "$tmpfile"
|
|
462
|
+
fi
|
|
463
|
+
fi
|
|
464
|
+
|
|
465
|
+
# Detect subprojects (directories with their own package.json)
|
|
466
|
+
for subdir in */; do
|
|
467
|
+
local subdir_name="${subdir%/}"
|
|
468
|
+
if [[ -f "${subdir}package.json" && "$subdir_name" != "node_modules" ]]; then
|
|
469
|
+
# Check if subproject already configured
|
|
470
|
+
if ! jq -e ".subprojects[\"$subdir_name\"]" "$tmpfile" >/dev/null 2>&1; then
|
|
471
|
+
local sub_lint="" sub_build="" sub_dev=""
|
|
472
|
+
# Detect scripts from package.json
|
|
473
|
+
if grep -q '"lint"' "${subdir}package.json" 2>/dev/null; then
|
|
474
|
+
sub_lint="npm run lint"
|
|
475
|
+
fi
|
|
476
|
+
if grep -q '"build"' "${subdir}package.json" 2>/dev/null; then
|
|
477
|
+
sub_build="npm run build"
|
|
478
|
+
fi
|
|
479
|
+
if grep -q '"dev"' "${subdir}package.json" 2>/dev/null; then
|
|
480
|
+
sub_dev="npm run dev"
|
|
481
|
+
fi
|
|
482
|
+
|
|
483
|
+
jq --arg name "$subdir_name" \
|
|
484
|
+
--arg path "$subdir_name" \
|
|
485
|
+
--arg lint "$sub_lint" \
|
|
486
|
+
--arg build "$sub_build" \
|
|
487
|
+
--arg dev "$sub_dev" \
|
|
488
|
+
'.subprojects[$name] = {path: $path, commands: {lint: $lint, build: $build, dev: $dev}}' \
|
|
489
|
+
"$tmpfile" > "${tmpfile}.new" && mv "${tmpfile}.new" "$tmpfile"
|
|
490
|
+
echo " Auto-detected subproject: $subdir_name"
|
|
491
|
+
updated=true
|
|
492
|
+
fi
|
|
493
|
+
fi
|
|
494
|
+
done
|
|
495
|
+
fi
|
|
496
|
+
|
|
375
497
|
# Save if updated
|
|
376
498
|
if [[ "$updated" == "true" ]]; then
|
|
377
499
|
mv "$tmpfile" "$config"
|
package/ralph/setup.sh
CHANGED
|
@@ -129,35 +129,74 @@ setup_ralph_dir() {
|
|
|
129
129
|
# Copy config template based on detected project type
|
|
130
130
|
if [[ ! -f ".ralph/config.json" ]]; then
|
|
131
131
|
local config_template=""
|
|
132
|
+
local detected_type=""
|
|
132
133
|
# Check for Go projects
|
|
133
134
|
if [[ -f "go.mod" ]]; then
|
|
134
135
|
config_template="$pkg_root/templates/config/go.json"
|
|
136
|
+
detected_type="go"
|
|
135
137
|
# Check for Rust projects
|
|
136
138
|
elif [[ -f "Cargo.toml" ]]; then
|
|
137
139
|
config_template="$pkg_root/templates/config/rust.json"
|
|
140
|
+
detected_type="rust"
|
|
138
141
|
# Check for Hugo projects
|
|
139
142
|
elif [[ -f "hugo.toml" || -f "hugo.yaml" || -f "config.toml" ]] && [[ -d "content" || -d "layouts" || -d "themes" ]]; then
|
|
140
143
|
config_template="$pkg_root/templates/config/go.json"
|
|
141
|
-
|
|
142
|
-
|
|
144
|
+
detected_type="hugo"
|
|
145
|
+
# Check for Python framework variants (more specific first)
|
|
146
|
+
elif [[ -f "pyproject.toml" ]]; then
|
|
147
|
+
if grep -qiE "(fastmcp|\"fastmcp\"|'fastmcp')" pyproject.toml 2>/dev/null; then
|
|
148
|
+
config_template="$pkg_root/templates/config/fastmcp.json"
|
|
149
|
+
detected_type="fastmcp"
|
|
150
|
+
elif grep -qiE "(fastapi|\"fastapi\"|'fastapi')" pyproject.toml 2>/dev/null; then
|
|
151
|
+
config_template="$pkg_root/templates/config/python.json"
|
|
152
|
+
detected_type="fastapi"
|
|
153
|
+
elif grep -qiE "(django|\"django\"|'django')" pyproject.toml 2>/dev/null || [[ -f "manage.py" ]]; then
|
|
154
|
+
config_template="$pkg_root/templates/config/python.json"
|
|
155
|
+
detected_type="django"
|
|
156
|
+
else
|
|
157
|
+
config_template="$pkg_root/templates/config/python.json"
|
|
158
|
+
detected_type="python"
|
|
159
|
+
fi
|
|
160
|
+
elif [[ -f "requirements.txt" ]]; then
|
|
161
|
+
if grep -qi 'fastmcp' requirements.txt 2>/dev/null; then
|
|
162
|
+
config_template="$pkg_root/templates/config/fastmcp.json"
|
|
163
|
+
detected_type="fastmcp"
|
|
164
|
+
elif grep -qi 'fastapi' requirements.txt 2>/dev/null; then
|
|
165
|
+
config_template="$pkg_root/templates/config/python.json"
|
|
166
|
+
detected_type="fastapi"
|
|
167
|
+
elif grep -qi 'django' requirements.txt 2>/dev/null || [[ -f "manage.py" ]]; then
|
|
168
|
+
config_template="$pkg_root/templates/config/python.json"
|
|
169
|
+
detected_type="django"
|
|
170
|
+
else
|
|
171
|
+
config_template="$pkg_root/templates/config/python.json"
|
|
172
|
+
detected_type="python"
|
|
173
|
+
fi
|
|
174
|
+
elif [[ -f "manage.py" ]]; then
|
|
143
175
|
config_template="$pkg_root/templates/config/python.json"
|
|
176
|
+
detected_type="django"
|
|
144
177
|
# Check for fullstack projects
|
|
145
178
|
elif [[ -d "frontend" ]] && [[ -d "backend" || -d "core" || -d "apps" ]]; then
|
|
146
179
|
config_template="$pkg_root/templates/config/fullstack.json"
|
|
180
|
+
detected_type="fullstack"
|
|
147
181
|
# Check for Node projects
|
|
148
182
|
elif [[ -f "package.json" ]]; then
|
|
149
183
|
config_template="$pkg_root/templates/config/node.json"
|
|
184
|
+
detected_type="node"
|
|
150
185
|
fi
|
|
151
186
|
|
|
152
187
|
if [[ -n "$config_template" ]] && [[ -f "$config_template" ]]; then
|
|
153
188
|
cp "$config_template" ".ralph/config.json"
|
|
189
|
+
echo " Created .ralph/config.json (detected: $detected_type)"
|
|
154
190
|
else
|
|
155
191
|
echo '{"checks": {"build": true, "lint": true, "test": true}}' > ".ralph/config.json"
|
|
192
|
+
echo " Created .ralph/config.json"
|
|
156
193
|
fi
|
|
157
|
-
|
|
194
|
+
|
|
195
|
+
# Store detected type for CLAUDE.md generation
|
|
196
|
+
export RALPH_DETECTED_TYPE="$detected_type"
|
|
158
197
|
fi
|
|
159
198
|
|
|
160
|
-
# Copy signs template
|
|
199
|
+
# Copy or merge signs template
|
|
161
200
|
if [[ ! -f ".ralph/signs.json" ]]; then
|
|
162
201
|
if [[ -f "$pkg_root/templates/signs.json" ]]; then
|
|
163
202
|
cp "$pkg_root/templates/signs.json" ".ralph/signs.json"
|
|
@@ -165,6 +204,30 @@ setup_ralph_dir() {
|
|
|
165
204
|
echo '{"signs": []}' > ".ralph/signs.json"
|
|
166
205
|
fi
|
|
167
206
|
echo " Created .ralph/signs.json"
|
|
207
|
+
else
|
|
208
|
+
# Merge new default signs into existing file
|
|
209
|
+
if [[ -f "$pkg_root/templates/signs.json" ]] && command -v jq &>/dev/null; then
|
|
210
|
+
local existing_ids new_signs_added=0
|
|
211
|
+
existing_ids=$(jq -r '.signs[].id // empty' ".ralph/signs.json" 2>/dev/null | tr '\n' '|')
|
|
212
|
+
|
|
213
|
+
# Add signs from template that don't exist locally
|
|
214
|
+
while IFS= read -r sign; do
|
|
215
|
+
local sign_id
|
|
216
|
+
sign_id=$(echo "$sign" | jq -r '.id // empty')
|
|
217
|
+
if [[ -n "$sign_id" && ! "$existing_ids" =~ "$sign_id" ]]; then
|
|
218
|
+
# Add this sign to local file
|
|
219
|
+
local tmp_file
|
|
220
|
+
tmp_file=$(mktemp)
|
|
221
|
+
jq --argjson new_sign "$sign" '.signs += [$new_sign]' ".ralph/signs.json" > "$tmp_file"
|
|
222
|
+
mv "$tmp_file" ".ralph/signs.json"
|
|
223
|
+
((new_signs_added++))
|
|
224
|
+
fi
|
|
225
|
+
done < <(jq -c '.signs[]' "$pkg_root/templates/signs.json" 2>/dev/null)
|
|
226
|
+
|
|
227
|
+
if [[ $new_signs_added -gt 0 ]]; then
|
|
228
|
+
echo " Merged $new_signs_added new sign(s) into .ralph/signs.json"
|
|
229
|
+
fi
|
|
230
|
+
fi
|
|
168
231
|
fi
|
|
169
232
|
|
|
170
233
|
# Create or update PROMPT.md
|
|
@@ -323,6 +386,8 @@ setup_slash_commands() {
|
|
|
323
386
|
# Generate CLAUDE.md with detected project info
|
|
324
387
|
setup_claude_md() {
|
|
325
388
|
local marker="<!-- agentic-loop-detected -->"
|
|
389
|
+
local pkg_root
|
|
390
|
+
pkg_root="$(cd "$RALPH_LIB/.." && pwd)"
|
|
326
391
|
|
|
327
392
|
# Skip if we already added our section
|
|
328
393
|
[[ -f "CLAUDE.md" ]] && grep -q "$marker" "CLAUDE.md" 2>/dev/null && return 0
|
|
@@ -330,6 +395,7 @@ setup_claude_md() {
|
|
|
330
395
|
echo "Generating CLAUDE.md..."
|
|
331
396
|
|
|
332
397
|
local runtime="" framework="" language="" styling="" testing="" structure=""
|
|
398
|
+
local framework_type="" # For template selection
|
|
333
399
|
|
|
334
400
|
# Detect runtime/language
|
|
335
401
|
local fe_dir=""
|
|
@@ -344,18 +410,45 @@ setup_claude_md() {
|
|
|
344
410
|
[[ -f "pyproject.toml" || -f "requirements.txt" || -f "manage.py" ]] && runtime="${runtime:+$runtime + }Python"
|
|
345
411
|
[[ -f "Gemfile" ]] && runtime="Ruby"
|
|
346
412
|
|
|
347
|
-
# Detect framework
|
|
413
|
+
# Detect framework (with template type detection)
|
|
348
414
|
local pkg="package.json"
|
|
349
415
|
[[ -n "$fe_dir" && -f "${fe_dir}/package.json" ]] && pkg="${fe_dir}/package.json"
|
|
350
416
|
|
|
351
417
|
if [[ -f "$pkg" ]]; then
|
|
352
418
|
grep -q '"next"' "$pkg" 2>/dev/null && framework="Next.js"
|
|
353
|
-
grep -q '"react"' "$pkg" 2>/dev/null && [[ -z "$framework" ]] && framework="React"
|
|
419
|
+
grep -q '"react"' "$pkg" 2>/dev/null && [[ -z "$framework" ]] && framework="React" && framework_type="react"
|
|
354
420
|
grep -q '"vue"' "$pkg" 2>/dev/null && framework="Vue"
|
|
355
421
|
grep -q '"svelte"' "$pkg" 2>/dev/null && framework="Svelte"
|
|
356
422
|
grep -q '"express"' "$pkg" 2>/dev/null && framework="${framework:+$framework + }Express"
|
|
357
423
|
fi
|
|
358
|
-
|
|
424
|
+
|
|
425
|
+
# Detect Python frameworks (more specific detection)
|
|
426
|
+
if [[ -f "pyproject.toml" ]]; then
|
|
427
|
+
if grep -qiE "(fastmcp|\"fastmcp\"|'fastmcp')" pyproject.toml 2>/dev/null; then
|
|
428
|
+
framework="${framework:+$framework + }FastMCP"
|
|
429
|
+
framework_type="fastmcp"
|
|
430
|
+
elif grep -qiE "(fastapi|\"fastapi\"|'fastapi')" pyproject.toml 2>/dev/null; then
|
|
431
|
+
framework="${framework:+$framework + }FastAPI"
|
|
432
|
+
framework_type="fastapi"
|
|
433
|
+
elif grep -qiE "(django|\"django\"|'django')" pyproject.toml 2>/dev/null || [[ -f "manage.py" ]]; then
|
|
434
|
+
framework="${framework:+$framework + }Django"
|
|
435
|
+
framework_type="django"
|
|
436
|
+
fi
|
|
437
|
+
elif [[ -f "requirements.txt" ]]; then
|
|
438
|
+
if grep -qi 'fastmcp' requirements.txt 2>/dev/null; then
|
|
439
|
+
framework="${framework:+$framework + }FastMCP"
|
|
440
|
+
framework_type="fastmcp"
|
|
441
|
+
elif grep -qi 'fastapi' requirements.txt 2>/dev/null; then
|
|
442
|
+
framework="${framework:+$framework + }FastAPI"
|
|
443
|
+
framework_type="fastapi"
|
|
444
|
+
elif grep -qi 'django' requirements.txt 2>/dev/null || [[ -f "manage.py" ]]; then
|
|
445
|
+
framework="${framework:+$framework + }Django"
|
|
446
|
+
framework_type="django"
|
|
447
|
+
fi
|
|
448
|
+
elif [[ -f "manage.py" ]]; then
|
|
449
|
+
framework="${framework:+$framework + }Django"
|
|
450
|
+
framework_type="django"
|
|
451
|
+
fi
|
|
359
452
|
|
|
360
453
|
# Detect Hugo (Go static site generator)
|
|
361
454
|
for hugo_config in "hugo.toml" "hugo.yaml" "hugo.json" "config.toml"; do
|
|
@@ -376,6 +469,7 @@ setup_claude_md() {
|
|
|
376
469
|
[[ -f "vitest.config.ts" || -f "vitest.config.js" ]] && testing="Vitest"
|
|
377
470
|
[[ -f "jest.config.js" || -f "jest.config.ts" ]] && testing="Jest"
|
|
378
471
|
[[ -f "playwright.config.ts" || -f "playwright.config.js" ]] && testing="${testing:+$testing + }Playwright"
|
|
472
|
+
[[ -f "pytest.ini" || -f "pyproject.toml" ]] && grep -q 'pytest' pyproject.toml 2>/dev/null && testing="${testing:+$testing + }pytest"
|
|
379
473
|
|
|
380
474
|
# Detect Python package manager
|
|
381
475
|
local python_runner=""
|
|
@@ -404,17 +498,46 @@ ${python_runner:+- Python: Use \`$python_runner\` (not bare \`python\`)}
|
|
|
404
498
|
|
|
405
499
|
*Auto-detected by agentic-loop. Edit freely.*"
|
|
406
500
|
|
|
501
|
+
# Check for framework-specific template
|
|
502
|
+
local framework_template=""
|
|
503
|
+
if [[ -n "$framework_type" ]]; then
|
|
504
|
+
framework_template="$pkg_root/templates/examples/CLAUDE-${framework_type}.md"
|
|
505
|
+
fi
|
|
506
|
+
|
|
407
507
|
if [[ -f "CLAUDE.md" ]]; then
|
|
508
|
+
# Append framework template if it exists and not already included
|
|
509
|
+
if [[ -n "$framework_template" && -f "$framework_template" ]]; then
|
|
510
|
+
local template_marker="<!-- CLAUDE-${framework_type} -->"
|
|
511
|
+
if ! grep -q "$template_marker" "CLAUDE.md" 2>/dev/null; then
|
|
512
|
+
echo "" >> CLAUDE.md
|
|
513
|
+
echo "$template_marker" >> CLAUDE.md
|
|
514
|
+
# Skip the first line (# CLAUDE.md - ...) of the template
|
|
515
|
+
tail -n +2 "$framework_template" >> CLAUDE.md
|
|
516
|
+
echo " Appended ${framework_type} conventions to CLAUDE.md"
|
|
517
|
+
fi
|
|
518
|
+
fi
|
|
408
519
|
echo "$detected_section" >> CLAUDE.md
|
|
409
520
|
echo " Updated CLAUDE.md"
|
|
410
521
|
else
|
|
522
|
+
# Create new CLAUDE.md
|
|
411
523
|
cat > CLAUDE.md << EOF
|
|
412
524
|
# Project Guide for Claude
|
|
413
525
|
|
|
414
526
|
## Your Rules
|
|
415
527
|
<!-- Add your project-specific rules, patterns, and conventions here -->
|
|
416
|
-
$detected_section
|
|
417
528
|
EOF
|
|
529
|
+
|
|
530
|
+
# Include framework template if available
|
|
531
|
+
if [[ -n "$framework_template" && -f "$framework_template" ]]; then
|
|
532
|
+
local template_marker="<!-- CLAUDE-${framework_type} -->"
|
|
533
|
+
echo "" >> CLAUDE.md
|
|
534
|
+
echo "$template_marker" >> CLAUDE.md
|
|
535
|
+
# Skip the first line (# CLAUDE.md - ...) of the template
|
|
536
|
+
tail -n +2 "$framework_template" >> CLAUDE.md
|
|
537
|
+
echo " Included ${framework_type} conventions"
|
|
538
|
+
fi
|
|
539
|
+
|
|
540
|
+
echo "$detected_section" >> CLAUDE.md
|
|
418
541
|
echo " Created CLAUDE.md"
|
|
419
542
|
fi
|
|
420
543
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"projectType": "fastmcp",
|
|
3
|
+
|
|
4
|
+
"auth": {
|
|
5
|
+
"testUser": "",
|
|
6
|
+
"testPassword": "",
|
|
7
|
+
"loginEndpoint": "",
|
|
8
|
+
"loginMethod": "",
|
|
9
|
+
"tokenType": "",
|
|
10
|
+
"tokenHeader": "",
|
|
11
|
+
"tokenPrefix": ""
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
"docker": {
|
|
15
|
+
"enabled": true,
|
|
16
|
+
"composeFile": "docker-compose.yml",
|
|
17
|
+
"serviceName": "app",
|
|
18
|
+
"execPrefix": "docker compose exec -T"
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
"paths": {
|
|
22
|
+
"src": "src",
|
|
23
|
+
"tests": "tests",
|
|
24
|
+
"scripts": "scripts"
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
"commands": {
|
|
28
|
+
"dev": "python -m ${PROJECT_MODULE}.server",
|
|
29
|
+
"install": "uv pip install -e .",
|
|
30
|
+
"lint": "ruff check src/",
|
|
31
|
+
"format": "ruff format src/",
|
|
32
|
+
"typecheck": "mypy src/",
|
|
33
|
+
"test": "pytest",
|
|
34
|
+
"seed": "",
|
|
35
|
+
"resetDb": ""
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
"checks": {
|
|
39
|
+
"lint": true,
|
|
40
|
+
"typecheck": true,
|
|
41
|
+
"build": false,
|
|
42
|
+
"test": true,
|
|
43
|
+
"fastmcp": true
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
"api": {
|
|
47
|
+
"baseUrl": "http://localhost:8000",
|
|
48
|
+
"healthEndpoint": "/health",
|
|
49
|
+
"timeout": 30,
|
|
50
|
+
"transport": "sse"
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
"mcp": {
|
|
54
|
+
"transport": "stdio",
|
|
55
|
+
"serverModule": "",
|
|
56
|
+
"tools": [],
|
|
57
|
+
"resources": [],
|
|
58
|
+
"prompts": []
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
"playwright": {
|
|
62
|
+
"enabled": false,
|
|
63
|
+
"testDir": "tests/e2e",
|
|
64
|
+
"projects": ["chromium"],
|
|
65
|
+
"baseUrl": "http://localhost:8000"
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
"verification": {
|
|
69
|
+
"codeReviewEnabled": true,
|
|
70
|
+
"browserEnabled": false,
|
|
71
|
+
"mcpEnabled": true,
|
|
72
|
+
"screenshotOnFailure": false
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
"urls": {
|
|
76
|
+
"app": "http://localhost:8000",
|
|
77
|
+
"docs": ""
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
"env": {
|
|
81
|
+
"required": [],
|
|
82
|
+
"optional": []
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
"subprojects": {},
|
|
86
|
+
|
|
87
|
+
"maxIterations": 20,
|
|
88
|
+
"maxSessionSeconds": 600,
|
|
89
|
+
|
|
90
|
+
"contextRotThreshold": {
|
|
91
|
+
"maxStories": 10,
|
|
92
|
+
"maxFilesChanged": 20
|
|
93
|
+
}
|
|
94
|
+
}
|