Claude Skill

product-specify

Interactive PRD exploration and Product Decision Record (PDR) creation for greenfield products. Facilitates product discovery discussions, surfaces trade-offs, and documents decisions as individual PDR files. Use when starting a new product or major pivot.

LLM Mart · 0 points · 8 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download tikalk-adlc-team-skills-skills_product_product-specify-3035db2.zip · 13 KB
Part of tikalk/adlc-team-skills — 21 skills

Install

skills CLI npx skills add https://github.com/tikalk/adlc-team-skills/tree/main/skills/product/product-specify
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install tikalk-adlc-team-skills@llmmart
Git git clone https://github.com/tikalk/adlc-team-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole tikalk/adlc-team-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

product-specify

What this skill does

Transforms a high-level product idea into documented Product Decision Records (PDRs) through interactive exploration and trade-off analysis.

Key insight: Discussion and exploration happen before committing to formal documentation. The goal is to surface trade-offs, validate assumptions, and make informed decisions collaboratively.

Output: Individual PDR-{NNN}.md files (status Proposed) in .adlc/drafts/pdr/ with an auto-generated pdr.md index.

When to use

Note: Routine decision capture is handled by team-boot's continuous capture mechanism, which writes lightweight drafts directly to .adlc/drafts/. This skill is for interactive deep-dive exploration — when you want guided trade-off analysis, multi-option comparison, or structured decision facilitation before documenting.

  • New product from scratch
  • Major product pivots
  • Documenting verbal decisions formally
  • Team onboarding — walking through product rationale

When NOT to use

  • Existing product (use /product-init instead)
  • Minor PDR updates (use /product-clarify instead)

Execution Steps

Phase 0: Environment Setup

sh: scripts/bash/setup-product-specify.sh [--json]
ps: scripts/powershell/setup-product-specify.ps1

Setup output (JSON):

{
  "REPO_ROOT": "/path/to/project",
  "PDR_DRAFTS_DIR": "/path/to/project/.adlc/drafts/pdr",
  "PRD_FILE": "/path/to/project/PRD.md",
  "next_pdr": "001"
}

Phase 1: Feature-Area Decomposition (Optional)

Analyze the product for distinct business domains. Auto-decompose if multiple domains detected. Use --no-decompose to skip.

Present detected areas:

## Detected Feature Areas

| # | Feature Area | Key Domains | Rationale |
|---|--------------|-------------|-----------|
| 1 | **Auth** | Authentication, Authorization | Core user entry |
| 2 | **Core** | User Management, Profiles | Core data |
| 3 | **Business** | Payments, Subscriptions | Revenue domain |

Reply: Y to confirm, n for monolithic, or suggest changes.

Threshold:

  • ≤3 areas: Auto-approve
  • 4-6 areas: Confirm with user
  • 6 areas: Suggest grouping

Phase 2: Product Analysis

Extract product drivers:

  1. Problem Drivers: Core problem, who experiences it, current workarounds
  2. Market Drivers: Target segments, competitive landscape, trends
  3. Business Drivers: Revenue model, scaling expectations, strategic importance
  4. Constraint Drivers: Technology mandates, budget, team skills, regulatory
  5. Load Constitution: Read {REPO_ROOT}/.adlc/memory/constitution.md if exists
  6. Check Existing Docs: Scan README.md, AGENTS.md, CONTRIBUTING.md for context

Phase 3: Product Exploration (Interactive)

For each major decision area, present options and facilitate discussion:

Decision areas (5-7 key decisions):

  1. Problem Scope
  2. Target Personas
  3. Solution Approach (build vs buy vs partner)
  4. Monetization
  5. Go-to-Market
  6. Success Metrics

Exploration format:

## Product Decision: [Decision Area]

**Context**: [Why this decision matters]

**Options**:
| Option | Description | Trade-offs |
|--------|-------------|------------|
| A | [Option A] | Pros: [X] / Cons: [Y] |
| B | [Option B] | Pros: [X] / Cons: [Y] |

**Recommended**: Option [X]

**Questions**:
1. [Question about constraints]
2. [Question about trade-off priorities]

Rules:

  • Present one decision area at a time
  • Always provide a recommended option with reasoning
  • Ask targeted questions to surface hidden requirements
  • Skip decisions already determined by context or constitution
  • Limit to 5-7 key decisions

Phase 4: Cross-Feature-Area Pre-Analysis

During exploration, watch for:

Pattern Detection Action
Shared Personas Same user type in multiple areas Note for cross-area metadata
Priority Tensions Areas prioritize differently ⚠️ Flag potential conflict
Feature Overlap Similar features in different areas ⚠️ Flag for consolidation
Metric Conflicts Same metric, different targets ⚠️ Flag for alignment

Phase 5: Decision Documentation

After each decision is confirmed, create a PDR file.

PDR file format (individual file, YAML frontmatter — use the shared template):

