Files
gh-resolve-io-prism/hooks/enforce-story-context.py
2025-11-30 08:51:34 +08:00

94 lines
3.4 KiB
Python

#!/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()