mirror of
https://github.com/davila7/claude-code-templates.git
synced 2026-09-19 01:30:23 +08:00
fc83c2a9c4
* feat: add enterprise quality hooks and agents from pm-workspace Add 3 quality gate hooks and 2 specialized agents contributed from pm-workspace (https://github.com/gonzalezpazmonica/pm-workspace): Hooks (new category: quality-gates/): - tdd-gate: Enforces TDD — blocks editing production code without tests - plan-gate: Warns about implementing without an approved specification - scope-guard: Detects files modified outside the declared spec scope Agents: - sdd-spec-writer: Creates executable specifications for AI agents - commit-guardian: 10-check pre-commit verification protocol All components are generic (no Azure DevOps dependency), MIT licensed, and follow the project's component structure conventions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address all 12 cubic review issues on enterprise hooks and agents Fixes applied per cubic-dev-ai review: Hooks (JSON configs): - Add MultiEdit matcher to tdd-gate, plan-gate (was bypassing gate) - Use $CLAUDE_PROJECT_DIR for all hook paths (avoid CWD-dependent failures) tdd-gate.sh: - Narrow test file globs: use suffix-based patterns (*Test.ext, *.test.ext) instead of overly broad *test* substring match (P1 fix) - Improve test discovery: search nearby dirs first (same module), fallback to project-wide with -maxdepth 6 to limit latency on large repos plan-gate.sh: - Remove `set -euo pipefail` that caused unintentional blocking when find fails on non-existent dirs - Add `|| true` to find to prevent exit-on-error scope-guard.sh: - Fix spec selection: use -printf '%T@ %p' sorted by mtime (not path sort) with macOS fallback using xargs ls -t - Broaden declared-file regex: support longer extensions and @ in paths - Fix word-splitting: use while-read loop instead of unquoted for-in - Fix matching: use case-based suffix match instead of substring grep commit-guardian.md: - CHECK 3: Fix Python build command (per-file py_compile, not bare) - CHECK 3: Add Go and Rust build detection, add SKIP when no build system - CHECK 10: Change from WARN to BLOCK for malformed commit messages sdd-spec-writer.md: - Add explicit .spec.md naming convention requirement (quality gate hooks depend on this filename pattern for spec detection) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address cubic review round 2 — null-delimit macOS fallback, existence-only spec check - scope-guard.sh: use -print0/xargs -0 for macOS fallback to handle paths with spaces safely (cubic P2) - plan-gate.sh: replace full enumeration with find -print -quit for existence-only check, avoiding unnecessary latency in large repos (cubic P2) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
91 lines
3.1 KiB
Bash
91 lines
3.1 KiB
Bash
#!/bin/bash
|
|
# tdd-gate.sh — Enforce Test-Driven Development: tests must exist before production code
|
|
# Source: pm-workspace (https://github.com/gonzalezpazmonica/pm-workspace)
|
|
# License: MIT
|
|
#
|
|
# Blocks editing production code if no corresponding test file exists.
|
|
# Forces developers and AI agents to write tests FIRST, then implement.
|
|
|
|
INPUT=$(cat)
|
|
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
|
FILE_PATH=""
|
|
|
|
# Applies to Edit, MultiEdit and Write tools
|
|
case "$TOOL" in
|
|
Edit|MultiEdit|Write)
|
|
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
|
|
;;
|
|
*)
|
|
exit 0
|
|
;;
|
|
esac
|
|
|
|
[ -z "$FILE_PATH" ] && exit 0
|
|
|
|
# Get file extension
|
|
EXT="${FILE_PATH##*.}"
|
|
|
|
# Only check production code extensions
|
|
case "$EXT" in
|
|
cs|py|ts|tsx|js|jsx|go|rs|rb|php|java|kt|swift|dart) ;;
|
|
*) exit 0 ;;
|
|
esac
|
|
|
|
# Skip files that don't need TDD gate (precise patterns to avoid false skips)
|
|
BASENAME=$(basename "$FILE_PATH")
|
|
case "$BASENAME" in
|
|
# Explicit test file patterns — suffix-based, not substring
|
|
*Test.${EXT}|*Tests.${EXT}|*_test.${EXT}|test_*.${EXT}) exit 0 ;;
|
|
*.test.${EXT}|*.spec.${EXT}|*Spec.${EXT}|*Specs.${EXT}) exit 0 ;;
|
|
# Config, migration, and infrastructure files
|
|
*Migration*|*migration*|*.dto.*|*DTO*) exit 0 ;;
|
|
*Program.cs|*Startup.cs|*appsettings*|*.csproj|*.sln) exit 0 ;;
|
|
*.d.ts|*.config.ts|*.config.js|tsconfig*|package.json) exit 0 ;;
|
|
Dockerfile|docker-compose*|*.tf|*.tfvars|*.yml|*.yaml) exit 0 ;;
|
|
*.md|*.txt|*.json|*.xml|*.html|*.css|*.scss) exit 0 ;;
|
|
esac
|
|
|
|
# Skip paths that are not production code
|
|
case "$FILE_PATH" in
|
|
*/test/*|*/tests/*|*/Test/*|*/Tests/*|*/__tests__/*) exit 0 ;;
|
|
*/spec/*|*/specs/*|*/Spec/*|*/Specs/*) exit 0 ;;
|
|
*/fixtures/*|*/mocks/*|*/stubs/*|*/fakes/*) exit 0 ;;
|
|
*/migrations/*|*/Migrations/*|*/seeds/*) exit 0 ;;
|
|
*/config/*|*/Config/*|*/scripts/*) exit 0 ;;
|
|
esac
|
|
|
|
# Search for corresponding test file in the same directory first, then nearby
|
|
NAME_NO_EXT="${BASENAME%.*}"
|
|
FILE_DIR=$(dirname "$FILE_PATH")
|
|
PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo ".")
|
|
|
|
# Search strategy: first in nearby dirs (same module), then project-wide
|
|
# Use -maxdepth to limit latency on large repos
|
|
TESTS_FOUND=$(find "$FILE_DIR" "$FILE_DIR/../test" "$FILE_DIR/../tests" "$FILE_DIR/../Test" "$FILE_DIR/../Tests" "$FILE_DIR/../__tests__" -maxdepth 2 -type f \( \
|
|
-name "${NAME_NO_EXT}Test.*" -o \
|
|
-name "${NAME_NO_EXT}Tests.*" -o \
|
|
-name "${NAME_NO_EXT}.test.*" -o \
|
|
-name "${NAME_NO_EXT}.spec.*" -o \
|
|
-name "${NAME_NO_EXT}_test.*" -o \
|
|
-name "test_${NAME_NO_EXT}.*" \
|
|
\) 2>/dev/null | head -1)
|
|
|
|
# Fallback: project-wide search with depth limit
|
|
if [ -z "$TESTS_FOUND" ]; then
|
|
TESTS_FOUND=$(find "$PROJECT_ROOT" -maxdepth 6 -type f \( \
|
|
-name "${NAME_NO_EXT}Test.*" -o \
|
|
-name "${NAME_NO_EXT}Tests.*" -o \
|
|
-name "${NAME_NO_EXT}.test.*" -o \
|
|
-name "${NAME_NO_EXT}.spec.*" -o \
|
|
-name "${NAME_NO_EXT}_test.*" -o \
|
|
-name "test_${NAME_NO_EXT}.*" \
|
|
\) 2>/dev/null | head -1)
|
|
fi
|
|
|
|
if [ -z "$TESTS_FOUND" ]; then
|
|
echo "TDD GATE: No tests found for '$BASENAME'. Write tests BEFORE implementing production code. Create: ${NAME_NO_EXT}Test.${EXT} or ${NAME_NO_EXT}.test.${EXT}" >&2
|
|
exit 2
|
|
fi
|
|
|
|
exit 0
|