Use the template at {REPO_ROOT}/.agents/skills/product/templates/pdr-template.md as the canonical PDR format. The template includes YAML frontmatter (status, date, owner, category, feature-area, title) as the single source of truth for index generation, plus the standard PDR body sections (Context, Decision, Consequences, Alternatives, Links).

---
status: proposed
date: YYYY-MM-DD
owner: [User/AI collaboration]
category: Feature
feature-area: [core | business | growth | ...]
title: [Decision Title]
---

# PDR-[NNN]: [Decision Title]

## Context
**Problem/Opportunity:**
[Clear description]

**Market Forces:**
- [Market factor 1]
- [Customer feedback]

## Decision
**Decision Statement:**
[Clear statement of what was decided]

**Rationale:**
[Why this option was chosen]

### Consequences
#### Positive
- [Benefit 1]

#### Negative
- [Trade-off 1]

#### Risks
- [Risk with mitigation]

### Success Metrics
| Metric | Target | Measurement Method |
|--------|--------|-------------------|
| [Metric] | [Target] | [Method] |

### Alternatives Considered
#### Option A: [Alternative Name]
**Description:** [Brief description]
**Trade-offs:** [Neutral comparison]

### Constitution/Vision Alignment
| Principle | Alignment | Notes |
|-----------|-----------|-------|
| [Vision Principle] | ✅ Compliant / ⚠️ Deviation | [Explanation] |

Phase 6: Write PDR Files and Regenerate Index

  1. Number sequentially: Start from highest existing PDR number + 1
  2. Write individual files: {REPO_ROOT}/.adlc/drafts/pdr/PDR-{NNN}.md
  3. Regenerate index: {REPO_ROOT}/.adlc/drafts/pdr/pdr.md

Index format:

# Product Decision Records

## PDR Index

| ID | Feature-Area | Category | Status | Date | Owner |
|----|--------------|----------|--------|------|-------|
| PDR-001 | System | Target Market | Proposed | 2026-03-09 | User/AI |
| PDR-002 | Auth | Primary Persona | Proposed | 2026-03-09 | User/AI |

---

*Individual PDR files: PDR-*.md in this directory*

Phase 7: Summary Report

## Feature Area Decomposition Summary

### Feature Areas Identified: 3

| # | Feature Area | PDRs Created |
|---|--------------|--------------|
| 1 | System-Level | PDR-001: Target Market |
| 2 | Auth | PDR-002: Primary Persona, PDR-003: Authentication Approach |
| 3 | Business | PDR-004: Pricing Model, PDR-005: Payment Integration |

### Next Steps
1. Review PDRs with /product-clarify
2. Generate PRD.md with /product-implement

PDR Numbering Rules

  • Scan {REPO_ROOT}/.adlc/drafts/pdr/ for existing PDR-*.md files
  • Extract numeric suffix, find maximum
  • Next PDR = max + 1, zero-padded to 3 digits
  • Never reuse numbers

Key Rules

Exploration First

  • Do NOT generate PRD directly from product description
  • Engage in discussion to validate assumptions
  • Surface trade-offs before committing to decisions
  • Allow iteration — user can revisit earlier decisions

Constitution Compliance

  • PDRs must align with constitution principles
  • Flag conflicts between product requirements and constitution
  • Constitution violations require explicit override with justification

Incremental PDRs

  • Create focused PDRs — one decision per PDR
  • Link related PDRs when decisions interact
  • Defer decisions that can be made later
  • Mark provisional decisions that may need revision

Configuration

  • PDR_DRAFTS_DIR — {REPO_ROOT}/.adlc/drafts/pdr
  • PDR_INDEX — {REPO_ROOT}/.adlc/drafts/pdr/pdr.md
  • PRD_FILE — {REPO_ROOT}/PRD.md
  • CONSTITUTION — {REPO_ROOT}/.adlc/memory/constitution.md

12-Factor Alignment

  • Factor III (Mission Definition): Defines the product mission before execution
  • Factor XI (Directives as Code): PDRs are version-controlled decision records

Common Rationalizations

Rationalization Reality
"Let's just write the PRD directly." PRDs without PDRs lack traceable rationale. Decisions become undocumented assumptions.
"We already know what to build." Even "obvious" decisions have alternatives. Documenting them prevents future reversal.
"Exploration takes too long." A 10-minute discussion now prevents weeks of rework later.

Red Flags

  • Generating PRD before PDRs are accepted — /product-implement requires Accepted status; Proposed PDRs will be skipped.
  • Skipping the constitution check — misaligned decisions propagate into the PRD and become expensive to fix.
  • No alternatives documented — a PDR without alternatives is a statement, not a decision.

Verification

  • Setup script returns valid JSON with all paths
  • .adlc/drafts/pdr/ directory exists
  • At least one PDR-*.md file created with status "Proposed"
  • pdr.md index auto-generated with correct table
  • Constitution alignment checked (if constitution exists)
  • Cross-feature-area conflicts flagged
  • No duplicate PDR IDs
  • Each PDR has at least 2 alternatives documented
