thrivekit 2.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 (164) hide show
  1. package/.claude/commands/explain.md +114 -0
  2. package/.claude/commands/idea.md +370 -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/.claude/settings.json +75 -0
  13. package/.claude/settings.local.json +12 -0
  14. package/.pre-commit-hooks.yaml +102 -0
  15. package/LICENSE +21 -0
  16. package/README.md +214 -0
  17. package/bin/postinstall.sh +29 -0
  18. package/bin/ralph.sh +171 -0
  19. package/bin/thrivekit.sh +24 -0
  20. package/bin/vibe-check.js +19 -0
  21. package/dist/checks/check-any-types.d.ts +6 -0
  22. package/dist/checks/check-any-types.d.ts.map +1 -0
  23. package/dist/checks/check-any-types.js +73 -0
  24. package/dist/checks/check-any-types.js.map +1 -0
  25. package/dist/checks/check-commented-code.d.ts +6 -0
  26. package/dist/checks/check-commented-code.d.ts.map +1 -0
  27. package/dist/checks/check-commented-code.js +81 -0
  28. package/dist/checks/check-commented-code.js.map +1 -0
  29. package/dist/checks/check-console-error.d.ts +6 -0
  30. package/dist/checks/check-console-error.d.ts.map +1 -0
  31. package/dist/checks/check-console-error.js +41 -0
  32. package/dist/checks/check-console-error.js.map +1 -0
  33. package/dist/checks/check-debug-statements.d.ts +6 -0
  34. package/dist/checks/check-debug-statements.d.ts.map +1 -0
  35. package/dist/checks/check-debug-statements.js +120 -0
  36. package/dist/checks/check-debug-statements.js.map +1 -0
  37. package/dist/checks/check-deep-nesting.d.ts +6 -0
  38. package/dist/checks/check-deep-nesting.d.ts.map +1 -0
  39. package/dist/checks/check-deep-nesting.js +116 -0
  40. package/dist/checks/check-deep-nesting.js.map +1 -0
  41. package/dist/checks/check-docker-platform.d.ts +6 -0
  42. package/dist/checks/check-docker-platform.d.ts.map +1 -0
  43. package/dist/checks/check-docker-platform.js +42 -0
  44. package/dist/checks/check-docker-platform.js.map +1 -0
  45. package/dist/checks/check-dry-violations.d.ts +6 -0
  46. package/dist/checks/check-dry-violations.d.ts.map +1 -0
  47. package/dist/checks/check-dry-violations.js +124 -0
  48. package/dist/checks/check-dry-violations.js.map +1 -0
  49. package/dist/checks/check-empty-catch.d.ts +6 -0
  50. package/dist/checks/check-empty-catch.d.ts.map +1 -0
  51. package/dist/checks/check-empty-catch.js +111 -0
  52. package/dist/checks/check-empty-catch.js.map +1 -0
  53. package/dist/checks/check-function-length.d.ts +6 -0
  54. package/dist/checks/check-function-length.d.ts.map +1 -0
  55. package/dist/checks/check-function-length.js +152 -0
  56. package/dist/checks/check-function-length.js.map +1 -0
  57. package/dist/checks/check-hardcoded-ai-models.d.ts +10 -0
  58. package/dist/checks/check-hardcoded-ai-models.d.ts.map +1 -0
  59. package/dist/checks/check-hardcoded-ai-models.js +102 -0
  60. package/dist/checks/check-hardcoded-ai-models.js.map +1 -0
  61. package/dist/checks/check-hardcoded-urls.d.ts +6 -0
  62. package/dist/checks/check-hardcoded-urls.d.ts.map +1 -0
  63. package/dist/checks/check-hardcoded-urls.js +124 -0
  64. package/dist/checks/check-hardcoded-urls.js.map +1 -0
  65. package/dist/checks/check-magic-numbers.d.ts +6 -0
  66. package/dist/checks/check-magic-numbers.d.ts.map +1 -0
  67. package/dist/checks/check-magic-numbers.js +116 -0
  68. package/dist/checks/check-magic-numbers.js.map +1 -0
  69. package/dist/checks/check-secrets.d.ts +6 -0
  70. package/dist/checks/check-secrets.d.ts.map +1 -0
  71. package/dist/checks/check-secrets.js +138 -0
  72. package/dist/checks/check-secrets.js.map +1 -0
  73. package/dist/checks/check-snake-case-ts.d.ts +6 -0
  74. package/dist/checks/check-snake-case-ts.d.ts.map +1 -0
  75. package/dist/checks/check-snake-case-ts.js +78 -0
  76. package/dist/checks/check-snake-case-ts.js.map +1 -0
  77. package/dist/checks/check-todo-fixme.d.ts +6 -0
  78. package/dist/checks/check-todo-fixme.d.ts.map +1 -0
  79. package/dist/checks/check-todo-fixme.js +41 -0
  80. package/dist/checks/check-todo-fixme.js.map +1 -0
  81. package/dist/checks/check-unsafe-html.d.ts +6 -0
  82. package/dist/checks/check-unsafe-html.d.ts.map +1 -0
  83. package/dist/checks/check-unsafe-html.js +101 -0
  84. package/dist/checks/check-unsafe-html.js.map +1 -0
  85. package/dist/checks/index.d.ts +30 -0
  86. package/dist/checks/index.d.ts.map +1 -0
  87. package/dist/checks/index.js +57 -0
  88. package/dist/checks/index.js.map +1 -0
  89. package/dist/cli.d.ts +13 -0
  90. package/dist/cli.d.ts.map +1 -0
  91. package/dist/cli.js +206 -0
  92. package/dist/cli.js.map +1 -0
  93. package/dist/index.d.ts +9 -0
  94. package/dist/index.d.ts.map +1 -0
  95. package/dist/index.js +10 -0
  96. package/dist/index.js.map +1 -0
  97. package/dist/utils/file-reader.d.ts +24 -0
  98. package/dist/utils/file-reader.d.ts.map +1 -0
  99. package/dist/utils/file-reader.js +140 -0
  100. package/dist/utils/file-reader.js.map +1 -0
  101. package/dist/utils/patterns.d.ts +27 -0
  102. package/dist/utils/patterns.d.ts.map +1 -0
  103. package/dist/utils/patterns.js +84 -0
  104. package/dist/utils/patterns.js.map +1 -0
  105. package/dist/utils/reporters.d.ts +21 -0
  106. package/dist/utils/reporters.d.ts.map +1 -0
  107. package/dist/utils/reporters.js +115 -0
  108. package/dist/utils/reporters.js.map +1 -0
  109. package/dist/utils/types.d.ts +71 -0
  110. package/dist/utils/types.d.ts.map +1 -0
  111. package/dist/utils/types.js +5 -0
  112. package/dist/utils/types.js.map +1 -0
  113. package/package.json +82 -0
  114. package/ralph/api.sh +210 -0
  115. package/ralph/backup.sh +838 -0
  116. package/ralph/browser-verify/README.md +135 -0
  117. package/ralph/browser-verify/verify.ts +450 -0
  118. package/ralph/checks/check-fastapi-responses.py +155 -0
  119. package/ralph/hooks/hooks-config.json +72 -0
  120. package/ralph/hooks/inject-context.sh +44 -0
  121. package/ralph/hooks/install.sh +207 -0
  122. package/ralph/hooks/log-tools.sh +45 -0
  123. package/ralph/hooks/protect-prd.sh +27 -0
  124. package/ralph/hooks/save-learnings.sh +36 -0
  125. package/ralph/hooks/warn-debug.sh +54 -0
  126. package/ralph/hooks/warn-empty-catch.sh +63 -0
  127. package/ralph/hooks/warn-secrets.sh +89 -0
  128. package/ralph/hooks/warn-urls.sh +77 -0
  129. package/ralph/init.sh +388 -0
  130. package/ralph/loop.sh +570 -0
  131. package/ralph/playwright.sh +238 -0
  132. package/ralph/prd.sh +295 -0
  133. package/ralph/setup/feature-tour.sh +155 -0
  134. package/ralph/setup/quick-setup.sh +239 -0
  135. package/ralph/setup/tutorial.sh +159 -0
  136. package/ralph/setup/ui.sh +136 -0
  137. package/ralph/setup.sh +353 -0
  138. package/ralph/signs.sh +150 -0
  139. package/ralph/utils.sh +682 -0
  140. package/ralph/verify/browser.sh +324 -0
  141. package/ralph/verify/lint.sh +363 -0
  142. package/ralph/verify/review.sh +164 -0
  143. package/ralph/verify/tests.sh +81 -0
  144. package/ralph/verify.sh +224 -0
  145. package/templates/PROMPT.md +235 -0
  146. package/templates/config/fullstack.json +86 -0
  147. package/templates/config/go.json +81 -0
  148. package/templates/config/minimal.json +76 -0
  149. package/templates/config/node.json +81 -0
  150. package/templates/config/python.json +81 -0
  151. package/templates/config/rust.json +81 -0
  152. package/templates/examples/CLAUDE-django.md +174 -0
  153. package/templates/examples/CLAUDE-fastapi.md +270 -0
  154. package/templates/examples/CLAUDE-fastmcp.md +352 -0
  155. package/templates/examples/CLAUDE-fullstack.md +256 -0
  156. package/templates/examples/CLAUDE-node.md +246 -0
  157. package/templates/examples/CLAUDE-react.md +138 -0
  158. package/templates/optional/cursorrules.template +147 -0
  159. package/templates/optional/eslint.config.js +34 -0
  160. package/templates/optional/lint-staged.config.js +34 -0
  161. package/templates/optional/ruff.toml +125 -0
  162. package/templates/optional/vibe-check.yml +116 -0
  163. package/templates/optional/vscode-settings.json +127 -0
  164. package/templates/signs.json +46 -0
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env bash
2
+ # shellcheck shell=bash
3
+ # playwright.sh - Playwright test integration for ralph
4
+
5
+ # Ensure Playwright is installed and configured
6
+ ensure_playwright() {
7
+ # Check if playwright config exists
8
+ if [[ ! -f "playwright.config.ts" ]] && [[ ! -f "playwright.config.js" ]]; then
9
+ print_info "Playwright not configured, initializing..."
10
+
11
+ # Check if npx is available
12
+ if ! command -v npx &>/dev/null; then
13
+ print_error "npx not found - cannot install Playwright"
14
+ return 1
15
+ fi
16
+
17
+ # Install Playwright package and browsers
18
+ npm install playwright 2>/dev/null && npx playwright install chromium 2>/dev/null || {
19
+ print_warning "Could not install Playwright - run: npm install playwright && npx playwright install chromium"
20
+ }
21
+
22
+ # Create minimal config if it doesn't exist
23
+ if [[ ! -f "playwright.config.ts" ]] && [[ ! -f "playwright.config.js" ]]; then
24
+ print_info "Creating playwright.config.ts..."
25
+ cat > playwright.config.ts << 'EOF'
26
+ import { defineConfig, devices } from '@playwright/test';
27
+
28
+ export default defineConfig({
29
+ testDir: './tests/e2e',
30
+ fullyParallel: true,
31
+ forbidOnly: !!process.env.CI,
32
+ retries: process.env.CI ? 2 : 0,
33
+ workers: process.env.CI ? 1 : undefined,
34
+ reporter: 'html',
35
+ use: {
36
+ baseURL: process.env.BASE_URL || 'http://localhost:3000',
37
+ trace: 'on-first-retry',
38
+ screenshot: 'only-on-failure',
39
+ },
40
+ projects: [
41
+ {
42
+ name: 'chromium',
43
+ use: { ...devices['Desktop Chrome'] },
44
+ },
45
+ {
46
+ name: 'mobile',
47
+ use: { ...devices['iPhone 13'] },
48
+ },
49
+ ],
50
+ });
51
+ EOF
52
+ fi
53
+
54
+ # Create test directory
55
+ mkdir -p tests/e2e
56
+
57
+ print_success "Playwright initialized"
58
+ fi
59
+
60
+ return 0
61
+ }
62
+
63
+ # Find test file for a story - uses explicit config or story-level testFile
64
+ find_story_test_file() {
65
+ local story="$1"
66
+ local test_dir="$2"
67
+
68
+ # 1. Check story for explicit testFile path (preferred)
69
+ local explicit_file
70
+ explicit_file=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .testFile // empty' "$RALPH_DIR/prd.json" 2>/dev/null)
71
+ if [[ -n "$explicit_file" && -f "$explicit_file" ]]; then
72
+ echo "$explicit_file"
73
+ return 0
74
+ fi
75
+
76
+ # 2. Check config.json for e2e test directory pattern
77
+ local config_test_dir
78
+ config_test_dir=$(get_config '.playwright.testDir' "")
79
+ if [[ -n "$config_test_dir" ]]; then
80
+ test_dir="$config_test_dir"
81
+ fi
82
+
83
+ # 3. Standard naming: {testDir}/{story-id}.spec.ts
84
+ if [[ -f "${test_dir}/${story}.spec.ts" ]]; then
85
+ echo "${test_dir}/${story}.spec.ts"
86
+ return 0
87
+ fi
88
+ if [[ -f "${test_dir}/${story}.spec.js" ]]; then
89
+ echo "${test_dir}/${story}.spec.js"
90
+ return 0
91
+ fi
92
+
93
+ # 4. Slug-based naming from story title
94
+ local story_slug
95
+ story_slug=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .title // ""' "$RALPH_DIR/prd.json" 2>/dev/null | \
96
+ tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//' | sed 's/-$//')
97
+
98
+ if [[ -n "$story_slug" && -f "${test_dir}/${story_slug}.spec.ts" ]]; then
99
+ echo "${test_dir}/${story_slug}.spec.ts"
100
+ return 0
101
+ fi
102
+
103
+ return 1
104
+ }
105
+
106
+ # Run Playwright tests for a specific story or all tests
107
+ run_playwright_tests() {
108
+ local story="$1"
109
+
110
+ # Check if Playwright is enabled in config
111
+ local pw_enabled
112
+ pw_enabled=$(get_config '.playwright.enabled' "true")
113
+ if [[ "$pw_enabled" == "false" ]]; then
114
+ echo " (Playwright disabled in config, skipping)"
115
+ return 0
116
+ fi
117
+
118
+ # Ensure Playwright is set up
119
+ if ! ensure_playwright; then
120
+ return 1
121
+ fi
122
+
123
+ # Check if npx is available
124
+ if ! command -v npx &>/dev/null; then
125
+ print_error "npx not found - cannot run Playwright"
126
+ return 1
127
+ fi
128
+
129
+ # Get test directory from config or use default
130
+ local test_dir
131
+ test_dir=$(get_config '.playwright.testDir' "tests/e2e")
132
+
133
+ # Find the test file for this story
134
+ local test_file
135
+ test_file=$(find_story_test_file "$story" "$test_dir")
136
+
137
+ local log_file
138
+ log_file=$(create_temp_file ".log") || return 1
139
+
140
+ echo -n " Running Playwright tests... "
141
+
142
+ if [[ -n "$test_file" && -f "$test_file" ]]; then
143
+ # Run story-specific test
144
+ echo -n "(${test_file##*/}) "
145
+ if npx playwright test "$test_file" --reporter=line > "$log_file" 2>&1; then
146
+ print_success "passed"
147
+ rm -f "$log_file"
148
+ return 0
149
+ fi
150
+ else
151
+ # No story-specific test - check if e2e was expected
152
+ local e2e_required
153
+ e2e_required=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .e2e // false' "$RALPH_DIR/prd.json" 2>/dev/null)
154
+
155
+ if [[ "$e2e_required" == "true" ]]; then
156
+ # Show where we looked and how to fix
157
+ print_warning "missing (e2e: true but no test file found)"
158
+ echo ""
159
+ echo " Looked for: ${test_dir}/${story}.spec.ts"
160
+ echo ""
161
+ echo " Fix options:"
162
+ echo " 1. Add 'testFile' to story: \"testFile\": \"apps/web/tests/e2e/my-test.spec.ts\""
163
+ echo " 2. Set testDir in .ralph/config.json: \"playwright\": {\"testDir\": \"apps/web/tests/e2e\"}"
164
+ echo " 3. Name test file: ${test_dir}/${story}.spec.ts"
165
+ rm -f "$log_file"
166
+ return 1 # Fail if e2e was expected but not created
167
+ fi
168
+
169
+ # Check if any tests exist to run
170
+ if [[ -z "$(find "$test_dir" -name '*.spec.ts' -o -name '*.spec.js' 2>/dev/null | head -1)" ]]; then
171
+ echo "skipped (not required for this story)"
172
+ rm -f "$log_file"
173
+ return 0
174
+ fi
175
+
176
+ # Run all tests
177
+ if npx playwright test --reporter=line > "$log_file" 2>&1; then
178
+ print_success "passed"
179
+ rm -f "$log_file"
180
+ return 0
181
+ fi
182
+ fi
183
+
184
+ # Tests failed
185
+ print_error "failed"
186
+ echo ""
187
+ echo " Playwright output (last $MAX_LOG_LINES lines):"
188
+ tail -"$MAX_LOG_LINES" "$log_file" | sed 's/^/ /'
189
+
190
+ # Save failure for context
191
+ cp "$log_file" "$RALPH_DIR/last_playwright_failure.log"
192
+
193
+ rm -f "$log_file"
194
+ return 1
195
+ }
196
+
197
+ # Run Playwright with accessibility checks
198
+ run_playwright_a11y() {
199
+ local story="$1"
200
+ local url="$2"
201
+
202
+ # Check if @axe-core/playwright is available
203
+ if ! npm list @axe-core/playwright &>/dev/null 2>&1; then
204
+ print_info "Installing @axe-core/playwright for accessibility testing..."
205
+ npm install -D @axe-core/playwright 2>/dev/null || {
206
+ print_warning "Could not install axe-core, skipping a11y tests"
207
+ return 0
208
+ }
209
+ fi
210
+
211
+ # a11y tests are typically part of the Playwright tests
212
+ # This function can be extended to run standalone a11y audits
213
+ return 0
214
+ }
215
+
216
+ # Run Playwright at specific viewport (for mobile testing)
217
+ run_playwright_mobile() {
218
+ local story="$1"
219
+
220
+ local log_file
221
+ log_file=$(create_temp_file ".log") || return 1
222
+
223
+ echo -n " Running mobile viewport tests... "
224
+
225
+ # Run tests with mobile project
226
+ if npx playwright test --project=mobile --reporter=line > "$log_file" 2>&1; then
227
+ print_success "passed"
228
+ rm -f "$log_file"
229
+ return 0
230
+ else
231
+ print_error "failed"
232
+ echo ""
233
+ echo " Mobile test output:"
234
+ tail -"$MAX_OUTPUT_PREVIEW_LINES" "$log_file" | sed 's/^/ /'
235
+ rm -f "$log_file"
236
+ return 1
237
+ fi
238
+ }
package/ralph/prd.sh ADDED
@@ -0,0 +1,295 @@
1
+ #!/usr/bin/env bash
2
+ # shellcheck shell=bash
3
+ # prd.sh - PRD generation functions
4
+
5
+ ralph_prd() {
6
+ local notes=""
7
+ local from_file=""
8
+
9
+ # Parse arguments
10
+ while [[ $# -gt 0 ]]; do
11
+ case "$1" in
12
+ --file|-f)
13
+ from_file="$2"
14
+ shift 2
15
+ ;;
16
+ --accept)
17
+ ralph_prd_accept
18
+ return $?
19
+ ;;
20
+ *)
21
+ notes="$notes $1"
22
+ shift
23
+ ;;
24
+ esac
25
+ done
26
+
27
+ # Read from file if specified
28
+ if [[ -n "$from_file" ]]; then
29
+ if [[ ! -f "$from_file" ]]; then
30
+ print_error "File not found: $from_file"
31
+ return 1
32
+ fi
33
+ notes=$(cat "$from_file") || { print_error "Failed to read $from_file"; return 1; }
34
+ fi
35
+
36
+ # Trim leading/trailing whitespace (safer than xargs)
37
+ notes="${notes#"${notes%%[![:space:]]*}"}" # trim leading
38
+ notes="${notes%"${notes##*[![:space:]]}"}" # trim trailing
39
+
40
+ if [[ -z "$notes" ]]; then
41
+ print_error "No notes provided. Usage: ralph prd <description>"
42
+ echo ""
43
+ echo "Examples:"
44
+ echo " ralph prd 'Add user authentication with OAuth'"
45
+ echo " ralph prd --file docs/feature-spec.md"
46
+ return 1
47
+ fi
48
+
49
+ # Check if PRD already exists
50
+ if [[ -f "$RALPH_DIR/prd.json" ]]; then
51
+ print_warning "A PRD already exists. Archive or delete it first."
52
+ echo " Current feature: $(jq -r '.feature.name' "$RALPH_DIR/prd.json")"
53
+ echo ""
54
+ echo "Options:"
55
+ echo " 1. Archive it: mv .ralph/prd.json .ralph/archive/"
56
+ echo " 2. Delete it: rm .ralph/prd.json"
57
+ echo " 3. Check status: ralph status"
58
+ return 1
59
+ fi
60
+
61
+ # Check Claude CLI is available
62
+ if ! command -v claude &>/dev/null; then
63
+ print_error "Claude CLI not found. Install it with: npm install -g @anthropic-ai/claude-code"
64
+ return 1
65
+ fi
66
+
67
+ echo ""
68
+ print_info "Starting interactive PRD generation..."
69
+ echo "Claude will ask clarifying questions to refine the PRD."
70
+ echo ""
71
+
72
+ # Get testUrlBase from config (default to localhost:3000)
73
+ local test_url_base
74
+ test_url_base=$(get_config "testUrlBase" "http://localhost:3000")
75
+
76
+ # Create the PRD generation prompt using printf to avoid heredoc issues
77
+ local prompt_file
78
+ prompt_file=$(create_temp_file ".md") || return 1
79
+
80
+ printf '%s\n' "You are generating a PRD (Product Requirements Document) for an autonomous coding agent called Ralph." > "$prompt_file"
81
+ printf '\n%s\n' "The user provided these feature notes:" >> "$prompt_file"
82
+ printf '\n%s\n' "---" >> "$prompt_file"
83
+ printf '%s\n' "$notes" >> "$prompt_file"
84
+ printf '%s\n' "---" >> "$prompt_file"
85
+ printf '\n%s\n' "## Your Task" >> "$prompt_file"
86
+ printf '\n%s\n' "1. First, ask up to 5 clarifying questions about:" >> "$prompt_file"
87
+ printf '%s\n' " - Security requirements (auth, permissions, data access)" >> "$prompt_file"
88
+ printf '%s\n' " - Scaling concerns (caching, pagination, rate limits)" >> "$prompt_file"
89
+ printf '%s\n' " - Edge cases (empty states, error handling)" >> "$prompt_file"
90
+ printf '%s\n' " - Related features (what existing features does this touch?)" >> "$prompt_file"
91
+ printf '%s\n' " - Scope boundaries (what is explicitly OUT of scope?)" >> "$prompt_file"
92
+ printf '\n%s\n' "2. After the user answers, generate a prd.json file with this exact structure:" >> "$prompt_file"
93
+ printf '\n%s\n' '{' >> "$prompt_file"
94
+ printf '%s\n' ' "feature": {' >> "$prompt_file"
95
+ printf '%s\n' ' "name": "Feature Name",' >> "$prompt_file"
96
+ printf '%s\n' ' "branch": "feature/feature-name",' >> "$prompt_file"
97
+ printf '%s\n' ' "status": "pending"' >> "$prompt_file"
98
+ printf '%s\n' ' },' >> "$prompt_file"
99
+ printf '%s\n' ' "metadata": {' >> "$prompt_file"
100
+ printf '%s\n' ' "security": ["list of security requirements"],' >> "$prompt_file"
101
+ printf '%s\n' ' "scaling": ["list of scaling concerns"],' >> "$prompt_file"
102
+ printf '%s\n' ' "relatedFeatures": ["list of related features"],' >> "$prompt_file"
103
+ printf '%s\n' ' "estimatedStories": 5,' >> "$prompt_file"
104
+ printf '%s\n' ' "complexity": "low|medium|high"' >> "$prompt_file"
105
+ printf '%s\n' ' },' >> "$prompt_file"
106
+ printf '%s\n' ' "scalability": {' >> "$prompt_file"
107
+ printf '%s\n' ' "expectedScale": "100s | 1000s | 10000s+ users",' >> "$prompt_file"
108
+ printf '%s\n' ' "pagination": "cursor | offset | none",' >> "$prompt_file"
109
+ printf '%s\n' ' "caching": "none | in-memory | redis",' >> "$prompt_file"
110
+ printf '%s\n' ' "rateLimiting": "if needed, requests per minute"' >> "$prompt_file"
111
+ printf '%s\n' ' },' >> "$prompt_file"
112
+ printf '%s\n' ' "architecture": {' >> "$prompt_file"
113
+ printf '%s\n' ' "directories": {' >> "$prompt_file"
114
+ printf '%s\n' ' "components": "src/components/{feature}/",' >> "$prompt_file"
115
+ printf '%s\n' ' "api": "src/api/",' >> "$prompt_file"
116
+ printf '%s\n' ' "types": "src/types/",' >> "$prompt_file"
117
+ printf '%s\n' ' "scripts": "scripts/",' >> "$prompt_file"
118
+ printf '%s\n' ' "docs": "docs/"' >> "$prompt_file"
119
+ printf '%s\n' ' },' >> "$prompt_file"
120
+ printf '%s\n' ' "patterns": {' >> "$prompt_file"
121
+ printf '%s\n' ' "reuse": ["existing components/utils to use"],' >> "$prompt_file"
122
+ printf '%s\n' ' "follow": ["existing patterns to match"]' >> "$prompt_file"
123
+ printf '%s\n' ' },' >> "$prompt_file"
124
+ printf '%s\n' ' "principles": {' >> "$prompt_file"
125
+ printf '%s\n' ' "maxFileLines": 300,' >> "$prompt_file"
126
+ printf '%s\n' ' "singleResponsibility": true' >> "$prompt_file"
127
+ printf '%s\n' ' },' >> "$prompt_file"
128
+ printf '%s\n' ' "doNotCreate": ["things that already exist"]' >> "$prompt_file"
129
+ printf '%s\n' ' },' >> "$prompt_file"
130
+ printf '%s\n' ' "stories": [' >> "$prompt_file"
131
+ printf '%s\n' ' {' >> "$prompt_file"
132
+ printf '%s\n' ' "id": "TASK-001",' >> "$prompt_file"
133
+ printf '%s\n' ' "title": "Story title",' >> "$prompt_file"
134
+ printf '%s\n' ' "passes": false,' >> "$prompt_file"
135
+ printf '%s\n' " \"testUrl\": \"${test_url_base}/path/to/test\"," >> "$prompt_file"
136
+ printf '%s\n' ' "files": {' >> "$prompt_file"
137
+ printf '%s\n' ' "create": ["paths to new files"],' >> "$prompt_file"
138
+ printf '%s\n' ' "modify": ["paths to existing files"],' >> "$prompt_file"
139
+ printf '%s\n' ' "reuse": ["paths to files to import from"]' >> "$prompt_file"
140
+ printf '%s\n' ' },' >> "$prompt_file"
141
+ printf '%s\n' ' "acceptanceCriteria": ["AC 1", "AC 2"],' >> "$prompt_file"
142
+ printf '%s\n' ' "errorHandling": ["what happens on failure"],' >> "$prompt_file"
143
+ printf '%s\n' ' "testSteps": ["executable shell commands only"],' >> "$prompt_file"
144
+ printf '%s\n' ' "notes": ""' >> "$prompt_file"
145
+ printf '%s\n' ' }' >> "$prompt_file"
146
+ printf '%s\n' ' ]' >> "$prompt_file"
147
+ printf '%s\n' '}' >> "$prompt_file"
148
+ printf '\n%s\n' "## Test Steps - CRITICAL" >> "$prompt_file"
149
+ printf '\n%s\n' "testSteps MUST be executable shell commands. Ralph runs them with bash." >> "$prompt_file"
150
+ printf '\n%s\n' "GOOD: curl -s http://localhost:3000/api/health | grep ok" >> "$prompt_file"
151
+ printf '%s\n' "GOOD: test -f src/components/Button.tsx" >> "$prompt_file"
152
+ printf '%s\n' "GOOD: grep -q 'export function' src/api/users.ts" >> "$prompt_file"
153
+ printf '%s\n' "GOOD: cd frontend && npx tsc --noEmit" >> "$prompt_file"
154
+ printf '%s\n' "GOOD: npx playwright test tests/e2e/dashboard.spec.ts" >> "$prompt_file"
155
+ printf '%s\n' "GOOD: npx playwright test --grep 'login flow'" >> "$prompt_file"
156
+ printf '\n%s\n' "For UI/visual checks, use Playwright tests that can verify:" >> "$prompt_file"
157
+ printf '%s\n' "- Element visibility and positioning" >> "$prompt_file"
158
+ printf '%s\n' "- Console errors (no errors in DevTools)" >> "$prompt_file"
159
+ printf '%s\n' "- Network requests completing" >> "$prompt_file"
160
+ printf '%s\n' "- Accessibility (axe-core)" >> "$prompt_file"
161
+ printf '\n%s\n' "BAD: Visit the page (not a command)" >> "$prompt_file"
162
+ printf '%s\n' "BAD: User can see the form (not a command)" >> "$prompt_file"
163
+ printf '%s\n' "BAD: Click submit button (not a command)" >> "$prompt_file"
164
+ printf '%s\n' "BAD: Check DevTools for errors (not a command)" >> "$prompt_file"
165
+ printf '\n%s\n' "If something can't be automated, put it in acceptanceCriteria instead." >> "$prompt_file"
166
+ printf '\n%s\n' "## Context Size Limits" >> "$prompt_file"
167
+ printf '\n%s\n' "Each story must be completable in ONE Claude session without context overflow:" >> "$prompt_file"
168
+ printf '%s\n' "- Max ~1000 tokens for story description (title + criteria + error handling)" >> "$prompt_file"
169
+ printf '%s\n' "- Max 3-4 files created or modified per story" >> "$prompt_file"
170
+ printf '%s\n' "- If a story feels too big, SPLIT IT" >> "$prompt_file"
171
+ printf '\n%s\n' "## UI Stories Must Include Browser Verification" >> "$prompt_file"
172
+ printf '\n%s\n' "For frontend stories, acceptanceCriteria MUST include:" >> "$prompt_file"
173
+ printf '%s\n' '- "Page loads without console errors"' >> "$prompt_file"
174
+ printf '%s\n' '- "Required elements render" (specify which: header, form, button)' >> "$prompt_file"
175
+ printf '%s\n' '- "Works on mobile viewport (375px)"' >> "$prompt_file"
176
+ printf '\n%s\n' "These get verified by Playwright automatically." >> "$prompt_file"
177
+ printf '\n%s\n' "## Notes Field" >> "$prompt_file"
178
+ printf '\n%s\n' 'The "notes" field starts empty. Claude fills it as it works:' >> "$prompt_file"
179
+ printf '%s\n' "- Files created/modified" >> "$prompt_file"
180
+ printf '%s\n' "- Key decisions made" >> "$prompt_file"
181
+ printf '%s\n' "- Context for the next story" >> "$prompt_file"
182
+ printf '\n%s\n' "## Context Rot Check" >> "$prompt_file"
183
+ printf '\n%s\n' "Before finalizing, validate:" >> "$prompt_file"
184
+ printf '%s\n' "- If > 10 stories, recommend splitting into phases" >> "$prompt_file"
185
+ printf '%s\n' '- If complexity is "high" AND > 5 stories, MUST split' >> "$prompt_file"
186
+ printf '%s\n' "- Each story should be completable in one Claude session (~10 min)" >> "$prompt_file"
187
+ printf '\n%s\n' "If splitting is needed, generate ONLY phase 1 and note deferred items." >> "$prompt_file"
188
+ printf '\n%s\n' "## Output" >> "$prompt_file"
189
+ printf '\n%s\n' "After questions are answered, output the prd.json content between these exact markers:" >> "$prompt_file"
190
+ printf '%s\n' "--- BEGIN PRD.JSON ---" >> "$prompt_file"
191
+ printf '%s\n' "{the json here}" >> "$prompt_file"
192
+ printf '%s\n' "--- END PRD.JSON ---" >> "$prompt_file"
193
+
194
+ # Run Claude interactively (not -p mode so user can answer questions)
195
+ local output_file
196
+ output_file=$(create_temp_file ".txt") || return 1
197
+
198
+ claude < "$prompt_file" | tee "$output_file"
199
+
200
+ # Save output for --accept command (store in RALPH_DIR for security)
201
+ mkdir -p "$RALPH_DIR"
202
+ cp "$output_file" "$RALPH_DIR/prd_output.txt"
203
+
204
+ rm -f "$prompt_file" "$output_file"
205
+
206
+ echo ""
207
+ echo "---"
208
+ echo ""
209
+ print_info "PRD generation complete."
210
+ echo ""
211
+ echo "To save the PRD, run: ralph prd --accept"
212
+ echo "Or manually copy the JSON to .ralph/prd.json"
213
+ }
214
+
215
+ # Extract PRD from Claude output and save
216
+ ralph_prd_accept() {
217
+ local output_file="$RALPH_DIR/prd_output.txt"
218
+
219
+ if [[ ! -f "$output_file" ]]; then
220
+ print_error "No PRD output found. Run 'ralph prd' first."
221
+ return 1
222
+ fi
223
+
224
+ # Extract JSON between markers
225
+ local prd_json
226
+ prd_json=$(sed -n '/--- BEGIN PRD.JSON ---/,/--- END PRD.JSON ---/p' "$output_file" | sed '1d;$d')
227
+
228
+ if [[ -z "$prd_json" ]]; then
229
+ print_error "Could not find PRD JSON in output"
230
+ echo ""
231
+ echo "The output should contain JSON between markers:"
232
+ echo " --- BEGIN PRD.JSON ---"
233
+ echo " {...}"
234
+ echo " --- END PRD.JSON ---"
235
+ return 1
236
+ fi
237
+
238
+ # Validate JSON syntax
239
+ if ! echo "$prd_json" | jq . > /dev/null 2>&1; then
240
+ print_error "Invalid JSON in PRD"
241
+ echo ""
242
+ echo "JSON parsing error. Content:"
243
+ echo "$prd_json" | head -20
244
+ return 1
245
+ fi
246
+
247
+ # Validate required fields exist
248
+ if ! echo "$prd_json" | jq -e '.feature.name and .stories' > /dev/null 2>&1; then
249
+ print_error "PRD JSON missing required fields (feature.name or stories)"
250
+ echo ""
251
+ echo "Ensure the PRD has this structure:"
252
+ echo ' { "feature": { "name": "..." }, "stories": [...] }'
253
+ return 1
254
+ fi
255
+
256
+ # Ensure .ralph directory exists
257
+ mkdir -p "$RALPH_DIR"
258
+
259
+ # Normalize the PRD before saving:
260
+ # - Ensure all stories have passes: false
261
+ # - Ensure all stories have id and title
262
+ # - Set status to pending
263
+ prd_json=$(echo "$prd_json" | jq '
264
+ .feature.status = "pending" |
265
+ .stories = [.stories[] | . + {passes: (.passes // false)}]
266
+ ')
267
+
268
+ # Save
269
+ echo "$prd_json" > "$RALPH_DIR/prd.json"
270
+
271
+ # Run full validation
272
+ if ! validate_prd "$RALPH_DIR/prd.json"; then
273
+ rm -f "$RALPH_DIR/prd.json"
274
+ return 1
275
+ fi
276
+
277
+ # Show summary
278
+ local name stories complexity
279
+ name=$(echo "$prd_json" | jq -r '.feature.name')
280
+ stories=$(echo "$prd_json" | jq '.stories | length')
281
+ complexity=$(echo "$prd_json" | jq -r '.metadata.complexity // "unknown"')
282
+
283
+ print_success "PRD saved to $RALPH_DIR/prd.json"
284
+ echo ""
285
+ echo "Feature: $name"
286
+ echo "Stories: $stories"
287
+ echo "Complexity: $complexity"
288
+ echo ""
289
+ echo "Next: Run ralph run to start the autonomous loop"
290
+
291
+ # Clean up
292
+ rm -f "$RALPH_DIR/prd_output.txt"
293
+
294
+ return 0
295
+ }