Initial commit
This commit is contained in:
93
hooks/enforce-story-context.py
Normal file
93
hooks/enforce-story-context.py
Normal file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Enforce Story Context Hook
|
||||
Purpose: Block workflow commands that require a story if no story is active
|
||||
Trigger: PreToolUse on Bash commands (skill invocations)
|
||||
Part of: PRISM Core Development Lifecycle
|
||||
"""
|
||||
|
||||
import sys
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
# Fix Windows console encoding for emoji support
|
||||
if sys.stdout.encoding != 'utf-8':
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
|
||||
|
||||
def main():
|
||||
# Claude Code passes parameters via environment variables
|
||||
# Not via stdin JSON
|
||||
|
||||
# Extract command from environment variables
|
||||
command = os.environ.get('TOOL_PARAMS_command', '')
|
||||
|
||||
# Check if command is a PRISM skill command that requires a story context
|
||||
requires_story = False
|
||||
command_name = None
|
||||
|
||||
if '*develop-story' in command:
|
||||
requires_story = True
|
||||
command_name = 'develop-story'
|
||||
elif '*review ' in command:
|
||||
requires_story = True
|
||||
command_name = 'review'
|
||||
elif '*risk ' in command:
|
||||
requires_story = True
|
||||
command_name = 'risk-profile'
|
||||
elif '*design ' in command:
|
||||
requires_story = True
|
||||
command_name = 'test-design'
|
||||
elif '*validate-story-draft ' in command:
|
||||
requires_story = True
|
||||
command_name = 'validate-story-draft'
|
||||
elif '*gate ' in command:
|
||||
requires_story = True
|
||||
command_name = 'gate'
|
||||
elif '*review-qa' in command:
|
||||
requires_story = True
|
||||
command_name = 'review-qa'
|
||||
|
||||
if requires_story:
|
||||
# Check if there's an active story
|
||||
story_file_path = Path('.prism-current-story.txt')
|
||||
|
||||
if not story_file_path.exists():
|
||||
print(f"❌ ERROR: Command '{command_name}' requires an active story", file=sys.stderr)
|
||||
print("", file=sys.stderr)
|
||||
print(" No current story found in workflow context", file=sys.stderr)
|
||||
print("", file=sys.stderr)
|
||||
print(" REQUIRED: Draft a story first using the core-development-cycle workflow:", file=sys.stderr)
|
||||
print(" 1. Run: *planning-review (optional)", file=sys.stderr)
|
||||
print(" 2. Run: *draft", file=sys.stderr)
|
||||
print("", file=sys.stderr)
|
||||
print(" The draft command will create a story file and establish story context.", file=sys.stderr)
|
||||
sys.exit(2) # Block the command
|
||||
|
||||
story_file = story_file_path.read_text().strip()
|
||||
|
||||
# Verify story file exists
|
||||
if not Path(story_file).exists():
|
||||
print(f"❌ ERROR: Current story file not found: {story_file}", file=sys.stderr)
|
||||
print("", file=sys.stderr)
|
||||
print(" The story reference is stale or the file was deleted", file=sys.stderr)
|
||||
print("", file=sys.stderr)
|
||||
print(" REQUIRED: Create a new story:", file=sys.stderr)
|
||||
print(" Run: *draft", file=sys.stderr)
|
||||
sys.exit(2) # Block the command
|
||||
|
||||
# Log command with story context
|
||||
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
with open('.prism-workflow.log', 'a') as log:
|
||||
log.write(f"{timestamp} | COMMAND | {command_name} | {story_file}\n")
|
||||
|
||||
# Hooks should be silent on success
|
||||
# Success is indicated by exit code 0
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user