Files (adlc-team-skills)
  • scripts
    • bash
      • pdr-lib.sh 17.8 KB
        #!/usr/bin/env bash
        #
        # pdr-lib.sh — Shared PDR lifecycle library for adlc-skills product-* skills.
        #
        # Mirrors the ADR-side tooling in setup-architect.sh (generate_adr_index, move_adr,
        # parse_fm_field, parse_fm_title) so that the PDR side has the same structural
        # robustness: script-driven index generation, atomic move promotion, and a
        # frontmatter-primary + heading-fallback parser that handles both legacy H2
        # (## Status) and current H3 (### Status) metadata.
        #
        # Bundled with each product-* skill so it works standalone.
        # Sourced by setup-product-*.sh scripts and callable directly.
        #
        # Usage:
        #   source pdr-lib.sh                          # load functions
        #   generate_pdr_index memory                  # regenerate .adlc/memory/pdr/pdr.md
        #   generate_pdr_index drafts                  # regenerate .adlc/drafts/pdr/pdr.md
        #   move_pdr 020 drafts memory                # atomically move PDR-020 drafts→memory
        #   migrate_pdr_to_frontmatter .adlc/memory/pdr/PDR-001.md   # one-time legacy migration
        #
        
        set -euo pipefail
        
        # ============================================================================
        # Project root resolution (mirror of common.sh _get_project_root)
        # ============================================================================
        
        _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
        }
        
        # Resolve REPO_ROOT if not already set by the caller.
        if [ -z "${REPO_ROOT:-}" ]; then
            REPO_ROOT="$(_get_project_root)"
        fi
        export REPO_ROOT
        
        # ============================================================================
        # YAML frontmatter parser (copied from setup-architect.sh parse_fm_field)
        # ============================================================================
        
        # 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"
        }
        
        # Extract the H1 title (first "# " line after frontmatter) from a markdown file.
        # Usage: parse_fm_title "file"
        parse_fm_title() {
            [ -f "$1" ] || return 0
            awk '/^---[[:space:]]*$/ { fm++; next } fm >= 2 && /^#[[:space:]]+/ { sub(/^#[[:space:]]+/, ""); sub(/[[:space:]]+$/, ""); print; exit }' "$1"
        }
        
        # ============================================================================
        # Heading-based fallback parser (handles legacy H2 and current H3 PDR files)
        # ============================================================================
        
        # 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"
        }
        
        # Extract the title from a "# PDR-NNN: <title>" or "## PDR-NNN: <title>" heading.
        # Strips the heading marker and "PDR-NNN:" prefix.
        # Usage: parse_pdr_heading_title "file"
        parse_pdr_heading_title() {
            [ -f "$1" ] || return 0
            awk '
                $0 ~ "^#+[[:space:]]*PDR-[0-9]+:[[:space:]]*" {
                    sub(/^#+[[:space:]]*PDR-[0-9]+:[[:space:]]*/, "")
                    sub(/[[:space:]]+$/, "")
                    print
                    exit
                }
            ' "$1"
        }
        
        # ============================================================================
        # Combined parsers (frontmatter primary, heading fallback)
        # ============================================================================
        
        # 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 ""
        }
        
        # Extract the PDR title: tries frontmatter "title" field, falls back to H1 parsing.
        # Usage: parse_pdr_title "file"
        parse_pdr_title() {
            [ -f "$1" ] || return 0
            local value
            value=$(parse_fm_field "$1" "title")
            if [ -n "$value" ]; then
                echo "$value"
                return
            fi
            # Fallback: strip "PDR-NNN:" prefix from H1/H2
            value=$(parse_pdr_heading_title "$1")
            if [ -n "$value" ]; then
                echo "$value"
                return
            fi
            # Last resort: bare H1 (no PDR-NNN prefix)
            value=$(parse_fm_title "$1")
            echo "$value"
        }
        
        # ============================================================================
        # Index generation (mirrors generate_adr_index from setup-architect.sh)
        # ============================================================================
        
        # Generate pdr.md index from individual PDR files.
        # Usage: generate_pdr_index [scope]   (scope = drafts | memory; default: drafts)
        # Writes to: $REPO_ROOT/.adlc/{scope}/pdr/pdr.md  (INSIDE the pdr/ directory)
        # Schema: 7 columns — ID | Feature-Area | Category | Status | Date | Owner | Title
        # Fails loudly on blank cells (warns to stderr; applies defaults so no row is blank).
        generate_pdr_index() {
            local scope="${1:-drafts}"
            local pdr_dir="$REPO_ROOT/.adlc/$scope/pdr"
            local index_file="$pdr_dir/pdr.md"
        
            if [ ! -d "$pdr_dir" ]; then
                return 0
            fi
        
            local index_content="# Product Decision Records"
            if [ "$scope" = "memory" ]; then
                index_content="$index_content (Memory)
        
        > Auto-generated by /product-implement. Accepted PDRs only.
        > Source: .adlc/$scope/pdr/PDR-*.md"
            else
                index_content="$index_content (Drafts)
        
        > Auto-generated by /product-clarify. Proposed PDRs only.
        > Source: .adlc/$scope/pdr/PDR-*.md"
            fi
        
            index_content="$index_content
        
        ## PDR Index
        
        | ID | Feature-Area | Category | Status | Date | Owner | Title |
        |----|--------------|----------|--------|------|-------|-------|
        "
        
            # Sort PDR files numerically
            local f fname id title status date owner category feature_area padded_id
            local blank_warnings=""
        
            for f in $(ls -1 "$pdr_dir"/PDR-*.md 2>/dev/null | sort -t'-' -k2 -n); do
                fname=$(basename "$f")
                id=$(echo "$fname" | sed -E 's/PDR-([0-9]+)\.md/\1/')
                padded_id=$(printf "%03d" "$((10#$id))")
        
                title=$(parse_pdr_title "$f")
                status=$(parse_pdr_field "$f" "status")
                date=$(parse_pdr_field "$f" "date")
                owner=$(parse_pdr_field "$f" "owner")
                category=$(parse_pdr_field "$f" "category")
                feature_area=$(parse_pdr_field "$f" "feature-area")
        
                # Defaults — no silent blank cells
                [ -z "$status" ] && { status="Unknown"; blank_warnings="$blank_warnings\n  - PDR-$padded_id: Status"; }
                [ -z "$date" ] && { date="YYYY-MM-DD"; blank_warnings="$blank_warnings\n  - PDR-$padded_id: Date"; }
                [ -z "$owner" ] && { owner="Unknown"; blank_warnings="$blank_warnings\n  - PDR-$padded_id: Owner"; }
                [ -z "$category" ] && { category="Unknown"; blank_warnings="$blank_warnings\n  - PDR-$padded_id: Category"; }
                [ -z "$feature_area" ] && { feature_area="system"; blank_warnings="$blank_warnings\n  - PDR-$padded_id: Feature-Area"; }
                [ -z "$title" ] && { title="PDR-$padded_id"; blank_warnings="$blank_warnings\n  - PDR-$padded_id: Title"; }
        
                index_content="$index_content| PDR-$padded_id | $feature_area | $category | $status | $date | $owner | $title |
        "
            done
        
            # Write the index
            printf '%s\n' "$index_content" > "$index_file"
        
            # Warn on any blank cells that needed defaults
            if [ -n "$blank_warnings" ]; then
                echo "[WARN] generate_pdr_index ($scope): the following PDRs had blank metadata cells;" >&2
                echo "       defaults were applied. Run /product-clarify to fix the source files." >&2
                printf '%b\n' "$blank_warnings" >&2
            fi
        }
        
        # ============================================================================
        # Atomic move (mirrors move_adr from setup-architect.sh)
        # ============================================================================
        
        # Move a PDR from one scope to another (e.g., drafts -> memory).
        # Usage: move_pdr <pdr_id> [from_scope] [to_scope]
        # Performs an atomic mv, then regenerates both scopes' indexes.
        # Fails if the source file does not exist. Verifies no duplicates remain.
        move_pdr() {
            local pdr_id="$1"
            local from_scope="${2:-drafts}"
            local to_scope="${3:-memory}"
        
            local from_dir="$REPO_ROOT/.adlc/$from_scope/pdr"
            local to_dir="$REPO_ROOT/.adlc/$to_scope/pdr"
        
            local numeric_id
            numeric_id=$(echo "$pdr_id" | sed -E 's/[^0-9]//g')
            local padded_id
            padded_id=$(printf "%03d" "$((10#$numeric_id))")
        
            mkdir -p "$to_dir"
        
            if [ -f "$from_dir/PDR-$padded_id.md" ]; then
                mv "$from_dir/PDR-$padded_id.md" "$to_dir/PDR-$padded_id.md"
            else
                echo "[WARN] move_pdr: source file not found: $from_dir/PDR-$padded_id.md" >&2
                return 1
            fi
        
            # Duplicate check — the source must be gone
            if [ -f "$from_dir/PDR-$padded_id.md" ]; then
                echo "[ERROR] move_pdr: duplicate detected — PDR-$padded_id still exists in $from_scope after move" >&2
                return 1
            fi
        
            # Regenerate both scopes
            generate_pdr_index "$from_scope"
            generate_pdr_index "$to_scope"
        }
        
        # ============================================================================
        # One-time migration: heading-based metadata → YAML frontmatter
        # ============================================================================
        
        # Migrate a legacy PDR file (heading-based metadata) to YAML frontmatter.
        # Reads Status/Date/Owner/Category/Feature-Area/Title from headings, prepends
        # frontmatter, and preserves the body unchanged. Skips files that already have
        # frontmatter. Strips ** bold markers from Status.
        # Usage: migrate_pdr_to_frontmatter <file>
        migrate_pdr_to_frontmatter() {
            local file="$1"
            [ -f "$file" ] || { echo "[WARN] migrate: file not found: $file" >&2; return 1; }
        
            # Skip if already has frontmatter
            if head -1 "$file" | grep -q '^---[[:space:]]*$'; then
                return 0
            fi
        
            local title status date owner category feature_area
        
            title=$(parse_pdr_heading_title "$file")
            status=$(parse_pdr_heading_field "$file" "Status")
            date=$(parse_pdr_heading_field "$file" "Date")
            owner=$(parse_pdr_heading_field "$file" "Owner")
            category=$(parse_pdr_heading_field "$file" "Category")
            feature_area=$(parse_pdr_heading_field "$file" "Feature-Area")
        
            # Strip ** bold markers from status
            status="${status//\*\*/}"
        
            # Build frontmatter
            local fm="---
        status: ${status:-Unknown}
        date: ${date:-YYYY-MM-DD}
        owner: ${owner:-Unknown}
        category: ${category:-Unknown}
        feature-area: ${feature_area:-system}
        title: ${title:-Untitled}
        ---
        "
        
            # Prepend frontmatter to the original body
            printf '%s\n%s\n' "$fm" "$(cat "$file")" > "$file.tmp" && mv "$file.tmp" "$file"
        }
        
        # Migrate all PDR files in a directory.
        # Usage: migrate_pdr_dir <dir>
        migrate_pdr_dir() {
            local dir="$1"
            [ -d "$dir" ] || { echo "[WARN] migrate_pdr_dir: dir not found: $dir" >&2; return 1; }
            local f count=0 skipped=0
            for f in "$dir"/PDR-*.md; do
                [ -f "$f" ] || continue
                if head -1 "$f" | grep -q '^---[[:space:]]*$'; then
                    skipped=$((skipped + 1))
                else
                    migrate_pdr_to_frontmatter "$f" && count=$((count + 1))
                fi
            done
            echo "[INFO] migrate_pdr_dir: migrated $count file(s), skipped $skipped (already had frontmatter)"
        }
        
        # ============================================================================
        # Fix frontmatter: re-extract metadata from body headings and update frontmatter
        # ============================================================================
        
        # Fix PDR frontmatter by re-reading heading-based metadata from the body.
        # For files that were migrated with incorrect/blank frontmatter (e.g., due to
        # a parser bug), this function re-extracts Status/Date/Owner/Category/Feature-Area/Title
        # from the body headings and rewrites the frontmatter block.
        # Usage: fix_pdr_frontmatter <file>
        fix_pdr_frontmatter() {
            local file="$1"
            [ -f "$file" ] || { echo "[WARN] fix_pdr_frontmatter: file not found: $file" >&2; return 1; }
        
            # Must have frontmatter to fix
            if ! head -1 "$file" | grep -q '^---[[:space:]]*$'; then
                return 0  # Not migrated yet — skip
            fi
        
            # Extract metadata from body headings (these are always present, even after migration)
            local title status date owner category feature_area
            title=$(parse_pdr_heading_title "$file")
            status=$(parse_pdr_heading_field "$file" "Status")
            date=$(parse_pdr_heading_field "$file" "Date")
            owner=$(parse_pdr_heading_field "$file" "Owner")
            category=$(parse_pdr_heading_field "$file" "Category")
            feature_area=$(parse_pdr_heading_field "$file" "Feature-Area")
        
            # Strip ** bold markers
            status="${status//\*\*/}"
        
            # Skip if all fields are empty (can't fix)
            if [ -z "$status" ] && [ -z "$date" ] && [ -z "$owner" ] && [ -z "$category" ] && [ -z "$feature_area" ] && [ -z "$title" ]; then
                return 0
            fi
        
            # Read current frontmatter values to preserve any non-empty ones
            local fm_status fm_date fm_owner fm_category fm_feature_area fm_title
            fm_status=$(parse_fm_field "$file" "status")
            fm_date=$(parse_fm_field "$file" "date")
            fm_owner=$(parse_fm_field "$file" "owner")
            fm_category=$(parse_fm_field "$file" "category")
            fm_feature_area=$(parse_fm_field "$file" "feature-area")
            fm_title=$(parse_fm_field "$file" "title")
        
            # Use heading value if non-empty, else keep frontmatter value, else default
            [ -n "$status" ] && fm_status="$status"
            [ -n "$date" ] && fm_date="$date"
            [ -n "$owner" ] && fm_owner="$owner"
            [ -n "$category" ] && fm_category="$category"
            [ -n "$feature_area" ] && fm_feature_area="$feature_area"
            [ -n "$title" ] && fm_title="$title"
        
            # Defaults for any remaining blanks
            [ -z "$fm_status" ] && fm_status="Unknown"
            [ -z "$fm_date" ] && fm_date="YYYY-MM-DD"
            [ -z "$fm_owner" ] && fm_owner="Unknown"
            [ -z "$fm_category" ] && fm_category="Unknown"
            [ -z "$fm_feature_area" ] && fm_feature_area="system"
            [ -z "$fm_title" ] && fm_title="Untitled"
        
            # Build new frontmatter
            local new_fm="---
        status: $fm_status
        date: $fm_date
        owner: $fm_owner
        category: $fm_category
        feature-area: $fm_feature_area
        title: $fm_title
        ---"
        
            # Replace the old frontmatter block (everything between the first and second `---`)
            # with the new frontmatter, keeping the body unchanged.
            local body
            body=$(awk '
                BEGIN { fm_count = 0; printing = 0 }
                /^---[[:space:]]*$/ { fm_count++; if (fm_count == 2) { printing = 1; next } else { next } }
                fm_count >= 2 && printing { print }
            ' "$file")
        
            printf '%s\n\n%s\n' "$new_fm" "$body" > "$file.tmp" && mv "$file.tmp" "$file"
        }
        
        # Fix all PDR files in a directory.
        # Usage: fix_pdr_dir <dir>
        fix_pdr_dir() {
            local dir="$1"
            [ -d "$dir" ] || { echo "[WARN] fix_pdr_dir: dir not found: $dir" >&2; return 1; }
            local f count=0
            for f in "$dir"/PDR-*.md; do
                [ -f "$f" ] || continue
                fix_pdr_frontmatter "$f" && count=$((count + 1))
            done
            echo "[INFO] fix_pdr_dir: fixed $count file(s)"
        }
        
        # ============================================================================
        # Level-agnostic Accepted counter (replaces the H3-hardcoded grep)
        # ============================================================================
        
        # Count PDRs with Accepted status in a directory, handling both YAML frontmatter
        # and heading-based metadata (H2 or H3). Replaces setup-product-clarify.sh:14
        # which hardcoded '^### Status'.
        # Usage: count_pdr_accepted <dir>
        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=$(echo "$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"
        }
        
      • setup-product-specify.sh 1.2 KB
        #!/bin/bash
        # product-specify setup script
        set -euo pipefail
        
        JSON_MODE=false
        for arg in "$@"; do
          case "$arg" in --json) JSON_MODE=true ;; esac
        done
        
        source "$(dirname "${BASH_SOURCE[0]}")/pdr-lib.sh" 2>/dev/null || true
        REPO_ROOT="${REPO_ROOT:-$(_get_project_root)}"
        PDR_DRAFTS_DIR="$REPO_ROOT/.adlc/drafts/pdr"
        PRD_FILE="$REPO_ROOT/PRD.md"
        
        mkdir -p "$PDR_DRAFTS_DIR"
        
        next_pdr_number() {
          local max=0
          if [[ -d "$PDR_DRAFTS_DIR" ]]; then
            for f in "$PDR_DRAFTS_DIR"/PDR-*.md; do
              if [[ -f "$f" ]]; then
                local num; num=$(basename "$f" | sed 's/PDR-//' | sed 's/\.md//')
                if [[ "$num" =~ ^[0-9]+$ ]]; then ((10#$num > max)) && max=$((10#$num)); fi
              fi
            done
          fi
          printf '%03d' $((max + 1))
        }
        
        NEXT_PDR=$(next_pdr_number)
        PDR_COUNT=$(find "$PDR_DRAFTS_DIR" -name 'PDR-*.md' 2>/dev/null | wc -l)
        
        if $JSON_MODE; then
          cat <<EOF
        {"REPO_ROOT":"$REPO_ROOT","PDR_DRAFTS_DIR":"$PDR_DRAFTS_DIR","PRD_FILE":"$PRD_FILE","next_pdr":"$NEXT_PDR","pdr_count":$PDR_COUNT}
        EOF
        else
          echo "[INFO] product-specify setup"
          echo "  REPO_ROOT: $REPO_ROOT"
          echo "  PDR_DRAFTS_DIR: $PDR_DRAFTS_DIR"
          echo "  Next PDR: PDR-$NEXT_PDR"
          echo "  Existing PDRs: $PDR_COUNT"
        fi
        
    • powershell
      • pdr-lib.ps1 10.7 KB · in bundle
      • setup-product-specify.ps1 1.1 KB · in bundle
  • SKILL.md 9.5 KB
    ---
    name: product-specify
    description: Use when you want guided trade-off analysis, multi-option comparison, or structured product decision facilitation before documenting. Optional for routine capture — team-boot writes lightweight PDR drafts directly.
    disable-model-invocation: true
    ---
    
    # product-specify
    
    ## What this skill does
    
    Transforms a high-level product idea into documented Product Decision Records (PDRs) through **interactive exploration** and trade-off analysis.
    
    **Key insight**: Discussion and exploration happen *before* committing to formal documentation. The goal is to surface trade-offs, validate assumptions, and make informed decisions collaboratively.
    
    **Output**: Individual `PDR-{NNN}.md` files (status **Proposed**) in `.adlc/drafts/pdr/` with an auto-generated `pdr.md` index.
    
    ## When to use
    
    > **Note**: Routine decision capture is handled by team-boot's continuous
    > capture mechanism, which writes lightweight drafts directly to
    > `.adlc/drafts/`. This skill is for **interactive deep-dive exploration**
    > — when you want guided trade-off analysis, multi-option comparison, or
    > structured decision facilitation before documenting.
    
    - New product from scratch
    - Major product pivots
    - Documenting verbal decisions formally
    - Team onboarding — walking through product rationale
    
    ## When NOT to use
    
    - Existing product (use `/product-init` instead)
    - Minor PDR updates (use `/product-clarify` instead)
    
    ## Execution Steps
    
    ### Phase 0: Environment Setup
    
    ```bash
    sh: scripts/bash/setup-product-specify.sh [--json]
    ps: scripts/powershell/setup-product-specify.ps1
    ```
    
    **Setup output** (JSON):
    ```json
    {
      "REPO_ROOT": "/path/to/project",
      "PDR_DRAFTS_DIR": "/path/to/project/.adlc/drafts/pdr",
      "PRD_FILE": "/path/to/project/PRD.md",
      "next_pdr": "001"
    }
    ```
    
    ### Phase 1: Feature-Area Decomposition (Optional)
    
    Analyze the product for distinct business domains. Auto-decompose if multiple domains detected. Use `--no-decompose` to skip.
    
    **Present detected areas**:
    ```markdown
    ## Detected Feature Areas
    
    | # | Feature Area | Key Domains | Rationale |
    |---|--------------|-------------|-----------|
    | 1 | **Auth** | Authentication, Authorization | Core user entry |
    | 2 | **Core** | User Management, Profiles | Core data |
    | 3 | **Business** | Payments, Subscriptions | Revenue domain |
    
    Reply: Y to confirm, n for monolithic, or suggest changes.
    ```
    
    **Threshold**:
    - ≤3 areas: Auto-approve
    - 4-6 areas: Confirm with user
    - >6 areas: Suggest grouping
    
    ### Phase 2: Product Analysis
    
    Extract product drivers:
    
    1. **Problem Drivers**: Core problem, who experiences it, current workarounds
    2. **Market Drivers**: Target segments, competitive landscape, trends
    3. **Business Drivers**: Revenue model, scaling expectations, strategic importance
    4. **Constraint Drivers**: Technology mandates, budget, team skills, regulatory
    5. **Load Constitution**: Read `{REPO_ROOT}/.adlc/memory/constitution.md` if exists
    6. **Check Existing Docs**: Scan `README.md`, `AGENTS.md`, `CONTRIBUTING.md` for context
    
    ### Phase 3: Product Exploration (Interactive)
    
    For each major decision area, present options and facilitate discussion:
    
    **Decision areas** (5-7 key decisions):
    1. Problem Scope
    2. Target Personas
    3. Solution Approach (build vs buy vs partner)
    4. Monetization
    5. Go-to-Market
    6. Success Metrics
    
    **Exploration format**:
    ```markdown
    ## Product Decision: [Decision Area]
    
    **Context**: [Why this decision matters]
    
    **Options**:
    | Option | Description | Trade-offs |
    |--------|-------------|------------|
    | A | [Option A] | Pros: [X] / Cons: [Y] |
    | B | [Option B] | Pros: [X] / Cons: [Y] |
    
    **Recommended**: Option [X]
    
    **Questions**:
    1. [Question about constraints]
    2. [Question about trade-off priorities]
    ```
    
    **Rules**:
    - Present **one decision area at a time**
    - Always provide a **recommended option** with reasoning
    - Ask **targeted questions** to surface hidden requirements
    - Skip decisions already determined by context or constitution
    - Limit to **5-7 key decisions**
    
    ### Phase 4: Cross-Feature-Area Pre-Analysis
    
    During exploration, watch for:
    
    | Pattern | Detection | Action |
    |---------|-----------|--------|
    | Shared Personas | Same user type in multiple areas | Note for cross-area metadata |
    | Priority Tensions | Areas prioritize differently | ⚠️ Flag potential conflict |
    | Feature Overlap | Similar features in different areas | ⚠️ Flag for consolidation |
    | Metric Conflicts | Same metric, different targets | ⚠️ Flag for alignment |
    
    ### Phase 5: Decision Documentation
    
    After each decision is confirmed, create a PDR file.
    
    **PDR file format** (individual file, YAML frontmatter — use the shared template):
    
    Use the template at `{REPO_ROOT}/.agents/skills/product/templates/pdr-template.md` as the canonical PDR format. The template includes YAML frontmatter (status, date, owner, category, feature-area, title) as the single source of truth for index generation, plus the standard PDR body sections (Context, Decision, Consequences, Alternatives, Links).
    
    ```markdown
    ---
    status: proposed
    date: YYYY-MM-DD
    owner: [User/AI collaboration]
    category: Feature
    feature-area: [core | business | growth | ...]
    title: [Decision Title]
    ---
    
    # PDR-[NNN]: [Decision Title]
    
    ## Context
    **Problem/Opportunity:**
    [Clear description]
    
    **Market Forces:**
    - [Market factor 1]
    - [Customer feedback]
    
    ## Decision
    **Decision Statement:**
    [Clear statement of what was decided]
    
    **Rationale:**
    [Why this option was chosen]
    
    ### Consequences
    #### Positive
    - [Benefit 1]
    
    #### Negative
    - [Trade-off 1]
    
    #### Risks
    - [Risk with mitigation]
    
    ### Success Metrics
    | Metric | Target | Measurement Method |
    |--------|--------|-------------------|
    | [Metric] | [Target] | [Method] |
    
    ### Alternatives Considered
    #### Option A: [Alternative Name]
    **Description:** [Brief description]
    **Trade-offs:** [Neutral comparison]
    
    ### Constitution/Vision Alignment
    | Principle | Alignment | Notes |
    |-----------|-----------|-------|
    | [Vision Principle] | ✅ Compliant / ⚠️ Deviation | [Explanation] |
    ```
    
    ### Phase 6: Write PDR Files and Regenerate Index
    
    1. **Number sequentially**: Start from highest existing PDR number + 1
    2. **Write individual files**: `{REPO_ROOT}/.adlc/drafts/pdr/PDR-{NNN}.md`
    3. **Regenerate index**: `{REPO_ROOT}/.adlc/drafts/pdr/pdr.md`
    
    **Index format**:
    ```markdown
    # Product Decision Records
    
    ## PDR Index
    
    | ID | Feature-Area | Category | Status | Date | Owner |
    |----|--------------|----------|--------|------|-------|
    | PDR-001 | System | Target Market | Proposed | 2026-03-09 | User/AI |
    | PDR-002 | Auth | Primary Persona | Proposed | 2026-03-09 | User/AI |
    
    ---
    
    *Individual PDR files: PDR-*.md in this directory*
    ```
    
    ### Phase 7: Summary Report
    
    ```markdown
    ## Feature Area Decomposition Summary
    
    ### Feature Areas Identified: 3
    
    | # | Feature Area | PDRs Created |
    |---|--------------|--------------|
    | 1 | System-Level | PDR-001: Target Market |
    | 2 | Auth | PDR-002: Primary Persona, PDR-003: Authentication Approach |
    | 3 | Business | PDR-004: Pricing Model, PDR-005: Payment Integration |
    
    ### Next Steps
    1. Review PDRs with /product-clarify
    2. Generate PRD.md with /product-implement
    ```
    
    ## PDR Numbering Rules
    
    - Scan `{REPO_ROOT}/.adlc/drafts/pdr/` for existing `PDR-*.md` files
    - Extract numeric suffix, find maximum
    - Next PDR = max + 1, zero-padded to 3 digits
    - Never reuse numbers
    
    ## Key Rules
    
    ### Exploration First
    - **Do NOT generate PRD directly** from product description
    - **Engage in discussion** to validate assumptions
    - **Surface trade-offs** before committing to decisions
    - **Allow iteration** — user can revisit earlier decisions
    
    ### Constitution Compliance
    - PDRs must **align with constitution** principles
    - **Flag conflicts** between product requirements and constitution
    - Constitution violations require explicit override with justification
    
    ### Incremental PDRs
    - Create **focused PDRs** — one decision per PDR
    - **Link related PDRs** when decisions interact
    - **Defer decisions** that can be made later
    - Mark **provisional decisions** that may need revision
    
    ## Configuration
    
    - `PDR_DRAFTS_DIR` — `{REPO_ROOT}/.adlc/drafts/pdr`
    - `PDR_INDEX` — `{REPO_ROOT}/.adlc/drafts/pdr/pdr.md`
    - `PRD_FILE` — `{REPO_ROOT}/PRD.md`
    - `CONSTITUTION` — `{REPO_ROOT}/.adlc/memory/constitution.md`
    
    ## 12-Factor Alignment
    
    - **Factor III (Mission Definition)**: Defines the product mission before execution
    - **Factor XI (Directives as Code)**: PDRs are version-controlled decision records
    
    ## Common Rationalizations
    
    | Rationalization | Reality |
    |-----------------|---------|
    | "Let's just write the PRD directly." | PRDs without PDRs lack traceable rationale. Decisions become undocumented assumptions. |
    | "We already know what to build." | Even "obvious" decisions have alternatives. Documenting them prevents future reversal. |
    | "Exploration takes too long." | A 10-minute discussion now prevents weeks of rework later. |
    
    ## Red Flags
    
    - **Generating PRD before PDRs are accepted** — `/product-implement` requires Accepted status; Proposed PDRs will be skipped.
    - **Skipping the constitution check** — misaligned decisions propagate into the PRD and become expensive to fix.
    - **No alternatives documented** — a PDR without alternatives is a statement, not a decision.
    
    ## Verification
    
    - [ ] Setup script returns valid JSON with all paths
    - [ ] `.adlc/drafts/pdr/` directory exists
    - [ ] At least one `PDR-*.md` file created with status "Proposed"
    - [ ] `pdr.md` index auto-generated with correct table
    - [ ] Constitution alignment checked (if constitution exists)
    - [ ] Cross-feature-area conflicts flagged
    - [ ] No duplicate PDR IDs
    - [ ] Each PDR has at least 2 alternatives documented
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related