Initial commit
This commit is contained in:
97
hooks/validate-story-updates.py
Normal file
97
hooks/validate-story-updates.py
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Validate Story Updates Hook
|
||||
Purpose: Ensure all workflow steps update the current story file appropriately
|
||||
Trigger: PostToolUse on Edit operations to story files
|
||||
Part of: PRISM Core Development Lifecycle
|
||||
"""
|
||||
|
||||
import sys
|
||||
import io
|
||||
import json
|
||||
import re
|
||||
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
|
||||
import os
|
||||
|
||||
# Extract file path from environment variables
|
||||
file_path = os.environ.get('TOOL_PARAMS_file_path', '')
|
||||
|
||||
# Only validate story files
|
||||
if not re.match(r'^docs/stories/.*\.md$', file_path):
|
||||
sys.exit(0)
|
||||
|
||||
# Verify this is the current story
|
||||
story_file_path = Path('.prism-current-story.txt')
|
||||
if story_file_path.exists():
|
||||
current_story = story_file_path.read_text().strip()
|
||||
|
||||
if file_path != current_story:
|
||||
print("⚠️ WARNING: Editing story file that is not the current story", file=sys.stderr)
|
||||
print(f" Current: {current_story}", file=sys.stderr)
|
||||
print(f" Editing: {file_path}", file=sys.stderr)
|
||||
print(" HINT: Use *draft to set a new current story", file=sys.stderr)
|
||||
|
||||
# Check that story file exists
|
||||
story_path = Path(file_path)
|
||||
if not story_path.exists():
|
||||
print(f"❌ ERROR: Story file not found: {file_path}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
# Log the story update
|
||||
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
with open('.prism-workflow.log', 'a') as log:
|
||||
log.write(f"{timestamp} | STORY_UPDATED | {file_path}\n")
|
||||
|
||||
# Read story content
|
||||
story_content = story_path.read_text()
|
||||
|
||||
# Validate required story sections exist
|
||||
missing_sections = []
|
||||
|
||||
required_sections = [
|
||||
"## Story Description",
|
||||
"## Acceptance Criteria",
|
||||
"## Tasks",
|
||||
"## PSP Estimation Tracking"
|
||||
]
|
||||
|
||||
for section in required_sections:
|
||||
if section not in story_content:
|
||||
missing_sections.append(section)
|
||||
|
||||
if missing_sections:
|
||||
print("⚠️ WARNING: Story file missing required sections:", file=sys.stderr)
|
||||
for section in missing_sections:
|
||||
print(f" - {section}", file=sys.stderr)
|
||||
print(" These sections are required by PRISM workflow", file=sys.stderr)
|
||||
|
||||
# Check for workflow-specific required sections
|
||||
if "## Dev Agent Record" in story_content:
|
||||
if "### Completion Notes" not in story_content:
|
||||
print("⚠️ WARNING: Dev Agent Record missing 'Completion Notes' subsection", file=sys.stderr)
|
||||
|
||||
if "### File List" not in story_content:
|
||||
print("⚠️ WARNING: Dev Agent Record missing 'File List' subsection", file=sys.stderr)
|
||||
|
||||
# If QA Results exists, validate it has content
|
||||
if "## QA Results" in story_content:
|
||||
qa_section = re.search(r'## QA Results.*?(?=^##|\Z)', story_content, re.MULTILINE | re.DOTALL)
|
||||
if qa_section and len(qa_section.group(0).split('\n')) < 5:
|
||||
print("⚠️ WARNING: QA Results section appears empty or incomplete", file=sys.stderr)
|
||||
|
||||
# Hooks should be silent on success - no output for successful validation
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user