vibe-and-thrive 1.5.0 → 1.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/.claude/commands/idea.md +12 -2
- package/README.md +61 -2
- package/bin/postinstall.sh +113 -10
- package/bin/ralph.sh +8 -4
- package/package.json +2 -2
- package/ralph/hooks/install.sh +72 -5
- package/ralph/init.sh +22 -22
- package/ralph/loop.sh +38 -23
- package/ralph/prd.sh +15 -0
- package/ralph/utils.sh +71 -0
- package/ralph/verify.sh +49 -6
package/.claude/commands/idea.md
CHANGED
|
@@ -130,9 +130,19 @@ Break the idea into small, executable PRDs following the JSON structure below.
|
|
|
130
130
|
mkdir -p .ralph
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
2. Write to `.ralph/prd.json`
|
|
133
|
+
2. Write to `.ralph/prd.json` ensuring:
|
|
134
|
+
- Valid JSON syntax
|
|
135
|
+
- `feature.name` is set (not null/empty)
|
|
136
|
+
- `feature.status` is "pending"
|
|
137
|
+
- `stories` array is not empty
|
|
138
|
+
- Each story has `id`, `title`, and `passes: false`
|
|
139
|
+
|
|
140
|
+
3. Validate the PRD was written correctly:
|
|
141
|
+
```bash
|
|
142
|
+
jq -e '.feature.name and (.stories | length > 0) and (.stories | all(.id and .title and .passes == false))' .ralph/prd.json && echo "PRD valid" || echo "PRD invalid"
|
|
143
|
+
```
|
|
134
144
|
|
|
135
|
-
|
|
145
|
+
4. Say: "I've created the PRD with {N} stories in `.ralph/prd.json`.
|
|
136
146
|
|
|
137
147
|
Review and let me know:
|
|
138
148
|
- **'approved'** - Ready for `npx ralph run`
|
package/README.md
CHANGED
|
@@ -37,13 +37,20 @@ See More for on how the [RALPH loop](docs/RALPH.md) works in this repo
|
|
|
37
37
|
- Optional: [Playwright](https://playwright.dev/) for browser verification (installed automatically by `/tour`)
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
## Step 0: Install
|
|
40
|
+
## Step 0: Install
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
43
|
cd [your-project]
|
|
44
44
|
npm install vibe-and-thrive
|
|
45
45
|
```
|
|
46
|
-
|
|
46
|
+
|
|
47
|
+
This automatically sets up:
|
|
48
|
+
- Slash commands (`/idea`, `/review`, etc.)
|
|
49
|
+
- Pre-commit hooks (secrets, URLs, debug statements)
|
|
50
|
+
- Claude Code hooks (real-time warnings)
|
|
51
|
+
- Ralph configuration files
|
|
52
|
+
|
|
53
|
+
> **Note:** Claude Code hooks require `jq`. On macOS with Homebrew, it's installed automatically. On Linux, if you see a warning, run `sudo apt install jq` (or your package manager) then `npx ralph hooks`.
|
|
47
54
|
|
|
48
55
|
## Step 1: Start claude (--dangerously-skip-permissions is optional)
|
|
49
56
|
|
|
@@ -148,6 +155,58 @@ vibe-and-thrive/
|
|
|
148
155
|
| **Claude Code hooks** | While Claude writes code | Real-time warnings (debug statements, secrets) |
|
|
149
156
|
| **Pre-commit hooks** | At `git commit` | Blocks secrets, URLs, debug statements |
|
|
150
157
|
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Troubleshooting
|
|
161
|
+
|
|
162
|
+
### "SessionStart: startup hook error"
|
|
163
|
+
|
|
164
|
+
This means Claude Code hooks are configured but the hook scripts can't be found. Fix with:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# Reinstall project hooks
|
|
168
|
+
npx ralph hooks --force
|
|
169
|
+
|
|
170
|
+
# Or reinstall global hooks (if using vibe-and-thrive source repo)
|
|
171
|
+
/path/to/vibe-and-thrive/ralph/hooks/install.sh --global --force
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Hooks not working after moving project
|
|
175
|
+
|
|
176
|
+
Claude Code hooks use absolute paths. After moving a project or cloning on a new machine, reinstall hooks:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npx ralph hooks --force
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Team members getting hook errors
|
|
183
|
+
|
|
184
|
+
`.claude/settings.json` contains machine-specific paths and should not be committed to git. The postinstall script automatically adds it to `.gitignore`. Each team member's hooks are configured when they run `npm install`.
|
|
185
|
+
|
|
186
|
+
If `.claude/settings.json` was accidentally committed:
|
|
187
|
+
```bash
|
|
188
|
+
git rm --cached .claude/settings.json
|
|
189
|
+
echo ".claude/settings.json" >> .gitignore
|
|
190
|
+
git commit -m "Remove machine-specific settings from git"
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### jq not installed
|
|
194
|
+
|
|
195
|
+
Claude Code hooks require `jq` for JSON manipulation. On macOS with Homebrew, `npx ralph hooks` installs it automatically. On Linux:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# Ubuntu/Debian
|
|
199
|
+
sudo apt install jq
|
|
200
|
+
|
|
201
|
+
# Fedora/RHEL
|
|
202
|
+
sudo dnf install jq
|
|
203
|
+
|
|
204
|
+
# Then install hooks
|
|
205
|
+
npx ralph hooks
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
151
210
|
## License
|
|
152
211
|
|
|
153
212
|
MIT - see [LICENSE](LICENSE)
|
package/bin/postinstall.sh
CHANGED
|
@@ -16,6 +16,17 @@ PKG_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
16
16
|
# npm runs postinstall from package dir, we need project root
|
|
17
17
|
PROJECT_ROOT="$(cd "$PKG_ROOT/../.." && pwd)"
|
|
18
18
|
|
|
19
|
+
# Verify we're in a valid project (has package.json), not a global install
|
|
20
|
+
if [[ ! -f "$PROJECT_ROOT/package.json" ]]; then
|
|
21
|
+
# Might be global install or npm link - skip silently
|
|
22
|
+
exit 0
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# Don't run in the vibe-and-thrive package itself
|
|
26
|
+
if [[ -f "$PROJECT_ROOT/package.json" ]] && grep -q '"name": "vibe-and-thrive"' "$PROJECT_ROOT/package.json" 2>/dev/null; then
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
19
30
|
# Change to project root for setup
|
|
20
31
|
cd "$PROJECT_ROOT" || exit 0
|
|
21
32
|
|
|
@@ -55,8 +66,41 @@ EOF
|
|
|
55
66
|
}
|
|
56
67
|
|
|
57
68
|
install_ralph() {
|
|
58
|
-
|
|
59
|
-
|
|
69
|
+
# Just create the directory structure - don't run full init
|
|
70
|
+
# Users will run /idea or ralph prd to generate a PRD
|
|
71
|
+
mkdir -p ".ralph/archive" ".ralph/screenshots"
|
|
72
|
+
|
|
73
|
+
# Copy config template based on detected project type
|
|
74
|
+
if [[ ! -f ".ralph/config.json" ]]; then
|
|
75
|
+
local config_template=""
|
|
76
|
+
if [[ -f "manage.py" ]] || [[ -f "pyproject.toml" ]]; then
|
|
77
|
+
config_template="$PKG_ROOT/templates/config/python.json"
|
|
78
|
+
elif [[ -d "frontend" ]] && [[ -d "backend" || -d "core" ]]; then
|
|
79
|
+
config_template="$PKG_ROOT/templates/config/fullstack.json"
|
|
80
|
+
elif [[ -f "package.json" ]]; then
|
|
81
|
+
config_template="$PKG_ROOT/templates/config/node.json"
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
if [[ -n "$config_template" ]] && [[ -f "$config_template" ]]; then
|
|
85
|
+
cp "$config_template" ".ralph/config.json"
|
|
86
|
+
else
|
|
87
|
+
# Fall back to minimal config
|
|
88
|
+
echo '{"checks": {"build": true, "lint": true, "test": true}}' > ".ralph/config.json"
|
|
89
|
+
fi
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
# Copy signs template if available, otherwise create empty
|
|
93
|
+
if [[ ! -f ".ralph/signs.json" ]]; then
|
|
94
|
+
if [[ -f "$PKG_ROOT/templates/signs.json" ]]; then
|
|
95
|
+
cp "$PKG_ROOT/templates/signs.json" ".ralph/signs.json"
|
|
96
|
+
else
|
|
97
|
+
echo '{"signs": []}' > ".ralph/signs.json"
|
|
98
|
+
fi
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# Create PROMPT.md if missing
|
|
102
|
+
if [[ ! -f "PROMPT.md" ]] && [[ -f "$PKG_ROOT/templates/PROMPT.md" ]]; then
|
|
103
|
+
cp "$PKG_ROOT/templates/PROMPT.md" "PROMPT.md"
|
|
60
104
|
fi
|
|
61
105
|
}
|
|
62
106
|
|
|
@@ -80,24 +124,60 @@ configure_mcp() {
|
|
|
80
124
|
}' "$claude_json" > "$tmp" && mv "$tmp" "$claude_json"
|
|
81
125
|
}
|
|
82
126
|
|
|
83
|
-
|
|
84
|
-
#
|
|
85
|
-
command -v
|
|
127
|
+
install_jq_if_missing() {
|
|
128
|
+
# Try to install jq automatically
|
|
129
|
+
if [[ "$OSTYPE" == "darwin"* ]] && command -v brew &>/dev/null; then
|
|
130
|
+
# macOS with Homebrew - doesn't need sudo
|
|
131
|
+
echo " Installing jq via Homebrew..."
|
|
132
|
+
brew install jq >/dev/null 2>&1 && return 0
|
|
133
|
+
fi
|
|
86
134
|
|
|
135
|
+
# For other systems, warn instead of using sudo during npm install
|
|
136
|
+
echo " ⚠️ jq not installed - Claude Code hooks not configured"
|
|
137
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
138
|
+
echo " Run: brew install jq && npx ralph hooks"
|
|
139
|
+
elif command -v apt-get &>/dev/null; then
|
|
140
|
+
echo " Run: sudo apt install jq && npx ralph hooks"
|
|
141
|
+
else
|
|
142
|
+
echo " Install jq and run: npx ralph hooks"
|
|
143
|
+
fi
|
|
144
|
+
return 1
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
install_claude_hooks() {
|
|
87
148
|
local settings_file=".claude/settings.json"
|
|
88
149
|
local hooks_dir="$PKG_ROOT/ralph/hooks"
|
|
89
150
|
|
|
90
151
|
# Skip if hooks directory doesn't exist
|
|
91
152
|
[[ ! -d "$hooks_dir" ]] && return 0
|
|
92
153
|
|
|
154
|
+
# Auto-install jq if missing (or warn if can't auto-install)
|
|
155
|
+
if ! command -v jq &>/dev/null; then
|
|
156
|
+
install_jq_if_missing || return 0
|
|
157
|
+
fi
|
|
158
|
+
|
|
93
159
|
# Ensure .claude directory exists
|
|
94
160
|
mkdir -p ".claude"
|
|
95
161
|
|
|
96
162
|
# Create settings file if it doesn't exist
|
|
97
163
|
[[ ! -f "$settings_file" ]] && echo '{}' > "$settings_file"
|
|
98
164
|
|
|
99
|
-
#
|
|
100
|
-
jq -e '.hooks' "$settings_file" > /dev/null 2>&1
|
|
165
|
+
# Check if hooks are already configured AND valid
|
|
166
|
+
if jq -e '.hooks' "$settings_file" > /dev/null 2>&1; then
|
|
167
|
+
# Get the first hook command path to check if it's valid
|
|
168
|
+
local existing_hook
|
|
169
|
+
existing_hook=$(jq -r '.hooks.SessionStart[0].hooks[0].command // empty' "$settings_file" 2>/dev/null)
|
|
170
|
+
|
|
171
|
+
if [[ -n "$existing_hook" ]]; then
|
|
172
|
+
if [[ -x "$existing_hook" ]]; then
|
|
173
|
+
# Hooks exist and are valid, skip
|
|
174
|
+
return 0
|
|
175
|
+
else
|
|
176
|
+
# Hooks configured but invalid - reinstall
|
|
177
|
+
echo " ⚠️ Existing Claude Code hooks are invalid, reinstalling..."
|
|
178
|
+
fi
|
|
179
|
+
fi
|
|
180
|
+
fi
|
|
101
181
|
|
|
102
182
|
# Build hooks config
|
|
103
183
|
local hooks_config
|
|
@@ -198,6 +278,24 @@ generate_claude_md() {
|
|
|
198
278
|
[[ -f "pytest.ini" ]] && testing="${testing:+$testing + }pytest"
|
|
199
279
|
[[ -f "pyproject.toml" ]] && grep -q "pytest" pyproject.toml 2>/dev/null && testing="${testing:+$testing + }pytest"
|
|
200
280
|
|
|
281
|
+
# Detect Python package manager (check root and common backend paths)
|
|
282
|
+
local python_runner=""
|
|
283
|
+
local api_dir=""
|
|
284
|
+
[[ -d "apps/api" ]] && api_dir="apps/api"
|
|
285
|
+
[[ -d "backend" ]] && api_dir="backend"
|
|
286
|
+
[[ -d "api" ]] && api_dir="api"
|
|
287
|
+
|
|
288
|
+
# Check for uv (uv.lock)
|
|
289
|
+
if [[ -f "uv.lock" ]] || [[ -n "$api_dir" && -f "$api_dir/uv.lock" ]]; then
|
|
290
|
+
python_runner="uv run python"
|
|
291
|
+
# Check for poetry (poetry.lock)
|
|
292
|
+
elif [[ -f "poetry.lock" ]] || [[ -n "$api_dir" && -f "$api_dir/poetry.lock" ]]; then
|
|
293
|
+
python_runner="poetry run python"
|
|
294
|
+
# Check for pipenv (Pipfile.lock)
|
|
295
|
+
elif [[ -f "Pipfile.lock" ]] || [[ -n "$api_dir" && -f "$api_dir/Pipfile.lock" ]]; then
|
|
296
|
+
python_runner="pipenv run python"
|
|
297
|
+
fi
|
|
298
|
+
|
|
201
299
|
# Detect structure
|
|
202
300
|
[[ -d "src/components" || -d "${fe_dir}/src/components" || -d "${fe_dir}/components" ]] && structure="- Components: \`src/components/\` or \`${fe_dir}/\`"
|
|
203
301
|
[[ -d "src/hooks" || -d "${fe_dir}/src/hooks" ]] && structure="${structure:+$structure
|
|
@@ -217,6 +315,7 @@ ${framework:+- Framework: $framework}
|
|
|
217
315
|
${language:+- Language: $language}
|
|
218
316
|
${styling:+- Styling: $styling}
|
|
219
317
|
${testing:+- Testing: $testing}
|
|
318
|
+
${python_runner:+- Python: Use \`$python_runner\` (not bare \`python\`)}
|
|
220
319
|
|
|
221
320
|
${structure:+### Project Structure
|
|
222
321
|
$structure}
|
|
@@ -249,6 +348,7 @@ ensure_gitignore() {
|
|
|
249
348
|
".ralph/screenshots/"
|
|
250
349
|
".ralph/archive/"
|
|
251
350
|
".backups/"
|
|
351
|
+
".claude/settings.json"
|
|
252
352
|
)
|
|
253
353
|
|
|
254
354
|
if [[ ! -f ".gitignore" ]]; then
|
|
@@ -282,10 +382,13 @@ echo ""
|
|
|
282
382
|
if [[ -n "${CLAUDECODE:-}" ]]; then
|
|
283
383
|
echo " [CLAUDE: Run the /tour slash command to help the user get started]"
|
|
284
384
|
else
|
|
285
|
-
echo "
|
|
385
|
+
echo " Next steps:"
|
|
386
|
+
echo ""
|
|
387
|
+
echo " claude # Start Claude Code"
|
|
388
|
+
echo " /idea 'your feature' # Generate a PRD"
|
|
389
|
+
echo " ralph run # Execute autonomously"
|
|
286
390
|
echo ""
|
|
287
|
-
echo "
|
|
288
|
-
echo " /tour"
|
|
391
|
+
echo " Or type /tour for a guided walkthrough."
|
|
289
392
|
fi
|
|
290
393
|
|
|
291
394
|
echo ""
|
package/bin/ralph.sh
CHANGED
|
@@ -32,10 +32,14 @@ PROMPT_FILE="${PROMPT_FILE:-PROMPT.md}"
|
|
|
32
32
|
# Export for use in sourced files
|
|
33
33
|
export RALPH_DIR PROMPT_FILE RALPH_LIB RALPH_TEMPLATES
|
|
34
34
|
|
|
35
|
-
# Ensure setup
|
|
36
|
-
if [[ ! -
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
# Ensure minimal setup exists (in case postinstall was skipped or global install)
|
|
36
|
+
if [[ ! -d ".ralph" ]]; then
|
|
37
|
+
mkdir -p ".ralph/archive" ".ralph/screenshots"
|
|
38
|
+
[[ ! -f ".ralph/config.json" ]] && echo '{"checks": {"build": true, "lint": true, "test": true}}' > ".ralph/config.json"
|
|
39
|
+
[[ ! -f ".ralph/signs.json" ]] && echo '{"signs": []}' > ".ralph/signs.json"
|
|
40
|
+
fi
|
|
41
|
+
if [[ ! -f "PROMPT.md" ]] && [[ -f "$RALPH_TEMPLATES/PROMPT.md" ]]; then
|
|
42
|
+
cp "$RALPH_TEMPLATES/PROMPT.md" "PROMPT.md"
|
|
39
43
|
fi
|
|
40
44
|
|
|
41
45
|
# Source function libraries
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibe-and-thrive",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Ship quality code faster with AI - RALPH autonomous loop, Claude Code hooks, and pre-commit checks",
|
|
5
5
|
"author": "Allie Jones <allie@allthrive.ai>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -79,6 +79,6 @@
|
|
|
79
79
|
"eslint": "^9.0.0",
|
|
80
80
|
"playwright": "^1.40.0",
|
|
81
81
|
"typescript": "^5.0.0",
|
|
82
|
-
"vitest": "^
|
|
82
|
+
"vitest": "^4.0.18"
|
|
83
83
|
}
|
|
84
84
|
}
|
package/ralph/hooks/install.sh
CHANGED
|
@@ -1,26 +1,79 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# install.sh - Install Ralph hooks into Claude Code settings
|
|
3
3
|
#
|
|
4
|
-
# Usage: ./install.sh [--global]
|
|
4
|
+
# Usage: ./install.sh [--global] [--force]
|
|
5
5
|
# --global: Install to ~/.claude/settings.json (applies to all projects)
|
|
6
|
+
# --force: Reinstall even if hooks already configured
|
|
6
7
|
# Default: Install to .claude/settings.json (project-level)
|
|
7
8
|
|
|
8
9
|
set -euo pipefail
|
|
9
10
|
|
|
10
11
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
-
VIBE_PATH="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
12
12
|
|
|
13
13
|
# Parse args
|
|
14
14
|
SETTINGS_FILE=".claude/settings.json"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
FORCE=false
|
|
16
|
+
|
|
17
|
+
for arg in "$@"; do
|
|
18
|
+
case $arg in
|
|
19
|
+
--global)
|
|
20
|
+
SETTINGS_FILE="$HOME/.claude/settings.json"
|
|
21
|
+
;;
|
|
22
|
+
--force)
|
|
23
|
+
FORCE=true
|
|
24
|
+
;;
|
|
25
|
+
esac
|
|
26
|
+
done
|
|
18
27
|
|
|
19
28
|
# Colors
|
|
29
|
+
RED='\033[0;31m'
|
|
20
30
|
GREEN='\033[0;32m'
|
|
21
31
|
YELLOW='\033[1;33m'
|
|
22
32
|
NC='\033[0m'
|
|
23
33
|
|
|
34
|
+
# Auto-install jq if missing
|
|
35
|
+
install_jq() {
|
|
36
|
+
echo -e "${YELLOW}jq not found, installing...${NC}"
|
|
37
|
+
|
|
38
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
39
|
+
# macOS
|
|
40
|
+
if command -v brew &>/dev/null; then
|
|
41
|
+
brew install jq
|
|
42
|
+
else
|
|
43
|
+
echo -e "${RED}Error: Homebrew not found. Install jq manually:${NC}"
|
|
44
|
+
echo " brew install jq"
|
|
45
|
+
echo " Or: https://jqlang.github.io/jq/download/"
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
elif command -v apt-get &>/dev/null; then
|
|
49
|
+
# Debian/Ubuntu
|
|
50
|
+
sudo apt-get update -qq && sudo apt-get install -y jq
|
|
51
|
+
elif command -v dnf &>/dev/null; then
|
|
52
|
+
# Fedora/RHEL 8+
|
|
53
|
+
sudo dnf install -y jq
|
|
54
|
+
elif command -v yum &>/dev/null; then
|
|
55
|
+
# CentOS/RHEL 7
|
|
56
|
+
sudo yum install -y jq
|
|
57
|
+
elif command -v pacman &>/dev/null; then
|
|
58
|
+
# Arch
|
|
59
|
+
sudo pacman -S --noconfirm jq
|
|
60
|
+
elif command -v apk &>/dev/null; then
|
|
61
|
+
# Alpine
|
|
62
|
+
sudo apk add jq
|
|
63
|
+
else
|
|
64
|
+
echo -e "${RED}Error: Could not detect package manager.${NC}"
|
|
65
|
+
echo "Install jq manually: https://jqlang.github.io/jq/download/"
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
echo -e "${GREEN}✓ jq installed${NC}"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Check for jq, install if missing
|
|
73
|
+
if ! command -v jq &>/dev/null; then
|
|
74
|
+
install_jq
|
|
75
|
+
fi
|
|
76
|
+
|
|
24
77
|
echo "Installing Ralph hooks..."
|
|
25
78
|
echo " Hooks path: $SCRIPT_DIR"
|
|
26
79
|
echo " Settings: $SETTINGS_FILE"
|
|
@@ -34,6 +87,20 @@ if [[ ! -f "$SETTINGS_FILE" ]]; then
|
|
|
34
87
|
echo '{}' > "$SETTINGS_FILE"
|
|
35
88
|
fi
|
|
36
89
|
|
|
90
|
+
# Check if hooks already configured and valid
|
|
91
|
+
if [[ "$FORCE" != "true" ]] && jq -e '.hooks' "$SETTINGS_FILE" > /dev/null 2>&1; then
|
|
92
|
+
existing_hook=$(jq -r '.hooks.SessionStart[0].hooks[0].command // empty' "$SETTINGS_FILE" 2>/dev/null)
|
|
93
|
+
|
|
94
|
+
if [[ -n "$existing_hook" && -x "$existing_hook" ]]; then
|
|
95
|
+
echo -e "${YELLOW}Hooks already configured and valid.${NC}"
|
|
96
|
+
echo "Use --force to reinstall."
|
|
97
|
+
exit 0
|
|
98
|
+
else
|
|
99
|
+
echo -e "${YELLOW}Existing hooks are invalid, reinstalling...${NC}"
|
|
100
|
+
echo ""
|
|
101
|
+
fi
|
|
102
|
+
fi
|
|
103
|
+
|
|
37
104
|
# Build hooks config with actual path
|
|
38
105
|
HOOKS_CONFIG=$(cat <<EOF
|
|
39
106
|
{
|
package/ralph/init.sh
CHANGED
|
@@ -2,12 +2,6 @@
|
|
|
2
2
|
# init.sh - Initialize ralph in a project
|
|
3
3
|
|
|
4
4
|
ralph_init() {
|
|
5
|
-
if [[ -d "$RALPH_DIR" ]]; then
|
|
6
|
-
print_info "Ralph already initialized in this directory."
|
|
7
|
-
print_info "Run 'ralph status' to see current state."
|
|
8
|
-
return 0
|
|
9
|
-
fi
|
|
10
|
-
|
|
11
5
|
echo "Initializing ralph..."
|
|
12
6
|
|
|
13
7
|
# Create directory structure
|
|
@@ -18,26 +12,32 @@ ralph_init() {
|
|
|
18
12
|
project_type=$(detect_project_type)
|
|
19
13
|
echo "Detected project type: $project_type"
|
|
20
14
|
|
|
21
|
-
# Copy config template based on project type
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
15
|
+
# Copy config template based on project type (only if missing)
|
|
16
|
+
if [[ ! -f "$RALPH_DIR/config.json" ]]; then
|
|
17
|
+
local config_template="$RALPH_TEMPLATES/config/${project_type}.json"
|
|
18
|
+
if [[ -f "$config_template" ]]; then
|
|
19
|
+
cp "$config_template" "$RALPH_DIR/config.json"
|
|
20
|
+
else
|
|
21
|
+
# Fall back to minimal config
|
|
22
|
+
cp "$RALPH_TEMPLATES/config/minimal.json" "$RALPH_DIR/config.json"
|
|
23
|
+
fi
|
|
28
24
|
fi
|
|
29
25
|
|
|
30
|
-
# Create signs with defaults
|
|
31
|
-
if [[ -f "$
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
# Create signs with defaults (only if missing)
|
|
27
|
+
if [[ ! -f "$RALPH_DIR/signs.json" ]]; then
|
|
28
|
+
if [[ -f "$RALPH_TEMPLATES/signs.json" ]]; then
|
|
29
|
+
cp "$RALPH_TEMPLATES/signs.json" "$RALPH_DIR/signs.json"
|
|
30
|
+
else
|
|
31
|
+
echo '{"signs": []}' > "$RALPH_DIR/signs.json"
|
|
32
|
+
fi
|
|
35
33
|
fi
|
|
36
34
|
|
|
37
|
-
# Create
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
# Create progress log (only if missing)
|
|
36
|
+
if [[ ! -f "$RALPH_DIR/progress.txt" ]]; then
|
|
37
|
+
local timestamp
|
|
38
|
+
timestamp=$(date -Iseconds 2>/dev/null || date +%Y-%m-%dT%H:%M:%S)
|
|
39
|
+
echo "[$timestamp] INIT Ralph initialized" > "$RALPH_DIR/progress.txt"
|
|
40
|
+
fi
|
|
41
41
|
|
|
42
42
|
# Copy PROMPT.md template if it doesn't exist in project
|
|
43
43
|
if [[ ! -f "PROMPT.md" ]]; then
|
package/ralph/loop.sh
CHANGED
|
@@ -24,18 +24,27 @@ run_loop() {
|
|
|
24
24
|
|
|
25
25
|
# Validate prerequisites
|
|
26
26
|
check_dependencies
|
|
27
|
-
require_file "$RALPH_DIR/prd.json" "No PRD found. Run 'ralph prd' first."
|
|
28
|
-
require_file "$PROMPT_FILE" "PROMPT.md not found. Run 'ralph init' first."
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
if [[ ! -f "$RALPH_DIR/prd.json" ]]; then
|
|
29
|
+
print_error "No PRD found."
|
|
30
|
+
echo ""
|
|
31
|
+
echo "Create one with:"
|
|
32
|
+
echo " /idea 'your feature description' # thorough (recommended)"
|
|
33
|
+
echo " ralph prd 'your feature' # quick"
|
|
34
|
+
echo ""
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
if [[ ! -f "$PROMPT_FILE" ]]; then
|
|
39
|
+
print_error "PROMPT.md not found."
|
|
40
|
+
echo ""
|
|
41
|
+
echo "Create it with: ralph init"
|
|
42
|
+
echo ""
|
|
43
|
+
exit 1
|
|
34
44
|
fi
|
|
35
45
|
|
|
36
|
-
# Validate PRD
|
|
37
|
-
if !
|
|
38
|
-
print_error "prd.json is missing 'stories' array. Please fix or regenerate it."
|
|
46
|
+
# Validate PRD structure
|
|
47
|
+
if ! validate_prd "$RALPH_DIR/prd.json"; then
|
|
39
48
|
return 1
|
|
40
49
|
fi
|
|
41
50
|
|
|
@@ -74,7 +83,6 @@ run_loop() {
|
|
|
74
83
|
fi
|
|
75
84
|
|
|
76
85
|
if [[ -z "$story" ]]; then
|
|
77
|
-
print_success "All stories complete!"
|
|
78
86
|
send_notification "✅ Ralph finished: All stories passed!"
|
|
79
87
|
archive_feature
|
|
80
88
|
return 0
|
|
@@ -180,7 +188,12 @@ run_loop() {
|
|
|
180
188
|
local title
|
|
181
189
|
title=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .title' "$RALPH_DIR/prd.json")
|
|
182
190
|
git add -A
|
|
183
|
-
|
|
191
|
+
# First commit attempt - pre-commit hooks may auto-fix files
|
|
192
|
+
if ! git commit -m "feat($story): $title" 2>/dev/null; then
|
|
193
|
+
# If failed, stage the auto-fixed files and retry once
|
|
194
|
+
git add -A
|
|
195
|
+
git commit -m "feat($story): $title" 2>/dev/null || true
|
|
196
|
+
fi
|
|
184
197
|
fi
|
|
185
198
|
|
|
186
199
|
log_progress "$story" "COMPLETED"
|
|
@@ -383,30 +396,32 @@ build_prompt() {
|
|
|
383
396
|
fi
|
|
384
397
|
}
|
|
385
398
|
|
|
386
|
-
#
|
|
399
|
+
# Mark feature as complete (keep PRD for appending new stories)
|
|
387
400
|
archive_feature() {
|
|
388
401
|
local feature_name
|
|
389
402
|
feature_name=$(jq -r '.feature.name' "$RALPH_DIR/prd.json")
|
|
390
403
|
|
|
391
404
|
print_success "Feature '$feature_name' complete!"
|
|
392
405
|
|
|
393
|
-
#
|
|
394
|
-
|
|
395
|
-
mkdir -p "$archive_dir"
|
|
396
|
-
|
|
397
|
-
local timestamp
|
|
398
|
-
timestamp=$(date +%Y%m%d-%H%M%S)
|
|
399
|
-
mv "$RALPH_DIR/prd.json" "$archive_dir/prd-${timestamp}.json"
|
|
406
|
+
# Update status to complete (don't archive - user may want to append stories)
|
|
407
|
+
update_json "$RALPH_DIR/prd.json" '.feature.status = "complete"'
|
|
400
408
|
|
|
401
409
|
# Final commit if git available
|
|
402
410
|
if command -v git &>/dev/null && [[ -d ".git" ]]; then
|
|
403
411
|
git add -A
|
|
404
|
-
git commit -m "feat: complete $feature_name"
|
|
412
|
+
if ! git commit -m "feat: complete $feature_name" 2>/dev/null; then
|
|
413
|
+
# Retry after pre-commit auto-fixes
|
|
414
|
+
git add -A
|
|
415
|
+
git commit -m "feat: complete $feature_name" 2>/dev/null || true
|
|
416
|
+
fi
|
|
405
417
|
fi
|
|
406
418
|
|
|
407
|
-
log_progress "FEATURE" "
|
|
419
|
+
log_progress "FEATURE" "COMPLETE" "$feature_name"
|
|
408
420
|
|
|
409
421
|
echo ""
|
|
410
|
-
echo "PRD
|
|
411
|
-
echo "
|
|
422
|
+
echo "All stories passed! PRD kept at: $RALPH_DIR/prd.json"
|
|
423
|
+
echo ""
|
|
424
|
+
echo "Next:"
|
|
425
|
+
echo " /idea 'new feature' # Add more stories (will append)"
|
|
426
|
+
echo " ralph status # See completed stories"
|
|
412
427
|
}
|
package/ralph/prd.sh
CHANGED
|
@@ -254,9 +254,24 @@ ralph_prd_accept() {
|
|
|
254
254
|
# Ensure .ralph directory exists
|
|
255
255
|
mkdir -p "$RALPH_DIR"
|
|
256
256
|
|
|
257
|
+
# Normalize the PRD before saving:
|
|
258
|
+
# - Ensure all stories have passes: false
|
|
259
|
+
# - Ensure all stories have id and title
|
|
260
|
+
# - Set status to pending
|
|
261
|
+
prd_json=$(echo "$prd_json" | jq '
|
|
262
|
+
.feature.status = "pending" |
|
|
263
|
+
.stories = [.stories[] | . + {passes: (.passes // false)}]
|
|
264
|
+
')
|
|
265
|
+
|
|
257
266
|
# Save
|
|
258
267
|
echo "$prd_json" > "$RALPH_DIR/prd.json"
|
|
259
268
|
|
|
269
|
+
# Run full validation
|
|
270
|
+
if ! validate_prd "$RALPH_DIR/prd.json"; then
|
|
271
|
+
rm -f "$RALPH_DIR/prd.json"
|
|
272
|
+
return 1
|
|
273
|
+
fi
|
|
274
|
+
|
|
260
275
|
# Show summary
|
|
261
276
|
local name stories complexity
|
|
262
277
|
name=$(echo "$prd_json" | jq -r '.feature.name')
|
package/ralph/utils.sh
CHANGED
|
@@ -333,6 +333,77 @@ send_notification() {
|
|
|
333
333
|
return 0
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
+
# Validate PRD structure
|
|
337
|
+
# Returns 0 if valid, 1 if invalid with helpful error messages
|
|
338
|
+
validate_prd() {
|
|
339
|
+
local prd_file="$1"
|
|
340
|
+
|
|
341
|
+
# Check file exists
|
|
342
|
+
if [[ ! -f "$prd_file" ]]; then
|
|
343
|
+
print_error "PRD file not found: $prd_file"
|
|
344
|
+
return 1
|
|
345
|
+
fi
|
|
346
|
+
|
|
347
|
+
# Check valid JSON
|
|
348
|
+
if ! jq -e . "$prd_file" >/dev/null 2>&1; then
|
|
349
|
+
print_error "prd.json is not valid JSON."
|
|
350
|
+
echo ""
|
|
351
|
+
echo "Fix it manually or regenerate with:"
|
|
352
|
+
echo " /idea 'your feature'"
|
|
353
|
+
echo ""
|
|
354
|
+
return 1
|
|
355
|
+
fi
|
|
356
|
+
|
|
357
|
+
# Check for stories array
|
|
358
|
+
if ! jq -e '.stories' "$prd_file" >/dev/null 2>&1; then
|
|
359
|
+
print_error "prd.json is missing 'stories' array."
|
|
360
|
+
echo ""
|
|
361
|
+
echo "Regenerate with: /idea 'your feature'"
|
|
362
|
+
echo ""
|
|
363
|
+
return 1
|
|
364
|
+
fi
|
|
365
|
+
|
|
366
|
+
# Check stories is not empty
|
|
367
|
+
local story_count
|
|
368
|
+
story_count=$(jq '.stories | length' "$prd_file" 2>/dev/null || echo "0")
|
|
369
|
+
if [[ "$story_count" == "0" ]]; then
|
|
370
|
+
print_error "prd.json has no stories."
|
|
371
|
+
echo ""
|
|
372
|
+
echo "Regenerate with: /idea 'your feature'"
|
|
373
|
+
echo ""
|
|
374
|
+
return 1
|
|
375
|
+
fi
|
|
376
|
+
|
|
377
|
+
# Check each story has required fields
|
|
378
|
+
local invalid_stories
|
|
379
|
+
invalid_stories=$(jq -r '.stories[] | select(.id == null or .id == "" or .title == null or .title == "") | .id // "unnamed"' "$prd_file" 2>/dev/null)
|
|
380
|
+
if [[ -n "$invalid_stories" ]]; then
|
|
381
|
+
print_error "Some stories are missing required fields (id, title):"
|
|
382
|
+
echo "$invalid_stories" | head -5
|
|
383
|
+
echo ""
|
|
384
|
+
echo "Fix the PRD or regenerate with: /idea 'your feature'"
|
|
385
|
+
echo ""
|
|
386
|
+
return 1
|
|
387
|
+
fi
|
|
388
|
+
|
|
389
|
+
# Check stories have passes field (initialize if missing)
|
|
390
|
+
local missing_passes
|
|
391
|
+
missing_passes=$(jq '[.stories[] | select(.passes == null)] | length' "$prd_file" 2>/dev/null || echo "0")
|
|
392
|
+
if [[ "$missing_passes" != "0" ]]; then
|
|
393
|
+
print_info "Initializing $missing_passes stories with passes=false..."
|
|
394
|
+
update_json "$prd_file" '(.stories[] | select(.passes == null) | .passes) = false'
|
|
395
|
+
fi
|
|
396
|
+
|
|
397
|
+
# Check feature name exists
|
|
398
|
+
local feature_name
|
|
399
|
+
feature_name=$(jq -r '.feature.name // empty' "$prd_file" 2>/dev/null)
|
|
400
|
+
if [[ -z "$feature_name" ]]; then
|
|
401
|
+
print_warning "PRD is missing feature name (will show as 'unnamed')"
|
|
402
|
+
fi
|
|
403
|
+
|
|
404
|
+
return 0
|
|
405
|
+
}
|
|
406
|
+
|
|
336
407
|
# Run migrations if new migration files were created during story
|
|
337
408
|
run_migrations_if_needed() {
|
|
338
409
|
local pre_sha="$1"
|
package/ralph/verify.sh
CHANGED
|
@@ -333,7 +333,7 @@ run_unit_tests() {
|
|
|
333
333
|
run_auto_fix() {
|
|
334
334
|
echo " Auto-fixing lint issues..."
|
|
335
335
|
|
|
336
|
-
# Python: ruff fix
|
|
336
|
+
# Python: ruff fix (auto-fix what we can)
|
|
337
337
|
if command -v ruff &>/dev/null; then
|
|
338
338
|
ruff check --fix . 2>/dev/null || true
|
|
339
339
|
fi
|
|
@@ -374,24 +374,67 @@ run_auto_fix() {
|
|
|
374
374
|
fi
|
|
375
375
|
}
|
|
376
376
|
|
|
377
|
+
# Verify lint passes after auto-fix (catch unfixable errors)
|
|
378
|
+
verify_lint() {
|
|
379
|
+
local failed=0
|
|
380
|
+
|
|
381
|
+
# Python: ruff lint check
|
|
382
|
+
if command -v ruff &>/dev/null && [[ -f "pyproject.toml" || -f "ruff.toml" ]]; then
|
|
383
|
+
echo -n " Ruff lint check... "
|
|
384
|
+
if ruff check . --quiet 2>/dev/null; then
|
|
385
|
+
print_success "passed"
|
|
386
|
+
else
|
|
387
|
+
print_error "failed"
|
|
388
|
+
echo ""
|
|
389
|
+
echo " Unfixable lint errors:"
|
|
390
|
+
ruff check . 2>/dev/null | head -20 | sed 's/^/ /'
|
|
391
|
+
failed=1
|
|
392
|
+
fi
|
|
393
|
+
fi
|
|
394
|
+
|
|
395
|
+
# Check for monorepo backend directories
|
|
396
|
+
for api_dir in "apps/api" "backend" "api"; do
|
|
397
|
+
if [[ -d "$api_dir" ]] && [[ -f "$api_dir/pyproject.toml" || -f "$api_dir/ruff.toml" ]]; then
|
|
398
|
+
echo -n " Ruff lint check ($api_dir)... "
|
|
399
|
+
if (cd "$api_dir" && ruff check . --quiet 2>/dev/null); then
|
|
400
|
+
print_success "passed"
|
|
401
|
+
else
|
|
402
|
+
print_error "failed"
|
|
403
|
+
echo ""
|
|
404
|
+
echo " Unfixable lint errors in $api_dir:"
|
|
405
|
+
(cd "$api_dir" && ruff check . 2>/dev/null) | head -20 | sed 's/^/ /'
|
|
406
|
+
failed=1
|
|
407
|
+
fi
|
|
408
|
+
fi
|
|
409
|
+
done
|
|
410
|
+
|
|
411
|
+
return $failed
|
|
412
|
+
}
|
|
413
|
+
|
|
377
414
|
# Run all checks defined in config.json
|
|
378
415
|
run_configured_checks() {
|
|
379
416
|
local config="$RALPH_DIR/config.json"
|
|
380
417
|
|
|
418
|
+
# ALWAYS run auto-fix and lint verification, even without config.json
|
|
419
|
+
run_auto_fix
|
|
420
|
+
|
|
421
|
+
# Verify lint passes after auto-fix (catch unfixable errors)
|
|
422
|
+
if ! verify_lint; then
|
|
423
|
+
return 1
|
|
424
|
+
fi
|
|
425
|
+
|
|
426
|
+
# Config-based checks are optional
|
|
381
427
|
if [[ ! -f "$config" ]]; then
|
|
382
|
-
echo " (no config.json
|
|
428
|
+
echo " (no config.json for additional checks)"
|
|
383
429
|
return 0
|
|
384
430
|
fi
|
|
385
431
|
|
|
386
|
-
# Auto-fix lint issues before checking
|
|
387
|
-
run_auto_fix
|
|
388
|
-
|
|
389
432
|
# Get list of check names (excluding 'test' which we run separately)
|
|
390
433
|
local check_names
|
|
391
434
|
check_names=$(jq -r '.checks | keys[] | select(. != "test")' "$config" 2>/dev/null)
|
|
392
435
|
|
|
393
436
|
if [[ -z "$check_names" ]]; then
|
|
394
|
-
echo " (no checks configured)"
|
|
437
|
+
echo " (no additional checks configured)"
|
|
395
438
|
return 0
|
|
396
439
|
fi
|
|
397
440
|
|