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>
92 lines
2.9 KiB
Bash
92 lines
2.9 KiB
Bash
#!/bin/bash
|
|
# scope-guard.sh — Detect files modified outside the scope of an active spec
|
|
# Source: pm-workspace (https://github.com/gonzalezpazmonica/pm-workspace)
|
|
# License: MIT
|
|
#
|
|
# Compares git-modified files against files declared in the active .spec.md.
|
|
# Warns about out-of-scope modifications but does NOT block (exit 0).
|
|
# Essential for preventing scope creep during Spec-Driven Development.
|
|
|
|
INPUT=$(cat)
|
|
|
|
PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo ".")
|
|
|
|
# Get modified files (tracked unstaged + staged), handle safely
|
|
MODIFIED=$({ git diff --name-only 2>/dev/null; git diff --cached --name-only 2>/dev/null; } | sort -u)
|
|
|
|
if [ -z "$MODIFIED" ]; then
|
|
exit 0
|
|
fi
|
|
|
|
# Find active spec: most recently modified .spec.md (within last 60 minutes)
|
|
# Sort by modification time (-t) and take the newest one
|
|
SPEC_FILE=$(find "$PROJECT_ROOT" -name "*.spec.md" -mmin -60 -type f -printf '%T@ %p\n' 2>/dev/null \
|
|
| sort -rn | head -1 | cut -d' ' -f2-)
|
|
|
|
# Fallback for systems without -printf (macOS)
|
|
if [ -z "$SPEC_FILE" ]; then
|
|
SPEC_FILE=$(find "$PROJECT_ROOT" -name "*.spec.md" -mmin -60 -type f -print0 2>/dev/null \
|
|
| xargs -0 ls -t 2>/dev/null | head -1)
|
|
fi
|
|
|
|
# If no active spec, cannot verify scope
|
|
if [ -z "$SPEC_FILE" ] || [ ! -f "$SPEC_FILE" ]; then
|
|
exit 0
|
|
fi
|
|
|
|
# Extract declared files from spec using broader regex
|
|
# Matches paths like: src/Application/Orders/Handler.cs, tests/unit/test_foo.py
|
|
DECLARED=$(sed -n '/[Ff]icheros\|[Ff]iles to [Cc]reate\|[Ff]iles to [Mm]odify/,/^## /p' "$SPEC_FILE" \
|
|
| grep -oE '[a-zA-Z0-9_./@\-]+\.[a-zA-Z0-9]{1,8}' \
|
|
| sort -u)
|
|
|
|
if [ -z "$DECLARED" ]; then
|
|
exit 0
|
|
fi
|
|
|
|
# Compare: find modified files NOT in the declared list
|
|
# Use while-read to handle filenames with spaces safely
|
|
OUT_OF_SCOPE=""
|
|
while IFS= read -r FILE; do
|
|
[ -z "$FILE" ] && continue
|
|
BASENAME=$(basename "$FILE")
|
|
MATCH=0
|
|
|
|
while IFS= read -r DECL; do
|
|
[ -z "$DECL" ] && continue
|
|
# Exact path match
|
|
if [ "$FILE" = "$DECL" ]; then
|
|
MATCH=1
|
|
break
|
|
fi
|
|
# Check if declared path is a suffix of the modified file path
|
|
case "$FILE" in
|
|
*/"$DECL") MATCH=1; break ;;
|
|
esac
|
|
done <<< "$DECLARED"
|
|
|
|
if [ "$MATCH" -eq 0 ]; then
|
|
# Exclude files that are always legitimate outside scope
|
|
case "$BASENAME" in
|
|
*.spec.md|*.test.*|*Test.*|*Tests.*|*_test.*|test_*) continue ;;
|
|
*.md|*.json|*.yml|*.yaml|*.lock) continue ;;
|
|
.gitignore|Dockerfile|docker-compose*|*.csproj|*.sln|package.json) continue ;;
|
|
esac
|
|
case "$FILE" in
|
|
*/test/*|*/tests/*|*/Test/*|*/Tests/*|*/__tests__/*) continue ;;
|
|
esac
|
|
OUT_OF_SCOPE="${OUT_OF_SCOPE} - ${FILE}\n"
|
|
fi
|
|
done <<< "$MODIFIED"
|
|
|
|
if [ -n "$OUT_OF_SCOPE" ]; then
|
|
echo "SCOPE GUARD: Files modified OUTSIDE the scope of active spec ($(basename "$SPEC_FILE")):" >&2
|
|
printf "%b" "$OUT_OF_SCOPE" >&2
|
|
echo "" >&2
|
|
echo "Review if these changes are intentional or if the agent expanded the scope." >&2
|
|
# Warning only, does not block
|
|
exit 0
|
|
fi
|
|
|
|
exit 0
|