223 lines
6.9 KiB
Python
Executable File
223 lines
6.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Pre-commit hook: Enforce pipeline completeness for /auto-implement features
|
|
|
|
This hook ensures that features developed with /auto-implement go through
|
|
the full 7-agent pipeline before being committed.
|
|
|
|
Pipeline agents:
|
|
1. researcher
|
|
2. planner
|
|
3. test-master
|
|
4. implementer
|
|
5. reviewer
|
|
6. security-auditor
|
|
7. doc-master
|
|
|
|
If pipeline is incomplete, the commit is blocked with instructions on how to fix.
|
|
|
|
Relevant Skills:
|
|
- project-alignment-validation: Feature alignment patterns for validation
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
|
|
def get_today_pipeline_file():
|
|
"""Find today's pipeline JSON file."""
|
|
sessions_dir = Path("docs/sessions")
|
|
if not sessions_dir.exists():
|
|
return None
|
|
|
|
today = datetime.now().strftime("%Y%m%d")
|
|
|
|
# Find most recent pipeline file for today
|
|
pipeline_files = sorted(
|
|
sessions_dir.glob(f"{today}-*-pipeline.json"),
|
|
reverse=True
|
|
)
|
|
|
|
return pipeline_files[0] if pipeline_files else None
|
|
|
|
|
|
def get_agent_count(pipeline_file):
|
|
"""Get count of agents that ran from pipeline file."""
|
|
try:
|
|
with open(pipeline_file) as f:
|
|
data = json.load(f)
|
|
|
|
agents = data.get("agents", [])
|
|
completed_agents = [
|
|
a for a in agents
|
|
if a.get("status") == "completed"
|
|
]
|
|
|
|
return len(completed_agents), [a.get("agent") for a in completed_agents]
|
|
except (json.JSONDecodeError, FileNotFoundError, KeyError):
|
|
return 0, []
|
|
|
|
|
|
def get_missing_agents(completed_agents):
|
|
"""Get list of agents that didn't run."""
|
|
expected_agents = [
|
|
"researcher",
|
|
"planner",
|
|
"test-master",
|
|
"implementer",
|
|
"reviewer",
|
|
"security-auditor",
|
|
"doc-master"
|
|
]
|
|
|
|
return [a for a in expected_agents if a not in completed_agents]
|
|
|
|
|
|
def is_feature_commit():
|
|
"""Check if this is a feature commit based on commit message."""
|
|
import subprocess
|
|
|
|
try:
|
|
# Get the commit message
|
|
result = subprocess.run(
|
|
["git", "log", "-1", "--pretty=%B"],
|
|
capture_output=True,
|
|
text=True,
|
|
check=True
|
|
)
|
|
commit_msg = result.stdout.strip()
|
|
|
|
# Check if it's a feature commit
|
|
return commit_msg.startswith(("feat:", "feature:", "feat(", "feature("))
|
|
except subprocess.CalledProcessError:
|
|
return False
|
|
|
|
|
|
def is_auto_implement_commit():
|
|
"""Check if this is a commit from /auto-implement workflow."""
|
|
# Check if pipeline file exists for today
|
|
pipeline_file = get_today_pipeline_file()
|
|
return pipeline_file is not None
|
|
|
|
|
|
def main():
|
|
"""Main enforcement logic."""
|
|
|
|
# Check if this is a feature commit
|
|
if not is_feature_commit():
|
|
# Not a feature commit - allow it (docs, chore, fix, etc.)
|
|
sys.exit(0)
|
|
|
|
# This is a feature commit - enforce pipeline
|
|
if not is_auto_implement_commit():
|
|
# Feature commit but no pipeline file = manual implementation!
|
|
print("=" * 70)
|
|
print("❌ FEATURE COMMIT WITHOUT PIPELINE - COMMIT BLOCKED")
|
|
print("=" * 70)
|
|
print()
|
|
print("This is a feature commit (starts with 'feat:' or 'feature:')")
|
|
print("but no /auto-implement pipeline was detected.")
|
|
print()
|
|
print("=" * 70)
|
|
print("Why this matters:")
|
|
print("=" * 70)
|
|
print()
|
|
print("Feature commits MUST use /auto-implement to ensure:")
|
|
print(" ✓ Research done (researcher)")
|
|
print(" ✓ Architecture planned (planner)")
|
|
print(" ✓ Tests written FIRST (test-master)")
|
|
print(" ✓ Implementation follows TDD (implementer)")
|
|
print(" ✓ Code reviewed (reviewer)")
|
|
print(" ✓ Security scanned (security-auditor)")
|
|
print(" ✓ Documentation updated (doc-master)")
|
|
print()
|
|
print("=" * 70)
|
|
print("How to fix:")
|
|
print("=" * 70)
|
|
print()
|
|
print("Option 1: Use /auto-implement (REQUIRED for features)")
|
|
print(" Run: /auto-implement <your feature description>")
|
|
print(" Wait for all 7 agents to complete")
|
|
print(" Then commit")
|
|
print()
|
|
print("Option 2: Change commit type (if not a feature)")
|
|
print(" If this is a:")
|
|
print(" - Bug fix: Use 'fix:' instead of 'feat:'")
|
|
print(" - Documentation: Use 'docs:' instead of 'feat:'")
|
|
print(" - Chore: Use 'chore:' instead of 'feat:'")
|
|
print()
|
|
print("Option 3: Skip enforcement (STRONGLY NOT RECOMMENDED)")
|
|
print(" git commit --no-verify")
|
|
print(" WARNING: This bypasses ALL quality gates")
|
|
print()
|
|
print("=" * 70)
|
|
sys.exit(1)
|
|
|
|
# Pipeline file exists - check if complete
|
|
pipeline_file = get_today_pipeline_file()
|
|
agent_count, completed_agents = get_agent_count(pipeline_file)
|
|
|
|
# Check if full pipeline (7 agents) completed
|
|
if agent_count >= 7:
|
|
# Full pipeline completed - allow commit
|
|
print(f"✅ Pipeline complete: {agent_count}/7 agents ran")
|
|
sys.exit(0)
|
|
|
|
# Pipeline incomplete - block commit
|
|
missing_agents = get_missing_agents(completed_agents)
|
|
|
|
print("=" * 70)
|
|
print("❌ PIPELINE INCOMPLETE - COMMIT BLOCKED")
|
|
print("=" * 70)
|
|
print()
|
|
print(f"Agents that ran: {agent_count}/7")
|
|
print(f"Completed: {', '.join(completed_agents) if completed_agents else 'none'}")
|
|
print()
|
|
print(f"Missing agents ({len(missing_agents)}):")
|
|
for agent in missing_agents:
|
|
print(f" - {agent}")
|
|
print()
|
|
print("=" * 70)
|
|
print("Why this matters:")
|
|
print("=" * 70)
|
|
print()
|
|
print("The /auto-implement workflow requires ALL 7 agents to ensure:")
|
|
print(" ✓ Tests written (test-master)")
|
|
print(" ✓ Security scanned (security-auditor)")
|
|
print(" ✓ Code reviewed (reviewer)")
|
|
print(" ✓ Documentation updated (doc-master)")
|
|
print()
|
|
print("Skipping agents has led to shipping:")
|
|
print(" ✗ Code without tests (0% coverage)")
|
|
print(" ✗ CRITICAL security vulnerabilities (CVSS 7.1+)")
|
|
print(" ✗ Inconsistent documentation")
|
|
print()
|
|
print("=" * 70)
|
|
print("How to fix:")
|
|
print("=" * 70)
|
|
print()
|
|
print("Option 1: Complete the pipeline (RECOMMENDED)")
|
|
print(f" Run: /auto-implement again with the same feature")
|
|
print(f" Claude will invoke the {len(missing_agents)} missing agents")
|
|
print(f" Then commit again")
|
|
print()
|
|
print("Option 2: Manual implementation (if you didn't use /auto-implement)")
|
|
print(" If this was a manual change, the pipeline file shouldn't exist")
|
|
print(f" Remove: {pipeline_file}")
|
|
print(" Then commit again (hooks will still validate)")
|
|
print()
|
|
print("Option 3: Skip enforcement (NOT RECOMMENDED)")
|
|
print(" git commit --no-verify")
|
|
print(" WARNING: This bypasses ALL quality gates")
|
|
print()
|
|
print("=" * 70)
|
|
|
|
# Block the commit
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|