Initial commit
This commit is contained in:
42
hooks/hooks.json
Normal file
42
hooks/hooks.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/init-session.sh",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/recommend-skills.sh",
|
||||
"timeout": 100
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/check-prisma-client.sh",
|
||||
"timeout": 30
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/check-sql-injection.sh",
|
||||
"timeout": 30
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/check-deprecated-apis.sh",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
7
hooks/scripts/analyze-imports.sh
Executable file
7
hooks/scripts/analyze-imports.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
FILE_PATH="$1"
|
||||
|
||||
[[ ! -f "$FILE_PATH" ]] && exit 0
|
||||
|
||||
grep -E "from ['\"]@prisma/client['\"]|import.*@prisma/client|\\\$queryRaw|\\\$executeRaw" "$FILE_PATH" 2>/dev/null
|
||||
107
hooks/scripts/check-deprecated-apis.sh
Executable file
107
hooks/scripts/check-deprecated-apis.sh
Executable file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CLAUDE_MARKETPLACE_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
|
||||
|
||||
source "${CLAUDE_MARKETPLACE_ROOT}/marketplace-utils/hook-lifecycle.sh"
|
||||
|
||||
init_hook "prisma-6" "PreToolUse"
|
||||
|
||||
INPUT=$(read_hook_input)
|
||||
|
||||
if ! command -v grep &> /dev/null; then
|
||||
log_error "grep command not found"
|
||||
pretooluse_respond "allow"
|
||||
finish_hook 0
|
||||
fi
|
||||
|
||||
TS_FILES=$(find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \) \
|
||||
! -path "*/node_modules/*" \
|
||||
! -path "*/dist/*" \
|
||||
! -path "*/build/*" \
|
||||
! -path "*/.next/*" 2>/dev/null || true)
|
||||
|
||||
if [ -z "$TS_FILES" ]; then
|
||||
pretooluse_respond "allow"
|
||||
finish_hook 0
|
||||
fi
|
||||
|
||||
PRISMA_FILES=$(echo "$TS_FILES" | xargs grep -l '@prisma/client' 2>/dev/null || true)
|
||||
|
||||
BUFFER_USAGE=""
|
||||
if [ -n "$PRISMA_FILES" ]; then
|
||||
BUFFER_USAGE=$(echo "$PRISMA_FILES" | xargs grep -nE 'Buffer\.from\(' 2>/dev/null || true)
|
||||
fi
|
||||
|
||||
if [ -n "$BUFFER_USAGE" ]; then
|
||||
log_warn "Buffer.from() usage detected in files importing @prisma/client"
|
||||
pretooluse_respond "allow" "Warning: Buffer.from() usage detected in files importing @prisma/client
|
||||
|
||||
Prisma 6 Bytes fields use Uint8Array instead of Buffer:
|
||||
✗ const bytes = Buffer.from(data)
|
||||
✗ reportData: Buffer.from(jsonString)
|
||||
✓ reportData: new TextEncoder().encode(jsonString)
|
||||
|
||||
Bytes fields are returned as Uint8Array, no conversion needed:
|
||||
✗ Buffer.from(user.profilePicture)
|
||||
✓ user.profilePicture (already Uint8Array)"
|
||||
finish_hook 0
|
||||
fi
|
||||
|
||||
TOSTRING_ON_BYTES=$(echo "$TS_FILES" | xargs grep -nE '\.(avatar|file|attachment|document|reportData|image|photo|content|data|binary)\.toString\(' 2>/dev/null || true)
|
||||
|
||||
if [ -n "$TOSTRING_ON_BYTES" ]; then
|
||||
log_warn ".toString() on potential Bytes fields detected"
|
||||
pretooluse_respond "allow" "Warning: .toString() on potential Bytes fields detected
|
||||
|
||||
Bytes fields are now Uint8Array, not Buffer:
|
||||
✗ reportData.toString('utf-8')
|
||||
✓ new TextDecoder().decode(reportData)
|
||||
|
||||
For base64 encoding:
|
||||
✗ avatar.toString('base64')
|
||||
✓ Buffer.from(avatar).toString('base64')"
|
||||
finish_hook 0
|
||||
fi
|
||||
|
||||
NOT_FOUND_ERROR=$(echo "$TS_FILES" | xargs grep -En --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" 'Prisma.*NotFoundError|@prisma/client.*NotFoundError|PrismaClient.*NotFoundError|from.*["\047]@prisma/client["\047].*NotFoundError' 2>/dev/null || true)
|
||||
|
||||
if [ -n "$NOT_FOUND_ERROR" ]; then
|
||||
log_warn "Deprecated NotFoundError handling detected"
|
||||
pretooluse_respond "allow" "Warning: Deprecated NotFoundError handling detected
|
||||
|
||||
Use error code P2025 instead of NotFoundError:
|
||||
✗ if (error instanceof NotFoundError)
|
||||
✓ if (error.code === 'P2025')"
|
||||
finish_hook 0
|
||||
fi
|
||||
|
||||
REJECTONFOUND=$(echo "$TS_FILES" | xargs grep -n 'rejectOnNotFound' 2>/dev/null || true)
|
||||
|
||||
if [ -n "$REJECTONFOUND" ]; then
|
||||
log_warn "Deprecated rejectOnNotFound option detected"
|
||||
pretooluse_respond "allow" "Warning: Deprecated rejectOnNotFound option detected
|
||||
|
||||
Use findUniqueOrThrow() or findFirstOrThrow() instead:
|
||||
✗ findUnique({ where: { id }, rejectOnNotFound: true })
|
||||
✓ findUniqueOrThrow({ where: { id } })"
|
||||
finish_hook 0
|
||||
fi
|
||||
|
||||
EXPERIMENTAL_FEATURES=$(echo "$TS_FILES" | xargs grep -n 'experimentalFeatures.*extendedWhereUnique\|experimentalFeatures.*fullTextSearch' 2>/dev/null || true)
|
||||
|
||||
if [ -n "$EXPERIMENTAL_FEATURES" ]; then
|
||||
log_warn "Deprecated experimental features detected"
|
||||
pretooluse_respond "allow" "Warning: Deprecated experimental features detected
|
||||
|
||||
These features are now stable in Prisma 6:
|
||||
- extendedWhereUnique (enabled by default)
|
||||
- fullTextSearch (enabled by default)
|
||||
|
||||
Remove from schema.prisma generator block."
|
||||
finish_hook 0
|
||||
fi
|
||||
|
||||
pretooluse_respond "allow"
|
||||
finish_hook 0
|
||||
108
hooks/scripts/check-prisma-client.sh
Executable file
108
hooks/scripts/check-prisma-client.sh
Executable file
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CLAUDE_MARKETPLACE_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
|
||||
|
||||
source "${CLAUDE_MARKETPLACE_ROOT}/marketplace-utils/hook-lifecycle.sh"
|
||||
|
||||
init_hook "prisma-6" "PreToolUse"
|
||||
|
||||
INPUT=$(read_hook_input)
|
||||
|
||||
if ! command -v grep &> /dev/null; then
|
||||
log_error "grep command not found"
|
||||
pretooluse_respond "allow"
|
||||
finish_hook 0
|
||||
fi
|
||||
|
||||
TS_FILES=$(find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \) \
|
||||
! -path "*/node_modules/*" \
|
||||
! -path "*/dist/*" \
|
||||
! -path "*/build/*" \
|
||||
! -path "*/.next/*" 2>/dev/null || true)
|
||||
|
||||
if [ -z "$TS_FILES" ]; then
|
||||
pretooluse_respond "allow"
|
||||
finish_hook 0
|
||||
fi
|
||||
|
||||
INSTANCES=$(echo "$TS_FILES" | xargs grep -n --exclude="*.test.ts" --exclude="*.spec.ts" --exclude-dir="__tests__" --exclude-dir="test" "new PrismaClient()" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$INSTANCES" ]; then
|
||||
INSTANCE_COUNT=$(echo "$INSTANCES" | wc -l | tr -d ' ')
|
||||
|
||||
if [ "$INSTANCE_COUNT" -gt 1 ]; then
|
||||
log_warn "Multiple PrismaClient instances detected: $INSTANCE_COUNT"
|
||||
pretooluse_respond "allow" "Warning: Multiple PrismaClient instances detected ($INSTANCE_COUNT)
|
||||
|
||||
Use global singleton pattern to prevent connection pool exhaustion:
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined }
|
||||
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
|
||||
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma"
|
||||
finish_hook 0
|
||||
fi
|
||||
fi
|
||||
|
||||
FUNCTION_SCOPED=$(echo "$TS_FILES" | xargs grep -B5 --exclude="*.test.ts" --exclude="*.spec.ts" --exclude-dir="__tests__" --exclude-dir="test" "new PrismaClient()" 2>/dev/null | \
|
||||
grep -E "(function|const.*=.*\(|async.*\()" || true)
|
||||
|
||||
if [ -n "$FUNCTION_SCOPED" ]; then
|
||||
IS_SINGLETON_WRAPPER=false
|
||||
|
||||
for file in $TS_FILES; do
|
||||
if grep -q "new PrismaClient()" "$file" 2>/dev/null; then
|
||||
HAS_SINGLETON_FUNC=$(grep -E "const.*singleton.*=.*\(\).*=>.*new PrismaClient\(\)" "$file" || true)
|
||||
HAS_GLOBAL_CACHE=$(grep -E "globalThis|globalForPrisma" "$file" || true)
|
||||
SINGLETON_CALLS=$(grep -o "singleton()" "$file" 2>/dev/null | wc -l | tr -d ' ')
|
||||
|
||||
if [ -n "$HAS_SINGLETON_FUNC" ] && [ -n "$HAS_GLOBAL_CACHE" ] && [ "$SINGLETON_CALLS" -le 1 ]; then
|
||||
IS_SINGLETON_WRAPPER=true
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$IS_SINGLETON_WRAPPER" = false ]; then
|
||||
log_warn "PrismaClient instantiated inside function scope"
|
||||
pretooluse_respond "allow" "Warning: PrismaClient instantiated inside function scope
|
||||
|
||||
This creates new instances on each function call, exhausting connections.
|
||||
|
||||
Move PrismaClient to module scope with singleton pattern."
|
||||
finish_hook 0
|
||||
fi
|
||||
fi
|
||||
|
||||
RAW_INTERPOLATION=$(echo "$TS_FILES" | xargs grep -n "prisma\.\(raw\|queryRaw\|executeRaw\)" 2>/dev/null | \
|
||||
grep -E '\$\{.*\}|"\s*\+\s*[^+]|\`\$\{' || true)
|
||||
|
||||
if [ -n "$RAW_INTERPOLATION" ]; then
|
||||
log_warn "Potential SQL interpolation in Prisma.raw() call"
|
||||
pretooluse_respond "allow" "Warning: Potential SQL interpolation detected in Prisma.raw() call
|
||||
|
||||
Use parameterized queries to prevent SQL injection:
|
||||
prisma.\$queryRaw\`SELECT * FROM users WHERE id = \${id}\`
|
||||
|
||||
Avoid string concatenation:
|
||||
❌ prisma.\$queryRaw('SELECT * FROM users WHERE id = ' + id)
|
||||
❌ prisma.\$queryRaw(\`SELECT * FROM users WHERE id = \${unsafeVar}\`)"
|
||||
finish_hook 0
|
||||
fi
|
||||
|
||||
MISSING_GLOBAL=$(echo "$TS_FILES" | xargs grep -L "globalForPrisma\|globalThis.*prisma" 2>/dev/null | \
|
||||
xargs grep -l "new PrismaClient()" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$MISSING_GLOBAL" ]; then
|
||||
log_warn "PrismaClient instantiation without global singleton pattern"
|
||||
pretooluse_respond "allow" "Warning: PrismaClient instantiation without global singleton pattern
|
||||
|
||||
Recommended pattern for Next.js and serverless environments:
|
||||
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined }
|
||||
export const prisma = globalForPrisma.prisma ?? new PrismaClient()"
|
||||
finish_hook 0
|
||||
fi
|
||||
|
||||
pretooluse_respond "allow"
|
||||
finish_hook 0
|
||||
80
hooks/scripts/check-sql-injection.sh
Executable file
80
hooks/scripts/check-sql-injection.sh
Executable file
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CLAUDE_MARKETPLACE_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
|
||||
|
||||
source "${CLAUDE_MARKETPLACE_ROOT}/marketplace-utils/hook-lifecycle.sh"
|
||||
|
||||
init_hook "prisma-6" "PreToolUse"
|
||||
|
||||
INPUT=$(read_hook_input)
|
||||
|
||||
if ! command -v grep &> /dev/null; then
|
||||
log_error "grep command not found"
|
||||
pretooluse_respond "allow"
|
||||
finish_hook 0
|
||||
fi
|
||||
|
||||
TS_FILES=$(find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \) \
|
||||
! -path "*/node_modules/*" \
|
||||
! -path "*/dist/*" \
|
||||
! -path "*/build/*" \
|
||||
! -path "*/.next/*" 2>/dev/null || true)
|
||||
|
||||
if [ -z "$TS_FILES" ]; then
|
||||
pretooluse_respond "allow"
|
||||
finish_hook 0
|
||||
fi
|
||||
|
||||
UNSAFE_QUERY_RAW=$(echo "$TS_FILES" | xargs grep -n --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" '\$queryRawUnsafe' 2>/dev/null || true)
|
||||
|
||||
if [ -n "$UNSAFE_QUERY_RAW" ]; then
|
||||
log_error "Unsafe raw SQL query detected - SQL injection risk"
|
||||
pretooluse_respond "block" "Warning: Unsafe raw SQL query detected - SQL injection risk
|
||||
|
||||
Use \$queryRaw with tagged template syntax instead:
|
||||
✗ prisma.\$queryRawUnsafe(\`SELECT * FROM User WHERE id = \${id}\`)
|
||||
✓ prisma.\$queryRaw\`SELECT * FROM User WHERE id = \${id}\`"
|
||||
finish_hook 0
|
||||
fi
|
||||
|
||||
RAW_WITH_INTERPOLATION=$(echo "$TS_FILES" | xargs grep -n --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" 'Prisma\.raw(' 2>/dev/null | \
|
||||
grep -E '\$\{|\+.*["\`]' || true)
|
||||
|
||||
if [ -n "$RAW_WITH_INTERPOLATION" ]; then
|
||||
log_error "Prisma.raw() with string interpolation - SQL injection risk"
|
||||
pretooluse_respond "block" "Warning: Prisma.raw() with string interpolation - SQL injection risk
|
||||
|
||||
Use Prisma.sql with tagged template syntax:
|
||||
✗ Prisma.raw(\`WHERE id = \${id}\`)
|
||||
✓ Prisma.sql\`WHERE id = \${id}\`"
|
||||
finish_hook 0
|
||||
fi
|
||||
|
||||
MISSING_TAGGED_TEMPLATE=$(echo "$TS_FILES" | xargs grep -n --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" '\$queryRaw(' 2>/dev/null || true)
|
||||
|
||||
if [ -n "$MISSING_TAGGED_TEMPLATE" ]; then
|
||||
log_warn "\$queryRaw with function call syntax instead of tagged template"
|
||||
pretooluse_respond "allow" "Warning: \$queryRaw with function call syntax instead of tagged template
|
||||
|
||||
Use tagged template syntax for automatic parameterization:
|
||||
✗ prisma.\$queryRaw(Prisma.sql\`...\`)
|
||||
✓ prisma.\$queryRaw\`...\`"
|
||||
finish_hook 0
|
||||
fi
|
||||
|
||||
EXECUTE_RAW_UNSAFE=$(echo "$TS_FILES" | xargs grep -n --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" '\$executeRawUnsafe' 2>/dev/null || true)
|
||||
|
||||
if [ -n "$EXECUTE_RAW_UNSAFE" ]; then
|
||||
log_error "Unsafe raw SQL execution detected - SQL injection risk"
|
||||
pretooluse_respond "block" "Warning: Unsafe raw SQL execution detected - SQL injection risk
|
||||
|
||||
Use \$executeRaw with tagged template syntax instead:
|
||||
✗ prisma.\$executeRawUnsafe(\`UPDATE User SET name = '\${name}'\`)
|
||||
✓ prisma.\$executeRaw\`UPDATE User SET name = \${name}\`"
|
||||
finish_hook 0
|
||||
fi
|
||||
|
||||
pretooluse_respond "allow"
|
||||
finish_hook 0
|
||||
14
hooks/scripts/init-session.sh
Executable file
14
hooks/scripts/init-session.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CLAUDE_MARKETPLACE_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
|
||||
|
||||
source "${CLAUDE_MARKETPLACE_ROOT}/marketplace-utils/hook-lifecycle.sh"
|
||||
|
||||
init_hook "prisma-6" "SessionStart"
|
||||
|
||||
log_info "Prisma 6 session initialized"
|
||||
|
||||
inject_context "Prisma 6 plugin session started"
|
||||
finish_hook 0
|
||||
73
hooks/scripts/recommend-skills.sh
Executable file
73
hooks/scripts/recommend-skills.sh
Executable file
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CLAUDE_MARKETPLACE_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
|
||||
|
||||
source "${CLAUDE_MARKETPLACE_ROOT}/marketplace-utils/hook-lifecycle.sh"
|
||||
|
||||
init_hook "prisma-6" "PostToolUse"
|
||||
|
||||
INPUT=$(read_hook_input)
|
||||
FILE_PATH=$(get_input_field "tool_input.file_path")
|
||||
|
||||
if [[ -z "$FILE_PATH" ]]; then
|
||||
FILE_PATH=$(get_input_field "tool_input.path")
|
||||
fi
|
||||
|
||||
if [[ -z "$FILE_PATH" ]]; then
|
||||
finish_hook 0
|
||||
fi
|
||||
|
||||
FILE_NAME="${FILE_PATH##*/}"
|
||||
FILE_DIR="${FILE_PATH%/*}"
|
||||
|
||||
RECOMMENDATION_TYPE=""
|
||||
SKILLS=""
|
||||
MESSAGE=""
|
||||
|
||||
if [[ "$FILE_NAME" == "schema.prisma" ]]; then
|
||||
RECOMMENDATION_TYPE="schema_files"
|
||||
SKILLS="MIGRATIONS-*, CLIENT-*, QUERIES-type-safety"
|
||||
MESSAGE="Prisma Schema: $SKILLS"
|
||||
elif [[ "$FILE_DIR" == *"migrations"* ]]; then
|
||||
RECOMMENDATION_TYPE="migration_files"
|
||||
SKILLS="MIGRATIONS-dev-workflow, MIGRATIONS-production, MIGRATIONS-v6-upgrade"
|
||||
MESSAGE="Prisma Migrations: $SKILLS"
|
||||
elif [[ "$FILE_PATH" =~ \.(ts|js|tsx|jsx)$ ]]; then
|
||||
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "$SCRIPT_DIR")")}"
|
||||
IMPORTS=$(bash "$PLUGIN_ROOT/hooks/scripts/analyze-imports.sh" "$FILE_PATH" 2>/dev/null || true)
|
||||
|
||||
if [[ "$IMPORTS" == *"@prisma/client"* ]]; then
|
||||
RECOMMENDATION_TYPE="prisma_files"
|
||||
SKILLS="CLIENT-*, QUERIES-*, TRANSACTIONS-*, SECURITY-*"
|
||||
MESSAGE="Prisma Client Usage: $SKILLS"
|
||||
|
||||
if [[ "$IMPORTS" == *"\$queryRaw"* ]]; then
|
||||
RECOMMENDATION_TYPE="raw_sql_context"
|
||||
SKILLS="SECURITY-sql-injection (CRITICAL)"
|
||||
MESSAGE="Raw SQL Detected: $SKILLS"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$FILE_PATH" == *"vercel"* || "$FILE_PATH" == *"lambda"* || "$FILE_PATH" == *"app/"* ]]; then
|
||||
if ! has_shown_recommendation "prisma-6" "serverless_context"; then
|
||||
log_info "Recommending skills: CLIENT-serverless-config, PERFORMANCE-connection-pooling for serverless context in $FILE_PATH"
|
||||
mark_recommendation_shown "prisma-6" "serverless_context"
|
||||
inject_context "Serverless Context: CLIENT-serverless-config, PERFORMANCE-connection-pooling"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$RECOMMENDATION_TYPE" ]]; then
|
||||
finish_hook 0
|
||||
fi
|
||||
|
||||
if ! has_shown_recommendation "prisma-6" "$RECOMMENDATION_TYPE"; then
|
||||
log_info "Recommending skills: $SKILLS for $FILE_PATH"
|
||||
mark_recommendation_shown "prisma-6" "$RECOMMENDATION_TYPE"
|
||||
inject_context "$MESSAGE
|
||||
Use Skill tool to activate specific skills when needed."
|
||||
fi
|
||||
|
||||
finish_hook 0
|
||||
Reference in New Issue
Block a user