@claude-code-mastery/starter-kit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/.claude/.starter-kit/profiles/clean.md +113 -0
  2. package/.claude/.starter-kit/profiles/go.md +458 -0
  3. package/.claude/.starter-kit/profiles/node.md +429 -0
  4. package/.claude/.starter-kit/profiles/python.md +475 -0
  5. package/.claude/.starter-kit/shared/analytics-rybbit.md +55 -0
  6. package/.claude/.starter-kit/shared/claude-md-base.md +93 -0
  7. package/.claude/.starter-kit/shared/deployment-dokploy.md +158 -0
  8. package/.claude/.starter-kit/shared/feature-manifest.md +43 -0
  9. package/.claude/.starter-kit/shared/mcp-and-pooler.md +38 -0
  10. package/.claude/.starter-kit/shared/mongo-setup.md +20 -0
  11. package/.claude/.starter-kit/shared/profile-config.md +65 -0
  12. package/.claude/.starter-kit/shared/seo.md +113 -0
  13. package/.claude/.starter-kit/shared/sql-setup.md +37 -0
  14. package/.claude/commands/add-feature.md +349 -0
  15. package/.claude/commands/add-project-setup.md +156 -0
  16. package/.claude/commands/architecture.md +27 -0
  17. package/.claude/commands/commit.md +61 -0
  18. package/.claude/commands/convert-project-to-starter-kit.md +508 -0
  19. package/.claude/commands/create-api.md +385 -0
  20. package/.claude/commands/create-e2e.md +230 -0
  21. package/.claude/commands/diagram.md +301 -0
  22. package/.claude/commands/help.md +120 -0
  23. package/.claude/commands/install-global.md +145 -0
  24. package/.claude/commands/new-project.md +244 -0
  25. package/.claude/commands/optimize-docker.md +352 -0
  26. package/.claude/commands/progress.md +61 -0
  27. package/.claude/commands/projects-created.md +79 -0
  28. package/.claude/commands/quickstart.md +105 -0
  29. package/.claude/commands/refactor.md +267 -0
  30. package/.claude/commands/remove-project.md +95 -0
  31. package/.claude/commands/review.md +59 -0
  32. package/.claude/commands/security-check.md +77 -0
  33. package/.claude/commands/set-project-profile-default.md +79 -0
  34. package/.claude/commands/setup.md +337 -0
  35. package/.claude/commands/show-user-guide.md +58 -0
  36. package/.claude/commands/starter-kit.md +90 -0
  37. package/.claude/commands/test-plan.md +118 -0
  38. package/.claude/commands/update-project.md +413 -0
  39. package/.claude/commands/what-is-my-ai-doing.md +42 -0
  40. package/.claude/commands/worktree.md +124 -0
  41. package/.claude/hooks/block-dangerous-bash.py +55 -0
  42. package/.claude/hooks/check-branch.sh +116 -0
  43. package/.claude/hooks/check-e2e.sh +71 -0
  44. package/.claude/hooks/check-env-sync.sh +41 -0
  45. package/.claude/hooks/check-file-length.py +47 -0
  46. package/.claude/hooks/check-ports.sh +59 -0
  47. package/.claude/hooks/check-rulecatch.sh +33 -0
  48. package/.claude/hooks/check-rybbit.sh +63 -0
  49. package/.claude/hooks/lint-on-save.sh +59 -0
  50. package/.claude/hooks/verify-no-secrets.sh +80 -0
  51. package/.claude/settings.json +34 -0
  52. package/.claude/skills/api-conventions/SKILL.md +34 -0
  53. package/.claude/skills/code-review/SKILL.md +87 -0
  54. package/.claude/skills/code-review/references/mongodb-checks.md +25 -0
  55. package/.claude/skills/code-review/references/project-checks.md +38 -0
  56. package/.claude/skills/create-service/SKILL.md +222 -0
  57. package/.claude/skills/debugger/SKILL.md +39 -0
  58. package/.claude/skills/dependency-vetting/SKILL.md +46 -0
  59. package/.claude/skills/design-review/SKILL.md +50 -0
  60. package/.claude/skills/mcp-builder/SKILL.md +57 -0
  61. package/.claude/skills/mongodb-rules/SKILL.md +62 -0
  62. package/.claude/skills/terminal-tui/SKILL.md +106 -0
  63. package/.claude/skills/test-writer/SKILL.md +78 -0
  64. package/LICENSE +21 -0
  65. package/README.md +2152 -0
  66. package/bin/cli.js +205 -0
  67. package/claude-mastery-project.conf +220 -0
  68. package/global-claude-md/CLAUDE.md +212 -0
  69. package/global-claude-md/settings.json +3 -0
  70. package/package.json +81 -0
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env bash
2
+ # Branch Protection Hook — PreToolUse (Bash)
3
+ # Blocks committing directly to main/master when auto_branch is enabled.
4
+ # Exit code 2 = block operation and tell Claude why.
5
+ #
6
+ # Based on Claude Code Mastery Guides V1-V5 by TheDecipherist
7
+
8
+ INPUT=$(cat)
9
+
10
+ # Extract command from JSON — try jq first, fall back to grep/sed (no Python needed)
11
+ if command -v jq &>/dev/null; then
12
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""' 2>/dev/null)
13
+ else
14
+ # Lightweight fallback: extract "command" value from JSON via grep/sed
15
+ COMMAND=$(echo "$INPUT" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*:[[:space:]]*"//;s/"$//')
16
+ fi
17
+
18
+ if [ -z "$COMMAND" ]; then
19
+ exit 0
20
+ fi
21
+
22
+ # Only check git commit commands
23
+ if ! echo "$COMMAND" | grep -qE 'git\s+commit'; then
24
+ exit 0
25
+ fi
26
+
27
+ # Detect target directory from git -C <path> in the command
28
+ # Only match real paths (starting with / ~ . or alphanumeric), not placeholders like <dir>
29
+ TARGET_DIR=""
30
+ if echo "$COMMAND" | grep -qE 'git\s+-C\s+[/~.\w]'; then
31
+ TARGET_DIR=$(echo "$COMMAND" | sed -nE 's/.*git\s+-C\s+([^ ]+)\s+.*/\1/p' | head -1)
32
+ fi
33
+
34
+ # Also detect: cd /some/path && git commit (common cross-repo pattern)
35
+ # Extract the first cd target from commands like "cd /path && git ..."
36
+ if [ -z "$TARGET_DIR" ] && echo "$COMMAND" | grep -qE '^cd\s+[^ ]+'; then
37
+ CD_DIR=$(echo "$COMMAND" | sed -nE 's/^cd\s+([^ &;]+).*/\1/p' | head -1)
38
+ # Expand leading tilde — [ -d "~/path" ] doesn't expand tilde inside quotes
39
+ CD_DIR="${CD_DIR/#\~/$HOME}"
40
+ if [ -n "$CD_DIR" ] && [ -d "$CD_DIR" ]; then
41
+ TARGET_DIR="$CD_DIR"
42
+ fi
43
+ fi
44
+
45
+ # Resolve git dir — if git -C was used with a valid path, check THAT repo
46
+ if [ -n "$TARGET_DIR" ] && [ -d "$TARGET_DIR" ]; then
47
+ if ! git -C "$TARGET_DIR" rev-parse --is-inside-work-tree &>/dev/null; then
48
+ exit 0
49
+ fi
50
+ BRANCH=$(git -C "$TARGET_DIR" branch --show-current 2>/dev/null)
51
+ # Allow initial commits (no previous commits in the target repo)
52
+ if ! git -C "$TARGET_DIR" rev-parse HEAD &>/dev/null 2>&1; then
53
+ exit 0
54
+ fi
55
+ else
56
+ # CWD-based git check
57
+ if ! git rev-parse --is-inside-work-tree &>/dev/null; then
58
+ exit 0
59
+ fi
60
+ BRANCH=$(git branch --show-current 2>/dev/null)
61
+ # Allow initial commits (no previous commits yet)
62
+ if ! git rev-parse HEAD &>/dev/null 2>&1; then
63
+ exit 0
64
+ fi
65
+ fi
66
+
67
+ # Only care about main/master
68
+ if [ "$BRANCH" != "main" ] && [ "$BRANCH" != "master" ]; then
69
+ exit 0
70
+ fi
71
+
72
+ # Check auto_branch setting (default: true)
73
+ # Look for claude-mastery-project.conf in the target repo first, then CWD.
74
+ # If neither has the conf file, this is not a starter kit project — skip the check.
75
+ AUTO_BRANCH="true"
76
+ CONF_FOUND=false
77
+ CONF="claude-mastery-project.conf"
78
+
79
+ # Determine where to look for the conf — target dir or CWD
80
+ CONF_SEARCH_DIR=""
81
+ if [ -n "$TARGET_DIR" ] && [ -d "$TARGET_DIR" ]; then
82
+ CONF_SEARCH_DIR="$TARGET_DIR"
83
+ else
84
+ CONF_SEARCH_DIR="."
85
+ fi
86
+
87
+ if [ -f "${CONF_SEARCH_DIR}/${CONF}" ]; then
88
+ CONF_FOUND=true
89
+ SETTING=$(grep -E '^\s*auto_branch\s*=' "${CONF_SEARCH_DIR}/${CONF}" 2>/dev/null | head -1 | sed 's/.*=\s*//' | sed 's/\s*#.*//' | tr -d ' ')
90
+ if [ -n "$SETTING" ]; then
91
+ AUTO_BRANCH="$SETTING"
92
+ fi
93
+ elif [ -z "$TARGET_DIR" ] && [ -f "$CONF" ]; then
94
+ # Fallback: conf exists in CWD — only when no cross-repo target was detected
95
+ CONF_FOUND=true
96
+ SETTING=$(grep -E '^\s*auto_branch\s*=' "$CONF" 2>/dev/null | head -1 | sed 's/.*=\s*//' | sed 's/\s*#.*//' | tr -d ' ')
97
+ if [ -n "$SETTING" ]; then
98
+ AUTO_BRANCH="$SETTING"
99
+ fi
100
+ fi
101
+
102
+ # If no starter-kit conf found anywhere, this isn't a starter-kit project — allow commit
103
+ if [ "$CONF_FOUND" = false ]; then
104
+ exit 0
105
+ fi
106
+
107
+ if [ "$AUTO_BRANCH" = "true" ]; then
108
+ echo "BLOCKED: You're committing directly to '$BRANCH' with auto_branch enabled." >&2
109
+ echo "Create a feature branch first:" >&2
110
+ echo " git checkout -b feat/<feature-name>" >&2
111
+ echo " Or use: /worktree <name>" >&2
112
+ exit 2
113
+ fi
114
+
115
+ # auto_branch is explicitly false — user chose to work on main
116
+ exit 0
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env bash
2
+ # E2E Test Check Hook — PreToolUse (Bash)
3
+ # Warns before pushing to main if no real E2E tests exist.
4
+ # Exit code 2 = block operation and tell Claude why.
5
+ #
6
+ # Based on Claude Code Mastery Guides V1-V5 by TheDecipherist
7
+
8
+ INPUT=$(cat)
9
+ COMMAND=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('command',''))" 2>/dev/null)
10
+
11
+ if [ -z "$COMMAND" ]; then
12
+ exit 0
13
+ fi
14
+
15
+ # Only check git push commands
16
+ if ! echo "$COMMAND" | grep -qE 'git\s+push'; then
17
+ exit 0
18
+ fi
19
+
20
+ # Must be in a git repo
21
+ if ! git rev-parse --is-inside-work-tree &>/dev/null; then
22
+ exit 0
23
+ fi
24
+
25
+ # Determine if pushing to main/master
26
+ BRANCH=$(git branch --show-current 2>/dev/null)
27
+ PUSHING_TO_MAIN=false
28
+
29
+ # Check if command explicitly targets main/master
30
+ if echo "$COMMAND" | grep -qE 'git\s+push\s+\S+\s+(main|master)'; then
31
+ PUSHING_TO_MAIN=true
32
+ fi
33
+
34
+ # Check if current branch is main/master (push without explicit branch)
35
+ if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
36
+ if ! echo "$COMMAND" | grep -qE 'git\s+push\s+\S+\s+\S+'; then
37
+ # No explicit branch in command — pushing current branch
38
+ PUSHING_TO_MAIN=true
39
+ fi
40
+ fi
41
+
42
+ if [ "$PUSHING_TO_MAIN" = "false" ]; then
43
+ exit 0
44
+ fi
45
+
46
+ # Check for real E2E test files (excluding the example template)
47
+ E2E_DIR="tests/e2e"
48
+ if [ ! -d "$E2E_DIR" ]; then
49
+ echo "WARNING: No tests/e2e/ directory found." >&2
50
+ echo "Consider creating E2E tests: /create-e2e <feature>" >&2
51
+ exit 0
52
+ fi
53
+
54
+ # Count .spec.ts and .test.ts files, excluding the example template
55
+ REAL_TESTS=$(find "$E2E_DIR" -name "*.spec.ts" -o -name "*.test.ts" 2>/dev/null \
56
+ | grep -v "example-homepage.spec.ts" \
57
+ | wc -l | tr -d ' ')
58
+
59
+ # Also check for Go test files
60
+ GO_TESTS=$(find "$E2E_DIR" -name '*_test.go' 2>/dev/null | wc -l | tr -d ' ')
61
+ if [ "$REAL_TESTS" -eq 0 ] && [ "$GO_TESTS" -gt 0 ]; then
62
+ REAL_TESTS=$GO_TESTS
63
+ fi
64
+
65
+ if [ "$REAL_TESTS" -eq 0 ]; then
66
+ echo "WARNING: No E2E tests found (only the example template exists)." >&2
67
+ echo "Consider creating E2E tests: /create-e2e <feature>" >&2
68
+ exit 0
69
+ fi
70
+
71
+ exit 0
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env bash
2
+ # Env Example Sync Hook — Stop
3
+ # Warns if .env has keys that .env.example doesn't document.
4
+ # SECURITY: Only reads key NAMES, never values.
5
+ #
6
+ # Based on Claude Code Mastery Guides V1-V5 by TheDecipherist
7
+
8
+ # Both files must exist
9
+ if [ ! -f ".env" ] || [ ! -f ".env.example" ]; then
10
+ exit 0
11
+ fi
12
+
13
+ # Extract sorted key names from a file (ignoring comments, blank lines, export prefix)
14
+ extract_keys() {
15
+ grep -E '^(export\s+)?[A-Za-z_][A-Za-z0-9_]*=' "$1" 2>/dev/null \
16
+ | sed 's/^export\s*//' \
17
+ | cut -d'=' -f1 \
18
+ | sort -u
19
+ }
20
+
21
+ ENV_KEYS=$(extract_keys .env)
22
+ EXAMPLE_KEYS=$(extract_keys .env.example)
23
+
24
+ # Find keys in .env that are missing from .env.example
25
+ MISSING=""
26
+ while IFS= read -r key; do
27
+ if [ -n "$key" ] && ! echo "$EXAMPLE_KEYS" | grep -qx "$key"; then
28
+ MISSING="${MISSING}\n - $key"
29
+ fi
30
+ done <<< "$ENV_KEYS"
31
+
32
+ if [ -n "$MISSING" ]; then
33
+ echo "" >&2
34
+ echo "ENV SYNC: Keys in .env missing from .env.example:" >&2
35
+ echo -e "$MISSING" >&2
36
+ echo "" >&2
37
+ echo "Other developers won't know these variables exist." >&2
38
+ echo "Add them to .env.example with placeholder values." >&2
39
+ fi
40
+
41
+ exit 0
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env python3
2
+ """PostToolUse (Write|Edit) — enforce the 300-line file limit.
3
+
4
+ The write already happened (PostToolUse can't undo it), so exit 2 feeds the file
5
+ back to Claude as a "split this now" instruction before it moves on. Only counts
6
+ real source files; skips generated and vendored paths.
7
+ """
8
+ import json
9
+ import os
10
+ import sys
11
+
12
+ LIMIT = 300
13
+ CODE_EXT = (".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".java")
14
+ SKIP_SUBSTR = ("/node_modules/", "/dist/", "/build/", "/.next/", "/coverage/", "/.git/")
15
+
16
+
17
+ def main():
18
+ try:
19
+ data = json.load(sys.stdin)
20
+ except Exception:
21
+ sys.exit(0)
22
+
23
+ path = (data.get("tool_input", {}) or {}).get("file_path", "") or ""
24
+ if not path or not path.endswith(CODE_EXT):
25
+ sys.exit(0)
26
+ if any(s in path for s in SKIP_SUBSTR):
27
+ sys.exit(0)
28
+
29
+ try:
30
+ with open(path, "r", encoding="utf-8", errors="ignore") as f:
31
+ n = sum(1 for _ in f)
32
+ except OSError:
33
+ sys.exit(0)
34
+
35
+ if n > LIMIT:
36
+ sys.stderr.write(
37
+ os.path.basename(path) + " is now " + str(n) + " lines, over the "
38
+ + str(LIMIT) + "-line limit. Split it by concern (one responsibility "
39
+ "per file) before moving on.\n"
40
+ )
41
+ sys.exit(2)
42
+
43
+ sys.exit(0)
44
+
45
+
46
+ if __name__ == "__main__":
47
+ main()
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env bash
2
+ # Port Conflict Check Hook — PreToolUse (Bash)
3
+ # Blocks starting a server when the target port is already in use.
4
+ # Exit code 2 = block operation and tell Claude why.
5
+ #
6
+ # Based on Claude Code Mastery Guides V1-V5 by TheDecipherist
7
+
8
+ INPUT=$(cat)
9
+ COMMAND=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('command',''))" 2>/dev/null)
10
+
11
+ if [ -z "$COMMAND" ]; then
12
+ exit 0
13
+ fi
14
+
15
+ PORT=""
16
+
17
+ # 1. Explicit port flags: -p 3000, --port 3000, --port=3000
18
+ if echo "$COMMAND" | grep -qoE '(-p|--port[= ])\s*[0-9]+'; then
19
+ PORT=$(echo "$COMMAND" | grep -oE '(-p|--port[= ])\s*[0-9]+' | grep -oE '[0-9]+' | head -1)
20
+ fi
21
+
22
+ # 2. PORT= environment variable prefix
23
+ if [ -z "$PORT" ] && echo "$COMMAND" | grep -qE 'PORT=[0-9]+'; then
24
+ PORT=$(echo "$COMMAND" | grep -oE 'PORT=[0-9]+' | head -1 | cut -d'=' -f2)
25
+ fi
26
+
27
+ # 3. Known pnpm script names -> known ports
28
+ if [ -z "$PORT" ]; then
29
+ case "$COMMAND" in
30
+ *dev:test:website*) PORT=4000 ;;
31
+ *dev:test:api*) PORT=4010 ;;
32
+ *dev:test:dashboard*) PORT=4020 ;;
33
+ *dev:website*) PORT=3000 ;;
34
+ *dev:api*) PORT=3001 ;;
35
+ *dev:dashboard*) PORT=3002 ;;
36
+ esac
37
+ fi
38
+
39
+ # No port detected — nothing to check
40
+ if [ -z "$PORT" ]; then
41
+ exit 0
42
+ fi
43
+
44
+ # Check if lsof is available
45
+ if ! command -v lsof &>/dev/null; then
46
+ exit 0
47
+ fi
48
+
49
+ # Check if port is in use
50
+ PID=$(lsof -ti:"$PORT" 2>/dev/null | head -1)
51
+
52
+ if [ -n "$PID" ]; then
53
+ PROC=$(ps -p "$PID" -o comm= 2>/dev/null || echo "unknown")
54
+ echo "BLOCKED: Port $PORT is already in use by $PROC (PID: $PID)." >&2
55
+ echo "Kill it first: lsof -ti:$PORT | xargs kill -9" >&2
56
+ exit 2
57
+ fi
58
+
59
+ exit 0
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env bash
2
+ # RuleCatch Check Hook — Stop
3
+ # Runs when Claude finishes a turn — reports any rule violations detected.
4
+ # Guaranteed to run (stronger than CLAUDE.md command instructions).
5
+ #
6
+ # Based on Claude Code Mastery Guides V1-V5 by TheDecipherist
7
+
8
+ # Check if RuleCatch CLI is available
9
+ if ! command -v npx &>/dev/null; then
10
+ exit 0
11
+ fi
12
+
13
+ # Check if @rulecatch/ai-pooler is available (quick check)
14
+ # If not installed, skip silently — don't block the user
15
+ # Always use latest to make sure the ai-pooler is up to date
16
+ if ! npx @rulecatch/ai-pooler@latest check --help &>/dev/null 2>&1; then
17
+ exit 0
18
+ fi
19
+
20
+ # Run RuleCatch violation check for the current session
21
+ # --quiet: only output if violations found
22
+ # --format: short summary suitable for hook output
23
+ RESULT=$(npx @rulecatch/ai-pooler@latest check --quiet --format summary 2>/dev/null)
24
+
25
+ if [ -n "$RESULT" ] && [ "$RESULT" != "0 violations" ]; then
26
+ echo "" >&2
27
+ echo "📋 RuleCatch: $RESULT" >&2
28
+ echo " Run 'pnpm ai:monitor' for details or check your dashboard." >&2
29
+ # Exit 0 = inform but don't block (violations are warnings, not blockers)
30
+ exit 0
31
+ fi
32
+
33
+ exit 0
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env bash
2
+ # Rybbit Pre-Deploy Check Hook — PreToolUse (Bash)
3
+ # Blocks deployment commands when Rybbit analytics is configured but not set up.
4
+ # Exit code 2 = block operation and tell Claude why.
5
+ #
6
+ # Based on Claude Code Mastery Guides V1-V5 by TheDecipherist
7
+
8
+ INPUT=$(cat)
9
+ COMMAND=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('command',''))" 2>/dev/null)
10
+
11
+ if [ -z "$COMMAND" ]; then
12
+ exit 0
13
+ fi
14
+
15
+ # Skip commands that are git operations — commits, adds, etc. are never deployments
16
+ if echo "$COMMAND" | grep -qE 'git\s+(commit|add|push|merge|rebase|checkout|branch|tag|log|diff|status)'; then
17
+ exit 0
18
+ fi
19
+
20
+ # Only check actual deployment commands (not file writes that mention deployment keywords)
21
+ # - docker push: actual push to registry
22
+ # - vercel deploy/--prod: actual Vercel deploy
23
+ # - curl.*application.deploy: actual Dokploy API call
24
+ # Avoids false positives from heredocs, cat, echo, python file writes, etc.
25
+ if ! echo "$COMMAND" | grep -qEi '(docker\s+push|vercel\s+deploy|vercel\s+--prod|curl.*application\.deploy)'; then
26
+ exit 0
27
+ fi
28
+
29
+ # Check if project uses Rybbit
30
+ CONF="claude-mastery-project.conf"
31
+ if [ ! -f "$CONF" ]; then
32
+ exit 0
33
+ fi
34
+
35
+ if ! grep -q 'analytics\s*=\s*rybbit' "$CONF" 2>/dev/null; then
36
+ exit 0
37
+ fi
38
+
39
+ # Project uses Rybbit — verify it's configured
40
+ if [ ! -f ".env" ]; then
41
+ echo "BLOCKED: Rybbit analytics is configured in this project but .env file is missing." >&2
42
+ echo "Create .env with NEXT_PUBLIC_RYBBIT_SITE_ID=<your-site-id> before deploying." >&2
43
+ echo "Get your site ID from https://app.rybbit.io" >&2
44
+ exit 2
45
+ fi
46
+
47
+ SITE_ID=$(grep -E '^NEXT_PUBLIC_RYBBIT_SITE_ID=' .env 2>/dev/null | head -1 | cut -d'=' -f2- | tr -d ' "'"'"'')
48
+
49
+ if [ -z "$SITE_ID" ]; then
50
+ echo "BLOCKED: NEXT_PUBLIC_RYBBIT_SITE_ID is missing from .env." >&2
51
+ echo "Add NEXT_PUBLIC_RYBBIT_SITE_ID=<your-site-id> to .env before deploying." >&2
52
+ echo "Get your site ID from https://app.rybbit.io" >&2
53
+ exit 2
54
+ fi
55
+
56
+ # Check for placeholder values
57
+ if echo "$SITE_ID" | grep -qEi '(your_site|placeholder|changeme|your-site|example)'; then
58
+ echo "BLOCKED: NEXT_PUBLIC_RYBBIT_SITE_ID appears to be a placeholder ('$SITE_ID')." >&2
59
+ echo "Set a real site ID from https://app.rybbit.io before deploying." >&2
60
+ exit 2
61
+ fi
62
+
63
+ exit 0
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env bash
2
+ # Lint on Save Hook — PostToolUse
3
+ # Runs linter after Claude writes/edits a file.
4
+ # Keep hooks FAST (<5 seconds) or Claude may not wait (V5 lesson).
5
+ #
6
+ # Based on Claude Code Mastery Guides V1-V5 by TheDecipherist
7
+
8
+ # Read the tool input from stdin
9
+ INPUT=$(cat)
10
+ FILE_PATH=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('file_path',''))" 2>/dev/null)
11
+
12
+ if [ -z "$FILE_PATH" ]; then
13
+ exit 0
14
+ fi
15
+
16
+ EXTENSION="${FILE_PATH##*.}"
17
+
18
+ case "$EXTENSION" in
19
+ ts|tsx)
20
+ # TypeScript — run type check on the specific file if tsc is available
21
+ if command -v npx &> /dev/null && [ -f "tsconfig.json" ]; then
22
+ npx tsc --noEmit --pretty "$FILE_PATH" 2>&1 | head -20
23
+ fi
24
+ ;;
25
+ js|jsx)
26
+ # JavaScript — run eslint if available
27
+ if command -v npx &> /dev/null && [ -f ".eslintrc*" ] || [ -f "eslint.config.*" ]; then
28
+ npx eslint --no-error-on-unmatched-pattern "$FILE_PATH" 2>&1 | head -20
29
+ fi
30
+ ;;
31
+ py)
32
+ # Python — run ruff or flake8 if available
33
+ if command -v ruff &> /dev/null; then
34
+ ruff check "$FILE_PATH" 2>&1 | head -20
35
+ elif command -v flake8 &> /dev/null; then
36
+ flake8 "$FILE_PATH" 2>&1 | head -20
37
+ fi
38
+ ;;
39
+ vue)
40
+ # Vue — run vue-tsc if available
41
+ if command -v npx &> /dev/null && [ -f "tsconfig.json" ]; then
42
+ npx vue-tsc --noEmit 2>&1 | head -20
43
+ fi
44
+ ;;
45
+ svelte)
46
+ # Svelte — run svelte-check if available
47
+ if command -v npx &> /dev/null; then
48
+ npx svelte-check --tsconfig ./tsconfig.json 2>&1 | head -20
49
+ fi
50
+ ;;
51
+ go)
52
+ # Go — run go vet if available
53
+ if command -v go &> /dev/null; then
54
+ go vet "$FILE_PATH" 2>&1 | head -20
55
+ fi
56
+ ;;
57
+ esac
58
+
59
+ exit 0
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env bash
2
+ # Verify No Secrets Hook — Stop
3
+ # Checks staged git files for accidentally committed secrets.
4
+ # Runs when Claude finishes a turn — catches secrets before they're committed.
5
+ #
6
+ # Based on Claude Code Mastery Guides V1-V5 by TheDecipherist
7
+
8
+ # Only run if we're in a git repo
9
+ if ! git rev-parse --is-inside-work-tree &>/dev/null 2>&1; then
10
+ exit 0
11
+ fi
12
+
13
+ # Check if there are staged files
14
+ STAGED=$(git diff --cached --name-only 2>/dev/null)
15
+ if [ -z "$STAGED" ]; then
16
+ exit 0
17
+ fi
18
+
19
+ VIOLATIONS=""
20
+
21
+ # Check for sensitive files being staged (match basename only — anchored)
22
+ SENSITIVE_BASENAMES=".env .env.local .env.production .env.staging secrets.json credentials.json service-account.json .npmrc"
23
+ for pattern in $SENSITIVE_BASENAMES; do
24
+ while IFS= read -r file; do
25
+ basename=$(basename "$file")
26
+ if [ "$basename" = "$pattern" ]; then
27
+ VIOLATIONS="${VIOLATIONS}\n - SENSITIVE FILE STAGED: $file"
28
+ fi
29
+ done <<< "$STAGED"
30
+ done
31
+
32
+ # Check for private key files (anchored to basename)
33
+ while IFS= read -r file; do
34
+ basename=$(basename "$file")
35
+ case "$basename" in
36
+ id_rsa|id_ed25519|id_ecdsa|id_dsa|*.pem|*.key)
37
+ VIOLATIONS="${VIOLATIONS}\n - PRIVATE KEY FILE STAGED: $file"
38
+ ;;
39
+ esac
40
+ done <<< "$STAGED"
41
+
42
+ # Check staged file contents for common secret patterns
43
+ while IFS= read -r file; do
44
+ if [ -f "$file" ]; then
45
+ # Generic API key / secret / password / token patterns
46
+ if grep -qEi '(api[_-]?key|secret[_-]?key|password|token)\s*[:=]\s*["\x27][A-Za-z0-9+/=_-]{16,}' "$file" 2>/dev/null; then
47
+ VIOLATIONS="${VIOLATIONS}\n - POSSIBLE SECRET in $file"
48
+ fi
49
+ # AWS access keys
50
+ if grep -qE 'AKIA[0-9A-Z]{16}' "$file" 2>/dev/null; then
51
+ VIOLATIONS="${VIOLATIONS}\n - AWS ACCESS KEY in $file"
52
+ fi
53
+ # GitHub tokens (ghp_, gho_, ghs_, ghr_, github_pat_)
54
+ if grep -qE '(ghp_[A-Za-z0-9]{36,}|gho_[A-Za-z0-9]{36,}|ghs_[A-Za-z0-9]{36,}|ghr_[A-Za-z0-9]{36,}|github_pat_[A-Za-z0-9_]{22,})' "$file" 2>/dev/null; then
55
+ VIOLATIONS="${VIOLATIONS}\n - GITHUB TOKEN in $file"
56
+ fi
57
+ # Slack tokens
58
+ if grep -qE '(xoxb-|xoxp-|xoxo-|xoxa-)[0-9A-Za-z-]{20,}' "$file" 2>/dev/null; then
59
+ VIOLATIONS="${VIOLATIONS}\n - SLACK TOKEN in $file"
60
+ fi
61
+ # Stripe keys
62
+ if grep -qE '(sk_live_|pk_live_|rk_live_)[A-Za-z0-9]{20,}' "$file" 2>/dev/null; then
63
+ VIOLATIONS="${VIOLATIONS}\n - STRIPE KEY in $file"
64
+ fi
65
+ # PEM-format private keys
66
+ if grep -qE '-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----' "$file" 2>/dev/null; then
67
+ VIOLATIONS="${VIOLATIONS}\n - PEM PRIVATE KEY in $file"
68
+ fi
69
+ fi
70
+ done <<< "$STAGED"
71
+
72
+ if [ -n "$VIOLATIONS" ]; then
73
+ echo -e "⚠️ POTENTIAL SECRETS DETECTED:${VIOLATIONS}" >&2
74
+ echo "" >&2
75
+ echo "Review staged files before committing." >&2
76
+ # Exit 2 = block and inform Claude
77
+ exit 2
78
+ fi
79
+
80
+ exit 0
@@ -0,0 +1,34 @@
1
+ {
2
+ "hooks": {
3
+ "PreToolUse": [
4
+ {
5
+ "matcher": "Bash",
6
+ "hooks": [
7
+ { "type": "command", "command": "bash .claude/hooks/check-rybbit.sh" },
8
+ { "type": "command", "command": "bash .claude/hooks/check-branch.sh" },
9
+ { "type": "command", "command": "bash .claude/hooks/check-ports.sh" },
10
+ { "type": "command", "command": "bash .claude/hooks/check-e2e.sh" },
11
+ { "type": "command", "command": "python3 .claude/hooks/block-dangerous-bash.py" }
12
+ ]
13
+ }
14
+ ],
15
+ "PostToolUse": [
16
+ {
17
+ "matcher": "Write|Edit",
18
+ "hooks": [
19
+ { "type": "command", "command": "bash .claude/hooks/lint-on-save.sh" },
20
+ { "type": "command", "command": "python3 .claude/hooks/check-file-length.py" }
21
+ ]
22
+ }
23
+ ],
24
+ "Stop": [
25
+ {
26
+ "hooks": [
27
+ { "type": "command", "command": "bash .claude/hooks/verify-no-secrets.sh" },
28
+ { "type": "command", "command": "bash .claude/hooks/check-rulecatch.sh" },
29
+ { "type": "command", "command": "bash .claude/hooks/check-env-sync.sh" }
30
+ ]
31
+ }
32
+ ]
33
+ }
34
+ }
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: api-conventions
3
+ description: Service structure conventions for this codebase: routing, layering, and where code lives. Use whenever writing or modifying a service's server entry, route definitions, handlers, or adapters. Loads inline so it shapes structure as code is written.
4
+ ---
5
+
6
+ # Service Architecture Conventions
7
+
8
+ Every service is three layers, one direction: `server.ts` → `handlers/` → `adapters/`. Apply while writing, not after.
9
+
10
+ ## Versioning
11
+
12
+ - All routes live under `/api/v1/`. New endpoints are versioned from the first line. No unversioned routes "for now."
13
+
14
+ ## server.ts is thin
15
+
16
+ - `server.ts` defines routes and delegates. Nothing else.
17
+ - No business logic in `server.ts` or in a route definition. A route wires the request to a handler and returns its result.
18
+
19
+ ## handlers/ hold the logic
20
+
21
+ - Business logic lives in `handlers/`, one file per domain.
22
+ - A handler owns its domain's rules and orchestration. It calls adapters for anything external. It does not reach outside the process itself.
23
+
24
+ ## adapters/ wrap everything external
25
+
26
+ - Database, external APIs, queues, anything outside the process goes through an adapter in `adapters/`. Handlers never touch them directly.
27
+ - **The database adapter uses StrictDB when it's installed, otherwise the native driver. Never Mongoose.** A handler that imports a driver or calls an external API inline is wrong, that belongs in an adapter. The data adapter is the one place driver code lives, which is also where the `mongodb-rules` apply.
28
+
29
+ ## Service (package) separation
30
+
31
+ - A service owns its domain and is reached through its interface. A package does not reach into another package's internals or its data. Call the owning service.
32
+ - Code two services both need is hoisted to a shared layer, never imported sideways from a sibling.
33
+
34
+ The test: routes in `server.ts` read request-in, handler-call, response-out. Logic sits in `handlers/`. Anything that leaves the process goes through `adapters/`, and the data adapter is StrictDB.