Files
bootstrap/tools/prdy/prdy-validate.sh
Jason Woltje c9bf578396 feat: add mosaic prdy command for PRD creation and validation
Adds `mosaic prdy {init|update|validate}` subcommand:
- init: launches yolo Claude session with PRD-focused system prompt
- update: launches session to modify existing docs/PRD.md
- validate: bash-only completeness checker (15 checks against PRD guide)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:04:35 -06:00

171 lines
5.1 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
#
# prdy-validate.sh — Check PRD completeness against Mosaic guide requirements
#
# Usage:
# prdy-validate.sh [--project <path>]
#
# Performs static analysis of docs/PRD.md to verify it meets the minimum
# content requirements defined in the Mosaic PRD guide.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/_lib.sh"
# ─── Parse arguments ─────────────────────────────────────────────────────────
PROJECT="."
while [[ $# -gt 0 ]]; do
case "$1" in
--project) PROJECT="$2"; shift 2 ;;
-h|--help)
cat <<'USAGE'
prdy-validate.sh — Check PRD completeness against Mosaic guide
Usage: prdy-validate.sh [--project <path>]
Options:
--project <path> Project directory (default: CWD)
Checks:
- docs/PRD.md exists
- All 10 required sections present
- Metadata block (Owner, Date, Status)
- Functional requirements (FR-N) count
- User stories (US-NNN) count
- Acceptance criteria checklist count
- ASSUMPTION markers (informational)
Exit codes:
0 All required checks pass
1 One or more required checks failed
USAGE
exit 0
;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
done
# Expand tilde if passed literally (e.g., --project ~/src/foo)
PROJECT="${PROJECT/#\~/$HOME}"
# ─── Find PRD ────────────────────────────────────────────────────────────────
step "Validating PRD"
PRD_PATH="$(find_prd "$PROJECT")"
PASS=0
FAIL_COUNT=0
WARN_COUNT=0
check_pass() {
ok "$1"
PASS=$((PASS + 1))
}
check_fail() {
fail "$1"
FAIL_COUNT=$((FAIL_COUNT + 1))
}
check_warn() {
warn "$1"
WARN_COUNT=$((WARN_COUNT + 1))
}
if [[ -z "$PRD_PATH" ]]; then
check_fail "No PRD found. Expected $PROJECT/$PRD_CANONICAL or $PROJECT/$PRD_JSON_ALT"
echo ""
echo -e "${C_RED}PRD validation failed.${C_RESET} Run ${C_CYAN}mosaic prdy init${C_RESET} to create one."
exit 1
fi
check_pass "PRD found: $PRD_PATH"
# JSON PRDs get a limited check
if [[ "$PRD_PATH" == *.json ]]; then
info "JSON PRD detected — section checks skipped (markdown-only)"
echo ""
echo -e "${C_GREEN}PRD file exists.${C_RESET} JSON validation is limited."
exit 0
fi
PRD_CONTENT="$(cat "$PRD_PATH")"
# ─── Section checks ─────────────────────────────────────────────────────────
for entry in "${PRDY_REQUIRED_SECTIONS[@]}"; do
label="${entry%%|*}"
pattern="${entry#*|}"
if echo "$PRD_CONTENT" | grep -qiE "$pattern"; then
check_pass "$label section present"
else
check_fail "$label section MISSING"
fi
done
# ─── Metadata checks ────────────────────────────────────────────────────────
META_PASS=true
for field in "Owner" "Date" "Status"; do
if ! echo "$PRD_CONTENT" | grep -qiE "^- \*\*${field}:\*\*|^- ${field}:"; then
META_PASS=false
fi
done
if $META_PASS; then
check_pass "Metadata block present (Owner, Date, Status)"
else
check_fail "Metadata block incomplete (need Owner, Date, Status)"
fi
# ─── Content counts ─────────────────────────────────────────────────────────
FR_COUNT=$(echo "$PRD_CONTENT" | grep -cE '^\- FR-[0-9]+:|^FR-[0-9]+:' || true)
US_COUNT=$(echo "$PRD_CONTENT" | grep -cE '^#{1,4} US-[0-9]+' || true)
AC_COUNT=$(echo "$PRD_CONTENT" | grep -cE '^\s*- \[ \]' || true)
ASSUMPTION_COUNT=$(echo "$PRD_CONTENT" | grep -c 'ASSUMPTION:' || true)
if [[ "$FR_COUNT" -gt 0 ]]; then
check_pass "Functional requirements: $FR_COUNT FR items"
else
check_warn "No FR-N items found (expected numbered functional requirements)"
fi
if [[ "$US_COUNT" -gt 0 ]]; then
check_pass "User stories: $US_COUNT US items"
else
check_warn "No US-NNN items found (expected user stories)"
fi
if [[ "$AC_COUNT" -gt 0 ]]; then
check_pass "Acceptance criteria: $AC_COUNT checklist items"
else
check_warn "No acceptance criteria checkboxes found"
fi
if [[ "$ASSUMPTION_COUNT" -gt 0 ]]; then
info "ASSUMPTION markers: $ASSUMPTION_COUNT (informational)"
fi
# ─── Summary ─────────────────────────────────────────────────────────────────
TOTAL=$((PASS + FAIL_COUNT))
echo ""
if [[ "$FAIL_COUNT" -eq 0 ]]; then
echo -e "${C_GREEN}PRD validation passed.${C_RESET} ${PASS}/${TOTAL} checks OK."
if [[ "$WARN_COUNT" -gt 0 ]]; then
echo -e "${C_YELLOW}${WARN_COUNT} warning(s)${C_RESET} — review recommended."
fi
exit 0
else
echo -e "${C_RED}PRD validation failed.${C_RESET} ${FAIL_COUNT}/${TOTAL} checks failed."
if [[ "$WARN_COUNT" -gt 0 ]]; then
echo -e "${C_YELLOW}${WARN_COUNT} warning(s)${C_RESET}"
fi
exit 1
fi