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,155 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Check that FastAPI endpoints have Pydantic response models defined.
4
+ Ensures Swagger/OpenAPI docs show proper response schemas.
5
+
6
+ Usage: python check-fastapi-responses.py [directory]
7
+
8
+ Exit codes:
9
+ 0 - All endpoints have response models
10
+ 1 - Some endpoints missing response models
11
+ """
12
+
13
+ import ast
14
+ import sys
15
+ from pathlib import Path
16
+ from typing import NamedTuple
17
+
18
+
19
+ class EndpointIssue(NamedTuple):
20
+ file: str
21
+ line: int
22
+ method: str
23
+ path: str
24
+ issue: str
25
+
26
+
27
+ def check_file(filepath: Path) -> list[EndpointIssue]:
28
+ """Check a single Python file for FastAPI endpoints without response models."""
29
+ issues = []
30
+
31
+ try:
32
+ content = filepath.read_text()
33
+ tree = ast.parse(content)
34
+ except (SyntaxError, UnicodeDecodeError):
35
+ return issues
36
+
37
+ # HTTP methods that should have response models
38
+ http_methods = {'get', 'post', 'put', 'patch', 'delete'}
39
+
40
+ for node in ast.walk(tree):
41
+ if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
42
+ continue
43
+
44
+ # Check decorators for FastAPI route decorators
45
+ for decorator in node.decorator_list:
46
+ # Handle @router.get("/path") or @app.get("/path")
47
+ if isinstance(decorator, ast.Call):
48
+ if isinstance(decorator.func, ast.Attribute):
49
+ method = decorator.func.attr
50
+ if method not in http_methods:
51
+ continue
52
+
53
+ # Get the path from first argument
54
+ path = "unknown"
55
+ if decorator.args and isinstance(decorator.args[0], ast.Constant):
56
+ path = decorator.args[0].value
57
+
58
+ # Check for response_model in keyword arguments
59
+ has_response_model = any(
60
+ kw.arg == 'response_model'
61
+ for kw in decorator.keywords
62
+ )
63
+
64
+ # Check for return type annotation
65
+ has_return_annotation = node.returns is not None
66
+
67
+ # Check if return annotation is None or missing
68
+ is_none_return = (
69
+ isinstance(node.returns, ast.Constant) and
70
+ node.returns.value is None
71
+ )
72
+
73
+ if not has_response_model and (not has_return_annotation or is_none_return):
74
+ # Skip certain common patterns that don't need response models
75
+ if method == 'delete' and path.endswith('}'):
76
+ continue # DELETE /items/{id} often returns nothing
77
+
78
+ issues.append(EndpointIssue(
79
+ file=str(filepath),
80
+ line=node.lineno,
81
+ method=method.upper(),
82
+ path=path,
83
+ issue="Missing response_model or return type annotation"
84
+ ))
85
+
86
+ return issues
87
+
88
+
89
+ def check_directory(directory: Path) -> list[EndpointIssue]:
90
+ """Check all Python files in directory for FastAPI response model issues."""
91
+ all_issues = []
92
+
93
+ # Common patterns for API files
94
+ patterns = [
95
+ '**/router*.py',
96
+ '**/routes*.py',
97
+ '**/api*.py',
98
+ '**/endpoints*.py',
99
+ '**/views*.py',
100
+ ]
101
+
102
+ checked_files = set()
103
+
104
+ for pattern in patterns:
105
+ for filepath in directory.glob(pattern):
106
+ if filepath in checked_files:
107
+ continue
108
+ checked_files.add(filepath)
109
+ all_issues.extend(check_file(filepath))
110
+
111
+ # Also check any file with FastAPI imports
112
+ for filepath in directory.rglob('*.py'):
113
+ if filepath in checked_files:
114
+ continue
115
+ try:
116
+ content = filepath.read_text()
117
+ if 'from fastapi' in content or 'import fastapi' in content:
118
+ if 'APIRouter' in content or '@app.' in content or '@router.' in content:
119
+ all_issues.extend(check_file(filepath))
120
+ checked_files.add(filepath)
121
+ except (UnicodeDecodeError, PermissionError):
122
+ continue
123
+
124
+ return all_issues
125
+
126
+
127
+ def main():
128
+ directory = Path(sys.argv[1]) if len(sys.argv) > 1 else Path('.')
129
+
130
+ if not directory.exists():
131
+ print(f"Error: Directory not found: {directory}")
132
+ sys.exit(1)
133
+
134
+ issues = check_directory(directory)
135
+
136
+ if not issues:
137
+ print("✓ All FastAPI endpoints have response models defined")
138
+ sys.exit(0)
139
+
140
+ print(f"Found {len(issues)} endpoint(s) without response models:\n")
141
+
142
+ for issue in sorted(issues, key=lambda x: (x.file, x.line)):
143
+ print(f" {issue.file}:{issue.line}")
144
+ print(f" {issue.method} {issue.path}")
145
+ print(f" → {issue.issue}\n")
146
+
147
+ print("Fix: Add response_model parameter or return type annotation:")
148
+ print(' @router.get("/items", response_model=list[ItemSchema])')
149
+ print(" async def get_items() -> list[ItemSchema]:")
150
+
151
+ sys.exit(1)
152
+
153
+
154
+ if __name__ == '__main__':
155
+ main()
@@ -0,0 +1,72 @@
1
+ {
2
+ "_comment": "Copy this 'hooks' section into your ~/.claude/settings.json or .claude/settings.json",
3
+ "_instructions": "Replace VIBE_PATH with the path to your thrivekit installation",
4
+ "hooks": {
5
+ "PreToolUse": [
6
+ {
7
+ "matcher": "Edit|Write",
8
+ "hooks": [
9
+ {
10
+ "type": "command",
11
+ "command": "VIBE_PATH/ralph/hooks/protect-prd.sh",
12
+ "timeout": 5
13
+ }
14
+ ]
15
+ }
16
+ ],
17
+ "PostToolUse": [
18
+ {
19
+ "matcher": "Edit|Write",
20
+ "hooks": [
21
+ {
22
+ "type": "command",
23
+ "command": "VIBE_PATH/ralph/hooks/warn-debug.sh",
24
+ "timeout": 5
25
+ },
26
+ {
27
+ "type": "command",
28
+ "command": "VIBE_PATH/ralph/hooks/warn-secrets.sh",
29
+ "timeout": 5
30
+ },
31
+ {
32
+ "type": "command",
33
+ "command": "VIBE_PATH/ralph/hooks/warn-urls.sh",
34
+ "timeout": 5
35
+ }
36
+ ]
37
+ },
38
+ {
39
+ "matcher": "*",
40
+ "hooks": [
41
+ {
42
+ "type": "command",
43
+ "command": "VIBE_PATH/ralph/hooks/log-tools.sh",
44
+ "timeout": 3
45
+ }
46
+ ]
47
+ }
48
+ ],
49
+ "SessionStart": [
50
+ {
51
+ "hooks": [
52
+ {
53
+ "type": "command",
54
+ "command": "VIBE_PATH/ralph/hooks/inject-context.sh",
55
+ "timeout": 5
56
+ }
57
+ ]
58
+ }
59
+ ],
60
+ "Stop": [
61
+ {
62
+ "hooks": [
63
+ {
64
+ "type": "command",
65
+ "command": "VIBE_PATH/ralph/hooks/save-learnings.sh",
66
+ "timeout": 10
67
+ }
68
+ ]
69
+ }
70
+ ]
71
+ }
72
+ }
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env bash
2
+ # inject-context.sh - Inject signs and recent progress at session start
3
+ # Hook: SessionStart
4
+
5
+ set -euo pipefail
6
+
7
+ INPUT=$(cat)
8
+ CWD=$(echo "$INPUT" | jq -r '.cwd // "."')
9
+ RALPH_DIR="$CWD/.ralph"
10
+
11
+ CONTEXT=""
12
+
13
+ # Inject signs (learned patterns)
14
+ if [[ -f "$RALPH_DIR/signs.json" ]]; then
15
+ SIGNS=$(jq -r '.signs[]? | "- [\(.category)] \(.pattern)"' "$RALPH_DIR/signs.json" 2>/dev/null | head -20)
16
+ if [[ -n "$SIGNS" ]]; then
17
+ CONTEXT="## Learned Patterns (Signs)\nApply these lessons:\n$SIGNS\n\n"
18
+ fi
19
+ fi
20
+
21
+ # Inject recent progress
22
+ if [[ -f "$RALPH_DIR/progress.txt" ]]; then
23
+ PROGRESS=$(tail -10 "$RALPH_DIR/progress.txt" 2>/dev/null)
24
+ if [[ -n "$PROGRESS" ]]; then
25
+ CONTEXT="${CONTEXT}## Recent Progress\n$PROGRESS\n\n"
26
+ fi
27
+ fi
28
+
29
+ # Inject last failure context if exists
30
+ if [[ -f "$RALPH_DIR/last_failure.txt" ]]; then
31
+ FAILURE=$(cat "$RALPH_DIR/last_failure.txt" | head -50)
32
+ CONTEXT="${CONTEXT}## Previous Failure (FIX THIS)\n$FAILURE\n"
33
+ fi
34
+
35
+ if [[ -n "$CONTEXT" ]]; then
36
+ jq -n --arg ctx "$CONTEXT" '{
37
+ "continue": true,
38
+ "hookSpecificOutput": {
39
+ "additionalContext": $ctx
40
+ }
41
+ }'
42
+ else
43
+ echo '{"continue": true}'
44
+ fi
@@ -0,0 +1,207 @@
1
+ #!/usr/bin/env bash
2
+ # install.sh - Install Ralph hooks into Claude Code settings
3
+ #
4
+ # Usage: ./install.sh [--global] [--force]
5
+ # --global: Install to ~/.claude/settings.json (applies to all projects)
6
+ # --force: Reinstall even if hooks already configured
7
+ # Default: Install to .claude/settings.json (project-level)
8
+
9
+ set -euo pipefail
10
+
11
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+
13
+ # Parse args
14
+ SETTINGS_FILE=".claude/settings.json"
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
27
+
28
+ # Colors
29
+ RED='\033[0;31m'
30
+ GREEN='\033[0;32m'
31
+ YELLOW='\033[1;33m'
32
+ NC='\033[0m'
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
+
77
+ echo "Installing Ralph hooks..."
78
+ echo " Hooks path: $SCRIPT_DIR"
79
+ echo " Settings: $SETTINGS_FILE"
80
+ echo ""
81
+
82
+ # Ensure settings directory exists
83
+ mkdir -p "$(dirname "$SETTINGS_FILE")"
84
+
85
+ # Create settings file if it doesn't exist
86
+ if [[ ! -f "$SETTINGS_FILE" ]]; then
87
+ echo '{}' > "$SETTINGS_FILE"
88
+ fi
89
+
90
+ # Check if hooks already configured and valid
91
+ if [[ "$FORCE" != "true" ]] && jq -e '.hooks' "$SETTINGS_FILE" > /dev/null 2>&1; then
92
+ session_hook=$(jq -r '.hooks.SessionStart[0].hooks[0].command // empty' "$SETTINGS_FILE" 2>/dev/null)
93
+ stop_hook=$(jq -r '.hooks.Stop[0].hooks[0].command // empty' "$SETTINGS_FILE" 2>/dev/null)
94
+
95
+ # All hooks must exist AND point to current script directory
96
+ if [[ -n "$session_hook" && -x "$session_hook" && -n "$stop_hook" && -x "$stop_hook" ]]; then
97
+ if [[ "$session_hook" == "$SCRIPT_DIR/"* && "$stop_hook" == "$SCRIPT_DIR/"* ]]; then
98
+ echo -e "${YELLOW}Hooks already configured and valid.${NC}"
99
+ echo "Use --force to reinstall."
100
+ exit 0
101
+ else
102
+ echo -e "${YELLOW}Hooks point to different location, updating...${NC}"
103
+ echo ""
104
+ fi
105
+ else
106
+ echo -e "${YELLOW}Existing hooks are invalid, reinstalling...${NC}"
107
+ echo ""
108
+ fi
109
+ fi
110
+
111
+ # Build hooks config with actual path
112
+ HOOKS_CONFIG=$(cat <<EOF
113
+ {
114
+ "PreToolUse": [
115
+ {
116
+ "matcher": "Edit|Write",
117
+ "hooks": [
118
+ {
119
+ "type": "command",
120
+ "command": "$SCRIPT_DIR/protect-prd.sh",
121
+ "timeout": 5
122
+ }
123
+ ]
124
+ }
125
+ ],
126
+ "PostToolUse": [
127
+ {
128
+ "matcher": "Edit|Write",
129
+ "hooks": [
130
+ {
131
+ "type": "command",
132
+ "command": "$SCRIPT_DIR/warn-debug.sh",
133
+ "timeout": 5
134
+ },
135
+ {
136
+ "type": "command",
137
+ "command": "$SCRIPT_DIR/warn-secrets.sh",
138
+ "timeout": 5
139
+ },
140
+ {
141
+ "type": "command",
142
+ "command": "$SCRIPT_DIR/warn-urls.sh",
143
+ "timeout": 5
144
+ },
145
+ {
146
+ "type": "command",
147
+ "command": "$SCRIPT_DIR/warn-empty-catch.sh",
148
+ "timeout": 5
149
+ }
150
+ ]
151
+ },
152
+ {
153
+ "matcher": "*",
154
+ "hooks": [
155
+ {
156
+ "type": "command",
157
+ "command": "$SCRIPT_DIR/log-tools.sh",
158
+ "timeout": 3
159
+ }
160
+ ]
161
+ }
162
+ ],
163
+ "SessionStart": [
164
+ {
165
+ "hooks": [
166
+ {
167
+ "type": "command",
168
+ "command": "$SCRIPT_DIR/inject-context.sh",
169
+ "timeout": 5
170
+ }
171
+ ]
172
+ }
173
+ ],
174
+ "Stop": [
175
+ {
176
+ "hooks": [
177
+ {
178
+ "type": "command",
179
+ "command": "$SCRIPT_DIR/save-learnings.sh",
180
+ "timeout": 10
181
+ }
182
+ ]
183
+ }
184
+ ]
185
+ }
186
+ EOF
187
+ )
188
+
189
+ # Merge hooks into settings
190
+ CURRENT_SETTINGS=$(cat "$SETTINGS_FILE")
191
+ MERGED=$(echo "$CURRENT_SETTINGS" | jq --argjson hooks "$HOOKS_CONFIG" '.hooks = $hooks')
192
+
193
+ echo "$MERGED" > "$SETTINGS_FILE"
194
+
195
+ echo -e "${GREEN}✓ Hooks installed successfully!${NC}"
196
+ echo ""
197
+ echo "Hooks enabled:"
198
+ echo " • protect-prd.sh - Blocks edits to prd.json"
199
+ echo " • warn-debug.sh - Warns about console.log/debugger"
200
+ echo " • warn-secrets.sh - Warns about hardcoded secrets/API keys"
201
+ echo " • warn-urls.sh - Warns about hardcoded localhost URLs"
202
+ echo " • warn-empty-catch.sh - Warns about empty catch blocks"
203
+ echo " • inject-context.sh - Loads signs & progress at session start"
204
+ echo " • save-learnings.sh - Extracts learnings at session end"
205
+ echo " • log-tools.sh - Logs tool usage to .ralph/tool-log.txt"
206
+ echo ""
207
+ echo -e "${YELLOW}Note:${NC} Restart Claude Code for hooks to take effect."
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env bash
2
+ # log-tools.sh - Log tool usage for debugging and analysis
3
+ # Hook: PostToolUse matcher: "*"
4
+
5
+ set -euo pipefail
6
+
7
+ INPUT=$(cat)
8
+ CWD=$(echo "$INPUT" | jq -r '.cwd // "."')
9
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "unknown"')
10
+ RALPH_DIR="$CWD/.ralph"
11
+
12
+ # Only log if .ralph exists (we're in a Ralph session)
13
+ if [[ -d "$RALPH_DIR" ]]; then
14
+ TIMESTAMP=$(date '+%H:%M:%S')
15
+
16
+ # Extract relevant info based on tool
17
+ case "$TOOL_NAME" in
18
+ Read)
19
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""' | sed "s|$CWD/||")
20
+ echo "[$TIMESTAMP] READ: $FILE" >> "$RALPH_DIR/tool-log.txt"
21
+ ;;
22
+ Write)
23
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""' | sed "s|$CWD/||")
24
+ echo "[$TIMESTAMP] WRITE: $FILE" >> "$RALPH_DIR/tool-log.txt"
25
+ ;;
26
+ Edit)
27
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""' | sed "s|$CWD/||")
28
+ echo "[$TIMESTAMP] EDIT: $FILE" >> "$RALPH_DIR/tool-log.txt"
29
+ ;;
30
+ Bash)
31
+ CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""' | head -c 80)
32
+ echo "[$TIMESTAMP] BASH: $CMD" >> "$RALPH_DIR/tool-log.txt"
33
+ ;;
34
+ Grep|Glob)
35
+ PATTERN=$(echo "$INPUT" | jq -r '.tool_input.pattern // ""')
36
+ echo "[$TIMESTAMP] $TOOL_NAME: $PATTERN" >> "$RALPH_DIR/tool-log.txt"
37
+ ;;
38
+ Task)
39
+ DESC=$(echo "$INPUT" | jq -r '.tool_input.description // ""')
40
+ echo "[$TIMESTAMP] TASK: $DESC" >> "$RALPH_DIR/tool-log.txt"
41
+ ;;
42
+ esac
43
+ fi
44
+
45
+ echo '{"continue": true}'
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env bash
2
+ # protect-prd.sh - Protect prd.json from accidental edits
3
+ # Hook: PreToolUse matcher: "Edit|Write"
4
+ #
5
+ # Allows: /prd, /idea commands (they create .prd-edit-allowed marker)
6
+ # Blocks: Accidental edits during normal coding
7
+
8
+ set -euo pipefail
9
+
10
+ INPUT=$(cat)
11
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
12
+
13
+ # Check if editing prd.json
14
+ if [[ "$FILE_PATH" == *"prd.json"* ]]; then
15
+ # Allow if /prd or /idea set the bypass marker
16
+ if [[ -f ".ralph/.prd-edit-allowed" ]]; then
17
+ rm -f ".ralph/.prd-edit-allowed" # One-time use
18
+ echo '{"continue": true}'
19
+ exit 0
20
+ fi
21
+
22
+ echo "BLOCKED: prd.json is managed by Ralph. Use /prd or /idea to add stories." >&2
23
+ exit 2 # Exit code 2 = blocking error
24
+ fi
25
+
26
+ # Allow all other edits
27
+ echo '{"continue": true}'
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ # save-learnings.sh - Extract potential learnings from session transcript
3
+ # Hook: Stop
4
+ #
5
+ # This hook analyzes the transcript for patterns that might be worth saving as signs.
6
+ # It logs suggestions to .ralph/suggested-signs.txt for human review.
7
+
8
+ set -euo pipefail
9
+
10
+ INPUT=$(cat)
11
+ CWD=$(echo "$INPUT" | jq -r '.cwd // "."')
12
+ TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // ""')
13
+ RALPH_DIR="$CWD/.ralph"
14
+
15
+ # Ensure .ralph directory exists
16
+ mkdir -p "$RALPH_DIR"
17
+
18
+ # Log session end
19
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] Session ended" >> "$RALPH_DIR/progress.txt"
20
+
21
+ # If transcript exists, analyze for patterns
22
+ if [[ -f "$TRANSCRIPT_PATH" ]]; then
23
+ # Look for retry patterns (same error appearing multiple times)
24
+ # Look for "I learned" or "the issue was" phrases
25
+ # This is a simple heuristic - can be enhanced
26
+
27
+ PATTERNS=$(grep -E "(the issue was|I learned|the problem was|fixed by|solution was)" "$TRANSCRIPT_PATH" 2>/dev/null | tail -5 || true)
28
+
29
+ if [[ -n "$PATTERNS" ]]; then
30
+ echo "" >> "$RALPH_DIR/suggested-signs.txt"
31
+ echo "=== Session $(date '+%Y-%m-%d %H:%M:%S') ===" >> "$RALPH_DIR/suggested-signs.txt"
32
+ echo "$PATTERNS" >> "$RALPH_DIR/suggested-signs.txt"
33
+ fi
34
+ fi
35
+
36
+ echo '{"continue": true}'
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env bash
2
+ # warn-debug.sh - Warn about debug statements in written code
3
+ # Hook: PostToolUse matcher: "Edit|Write"
4
+
5
+ set -euo pipefail
6
+
7
+ INPUT=$(cat)
8
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
9
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
10
+
11
+ # Only check code files
12
+ case "$FILE_PATH" in
13
+ *.ts|*.tsx|*.js|*.jsx|*.py|*.go|*.rs)
14
+ ;;
15
+ *)
16
+ echo '{"continue": true}'
17
+ exit 0
18
+ ;;
19
+ esac
20
+
21
+ # Get the content that was written
22
+ NEW_CONTENT=""
23
+ if [[ "$TOOL_NAME" == "Write" ]]; then
24
+ NEW_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // ""')
25
+ elif [[ "$TOOL_NAME" == "Edit" ]]; then
26
+ NEW_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // ""')
27
+ fi
28
+
29
+ # Check for debug patterns
30
+ WARNINGS=""
31
+
32
+ if echo "$NEW_CONTENT" | grep -qE 'console\.(log|debug|info|warn|error)\s*\('; then
33
+ WARNINGS="⚠️ Debug statement detected: console.log/debug. Remove before commit."
34
+ fi
35
+
36
+ if echo "$NEW_CONTENT" | grep -qE '^\s*debugger\s*;?\s*$'; then
37
+ WARNINGS="${WARNINGS}\n⚠️ Debugger statement detected. Remove before commit."
38
+ fi
39
+
40
+ if echo "$NEW_CONTENT" | grep -qE '^\s*print\s*\('; then
41
+ WARNINGS="${WARNINGS}\n⚠️ Print statement detected. Remove before commit."
42
+ fi
43
+
44
+ # Output warning as additional context (non-blocking)
45
+ if [[ -n "$WARNINGS" ]]; then
46
+ jq -n --arg warn "$WARNINGS" '{
47
+ "continue": true,
48
+ "hookSpecificOutput": {
49
+ "additionalContext": $warn
50
+ }
51
+ }'
52
+ else
53
+ echo '{"continue": true}'
54
+ fi