scripts/bash/setup-product-implement.sh
#!/bin/bash
# product-implement setup script
set -euo pipefail
JSON_MODE=false
for arg in "$@"; do case "$arg" in --json) JSON_MODE=true ;; esac; done
# Layout roots (.adlc/ machine pipeline; docs/adlc/ human-facing deliverables)
ADLC_DIR="${ADLC_DIR:-.adlc}"
DOCS_ADLC_DIR="${DOCS_ADLC_DIR:-docs/adlc}"
DOCS_ADLC_MEMORY="${DOCS_ADLC_MEMORY:-docs/adlc/memory}"
DOCS_ADLC_PRODUCT="${DOCS_ADLC_PRODUCT:-docs/adlc/product}"
DOCS_ADLC_ARCHITECT="${DOCS_ADLC_ARCHITECT:-docs/adlc/architect}"
ADLC_DRAFTS="${ADLC_DRAFTS:-.adlc/drafts}"
_get_project_root() {
local dir
dir="$(pwd)"
while [ "$dir" != "/" ]; do
if [ -d "$dir/.adlc" ] || [ -d "$dir/.git" ]; then
echo "$dir"
return 0
fi
dir="$(dirname "$dir")"
done
pwd
}
REPO_ROOT="${REPO_ROOT:-$(_get_project_root)}"
# ============================================================================
# Frontmatter + heading status parsers (vendored from product-clarify
# pdr-lib.sh per the scope rule: scripts stay skill-local, helpers are
# vendored, never sourced across skills; keep these copies in sync with
# product-clarify, plus next_record_number and the three PRD.md slot
# scanners below).
# ============================================================================
# Parse a YAML frontmatter field from a markdown file.
# Usage: parse_fm_field "file" "fieldname"
# Returns the field value with quotes, inline comments, and surrounding whitespace stripped.
parse_fm_field() {
local file="$1"
local field="$2"
[ -f "$file" ] || return 0
awk -v fld="^[[:space:]]*${field}:[[:space:]]*" '
/^---[[:space:]]*$/ { fm++; next }
fm == 1 && $0 ~ fld {
sub(fld, "")
sub(/[[:space:]]+#.*$/, "")
gsub(/^["'\'']|["'\'']$/, "")
gsub(/^\[|\]$/, "")
sub(/^[[:space:]]+/, ""); sub(/[[:space:]]+$/, "")
print
exit
}
' "$file"
}
# Parse a metadata field from Markdown headings (## or ###).
# Looks for a heading line matching "^###+ <field>$", returns the next non-empty
# non-heading line with ** bold markers stripped.
# Usage: parse_pdr_heading_field "file" "Status" (case-sensitive field name)
parse_pdr_heading_field() {
local file="$1"
local field="$2"
[ -f "$file" ] || return 0
awk -v fld="$field" '
$0 ~ "^#+[[:space:]]*" fld "[[:space:]]*$" { found=1; next }
found && /^#+/ { exit }
found && NF > 0 {
gsub(/\*\*/, "")
sub(/^[[:space:]]+/, ""); sub(/[[:space:]]+$/, "")
print
exit
}
' "$file"
}
# Parse a PDR metadata field: tries YAML frontmatter first, falls back to headings.
# Usage: parse_pdr_field "file" "status" (frontmatter field name, lowercase hyphenated)
# parse_pdr_field "file" "Status" (heading field name, Title Case)
# Note: frontmatter uses lowercase hyphenated keys (status, feature-area);
# headings use Title Case (Status, Feature-Area). This function tries both.
parse_pdr_field() {
local file="$1"
local field="$2"
local value=""
# Try frontmatter (lowercase the field name for YAML key matching)
local fm_field
fm_field=$(echo "$field" | tr '[:upper:]' '[:lower:]')
value=$(parse_fm_field "$file" "$fm_field")
if [ -n "$value" ]; then
echo "$value"
return
fi
# Fallback: heading-based (Title Case the field name for heading matching)
# Accept both "Feature-Area" and "feature-area" heading styles
local title_field
title_field=$(echo "$field" | awk -F'-' '{for (i=1; i<=NF; i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))}1' OFS='-')
value=$(parse_pdr_heading_field "$file" "$title_field")
if [ -n "$value" ]; then
echo "$value"
return
fi
# Try the raw field name (handles "Feature-Area" where only first letter caps)
value=$(parse_pdr_heading_field "$file" "$field")
if [ -n "$value" ]; then
echo "$value"
return
fi
# Empty string — caller should apply defaults
echo ""
}
# Accepted-PDR counter.
# Both legacy H2 (## Status) and current H3 (### Status) shapes count;
# frontmatter `status: accepted` counts, inline-bold and next-line values count.
count_pdr_accepted() {
local dir="$1"
[ -d "$dir" ] || return 0
local count=0 f status
for f in "$dir"/PDR-*.md; do
[ -f "$f" ] || continue
status=$(parse_pdr_field "$f" "status")
# Normalize: strip ** bold, lowercase, trim
status=$(printf '%s' "$status" | sed 's/\*\*//g' | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
if [ "$status" = "accepted" ] || [ "$status" = "completed" ]; then
count=$((count + 1))
fi
done
echo "$count"
}
# Ledger slot allocation (vendored from product-clarify pdr-lib.sh per the
# scope rule: scripts stay skill-local, helpers are vendored, never sourced
# across skills). A record's number is the LEADING DIGITS of its filename
# stem; the maximum spans drafts AND published memory.
next_record_number() {
local prefix="$1"
shift
local max=0 f num dir
for dir in "$@"; do
[ -d "$dir" ] || continue
for f in "$dir"/"${prefix}"-*.md; do
[ -f "$f" ] || continue
num=$(basename "$f" .md | sed -n "s/^${prefix}-\([0-9]\{1,\}\).*/\1/p")
[ -n "$num" ] || continue
((10#$num > max)) && max=$((10#$num))
done
done
printf '%03d' "$((max + 1))"
}
# The three document-internal PRD.md slots. Each scan is deliberately
# over-inclusive: an unused number is harmless, a reissued one collides.
next_prd_section() {
local f="${1:-}"
local max=0
if [ -f "$f" ]; then
max=$(awk '/^## [0-9]+\. / { n=$2; sub(/\.$/, "", n); if (n+0 > m) m=n+0 }
END { print m+0 }' "$f")
fi
printf '%s' "$((max + 1))"
}
next_req_id() {
local f="${1:-}"
local max=0
if [ -f "$f" ]; then
max=$(grep -oE 'REQ-[0-9]+' "$f" | sed 's/^REQ-//' | sort -n | tail -1)
fi
printf '%s' "$((${max:-0} + 1))"
}
next_feature_number() {
local f="${1:-}"
local max=0
if [ -f "$f" ]; then
max=$(awk -F'|' '/^\| [0-9]+ \| / { gsub(/ /, "", $2); if ($2+0 > m) m=$2+0 }
END { print m+0 }' "$f")
fi
printf '%s' "$((max + 1))"
}
PDR_DRAFTS_DIR="$REPO_ROOT/${ADLC_DRAFTS:-.adlc/drafts}/pdr"
# canonical PDR memory root (legacy .adlc/memory stays read-compatible).
PDR_MEMORY_DIR="$REPO_ROOT/docs/adlc/memory/pdr"
PRD_FILE="$REPO_ROOT/docs/adlc/product/PRD.md"
SECTIONS_DIR="$REPO_ROOT/.adlc/product/sections"
STATE_FILE="$REPO_ROOT/.adlc/product/state.json"
mkdir -p "$PDR_DRAFTS_DIR"
mkdir -p "$PDR_MEMORY_DIR"
mkdir -p "$SECTIONS_DIR"
mkdir -p "$REPO_ROOT/.adlc/product"
# compiled PRD lives under docs/adlc/product/ — ensure the parent exists.
mkdir -p "$REPO_ROOT/${DOCS_ADLC_PRODUCT:-docs/adlc/product}"
ACCEPTED_COUNT=$(count_pdr_accepted "$PDR_DRAFTS_DIR" 2>/dev/null || echo 0)
# Ledger slots handed to the implement run: record id (drafts + published) and
# the three PRD.md document-internal slots.
NEXT_PDR=$(next_record_number PDR "$PDR_DRAFTS_DIR" "$PDR_MEMORY_DIR")
NEXT_PRD_SECTION=$(next_prd_section "$PRD_FILE")
NEXT_REQ_ID=$(next_req_id "$PRD_FILE")
NEXT_FEATURE_NUMBER=$(next_feature_number "$PRD_FILE")
if $JSON_MODE; then
jq -n -c --arg rr "$REPO_ROOT" --arg pdd "$PDR_DRAFTS_DIR" --arg pmd "$PDR_MEMORY_DIR" \
--arg pf "$PRD_FILE" --arg sd "$SECTIONS_DIR" --arg sf "$STATE_FILE" \
--argjson a "$ACCEPTED_COUNT" \
--arg np "$NEXT_PDR" --arg nps "$NEXT_PRD_SECTION" --arg nr "$NEXT_REQ_ID" --arg nfn "$NEXT_FEATURE_NUMBER" \
'{REPO_ROOT:$rr,PDR_DRAFTS_DIR:$pdd,PDR_MEMORY_DIR:$pmd,PRD_FILE:$pf,SECTIONS_DIR:$sd,STATE_FILE:$sf,accepted_count:$a,next_pdr:$np,next_prd_section:$nps,next_req_id:$nr,next_feature_number:$nfn}'
else
echo "[INFO] product-implement setup"
echo " Accepted PDRs: $ACCEPTED_COUNT"
echo " PRD_FILE: $PRD_FILE"
echo " SECTIONS_DIR: $SECTIONS_DIR"
echo " Next PDR slot: PDR-$NEXT_PDR"
echo " Next PRD section: $NEXT_PRD_SECTION"
echo " Next REQ id: REQ-$NEXT_REQ_ID"
echo " Next feature row: $NEXT_FEATURE_NUMBER"
fi
scripts/bash/validate-pdr.sh
#!/bin/bash
# PDR Validation Script for individual PDR files
# Usage: validate-pdr.sh [PDR_FILE]
set -e
PDR_FILE="${1:-}"
if [[ -z "$PDR_FILE" || ! -f "$PDR_FILE" ]]; then
echo "ERROR: PDR file not found: $PDR_FILE"
echo "Usage: validate-pdr.sh <pdr-file.md>"
exit 1
fi
ERRORS=0
WARNINGS=0
# v1.5.7-style fix: pre-increment — `((WARNINGS++))` returns status 1 from 0
# under `set -e` and aborts the script at the first warning (same bug the
# validate-prd.sh header documents for itself).
pass() { echo " ✓ $1"; }
warn() { echo " ⚠ $1"; ((++WARNINGS)); }
fail() { echo " ✗ $1"; ((++ERRORS)); }
echo "🔍 Validating PDR: $PDR_FILE"
# Check required sections
for header in "Context" "Decision" "Consequences"; do
if grep -qiE "^###?\s*$header" "$PDR_FILE"; then
pass "Has '$header' section"
else
fail "Missing '$header' section"
fi
done
# Check status
if grep -qiE '^\*\*Status\*\*' "$PDR_FILE" || grep -qiE '^\*\*(Proposed|Accepted|Discovered|Deprecated|Superseded)' "$PDR_FILE"; then
pass "Has valid status"
else
warn "No valid status marker found"
fi
# Check alternatives
if grep -qiE "Alternatives Considered" "$PDR_FILE"; then
pass "Has 'Alternatives Considered' section"
else
warn "Missing 'Alternatives Considered'"
fi
# Check for placeholders
if grep -qE '\[(Category|Problem|Decision Title|Owner)\]' "$PDR_FILE"; then
warn "Contains template placeholders"
fi
# Summary
echo ""
if [[ $ERRORS -eq 0 && $WARNINGS -eq 0 ]]; then
echo "✓ PDR validation passed"
exit 0
elif [[ $ERRORS -eq 0 ]]; then
echo "⚠ $WARNINGS warning(s)"
exit 2
else
echo "✗ $ERRORS error(s) and $WARNINGS warning(s)"
exit 1
fi
scripts/bash/validate-prd.sh
#!/bin/bash
# PRD Validation Script v1.5.7
# v1.5.7: fix set -e abort on ((VAR++)) from 0 (pre-increment); fix REQ regex to match '- **REQ-NNN:**' canonical format; fix BRE \| alternation in REQUIRED_SECTIONS used with grep -E
# Validates PRD compliance with product extension standards
# Checks: Section 1 = Doc Info, in-section diagrams, Mermaid, business sections, self-contained, PDR traceability
# Usage: validate-prd.sh [PRD_FILE] [--strict|--warn]
#
# Exit codes:
# 0 = All checks passed
# 1 = Critical failures (in strict mode) or validation errors
# 2 = Warnings only (default warn mode)
set -e
# Colors for output
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
STRICT_MODE=false
WARN_MODE=true
PRD_FILE="${1:-docs/adlc/product/PRD.md}"
# dual-read: bare invocation on a legacy-only repo falls back to the
# repo-root PRD.md (mirrors the dual-check in setup-product-analyze.sh).
# An explicit path argument is always honored verbatim.
if [ -z "${1:-}" ] && [ ! -f "$PRD_FILE" ] && [ -f "PRD.md" ]; then
PRD_FILE="PRD.md"
fi
WARNINGS=0
ERRORS=0
# Parse arguments
for arg in "$@"; do
case "$arg" in
--strict)
STRICT_MODE=true
WARN_MODE=false
;;
--warn)
STRICT_MODE=false
WARN_MODE=true
;;
--help|-h)
echo "Usage: $0 [PRD_FILE] [--strict|--warn]"
echo ""
echo "Validates PRD compliance with product extension v1.5.6 standards"
echo ""
echo "Options:"
echo " --strict Exit with error on any issue (default: warn only)"
echo " --warn Show warnings but don't exit with error (default)"
echo " --help Show this help message"
echo ""
echo "Examples:"
echo " $0 # Validate PRD.md with warnings"
echo " $0 my-prd.md --strict # Strict validation of my-prd.md"
echo " $0 .adlc/product/sections/ecosystem/overview.md # Validate section"
exit 0
;;
esac
done
# Check if file exists
if [[ ! -f "$PRD_FILE" ]]; then
echo -e "${RED}ERROR: PRD file not found: $PRD_FILE${NC}"
exit 1
fi
echo -e "${BLUE}🔍 Validating PRD: $PRD_FILE${NC}"
echo -e "${BLUE}Extension Version: 1.5.7 | Mode: $([ "$STRICT_MODE" == true ] && echo "STRICT" || echo "WARN")${NC}"
echo "================================================"
# Function to report success
pass() {
echo -e "${GREEN}✓${NC} $1"
}
# Function to report warning
warn() {
if [[ "$WARN_MODE" == true ]]; then
echo -e "${YELLOW}⚠ WARNING${NC}: $1"
else
echo -e "${RED}✗ ERROR${NC}: $1"
fi
((++WARNINGS))
}
# Function to report error
fail() {
echo -e "${RED}✗ FAIL${NC}: $1"
((++ERRORS))
}
# ============================================
# CHECK 1: Section 1 is Document Information (v1.5.6)
# ============================================
echo ""
echo -e "${BLUE}[1/9] Checking Section 1 is Document Information...${NC}"
# Get the first section header
FIRST_SECTION=$(grep -E '^## [0-9]+\.?\s' "$PRD_FILE" | head -1 || echo "")
if [[ -z "$FIRST_SECTION" ]]; then
fail "No numbered sections found (## 1. format required)"
else
if echo "$FIRST_SECTION" | grep -qiE '^## 1\.\s*Document Information'; then
pass "Section 1 is 'Document Information' (v1.5.6 compliant)"
elif echo "$FIRST_SECTION" | grep -qiE '^## 1\.\s*Visual Summary'; then
warn "Section 1 is 'Visual Summary' - v1.5.6 requires 'Document Information' as Section 1 (diagrams are now in-section)"
else
warn "Section 1 is: $FIRST_SECTION"
warn "Expected Section 1 to be 'Document Information' per v1.5.6 template"
fi
fi
# Check that Visual Summary does NOT exist as a separate section
if grep -qiE "^## [0-9]*\.?\s*Visual Summary" "$PRD_FILE"; then
warn "Found 'Visual Summary' as a separate section - v1.5.6 embeds diagrams in-section instead"
else
pass "No separate Visual Summary section (diagrams are embedded in-section per v1.5.6)"
fi
# ============================================
# CHECK 2: No ASCII Diagrams in Main Content
# ============================================
echo ""
echo -e "${BLUE}[2/9] Checking for ASCII diagrams...${NC}"
# Box-drawing characters (Unicode range U+2500 to U+257F)
ASCII_LINES=$(grep -n -P '[─━│┃┄┅┆┇┈┉┊┋┌┍┎┏┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋╌╍╎╏═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟╠╡╢╣╤╥╦╧╨╩╪╫╬╭╮╯╰╱╲╳╴╵╶╷╸╹╺╻╼╽╾╿]' "$PRD_FILE" 2>/dev/null || true)
if [[ -n "$ASCII_LINES" ]]; then
# Check context - are they in <details> blocks?
OUTSIDE_DETAILS=false
while IFS= read -r line; do
LINE_NUM=$(echo "$line" | cut -d: -f1)
# Simple check: count details tags before this line
DETAILS_OPEN=$(head -n "$LINE_NUM" "$PRD_FILE" | grep -c "<details>" || true)
DETAILS_CLOSE=$(head -n "$LINE_NUM" "$PRD_FILE" | grep -c "</details>" || true)
if [[ $DETAILS_OPEN -le $DETAILS_CLOSE ]]; then
OUTSIDE_DETAILS=true
break
fi
done <<< "$ASCII_LINES"
if [[ "$OUTSIDE_DETAILS" == true ]]; then
fail "ASCII box-drawing characters found OUTSIDE <details> blocks"
echo "$ASCII_LINES" | head -3 | while read line; do
echo " Line: $line"
done
if [[ $(echo "$ASCII_LINES" | wc -l) -gt 3 ]]; then
echo " ... and $(( $(echo "$ASCII_LINES" | wc -l) - 3 )) more lines"
fi
echo ""
echo " ASCII diagrams are ONLY allowed in <details> blocks as fallbacks."
echo " Main content MUST use Mermaid (\`\`\`mermaid)"
else
pass "ASCII characters found only in <details> blocks (allowed as fallback)"
fi
else
pass "No ASCII box-drawing characters found"
fi
# ============================================
# CHECK 3: Mermaid Diagrams Present
# ============================================
echo ""
echo -e "${BLUE}[3/9] Checking Mermaid diagrams...${NC}"
MERMAID_COUNT=$(grep -c "^\s*\`\`\`mermaid" "$PRD_FILE" || true)
if [[ $MERMAID_COUNT -eq 0 ]]; then
fail "No Mermaid diagrams found - MUST have at least 1"
echo " Use: \`\`\`mermaid blocks with flowchart/stateDiagram/etc"
elif [[ $MERMAID_COUNT -lt 2 ]]; then
warn "Only $MERMAID_COUNT Mermaid diagram(s) - recommend 2+ (hierarchy, deps, flows)"
else
pass "Found $MERMAID_COUNT Mermaid diagrams"
fi
# Check for deprecated 'graph' keyword
GRAPH_COUNT=$(grep -cE "^\s*graph\s+(TD|TB|LR|BT|RL)" "$PRD_FILE" || true)
if [[ $GRAPH_COUNT -gt 0 ]]; then
warn "Found $GRAPH_COUNT deprecated 'graph' keyword(s) - use 'flowchart' (Mermaid v10+)"
fi
# ============================================
# CHECK 4: No Unfilled Placeholders
# ============================================
echo ""
echo -e "${BLUE}[4/9] Checking for unfilled placeholders...${NC}"
PLACEHOLDER_PATTERNS=(
'\[PRODUCT_NAME\]'
'\[FEATURE_AREA_NAME\]'
'\[PDR_IDS\]'
'\[DATE\]'
'\[PLACEHOLDER\]'
'\[TODO\]'
'\[TBD\]'
'\[Author\]'
'\[X\.X\]'
)
FOUND_PLACEHOLDERS=0
for pattern in "${PLACEHOLDER_PATTERNS[@]}"; do
MATCHES=$(grep -n "$pattern" "$PRD_FILE" 2>/dev/null || true)
if [[ -n "$MATCHES" ]]; then
while IFS= read -r line; do
fail "Unfilled placeholder: $line"
done <<< "$MATCHES"
FOUND_PLACEHOLDERS=$((FOUND_PLACEHOLDERS + $(echo "$MATCHES" | wc -l)))
fi
done
if [[ $FOUND_PLACEHOLDERS -eq 0 ]]; then
pass "No unfilled placeholders found"
fi
# ============================================
# CHECK 5: PDR Traceability
# ============================================
echo ""
echo -e "${BLUE}[5/9] Checking PDR traceability...${NC}"
# Count requirements
REQ_COUNT=$(grep -cE '^\s*[-*+]?\s*\*\*REQ-[0-9]+:?\*\*' "$PRD_FILE" || true)
# Count PDR references
PDR_REFS=$(grep -cE 'PDR-[0-9]+' "$PRD_FILE" || true)
if [[ $REQ_COUNT -gt 0 ]]; then
pass "Found $REQ_COUNT requirements (REQ-XXX format)"
# Check if requirements have PDR references
REQ_LINES=$(grep -nE '^\s*[-*+]?\s*\*\*REQ-[0-9]+:?\*\*' "$PRD_FILE" | cut -d: -f1)
REQ_WITH_PDR=0
for line_num in $REQ_LINES; do
# Check next 3 lines for PDR reference
CONTEXT=$(sed -n "${line_num},$((line_num + 3))p" "$PRD_FILE")
if echo "$CONTEXT" | grep -qE 'PDR-[0-9]+'; then
((++REQ_WITH_PDR))
fi
done
if [[ $REQ_WITH_PDR -lt $REQ_COUNT ]]; then
warn "Only $REQ_WITH_PDR/$REQ_COUNT requirements have PDR traceability"
echo " Each requirement MUST reference a source PDR"
else
pass "All requirements trace to PDRs"
fi
else
warn "No REQ-XXX format requirements found - consider using this format for traceability"
fi
# ============================================
# CHECK 6: Required Sections Present
# ============================================
echo ""
echo -e "${BLUE}[6/9] Checking required sections...${NC}"
REQUIRED_SECTIONS=(
"1.*Document Information"
"2.*Overview"
"3.*Problem|3.*The Problem"
"4.*Goals|4.*Objectives"
"5.*Metrics|5.*Success Metrics"
"6.*Personas"
"7.*Functional Requirements|7.*Requirements"
"8.*Non-Functional|8.*NFRs"
"9.*Out of Scope"
"10.*Risks|10.*Mitigation"
"11.*Roadmap|11.*Milestones"
"12.*PDR Summary|12.*Product Decision"
)
MISSING_SECTIONS=0
for section_pattern in "${REQUIRED_SECTIONS[@]}"; do
if ! grep -qiE "^## [0-9]*\.?.*($section_pattern)" "$PRD_FILE"; then
warn "Missing or misnumbered section matching: $section_pattern"
((++MISSING_SECTIONS))
fi
done
if [[ $MISSING_SECTIONS -eq 0 ]]; then
pass "All required sections present and numbered"
fi
# ============================================
# CHECK 6.5: Business Sections (v1.5.3)
# ============================================
echo ""
echo -e "${BLUE}[6.5/9] Checking business stakeholder sections (v1.5.6)...${NC}"
BUSINESS_SECTIONS=(
"1\.5.*Executive Summary"
"3\.5.*Market Opportunity"
"10\.5.*Investment"
"11\.5.*Go-to-Market"
)
MISSING_BUSINESS=0
for biz_pattern in "${BUSINESS_SECTIONS[@]}"; do
if grep -qiE "^## $biz_pattern" "$PRD_FILE"; then
pass "Found business section: $biz_pattern"
else
warn "Missing business section: $biz_pattern (recommended for stakeholder PRDs)"
((++MISSING_BUSINESS))
fi
done
if [[ $MISSING_BUSINESS -eq 0 ]]; then
pass "All 4 business stakeholder sections present"
fi
# ============================================
# CHECK 6.6: Self-Contained PRD (v1.5.3)
# ============================================
echo ""
echo -e "${BLUE}[6.6/9] Checking self-contained PRD (v1.5.6 - no external .adlc/ links)...${NC}"
# Check for reader-facing links to .adlc/ paths
EXTERNAL_LINKS=$(grep -nE '\]\(\.adlc/' "$PRD_FILE" 2>/dev/null || true)
# Exclude links inside HTML comments
EXTERNAL_LINKS_FILTERED=""
if [[ -n "$EXTERNAL_LINKS" ]]; then
while IFS= read -r line; do
LINE_NUM=$(echo "$line" | cut -d: -f1)
LINE_CONTENT=$(sed -n "${LINE_NUM}p" "$PRD_FILE")
# Skip if inside HTML comment
if ! echo "$LINE_CONTENT" | grep -q '<!--'; then
EXTERNAL_LINKS_FILTERED="${EXTERNAL_LINKS_FILTERED}${line}\n"
fi
done <<< "$EXTERNAL_LINKS"
fi
if [[ -n "$EXTERNAL_LINKS_FILTERED" ]]; then
warn "PRD contains reader-facing links to .adlc/ files (should be self-contained)"
# v1.5.7-style fix: the `[[ -n ]] && echo` list returned 1 on the
# trailing empty line, the while loop inherited it, and `set -e`
# aborted the script right after the last reported link.
echo -e "$EXTERNAL_LINKS_FILTERED" | head -5 | while read line; do
if [[ -n "$line" ]]; then echo " $line"; fi
done
echo " Use in-document anchors instead: [Section 1.1](#11-feature-hierarchy)"
else
pass "PRD is self-contained (no reader-facing .adlc/ links)"
fi
# ============================================
# CHECK 7: Constitution Alignment Claims
# ============================================
echo ""
echo -e "${BLUE}[7/9] Checking constitution alignment...${NC}"
if grep -qi "Constitution Alignment\|Aligns with Constitution" "$PRD_FILE"; then
pass "Constitution alignment section found"
# Check if constitution is populated (not just template)
# dual-read: canonical docs/adlc/memory first, legacy fallback.
CONST_FILE="docs/adlc/memory/constitution.md"
if [ ! -f "$CONST_FILE" ]; then
CONST_FILE=".adlc/memory/constitution.md"
fi
if [[ -f "$CONST_FILE" ]]; then
if grep -qE '\[PRINCIPLE_[0-9]+_NAME\]|\[PROJECT_NAME\]' "$CONST_FILE" 2>/dev/null; then
warn "Constitution file contains template placeholders - populate or remove alignment claims"
else
pass "Constitution file appears to be populated"
fi
else
warn "Constitution file not found at $CONST_FILE"
fi
else
warn "No constitution alignment section found (recommended but not required)"
fi
# ============================================
# SUMMARY
# ============================================
echo ""
echo "================================================"
echo -e "${BLUE}Validation Summary${NC}"
echo "================================================"
if [[ $ERRORS -eq 0 && $WARNINGS -eq 0 ]]; then
echo -e "${GREEN}✓ All checks passed! PRD is compliant with v1.5.6${NC}"
echo ""
echo "You may proceed to mark sections as 'completed'"
exit 0
elif [[ $ERRORS -eq 0 ]]; then
echo -e "${YELLOW}⚠ $WARNINGS warning(s) found${NC}"
echo " PRD is usable but has issues that should be addressed"
echo ""
if [[ "$STRICT_MODE" == true ]]; then
echo -e "${RED}Exiting with error (strict mode)${NC}"
echo " Fix all warnings before marking 'completed'"
exit 1
else
echo " Run with --strict to enforce no warnings"
exit 2
fi
else
echo -e "${RED}✗ $ERRORS error(s) and $WARNINGS warning(s) found${NC}"
echo ""
echo "PRD is NON-COMPLIANT with v1.5.6 standards"
echo "Fix all errors before proceeding"
exit 1
fi
scripts/powershell/setup-product-implement.ps1
# product-implement setup script (PowerShell)
param([switch]$Json)
$ErrorActionPreference = "Stop"
# Layout roots (.adlc/ machine pipeline; docs/adlc/ human-facing deliverables)
$ADLC_DIR = if ($env:ADLC_DIR) { $env:ADLC_DIR } else { ".adlc" }
$DOCS_ADLC_DIR = if ($env:DOCS_ADLC_DIR) { $env:DOCS_ADLC_DIR } else { "docs/adlc" }
$DOCS_ADLC_MEMORY = if ($env:DOCS_ADLC_MEMORY) { $env:DOCS_ADLC_MEMORY } else { "docs/adlc/memory" }
$DOCS_ADLC_PRODUCT = if ($env:DOCS_ADLC_PRODUCT) { $env:DOCS_ADLC_PRODUCT } else { "docs/adlc/product" }
$DOCS_ADLC_ARCHITECT = if ($env:DOCS_ADLC_ARCHITECT) { $env:DOCS_ADLC_ARCHITECT } else { "docs/adlc/architect" }
$ADLC_DRAFTS = if ($env:ADLC_DRAFTS) { $env:ADLC_DRAFTS } else { ".adlc/drafts" }
$RepoRoot = $(git rev-parse --show-toplevel 2>$null); if (-not $RepoRoot) { $RepoRoot = Get-Location }
$PdrDraftsDir = Join-Path $RepoRoot ".adlc/drafts/pdr"
# canonical PDR memory root (legacy .adlc/memory stays read-compatible).
$PdrMemoryDir = Join-Path $RepoRoot "docs/adlc/memory/pdr"
$PrdFile = Join-Path $RepoRoot "docs/adlc/product/PRD.md"
$SectionsDir = Join-Path $RepoRoot ".adlc/product/sections"
$StateFile = Join-Path $RepoRoot ".adlc/product/state.json"
New-Item -ItemType Directory -Force -Path $PdrDraftsDir | Out-Null
New-Item -ItemType Directory -Force -Path $PdrMemoryDir | Out-Null
New-Item -ItemType Directory -Force -Path $SectionsDir | Out-Null
# compiled PRD lives under docs/adlc/product/ — ensure the parent exists.
New-Item -ItemType Directory -Force -Path (Join-Path $RepoRoot "docs/adlc/product") | Out-Null
# ============================================================================
# Frontmatter + heading status parsers (vendored from product-clarify
# pdr-lib.ps1 per the scope rule: scripts stay skill-local, helpers are
# vendored, never sourced across skills; keep these copies in sync with
# product-clarify).
# ============================================================================
function Parse-FrontmatterField {
param([string]$File, [string]$Field)
if (-not (Test-Path $File)) { return "" }
$lines = Get-Content $File
$inFm = $false
foreach ($line in $lines) {
if ($line -match '^---\s*$') {
if ($inFm) { break }
$inFm = $true
continue
}
if ($inFm -and ($line -match "^\s*$Field\s*:\s*(.*)$")) {
$val = $Matches[1]
$val = $val -replace '\s+#.*$', ''
$val = $val -replace '^["'']|[''"]$', ''
$val = $val -replace '^\[|\]$', ''
return $val.Trim()
}
}
return ""
}
function Parse-PdrHeadingField {
param([string]$File, [string]$Field)
if (-not (Test-Path $File)) { return "" }
$lines = Get-Content $File
$found = $false
foreach ($line in $lines) {
if (-not $found) {
if ($line -match "^###+\s*$Field\s*$") { $found = $true; continue }
} else {
if ($line -match '^###+') { break }
if ($line.Trim() -ne "") {
$val = $line -replace '\*\*', ''
return $val.Trim()
}
}
}
return ""
}
function Parse-PdrField {
param([string]$File, [string]$Field)
$fmField = $Field.ToLower()
$val = Parse-FrontmatterField -File $File -Field $fmField
if ($val) { return $val }
$titleField = ($Field -split '-' | ForEach-Object { $_.Substring(0, 1).ToUpper() + $_.Substring(1).ToLower() }) -join '-'
$val = Parse-PdrHeadingField -File $File -Field $titleField
if ($val) { return $val }
$val = Parse-PdrHeadingField -File $File -Field $Field
return $val
}
$acceptedCount = 0
if (Test-Path $PdrDraftsDir) {
# Accepted-PDR counter (uses the vendored frontmatter-primary +
# heading-fallback status parser above: legacy H2 and current H3 shapes
# count; frontmatter `status: accepted` counts; inline-bold and next-line
# values count).
Get-ChildItem -Path $PdrDraftsDir -Filter 'PDR-*.md' | ForEach-Object {
$status = Parse-PdrField -File $_.FullName -Field "status"
$status = ($status -replace '\*\*', '').ToLower().Trim()
if ($status -eq "accepted" -or $status -eq "completed") { $acceptedCount++ }
}
}
# Ledger slot allocation (vendored from product-clarify pdr-lib.ps1 per the
# scope rule: scripts stay skill-local, helpers are vendored, never sourced
# across skills). A record's number is the LEADING DIGITS of its filename
# stem; the maximum spans drafts AND published memory.
function Get-NextRecordNumber {
param(
[Parameter(Mandatory = $true)][string]$Prefix,
[Parameter(Mandatory = $true)][string[]]$Dirs
)
$max = 0
foreach ($dir in $Dirs) {
if (-not (Test-Path -LiteralPath $dir)) { continue }
foreach ($file in Get-ChildItem -LiteralPath $dir -Filter "$Prefix-*.md" -File -ErrorAction SilentlyContinue) {
if ($file.BaseName -match ('^' + [regex]::Escape($Prefix) + '-(\d+)')) {
$n = [int]$Matches[1]
if ($n -gt $max) { $max = $n }
}
}
}
return ('{0:D3}' -f ($max + 1))
}
# The three document-internal PRD.md slots. Each scan is deliberately
# over-inclusive: an unused number is harmless, a reissued one collides.
function Get-NextPrdSection {
param([string]$Path)
$max = 0
if (Test-Path -LiteralPath $Path) {
foreach ($line in Get-Content -LiteralPath $Path) {
if ($line -cmatch '^## (\d+)\. ') {
$n = [int]$Matches[1]
if ($n -gt $max) { $max = $n }
}
}
}
return ($max + 1)
}
function Get-NextReqId {
param([string]$Path)
$max = 0
if (Test-Path -LiteralPath $Path) {
foreach ($m in [regex]::Matches((Get-Content -LiteralPath $Path -Raw), 'REQ-(\d+)')) {
$n = [int]$m.Groups[1].Value
if ($n -gt $max) { $max = $n }
}
}
return ($max + 1)
}
function Get-NextFeatureNumber {
param([string]$Path)
$max = 0
if (Test-Path -LiteralPath $Path) {
foreach ($line in Get-Content -LiteralPath $Path) {
if ($line -cmatch '^\|\s*(\d+)\s*\|') {
$n = [int]$Matches[1]
if ($n -gt $max) { $max = $n }
}
}
}
return ($max + 1)
}
$nextPdr = Get-NextRecordNumber -Prefix 'PDR' -Dirs @($PdrDraftsDir, $PdrMemoryDir)
$nextPrdSection = Get-NextPrdSection -Path $PrdFile
$nextReqId = Get-NextReqId -Path $PrdFile
$nextFeatureNumber = Get-NextFeatureNumber -Path $PrdFile
if ($Json) {
Write-Output (@{ REPO_ROOT=$RepoRoot; PDR_DRAFTS_DIR=$PdrDraftsDir; PDR_MEMORY_DIR=$PdrMemoryDir; PRD_FILE=$PrdFile; SECTIONS_DIR=$SectionsDir; STATE_FILE=$StateFile; accepted_count=$acceptedCount; next_pdr=$nextPdr; next_prd_section=$nextPrdSection; next_req_id=$nextReqId; next_feature_number=$nextFeatureNumber } | ConvertTo-Json)
} else {
Write-Output "[INFO] product-implement setup"
Write-Output " Accepted PDRs: $acceptedCount"
Write-Output " PRD_FILE: $PrdFile"
Write-Output " SECTIONS_DIR: $SectionsDir"
Write-Output " Next PDR slot: PDR-$nextPdr"
Write-Output " Next PRD section: $nextPrdSection"
Write-Output " Next REQ id: REQ-$nextReqId"
Write-Output " Next feature row: $nextFeatureNumber"
}
scripts/powershell/validate-pdr.ps1
# validate-pdr.ps1 — PDR Validation Script for individual PDR files
# PowerShell twin of bash/validate-pdr.sh; one behavior, two platform
# scripts; keep in lockstep.
# Usage: validate-pdr.ps1 [PDR_FILE]
$ErrorActionPreference = 'Stop'
$script:ErrorView = 'ConciseView'
$PDR_FILE = if ($args.Count -ge 1) { [string]$args[0] } else { '' }
if ($PDR_FILE -eq '' -or -not (Test-Path -LiteralPath $PDR_FILE -PathType Leaf)) {
[Console]::Error.WriteLine("ERROR: PDR file not found: $PDR_FILE")
[Console]::Error.WriteLine("Usage: validate-pdr.ps1 <pdr-file.md>")
exit 1
}
$ERRORS = 0
$WARNINGS = 0
function pass([string]$msg) { [Console]::Out.WriteLine(" ✓ $msg") }
function warn([string]$msg) { [Console]::Out.WriteLine(" ⚠ $msg"); $script:WARNINGS++ }
function fail([string]$msg) { [Console]::Out.WriteLine(" ✗ $msg"); $script:ERRORS++ }
# PowerShell -match is case-insensitive (grep -qi parity); -cmatch where the
# bash grep is case-sensitive.
$lines = Get-Content -LiteralPath $PDR_FILE
$content = $lines -join "`n"
[Console]::Out.WriteLine("🔍 Validating PDR: $PDR_FILE")
# Check required sections
foreach ($header in 'Context', 'Decision', 'Consequences') {
if ($content -imatch "(?m)^###?\s*$header") {
pass "Has '$header' section"
} else {
fail "Missing '$header' section"
}
}
# Check status
if ($content -imatch '(?m)^\*\*Status\*\*' -or $content -imatch '(?m)^\*\*(Proposed|Accepted|Discovered|Deprecated|Superseded)') {
pass "Has valid status"
} else {
warn "No valid status marker found"
}
# Check alternatives
if ($content -imatch "Alternatives Considered") {
pass "Has 'Alternatives Considered' section"
} else {
warn "Missing 'Alternatives Considered'"
}
# Check for placeholders
if ($content -cmatch '\[(Category|Problem|Decision Title|Owner)\]') {
warn "Contains template placeholders"
}
# Summary
[Console]::Out.WriteLine("")
if ($ERRORS -eq 0 -and $WARNINGS -eq 0) {
[Console]::Out.WriteLine("✓ PDR validation passed")
exit 0
} elseif ($ERRORS -eq 0) {
[Console]::Out.WriteLine("⚠ $WARNINGS warning(s)")
exit 2
} else {
[Console]::Out.WriteLine("✗ $ERRORS error(s) and $WARNINGS warning(s)")
exit 1
}
scripts/powershell/validate-prd.ps1
# validate-prd.ps1 — PRD Validation Script v1.5.7
# PowerShell twin of bash/validate-prd.sh; one behavior, two platform
# scripts; keep in lockstep.
# Validates PRD compliance with product extension standards
# Checks: Section 1 = Doc Info, in-section diagrams, Mermaid, business sections, self-contained, PDR traceability
# Usage: validate-prd.ps1 [PRD_FILE] [--strict|--warn]
#
# Exit codes:
# 0 = All checks passed
# 1 = Critical failures (in strict mode) or validation errors
# 2 = Warnings only (default warn mode)
$ErrorActionPreference = 'Stop'
$script:ErrorView = 'ConciseView'
# Colors for output
$RED = "`e[0;31m"
$YELLOW = "`e[1;33m"
$GREEN = "`e[0;32m"
$BLUE = "`e[0;34m"
$NC = "`e[0m" # No Color
# Configuration
$STRICT_MODE = $false
$WARN_MODE = $true
$PRD_FILE = if ($args.Count -ge 1) { [string]$args[0] } else { 'docs/adlc/product/PRD.md' }
# dual-read: bare invocation on a legacy-only repo falls back to the
# repo-root PRD.md (mirrors the dual-check in setup-product-analyze.sh).
# An explicit path argument is always honored verbatim.
if ($args.Count -eq 0 -and -not (Test-Path -LiteralPath $PRD_FILE) -and (Test-Path -LiteralPath 'PRD.md')) {
$PRD_FILE = 'PRD.md'
}
$WARNINGS = 0
$ERRORS = 0
function Show-Help {
Write-Output "Usage: $PSCommandPath [PRD_FILE] [--strict|--warn]"
Write-Output ""
Write-Output "Validates PRD compliance with product extension v1.5.6 standards"
Write-Output ""
Write-Output "Options:"
Write-Output " --strict Exit with error on any issue (default: warn only)"
Write-Output " --warn Show warnings but don't exit with error (default)"
Write-Output " --help Show this help message"
Write-Output ""
Write-Output "Examples:"
Write-Output " $PSCommandPath # Validate PRD.md with warnings"
Write-Output " $PSCommandPath my-prd.md --strict # Strict validation of my-prd.md"
Write-Output " $PSCommandPath .adlc/product/sections/ecosystem/overview.md # Validate section"
}
# Parse arguments
foreach ($arg in $args) {
switch ([string]$arg) {
'--strict' {
$STRICT_MODE = $true
$WARN_MODE = $false
}
'--warn' {
$STRICT_MODE = $false
$WARN_MODE = $true
}
'--help' { Show-Help; exit 0 }
'-h' { Show-Help; exit 0 }
}
}
# Check if file exists
if (-not (Test-Path -LiteralPath $PRD_FILE -PathType Leaf)) {
[Console]::Out.WriteLine(('{0}ERROR: PRD file not found: {1}{2}' -f $RED, $PRD_FILE, $NC))
exit 1
}
# Read the file once; $lines is 1-based-mapped via [$i-1].
$lines = @(Get-Content -LiteralPath $PRD_FILE)
$content = $lines -join "`n"
# Function to report success
function pass {
param([string]$msg)
[Console]::Out.WriteLine(('{0}✓{1} {2}' -f $GREEN, $NC, $msg))
}
# Function to report warning
function warn {
param([string]$msg)
if ($WARN_MODE) {
[Console]::Out.WriteLine(('{0}⚠ WARNING{1}: {2}' -f $YELLOW, $NC, $msg))
} else {
[Console]::Out.WriteLine(('{0}✗ ERROR{1}: {2}' -f $RED, $NC, $msg))
}
$script:WARNINGS++
}
# Function to report error
function fail {
param([string]$msg)
[Console]::Out.WriteLine(('{0}✗ FAIL{1}: {2}' -f $RED, $NC, $msg))
$script:ERRORS++
}
# grep -c parity: count LINES matching (case-sensitive unless noted).
function Count-Lines {
param([string]$Pattern)
return @($lines | Where-Object { $_ -cmatch $Pattern }).Count
}
[Console]::Out.WriteLine(('{0}🔍 Validating PRD: {1}{2}' -f $BLUE, $PRD_FILE, $NC))
$modeLabel = if ($STRICT_MODE) { 'STRICT' } else { 'WARN' }
[Console]::Out.WriteLine(('{0}Extension Version: 1.5.7 | Mode: {1}{2}' -f $BLUE, $modeLabel, $NC))
[Console]::Out.WriteLine("================================================")
# ============================================
# CHECK 1: Section 1 is Document Information (v1.5.6)
# ============================================
[Console]::Out.WriteLine("")
[Console]::Out.WriteLine(('{0}[1/9] Checking Section 1 is Document Information...{1}' -f $BLUE, $NC))
# Get the first section header
$FIRST_SECTION = @($lines | Where-Object { $_ -cmatch '^## [0-9]+\.?\s' } | Select-Object -First 1)
if ($FIRST_SECTION.Count -eq 0) {
fail "No numbered sections found (## 1. format required)"
} else {
if ($FIRST_SECTION[0] -imatch '^## 1\.\s*Document Information') {
pass "Section 1 is 'Document Information' (v1.5.6 compliant)"
} elseif ($FIRST_SECTION[0] -imatch '^## 1\.\s*Visual Summary') {
warn "Section 1 is 'Visual Summary' - v1.5.6 requires 'Document Information' as Section 1 (diagrams are now in-section)"
} else {
warn "Section 1 is: $($FIRST_SECTION[0])"
warn "Expected Section 1 to be 'Document Information' per v1.5.6 template"
}
}
# Check that Visual Summary does NOT exist as a separate section
if ($content -imatch '(?m)^## [0-9]*\.?\s*Visual Summary') {
warn "Found 'Visual Summary' as a separate section - v1.5.6 embeds diagrams in-section instead"
} else {
pass "No separate Visual Summary section (diagrams are embedded in-section per v1.5.6)"
}
# ============================================
# CHECK 2: No ASCII Diagrams in Main Content
# ============================================
[Console]::Out.WriteLine("")
[Console]::Out.WriteLine(('{0}[2/9] Checking for ASCII diagrams...{1}' -f $BLUE, $NC))
# Box-drawing characters (Unicode range U+2500 to U+257F)
$ASCII_MATCHES = @(Get-Content -LiteralPath $PRD_FILE | Select-String -Pattern '[─━│┃┄┅┆┇┈┉┊┋┌┍┎┏┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋╌╍╎╏═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟╠╡╢╣╤╥╦╧╨╩╪╫╬╭╮╯╰╱╲╳╴╵╶╷╸╹╺╻╼╽╾╿]')
if ($ASCII_MATCHES.Count -gt 0) {
# Check context - are they in <details> blocks?
$OUTSIDE_DETAILS = $false
foreach ($m in $ASCII_MATCHES) {
$LINE_NUM = $m.LineNumber
# Simple check: count details tags before this line (head -n N parity)
$head = if ($LINE_NUM -ge 1) { $lines[0..($LINE_NUM - 1)] } else { @() }
$DETAILS_OPEN = @($head | Where-Object { $_ -cmatch '<details>' }).Count
$DETAILS_CLOSE = @($head | Where-Object { $_ -cmatch '</details>' }).Count
if ($DETAILS_OPEN -le $DETAILS_CLOSE) {
$OUTSIDE_DETAILS = $true
break
}
}
if ($OUTSIDE_DETAILS) {
fail "ASCII box-drawing characters found OUTSIDE <details> blocks"
foreach ($m in (@($ASCII_MATCHES) | Select-Object -First 3)) {
[Console]::Out.WriteLine(" Line: $($m.LineNumber):$($m.Line)")
}
if ($ASCII_MATCHES.Count -gt 3) {
[Console]::Out.WriteLine(" ... and $($ASCII_MATCHES.Count - 3) more lines")
}
[Console]::Out.WriteLine("")
[Console]::Out.WriteLine(" ASCII diagrams are ONLY allowed in <details> blocks as fallbacks.")
[Console]::Out.WriteLine(' Main content MUST use Mermaid (```mermaid)')
} else {
pass "ASCII characters found only in <details> blocks (allowed as fallback)"
}
} else {
pass "No ASCII box-drawing characters found"
}
# ============================================
# CHECK 3: Mermaid Diagrams Present
# ============================================
[Console]::Out.WriteLine("")
[Console]::Out.WriteLine(('{0}[3/9] Checking Mermaid diagrams...{1}' -f $BLUE, $NC))
$MERMAID_COUNT = Count-Lines '^\s*```mermaid'
if ($MERMAID_COUNT -eq 0) {
fail "No Mermaid diagrams found - MUST have at least 1"
[Console]::Out.WriteLine(' Use: ```mermaid blocks with flowchart/stateDiagram/etc')
} elseif ($MERMAID_COUNT -lt 2) {
warn "Only $MERMAID_COUNT Mermaid diagram(s) - recommend 2+ (hierarchy, deps, flows)"
} else {
pass "Found $MERMAID_COUNT Mermaid diagrams"
}
# Check for deprecated 'graph' keyword
$GRAPH_COUNT = Count-Lines '^\s*graph\s+(TD|TB|LR|BT|RL)'
if ($GRAPH_COUNT -gt 0) {
warn "Found $GRAPH_COUNT deprecated 'graph' keyword(s) - use 'flowchart' (Mermaid v10+)"
}
# ============================================
# CHECK 4: No Unfilled Placeholders
# ============================================
[Console]::Out.WriteLine("")
[Console]::Out.WriteLine(('{0}[4/9] Checking for unfilled placeholders...{1}' -f $BLUE, $NC))
$PLACEHOLDER_PATTERNS = @(
'\[PRODUCT_NAME\]'
'\[FEATURE_AREA_NAME\]'
'\[PDR_IDS\]'
'\[DATE\]'
'\[PLACEHOLDER\]'
'\[TODO\]'
'\[TBD\]'
'\[Author\]'
'\[X\.X\]'
)
$FOUND_PLACEHOLDERS = 0
foreach ($pattern in $PLACEHOLDER_PATTERNS) {
$lineNums = @()
for ($i = 0; $i -lt $lines.Count; $i++) {
if ($lines[$i] -cmatch $pattern) { $lineNums += $i }
}
if ($lineNums.Count -gt 0) {
foreach ($i in $lineNums) {
fail "Unfilled placeholder: $($i + 1):$($lines[$i])"
}
$FOUND_PLACEHOLDERS += $lineNums.Count
}
}
if ($FOUND_PLACEHOLDERS -eq 0) {
pass "No unfilled placeholders found"
}
# ============================================
# CHECK 5: PDR Traceability
# ============================================
[Console]::Out.WriteLine("")
[Console]::Out.WriteLine(('{0}[5/9] Checking PDR traceability...{1}' -f $BLUE, $NC))
# Count requirements
$REQ_COUNT = Count-Lines '^\s*[-*+]?\s*\*\*REQ-[0-9]+:?\*\*'
# Count PDR references
$PDR_REFS = Count-Lines 'PDR-[0-9]+'
if ($REQ_COUNT -gt 0) {
pass "Found $REQ_COUNT requirements (REQ-XXX format)"
# Check if requirements have PDR references
$REQ_WITH_PDR = 0
for ($i = 0; $i -lt $lines.Count; $i++) {
if ($lines[$i] -cmatch '^\s*[-*+]?\s*\*\*REQ-[0-9]+:?\*\*') {
# Check next 3 lines for PDR reference (sed N,N+3 parity)
$end = [Math]::Min($i + 3, $lines.Count - 1)
$CONTEXT = $lines[$i..$end] -join "`n"
if ($CONTEXT -cmatch 'PDR-[0-9]+') {
$REQ_WITH_PDR++
}
}
}
if ($REQ_WITH_PDR -lt $REQ_COUNT) {
warn "Only $REQ_WITH_PDR/$REQ_COUNT requirements have PDR traceability"
[Console]::Out.WriteLine(' Each requirement MUST reference a source PDR')
} else {
pass "All requirements trace to PDRs"
}
} else {
warn "No REQ-XXX format requirements found - consider using this format for traceability"
}
# ============================================
# CHECK 6: Required Sections Present
# ============================================
[Console]::Out.WriteLine("")
[Console]::Out.WriteLine(('{0}[6/9] Checking required sections...{1}' -f $BLUE, $NC))
$REQUIRED_SECTIONS = @(
"1.*Document Information"
"2.*Overview"
"3.*Problem|3.*The Problem"
"4.*Goals|4.*Objectives"
"5.*Metrics|5.*Success Metrics"
"6.*Personas"
"7.*Functional Requirements|7.*Requirements"
"8.*Non-Functional|8.*NFRs"
"9.*Out of Scope"
"10.*Risks|10.*Mitigation"
"11.*Roadmap|11.*Milestones"
"12.*PDR Summary|12.*Product Decision"
)
$MISSING_SECTIONS = 0
foreach ($section_pattern in $REQUIRED_SECTIONS) {
if (-not ($content -imatch "(?m)^## [0-9]*\.?.*($section_pattern)")) {
warn "Missing or misnumbered section matching: $section_pattern"
$MISSING_SECTIONS++
}
}
if ($MISSING_SECTIONS -eq 0) {
pass "All required sections present and numbered"
}
# ============================================
# CHECK 6.5: Business Sections (v1.5.3)
# ============================================
[Console]::Out.WriteLine("")
[Console]::Out.WriteLine(('{0}[6.5/9] Checking business stakeholder sections (v1.5.6)...{1}' -f $BLUE, $NC))
$BUSINESS_SECTIONS = @(
"1\.5.*Executive Summary"
"3\.5.*Market Opportunity"
"10\.5.*Investment"
"11\.5.*Go-to-Market"
)
$MISSING_BUSINESS = 0
foreach ($biz_pattern in $BUSINESS_SECTIONS) {
if ($content -imatch "(?m)^## $biz_pattern") {
pass "Found business section: $biz_pattern"
} else {
warn "Missing business section: $biz_pattern (recommended for stakeholder PRDs)"
$MISSING_BUSINESS++
}
}
if ($MISSING_BUSINESS -eq 0) {
pass "All 4 business stakeholder sections present"
}
# ============================================
# CHECK 6.6: Self-Contained PRD (v1.5.3)
# ============================================
[Console]::Out.WriteLine("")
[Console]::Out.WriteLine(('{0}[6.6/9] Checking self-contained PRD (v1.5.6 - no external .adlc/ links)...{1}' -f $BLUE, $NC))
# Check for reader-facing links to .adlc/ paths
$EXTERNAL_LINKS_FILTERED = @()
for ($i = 0; $i -lt $lines.Count; $i++) {
if ($lines[$i] -cmatch '\]\(\.adlc/') {
# Skip if inside HTML comment
if (-not ($lines[$i] -cmatch '<!--')) {
$EXTERNAL_LINKS_FILTERED += "$($i + 1):$($lines[$i])"
}
}
}
if ($EXTERNAL_LINKS_FILTERED.Count -gt 0) {
warn "PRD contains reader-facing links to .adlc/ files (should be self-contained)"
foreach ($line in ($EXTERNAL_LINKS_FILTERED | Select-Object -First 5)) {
[Console]::Out.WriteLine(" $line")
}
[Console]::Out.WriteLine(' Use in-document anchors instead: [Section 1.1](#11-feature-hierarchy)')
} else {
pass "PRD is self-contained (no reader-facing .adlc/ links)"
}
# ============================================
# CHECK 7: Constitution Alignment Claims
# ============================================
[Console]::Out.WriteLine("")
[Console]::Out.WriteLine(('{0}[7/9] Checking constitution alignment...{1}' -f $BLUE, $NC))
if ($content -imatch 'Constitution Alignment|Aligns with Constitution') {
pass "Constitution alignment section found"
# Check if constitution is populated (not just template)
# dual-read: canonical docs/adlc/memory first, legacy fallback.
$CONST_FILE = 'docs/adlc/memory/constitution.md'
if (-not (Test-Path -LiteralPath $CONST_FILE)) {
$CONST_FILE = '.adlc/memory/constitution.md'
}
if (Test-Path -LiteralPath $CONST_FILE -PathType Leaf) {
$constContent = (Get-Content -Raw -LiteralPath $CONST_FILE)
if ($constContent -cmatch '\[PRINCIPLE_[0-9]+_NAME\]|\[PROJECT_NAME\]') {
warn "Constitution file contains template placeholders - populate or remove alignment claims"
} else {
pass "Constitution file appears to be populated"
}
} else {
warn "Constitution file not found at $CONST_FILE"
}
} else {
warn "No constitution alignment section found (recommended but not required)"
}
# ============================================
# SUMMARY
# ============================================
[Console]::Out.WriteLine("")
[Console]::Out.WriteLine("================================================")
[Console]::Out.WriteLine(('{0}Validation Summary{1}' -f $BLUE, $NC))
[Console]::Out.WriteLine("================================================")
if ($ERRORS -eq 0 -and $WARNINGS -eq 0) {
[Console]::Out.WriteLine(('{0}✓ All checks passed! PRD is compliant with v1.5.6{1}' -f $GREEN, $NC))
[Console]::Out.WriteLine("")
[Console]::Out.WriteLine("You may proceed to mark sections as 'completed'")
exit 0
} elseif ($ERRORS -eq 0) {
[Console]::Out.WriteLine(('{0}⚠ {1} warning(s) found{2}' -f $YELLOW, $WARNINGS, $NC))
[Console]::Out.WriteLine(" PRD is usable but has issues that should be addressed")
[Console]::Out.WriteLine("")
if ($STRICT_MODE) {
[Console]::Out.WriteLine(('{0}Exiting with error (strict mode){1}' -f $RED, $NC))
[Console]::Out.WriteLine(" Fix all warnings before marking 'completed'")
exit 1
} else {
[Console]::Out.WriteLine(" Run with --strict to enforce no warnings")
exit 2
}
} else {
[Console]::Out.WriteLine(('{0}✗ {1} error(s) and {2} warning(s) found{3}' -f $RED, $ERRORS, $WARNINGS, $NC))
[Console]::Out.WriteLine("")
[Console]::Out.WriteLine("PRD is NON-COMPLIANT with v1.5.6 standards")
[Console]::Out.WriteLine("Fix all errors before proceeding")
exit 1
}
SKILL.md
---
name: product-implement
description: Use when accepted PDRs exist and PRD.md must be generated or updated.
disable-model-invocation: true
---
# product-implement
> **Scope rule**: scripts stay skill-local — never source a script from another skill. For another skill's operations, invoke that skill (e.g. `architect-clarify`, `product-clarify`, `team-boot`) via the Skill tool; for its documents, follow the `Requires` line. `scripts/` and `references/` below are relative to this skill. `{REPO_ROOT}` denotes the project root (where `.adlc/` lives).
## What this skill does
Transforms **accepted PDRs** into a comprehensive, self-contained `PRD.md` using a **three-phase DAG**:
1. **Plan Agent**: Analyze PDRs, detect feature-areas, generate customized DAG, get user approval
2. **Execute Agent**: Generate sections per feature-area with **mandatory checkpoint after Requirements**
3. **Summarize Agent**: Aggregate sections, resolve conflicts, produce unified `PRD.md`
**Output**:
- `docs/adlc/product/PRD.md` — self-contained product requirements (the shared layout; legacy repo-root `PRD.md` stays read-compatible)
- `{REPO_ROOT}/.adlc/product/sections/{feature-area}/{section}.md` — intermediate section files
- Accepted PDRs **moved** to `{REPO_ROOT}/docs/adlc/memory/pdr/` (the shared layout canonical memory root)
## When to use
- After `/product-clarify` has approved PDRs
- After `/product-init` to document existing product
- PDR updates requiring PRD regeneration
## When NOT to use
- No Accepted PDRs (run `/product-clarify` first)
- Minor PRD edits (edit `PRD.md` directly)
## Pre-Flight Validation
**Before starting, verify prerequisites:**
1. **Check PDRs exist**: `{REPO_ROOT}/.adlc/drafts/pdr/PDR-*.md`
2. **Check for Accepted PDRs**: Count files with status "Accepted"
- If **zero**: STOP and output:
```
Cannot proceed: No Accepted PDRs found.
Run /product-clarify to review and approve PDRs first.
```
- If ≥1: Proceed
## Three-Phase DAG Workflow
```
┌─────────────────────────────────────────────────────────┐
│ PHASE 1: PLAN (Plan Agent) │
│ Load PDRs → Detect Feature-Areas → Generate DAG → Approve│
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ PHASE 2: EXECUTE (Execute Agent) │
│ Overview → Problem → Goals → Metrics → Personas │
│ → [REQUIREMENTS CHECKPOINT] ← MANDATORY USER APPROVAL │
│ → NFRs → Out-of-Scope → Risks → Roadmap → PDR-Summary │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ PHASE 3: SUMMARIZE (Summarize Agent) │
│ Read sections → Detect conflicts → Resolve → PRD.md │
└─────────────────────────────────────────────────────────┘
```
## Execution Steps
### Phase 1: Plan
**Step 1.1: Load and Analyze PDRs**
1. Read all `PDR-*.md` files from `.adlc/drafts/pdr/`
2. Filter to **Accepted** status only
3. Parse feature-area from each PDR
4. Group PDRs by feature-area
**Step 1.2: Detect Feature-Area Characteristics**
| Characteristic | Detection Pattern | DAG Customization |
|---------------|-------------------|-------------------|
| B2B SaaS | Enterprise, admin, SSO | Include compliance sections |
| Consumer App | Mobile, freemium, social | Simplify requirements |
| Platform | API, integrations, developer | Expand NFRs |
| Marketplace | Multi-sided, transaction | Add business model sections |
**Step 1.3: Generate Customized DAG**
**Default DAG** (all 15 sections):
```
Document Information → Executive Summary → Overview → Problem
→ Market Opportunity → Goals → Metrics → Personas
→ [CHECKPOINT: Requirements]
→ NFRs → Out-of-Scope → Risks → Investment
→ Roadmap → Go-to-Market → PDR-Summary
```
**Section numbering** (fixed):
- 1. Document Information
- 1.5 Executive Summary
- 2. Overview
- 3. The Problem
- 3.5 Market Opportunity
- 4. Goals & Objectives
- 5. Success Metrics
- 6. Personas
- 7. Functional Requirements
- 8. Non-Functional Requirements
- 9. Out of Scope
- 10. Risks & Mitigation
- 10.5 Investment & Resources
- 11. Roadmap & Milestones
- 11.5 Go-to-Market Strategy
- 12. PDR Summary
**Step 1.4: Present Plan for Approval**
```markdown
## DAG Execution Plan
**Feature-Areas detected**: 3
**Total sections**: 15
**Feature-Area: Core**
**PDRs**: PDR-001, PDR-005, PDR-008
**DAG**: Document Info → Executive Summary → Overview → Problem
→ Market Opportunity → Goals → Metrics → Personas
→ [Requirements Checkpoint] → NFRs → Out-of-Scope → Risks
→ Investment → Roadmap → GTM → PDR-Summary
**Approve this plan?** [Yes/Modify/Cancel]
```
**Step 1.5: Write state.json**
```json
{
"version": "1.0",
"phase": "plan_approved",
"feature_areas": [
{
"id": "core",
"name": "Core",
"pdrs": ["PDR-001", "PDR-005"],
"dag": ["document-info", "executive-summary", "overview", "problem", ...],
"progress": {}
}
],
"checkpoint": {
"enabled": true,
"after_section": "requirements",
"status": "pending"
}
}
```
### Phase 2: Execute
For each section in the DAG:
1. **Check dependencies** — ensure all prerequisites completed
2. **Load section template** — the canonical template `skills/product/templates/sections/{section}.md` in the adlc-team-skills checkout. Templates ship only with the owning skill — the installed `product-clarify` skill has no `templates/` directory, so a relative `../../product-clarify/templates/` path resolves nowhere; do not use it. If the file is absent in your installed layout, generate the section from the PRD structure outline in Step 3.3 instead.
3. **Generate content** — fill template with PDR-derived content
4. **Write section file** — `.adlc/product/sections/{feature-area}/{section}.md`
5. **Validate** — run `scripts/bash/validate-prd.sh {section}.md`
6. **Update state.json** — mark section as "completed"
**Section template usage** (MANDATORY):
- Read template FIRST
- Fill ALL [PLACEHOLDERS]
- NEVER generate from scratch
**In-section diagrams** (MANDATORY):
- Use ` ```mermaid ` code blocks
- Use `flowchart` keyword (NOT deprecated `graph`)
- ASCII box-drawing characters are PROHIBITED
- Diagrams embedded in their home sections
| Section | Diagram Type | Subsection |
|---------|-------------|------------|
| 2. Overview | Feature Hierarchy (`flowchart TD`) | 2.4 |
| 2. Overview | Architecture (`flowchart TB`) | 2.5 |
| 6. Personas | User Journey (`journey`) | 6.4 |
| 7. Requirements | Req Dependencies (`flowchart LR`) | 7.4 |
| 7. Requirements | Feature Dependencies (`flowchart LR`) | 7.5 |
| 11. Roadmap | Gantt Chart (`gantt`) | 11.1 |
**Requirements Checkpoint** (MANDATORY):
After generating Requirements section:
```markdown
## CHECKPOINT: Requirements Section Complete
The Requirements section has been generated.
**Why checkpoint here?** Requirements shapes:
- NFRs (how requirements are met)
- Out-of-Scope (what's NOT required)
- Risks (technical feasibility)
- Roadmap (priority and sequencing)
**Options**:
A) Approve — Continue to remaining sections
B) Modify — Edit requirements, then continue
C) Restart — Regenerate from Problem phase
D) Cancel — Stop execution
```
### Phase 3: Summarize
**Step 3.1: Read All Sections FROM DISK**
> CRITICAL: Read each section file from filesystem. Do NOT use content from memory.
1. Scan `.adlc/product/sections/` for all `.md` files
2. Read each file
3. Validate: ≥20 lines, proper headers
**Step 3.2: Detect Cross-Feature-Area Conflicts**
| Conflict Type | Detection | Resolution |
|--------------|-----------|------------|
| Duplicate requirements | Same requirement, different wording | Standardize to PDR terminology |
| Priority mismatch | Same feature, different priority | Defer to PDR |
| Metric inconsistency | Same metric, different definition | Use PDR definition |
**Step 3.3: Aggregate into PRD.md**
> CRITICAL: PRD.md MUST be SELF-CONTAINED.
> - ALL diagrams embedded IN-SECTION
> - ZERO reader-facing links to `.adlc/` paths
> - Use in-document anchors only: `[Section 2.4](#24-feature-hierarchy)`
> - PDR references as plain text: `plain-text PDR references` (NOT linked)
**PRD structure** (must match template):
```markdown
# Product Requirements Document: [Product Name]
## 1. Document Information
[Quick Stats, revision history, approval]
## 1.5 Executive Summary
[Business case, ROI, recommendation]
## 2. Overview
[Product description, scope]
### 2.4 Feature Hierarchy [MERMAID flowchart TD]
### 2.5 Architecture Overview [MERMAID flowchart TB]
## 3. The Problem
[Problem statement, validation evidence]
## 3.5 Market Opportunity
[TAM/SAM/SOM, competitive landscape]
## 4. Goals & Objectives
[Primary, technical, business goals traced to PDRs]
## 5. Success Metrics
[Adoption, engagement, quality]
### 5.5 Business Outcome Metrics
### 5.6 Financial Metrics
## 6. Personas
[Primary, secondary, anti-personas]
### 6.4 User Journey [MERMAID journey]
## 7. Functional Requirements [CHECKPOINT]
[User stories, REQ-XXX IDs, priority matrix]
### 7.4 Requirement Dependencies [MERMAID flowchart LR]
### 7.5 Feature Dependencies [MERMAID flowchart LR]
## 8. Non-Functional Requirements
[Performance, security, reliability, scalability]
## 9. Out of Scope
[Feature, technical, market exclusions]
## 10. Risks & Mitigation
[Risk summary, technical, market, operational]
### 10.4 Business Risks
## 10.5 Investment & Resources
[Team, budget, ROI, go/no-go criteria]
## 11. Roadmap & Milestones
### 11.1 Roadmap Overview [MERMAID gantt]
[Milestone details with demo sentences]
### 11.2 Milestone Gates & Progress
[Per milestone: done-means definition, feature rollup, gate table, issue/evidence status — sourced from milestone PDRs]
## 11.5 Go-to-Market Strategy
[Launch phases, pricing, messaging]
## 12. PDR Summary
[Key decisions, constitution alignment — NO external links]
```
### Phase 4: PDR Lifecycle Management (MANDATORY)
**Requires:** the `product-clarify` skill for PDR lifecycle operations (index generation, promotion, clarification): `adlc-cli skills add tikalk/adlc-team-skills --skill product-clarify`
**Step 4.1: Move Accepted PDRs to Memory (atomic — owned by `product-clarify`)**
Invoke the `product-clarify` skill for the promotion; its `move_pdr` operation moves each Accepted PDR with an atomic `mv` (no copy-then-delete duplication risk) and regenerates both scopes' indexes automatically. Pass the Accepted PDR IDs (`<list of Accepted PDR IDs>`) with scope `drafts memory`.
- PDRs with status "Accepted" are moved (not copied) from `.adlc/drafts/pdr/` to `docs/adlc/memory/pdr/`.
- **Do NOT change status to "Completed"** — keep status as "Accepted" so the index header ("Accepted PDRs only") remains truthful.
- Proposed/Discovered PDRs remain in drafts (not moved).
- Both `drafts/pdr/pdr.md` and `memory/pdr/pdr.md` indexes are regenerated by `move_pdr`.
**Step 4.2: Generate Memory PDR Index (MANDATORY — script-driven)**
The promotion in Step 4.1 already regenerates the memory index. To manually regenerate (e.g., after bulk edits to PDR files), invoke the `product-clarify` skill and run its `generate_pdr_index memory` operation.
This writes `{REPO_ROOT}/docs/adlc/memory/pdr/pdr.md` using a frontmatter-primary + heading-fallback parser that handles both `##` (H2 legacy) and `###` (H3 current) metadata. Blank cells trigger a stderr warning and defaults are applied — no silent blank rows.
The generated index has this format:
```markdown
# Product Decision Records (Memory)
> Auto-generated by /product-implement. Accepted PDRs only.
> Source: docs/adlc/memory/pdr/PDR-*.md
## PDR Index
| ID | Feature-Area | Category | Status | Date | Owner | Title |
|----|--------------|----------|--------|------|-------|-------|
| PDR-001 | control-plane | Governance | Accepted | 2026-08-04 | User/AI collaboration | Lit Factory Operating Model |
```
This index is consumed by `team-boot` for session-start injection (similar to how `CDR.md` is used for team-level context).
**Step 4.3: Update state.json**
```json
{
"phase": "completed",
"pdr_lifecycle": {
"pdrs_promoted": [N],
"memory_pdr_moved": true,
"drafts_retained": true,
"drafts_reason": "Proposed/Discovered PDRs remain",
"memory_index_generated": true
}
}
```
### Phase 5: Final Verification
Before marking complete, verify ALL checks:
| # | Check | Expected |
|---|-------|----------|
| 1 | Section files on disk | N files in `.adlc/product/sections/` |
| 2 | PRD.md exists | Yes |
| 3 | PRD.md content size | >200 lines |
| 4 | PRD.md has all sections | Sections 1-12 + sub-sections |
| 5 | PRD.md is self-contained | 0 `.adlc/` links |
| 6 | Diagrams embedded | ≥4 `mermaid` blocks |
| 7 | Memory PDRs written | `docs/adlc/memory/pdr/PDR-*.md` exist |
| 8 | Memory PDR index generated | `docs/adlc/memory/pdr/pdr.md` exists with correct table |
| 9 | state.json consistent | All sections "completed" |
**Gate Rule**: If ANY check fails → do NOT mark as completed. Report failures.
## Claiming Ledger Slots (do this before writing any numbered id)
A number computed from a stale base is wrong even when you compute it
correctly — the working tree simply does not know what the merge target has
already used. Check the base first, then read each number from the setup
output; never eyeball it.
```bash
# 1. Refuse to proceed on a stale base.
git fetch origin --quiet
test "$(git rev-list --count "HEAD..${MERGE_TARGET:-origin/main}")" = 0 \
|| { echo "base is behind ${MERGE_TARGET:-origin/main}; rebase before claiming a slot" >&2; exit 1; }
# 2. Read the next free slot for each type from the setup JSON.
bash scripts/bash/setup-product-implement.sh --json
```
| Setup JSON field | Slot | Example |
|---|---|---|
| `next_pdr` | record id (`PDR-{NNN}`) — drafts + published | `138` |
| `next_prd_section` | `## N.` delta heading in PRD.md | `27` |
| `next_req_id` | `REQ-NNN` | `217` |
| `next_feature_number` | `\| N \|` Feature row | `70` |
The setup script vendors these scans (scripts stay skill-local), so the numbers
come from the checkout rather than from a helper that may not be installed.
Each scan is deliberately over-inclusive: a match anywhere in the document
raises the result. That is the safe direction — an unused number is harmless,
a reissued one collides.
## PDR Traceability Rules
- **Every section** must reference source PDRs with ID
- **Every requirement** (REQ-XXX) must trace to a PDR
- **No content** without PDR backing
- **PDRs are source of truth** for conflict resolution
## Configuration
- `PDR_DRAFTS_DIR` — `{REPO_ROOT}/.adlc/drafts/pdr`
- `PDR_MEMORY_DIR` — `{REPO_ROOT}/docs/adlc/memory/pdr`
- `PRD_FILE` — `{REPO_ROOT}/docs/adlc/product/PRD.md`
- `SECTIONS_DIR` — `{REPO_ROOT}/.adlc/product/sections`
- `STATE_FILE` — `{REPO_ROOT}/.adlc/product/state.json`
## 12-Factor Alignment
- **Factor III (Mission Definition)**: Compiles mission decisions into actionable requirements
- **Factor IV (Structured Planning)**: DAG orchestration separates planning from execution
- **Factor IX (Traceability)**: Every PRD element traces back to a PDR
## Common Rationalizations
| Rationalization | Reality |
|-----------------|---------|
| "I'll skip the checkpoint and just generate everything." | Requirements shapes NFRs, Out-of-Scope, Risks, and Roadmap. Skipping the checkpoint risks cascading errors. |
| "The PRD can reference section files." | PRD.md MUST be self-contained. External references break when section files are moved or deleted. |
| "I don't need to move PDRs to memory." | Without promotion, drafts and memory diverge. The next clarify session sees stale data. |
## Red Flags
- **Generating PRD from non-Accepted PDRs** — implement skips Proposed/Discovered; the PRD will be incomplete.
- **Writing PRD.md directly from PDRs** — content MUST come from section files to ensure validation passed.
- **Missing the Requirements checkpoint** — this is the cornerstone section; errors here cascade.
- **Leaving `.adlc/` links in PRD.md** — breaks self-containment; readers cannot follow internal paths.
## Verification
- [ ] Pre-flight: ≥1 Accepted PDR exists
- [ ] Plan approved by user
- [ ] state.json written with DAG
- [ ] Each section file ≥20 lines
- [ ] validate-prd.sh passes for each section
- [ ] Requirements checkpoint approved by user
- [ ] PRD.md >200 lines with all 15 sections
- [ ] Zero `.adlc/` links in PRD.md
- [ ] ≥4 Mermaid diagrams embedded in-section
- [ ] All requirements trace to PDRs
- [ ] Accepted PDRs moved to `docs/adlc/memory/pdr/`
- [ ] Final completion verification: all 8 checks pass