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>
This commit is contained in:
170
tools/prdy/prdy-validate.sh
Normal file
170
tools/prdy/prdy-validate.sh
Normal file
@@ -0,0 +1,170 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user