From 5c317deb9d161f56a6536b0d7617fe8d5349c56e Mon Sep 17 00:00:00 2001 From: buddhisthead Date: Thu, 29 Jan 2026 15:27:12 -0800 Subject: [PATCH 1/3] feat: Add feedback command and lessons.md integration for continuous improvement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR introduces a comprehensive feedback loop system that captures lessons from PR reviews and applies them to future development work. ## New Features ### 1. /speckit.feedback Command - Extracts lessons learned from PR review comments and discussions - Stores lessons in .specify/memory/lessons.md (alongside constitution.md) - Stores PR-specific summaries in .specify/memory/feedback/pr--lessons.md - Supports three modes: - Auto-detect current PR: --find-current - Specific PR: --pr - Manual lesson entry: inline text input - Categorizes lessons: code-quality, architecture, testing, documentation, security, performance, other - Deduplicates lessons and tracks frequency - Optional auto-commit with --commit flag ### 2. Lessons.md Integration Across Commands All core speckit commands now load and apply relevant lessons: - /speckit.specify loads architecture & documentation lessons - /speckit.plan loads architecture, testing & performance lessons - /speckit.implement loads code-quality, security & testing lessons ### 3. Supporting Infrastructure - fetch-pr-feedback.sh script: Fetches PR data using GitHub CLI (bash) - fetch-pr-feedback.ps1 script: PowerShell equivalent for Windows users - Command templates for all AI agents (Claude, Gemini, Cursor, Copilot, etc.) - Lessons stored in .specify/memory/ alongside project constitution - PR-specific feedback preserved in .specify/memory/feedback/ - Uses {SCRIPT} placeholder for cross-platform compatibility ## Files Added - scripts/bash/fetch-pr-feedback.sh (220 lines) - scripts/powershell/fetch-pr-feedback.ps1 (277 lines) - templates/commands/feedback.md (293 lines) ## Files Modified - templates/commands/specify.md (added Step 0: Load Lessons - 6 lines) - templates/commands/plan.md (added Step 0: Load Lessons - 6 lines) - templates/commands/implement.md (added Step 0: Load Lessons - 6 lines) - README.md (added Lessons Learned section explaining the system) - src/specify_cli/__init__.py (added feedback to next steps output) ## Benefits - Continuous Improvement: Lessons from PR reviews automatically inform future work - Universal Compatibility: Works with all 15+ supported AI agents - Cross-Platform: Both bash and PowerShell implementations - Non-Breaking: Optional integration - only activates when lessons.md exists - Category-Aware: Each command loads only relevant lessons to reduce noise - Team Learning: Captures institutional knowledge from code reviews - Compounding Knowledge: Team wisdom accumulates in memory alongside constitution ## How It Works 1. After PR review, run /speckit.feedback to extract lessons 2. Lessons are stored in .specify/memory/lessons.md with categories 3. PR-specific context preserved in .specify/memory/feedback/ 4. Future runs of /speckit.specify, /speckit.plan, /speckit.implement automatically load and apply relevant lessons 5. Teams avoid repeating past mistakes and compound knowledge over time 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 12 +- scripts/bash/fetch-pr-feedback.sh | 220 +++++++++++++++++ scripts/powershell/fetch-pr-feedback.ps1 | 277 +++++++++++++++++++++ src/specify_cli/__init__.py | 1 + templates/commands/feedback.md | 293 +++++++++++++++++++++++ templates/commands/implement.md | 6 + templates/commands/plan.md | 6 + templates/commands/specify.md | 6 + 8 files changed, 820 insertions(+), 1 deletion(-) create mode 100755 scripts/bash/fetch-pr-feedback.sh create mode 100644 scripts/powershell/fetch-pr-feedback.ps1 create mode 100644 templates/commands/feedback.md diff --git a/README.md b/README.md index 76149512f6..16b08cd565 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,7 @@ Additional commands for enhanced quality and validation: | `/speckit.clarify` | Clarify underspecified areas (recommended before `/speckit.plan`; formerly `/quizme`) | | `/speckit.analyze` | Cross-artifact consistency & coverage analysis (run after `/speckit.tasks`, before `/speckit.implement`) | | `/speckit.checklist` | Generate custom quality checklists that validate requirements completeness, clarity, and consistency (like "unit tests for English") | +| `/speckit.feedback` | Extract and store lessons learned from PR review comments for future reference | ### Environment Variables @@ -274,6 +275,15 @@ Additional commands for enhanced quality and validation: | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `SPECIFY_FEATURE` | Override feature detection for non-Git repositories. Set to the feature directory name (e.g., `001-photo-albums`) to work on a specific feature when not using Git branches.
\*\*Must be set in the context of the agent you're working with prior to using `/speckit.plan` or follow-up commands. | +### Lessons Learned + +Spec-kit implements a continuous improvement system that captures lessons from PR reviews and applies them to future development work. The `/speckit.feedback` command extracts insights from code review comments and stores them in two places: + +- **`memory/lessons.md`** - Central lessons database organized by category (architecture, code-quality, testing, security, performance, documentation) +- **`memory/feedback/pr--lessons.md`** - Per-PR detailed feedback summaries that preserve the full context of each review + +When you run `/speckit.specify`, `/speckit.plan`, or `/speckit.implement`, these commands automatically load relevant lessons from the central database for their phase and apply them proactively. This creates a compounding knowledge effect where your team's collective wisdom from past reviews continuously improves future implementations, helping avoid repeated mistakes and reinforcing best practices without manual intervention. + ## 📚 Core Philosophy Spec-Driven Development is a structured process that emphasizes: @@ -401,7 +411,7 @@ The first step should be establishing your project's governing principles using /speckit.constitution Create principles focused on code quality, testing standards, user experience consistency, and performance requirements. Include governance for how these principles should guide technical decisions and implementation choices. ``` -This step creates or updates the `.specify/memory/constitution.md` file with your project's foundational guidelines that the AI agent will reference during specification, planning, and implementation phases. +This step creates or updates the `memory/constitution.md` file with your project's foundational guidelines that the AI agent will reference during specification, planning, and implementation phases. ### **STEP 2:** Create project specifications diff --git a/scripts/bash/fetch-pr-feedback.sh b/scripts/bash/fetch-pr-feedback.sh new file mode 100755 index 0000000000..9c15dbda92 --- /dev/null +++ b/scripts/bash/fetch-pr-feedback.sh @@ -0,0 +1,220 @@ +#!/usr/bin/env bash +# +# fetch-pr-feedback.sh - Extract PR data for the speckit.feedback agent +# +# Usage: +# ./fetch-pr-feedback.sh +# ./fetch-pr-feedback.sh --find-current # Find open PR for current branch (preferred) +# ./fetch-pr-feedback.sh --find-recent # Find most recently merged PR +# +# Output: JSON with PR metadata, review comments, and discussion comments +# + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +error() { + echo -e "${RED}ERROR: $1${NC}" >&2 + exit 1 +} + +warn() { + echo -e "${YELLOW}WARNING: $1${NC}" >&2 +} + +info() { + echo -e "${GREEN}$1${NC}" >&2 +} + +# Check for gh CLI +command -v gh >/dev/null 2>&1 || error "GitHub CLI (gh) is required but not installed." + +# Check for jq +command -v jq >/dev/null 2>&1 || error "jq is required but not installed. Install with: brew install jq" + +# Verify authentication +gh auth status >/dev/null 2>&1 || error "Not authenticated with GitHub. Run 'gh auth login' first." + +# Get repository info +get_repo_info() { + gh repo view --json owner,name -q '"\(.owner.login)/\(.name)"' 2>/dev/null || error "Not in a GitHub repository." +} + +# Find most recently merged PR for current branch +find_recent_merged_pr() { + local current_branch + current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || error "Not in a git repository." + + # First try to find a merged PR for the current branch + local pr_number + pr_number=$(gh pr list --state merged --head "$current_branch" --json number -q '.[0].number' 2>/dev/null) + + if [[ -z "$pr_number" || "$pr_number" == "null" ]]; then + # Fallback: get the most recently merged PR in the repo + pr_number=$(gh pr list --state merged --json number,mergedAt --limit 10 -q 'sort_by(.mergedAt) | reverse | .[0].number' 2>/dev/null) + fi + + if [[ -z "$pr_number" || "$pr_number" == "null" ]]; then + error "No merged PRs found for branch '$current_branch' or in recent history." + fi + + echo "$pr_number" +} + +# Find open PR for current branch (preferred for pre-merge workflow) +find_current_pr() { + local current_branch + current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || error "Not in a git repository." + + # First try to find an open PR for the current branch + local pr_number + pr_number=$(gh pr list --state open --head "$current_branch" --json number -q '.[0].number' 2>/dev/null) + + if [[ -n "$pr_number" && "$pr_number" != "null" ]]; then + echo "$pr_number" + return + fi + + # Fallback: try to find a merged PR for the current branch + pr_number=$(gh pr list --state merged --head "$current_branch" --json number -q '.[0].number' 2>/dev/null) + + if [[ -n "$pr_number" && "$pr_number" != "null" ]]; then + warn "No open PR found, using most recently merged PR for this branch." + echo "$pr_number" + return + fi + + error "No open or merged PRs found for branch '$current_branch'." +} + +# Fetch PR basic info +fetch_pr_info() { + local pr_number=$1 + gh pr view "$pr_number" --json number,title,body,state,mergedAt,headRefName,url,author 2>/dev/null || error "Failed to fetch PR #$pr_number" +} + +# Fetch review comments via GraphQL +fetch_review_comments() { + local pr_number=$1 + local repo_info + repo_info=$(get_repo_info) + local owner="${repo_info%/*}" + local repo="${repo_info#*/}" + + local result + result=$(gh api graphql -f query=' + query($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $number) { + reviews(first: 100) { + nodes { + body + author { login } + state + submittedAt + comments(first: 50) { + nodes { + body + path + line + author { login } + } + } + } + } + } + } + } + ' -f owner="$owner" -f repo="$repo" -F number="$pr_number" 2>/dev/null) + + if [[ -z "$result" ]]; then + warn "Failed to fetch review comments for PR #$pr_number" + echo "null" + else + echo "$result" + fi +} + +# Fetch PR discussion comments +fetch_discussion_comments() { + local pr_number=$1 + local result + result=$(gh pr view "$pr_number" --json comments 2>/dev/null) + + if [[ -z "$result" ]]; then + warn "Failed to fetch discussion comments for PR #$pr_number" + echo "null" + else + echo "$result" + fi +} + +# Main function to fetch all PR feedback data +fetch_all_feedback() { + local pr_number=$1 + + info "Fetching PR #$pr_number data..." + + local pr_info + local review_comments + local discussion_comments + + pr_info=$(fetch_pr_info "$pr_number") + review_comments=$(fetch_review_comments "$pr_number") + discussion_comments=$(fetch_discussion_comments "$pr_number") + + # Normalize empty strings to "null" for jq + [[ -z "$review_comments" || "$review_comments" == "null" ]] && review_comments="null" + [[ -z "$discussion_comments" || "$discussion_comments" == "null" ]] && discussion_comments="null" + + # Combine all data into a single JSON object + jq -n \ + --argjson pr_info "$pr_info" \ + --argjson review_comments "$review_comments" \ + --argjson discussion_comments "$discussion_comments" \ + '{ + pr: $pr_info, + reviews: $review_comments, + discussion: $discussion_comments + }' +} + +# Parse arguments +case "${1:-}" in + --find-current) + pr_number=$(find_current_pr) + info "Found PR for current branch: #$pr_number" + fetch_all_feedback "$pr_number" + ;; + --find-recent) + pr_number=$(find_recent_merged_pr) + info "Found most recent merged PR: #$pr_number" + fetch_all_feedback "$pr_number" + ;; + --help|-h) + echo "Usage: $0 | --find-current | --find-recent" + echo "" + echo "Arguments:" + echo " Specific PR number to fetch feedback from" + echo " --find-current Find and fetch the open PR for current branch (falls back to merged)" + echo " --find-recent Find and fetch the most recently merged PR for current branch" + echo "" + echo "Output: JSON with PR metadata, review comments, and discussion comments" + exit 0 + ;; + "") + error "No PR number provided. Use '$0 ', '$0 --find-current', or '$0 --find-recent'" + ;; + *) + if [[ "$1" =~ ^[0-9]+$ ]]; then + fetch_all_feedback "$1" + else + error "Invalid PR number: $1" + fi + ;; +esac diff --git a/scripts/powershell/fetch-pr-feedback.ps1 b/scripts/powershell/fetch-pr-feedback.ps1 new file mode 100644 index 0000000000..56dac9d3b2 --- /dev/null +++ b/scripts/powershell/fetch-pr-feedback.ps1 @@ -0,0 +1,277 @@ +#!/usr/bin/env pwsh +# +# fetch-pr-feedback.ps1 - Extract PR data for the speckit.feedback agent +# +# Usage: +# .\fetch-pr-feedback.ps1 +# .\fetch-pr-feedback.ps1 -FindCurrent # Find open PR for current branch (preferred) +# .\fetch-pr-feedback.ps1 -FindRecent # Find most recently merged PR +# +# Output: JSON with PR metadata, review comments, and discussion comments +# + +[CmdletBinding()] +param( + [Parameter(Position = 0)] + [string]$PRNumber, + + [Parameter()] + [switch]$FindCurrent, + + [Parameter()] + [switch]$FindRecent, + + [Parameter()] + [switch]$Help +) + +$ErrorActionPreference = "Stop" + +# Helper functions for colored output +function Write-Error-Message { + param([string]$Message) + Write-Host "ERROR: $Message" -ForegroundColor Red + exit 1 +} + +function Write-Warning-Message { + param([string]$Message) + Write-Host "WARNING: $Message" -ForegroundColor Yellow +} + +function Write-Info-Message { + param([string]$Message) + Write-Host $Message -ForegroundColor Green +} + +# Check for gh CLI +function Test-GitHubCLI { + if (-not (Get-Command gh -ErrorAction SilentlyContinue)) { + Write-Error-Message "GitHub CLI (gh) is required but not installed." + } +} + +# Check for git +function Test-Git { + if (-not (Get-Command git -ErrorAction SilentlyContinue)) { + Write-Error-Message "Git is required but not installed." + } +} + +# Verify authentication +function Test-GitHubAuth { + $null = gh auth status 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Error-Message "Not authenticated with GitHub. Run 'gh auth login' first." + } +} + +# Get repository info +function Get-RepoInfo { + $repoInfo = gh repo view --json owner,name | ConvertFrom-Json + if ($LASTEXITCODE -ne 0) { + Write-Error-Message "Not in a GitHub repository." + } + return "$($repoInfo.owner.login)/$($repoInfo.name)" +} + +# Find most recently merged PR for current branch +function Find-RecentMergedPR { + $currentBranch = git rev-parse --abbrev-ref HEAD 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Error-Message "Not in a git repository." + } + + # First try to find a merged PR for the current branch + $prList = gh pr list --state merged --head $currentBranch --json number --limit 1 | ConvertFrom-Json + $prNumber = $null + + if ($prList -and $prList.Count -gt 0) { + $prNumber = $prList[0].number + } + + if (-not $prNumber) { + # Fallback: get the most recently merged PR in the repo + $prList = gh pr list --state merged --json number,mergedAt --limit 10 | ConvertFrom-Json + if ($prList -and $prList.Count -gt 0) { + $sorted = $prList | Sort-Object -Property mergedAt -Descending + $prNumber = $sorted[0].number + } + } + + if (-not $prNumber) { + Write-Error-Message "No merged PRs found for branch '$currentBranch' or in recent history." + } + + return $prNumber +} + +# Find open PR for current branch (preferred for pre-merge workflow) +function Find-CurrentPR { + $currentBranch = git rev-parse --abbrev-ref HEAD 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Error-Message "Not in a git repository." + } + + # First try to find an open PR for the current branch + $prList = gh pr list --state open --head $currentBranch --json number --limit 1 | ConvertFrom-Json + $prNumber = $null + + if ($prList -and $prList.Count -gt 0) { + $prNumber = $prList[0].number + return $prNumber + } + + # Fallback: try to find a merged PR for the current branch + $prList = gh pr list --state merged --head $currentBranch --json number --limit 1 | ConvertFrom-Json + + if ($prList -and $prList.Count -gt 0) { + $prNumber = $prList[0].number + Write-Warning-Message "No open PR found, using most recently merged PR for this branch." + return $prNumber + } + + Write-Error-Message "No open or merged PRs found for branch '$currentBranch'." +} + +# Fetch PR basic info +function Get-PRInfo { + param([int]$PRNumber) + + $prInfo = gh pr view $PRNumber --json number,title,body,state,mergedAt,headRefName,url,author 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Error-Message "Failed to fetch PR #$PRNumber" + } + + return $prInfo | ConvertFrom-Json +} + +# Fetch review comments via GraphQL +function Get-ReviewComments { + param([int]$PRNumber) + + $repoInfo = Get-RepoInfo + $parts = $repoInfo -split '/' + $owner = $parts[0] + $repo = $parts[1] + + $query = @" +query(`$owner: String!, `$repo: String!, `$number: Int!) { + repository(owner: `$owner, name: `$repo) { + pullRequest(number: `$number) { + reviews(first: 100) { + nodes { + body + author { login } + state + submittedAt + comments(first: 50) { + nodes { + body + path + line + author { login } + } + } + } + } + } + } +} +"@ + + try { + $result = gh api graphql -f query=$query -f owner=$owner -f repo=$repo -F number=$PRNumber 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Warning-Message "Failed to fetch review comments for PR #$PRNumber" + return $null + } + return $result | ConvertFrom-Json + } + catch { + Write-Warning-Message "Failed to fetch review comments for PR #$PRNumber" + return $null + } +} + +# Fetch PR discussion comments +function Get-DiscussionComments { + param([int]$PRNumber) + + try { + $result = gh pr view $PRNumber --json comments 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Warning-Message "Failed to fetch discussion comments for PR #$PRNumber" + return $null + } + return $result | ConvertFrom-Json + } + catch { + Write-Warning-Message "Failed to fetch discussion comments for PR #$PRNumber" + return $null + } +} + +# Main function to fetch all PR feedback data +function Get-AllFeedback { + param([int]$PRNumber) + + Write-Info-Message "Fetching PR #$PRNumber data..." + + $prInfo = Get-PRInfo -PRNumber $PRNumber + $reviewComments = Get-ReviewComments -PRNumber $PRNumber + $discussionComments = Get-DiscussionComments -PRNumber $PRNumber + + # Create combined JSON object + $combined = @{ + pr = $prInfo + reviews = $reviewComments + discussion = $discussionComments + } + + return $combined | ConvertTo-Json -Depth 100 -Compress +} + +# Show help +function Show-Help { + Write-Host "Usage: .\fetch-pr-feedback.ps1 | -FindCurrent | -FindRecent" + Write-Host "" + Write-Host "Arguments:" + Write-Host " Specific PR number to fetch feedback from" + Write-Host " -FindCurrent Find and fetch the open PR for current branch (falls back to merged)" + Write-Host " -FindRecent Find and fetch the most recently merged PR for current branch" + Write-Host "" + Write-Host "Output: JSON with PR metadata, review comments, and discussion comments" + exit 0 +} + +# Main execution +if ($Help) { + Show-Help +} + +Test-GitHubCLI +Test-Git +Test-GitHubAuth + +if ($FindCurrent) { + $prNum = Find-CurrentPR + Write-Info-Message "Found PR for current branch: #$prNum" + Get-AllFeedback -PRNumber $prNum +} +elseif ($FindRecent) { + $prNum = Find-RecentMergedPR + Write-Info-Message "Found most recent merged PR: #$prNum" + Get-AllFeedback -PRNumber $prNum +} +elseif ($PRNumber) { + if ($PRNumber -match '^\d+$') { + Get-AllFeedback -PRNumber ([int]$PRNumber) + } + else { + Write-Error-Message "Invalid PR number: $PRNumber" + } +} +else { + Write-Error-Message "No PR number provided. Use '.\fetch-pr-feedback.ps1 ', '-FindCurrent', or '-FindRecent'" +} diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 1dedb31949..b49bc4c2a6 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -1224,6 +1224,7 @@ def init( steps_lines.append(" 2.3 [cyan]/speckit.plan[/] - Create implementation plan") steps_lines.append(" 2.4 [cyan]/speckit.tasks[/] - Generate actionable tasks") steps_lines.append(" 2.5 [cyan]/speckit.implement[/] - Execute implementation") + steps_lines.append(" 2.6 [cyan]/speckit.feedback[/] - Capture lessons from PR reviews") steps_panel = Panel("\n".join(steps_lines), title="Next Steps", border_style="cyan", padding=(1,2)) console.print() diff --git a/templates/commands/feedback.md b/templates/commands/feedback.md new file mode 100644 index 0000000000..c420ed9756 --- /dev/null +++ b/templates/commands/feedback.md @@ -0,0 +1,293 @@ +--- +description: Extract lessons learned from PR review comments and discussions, storing them in per-PR lesson files and a central lessons database. +handoffs: + - label: Specify Feature + agent: speckit.specify + prompt: Create a new feature specification + - label: Plan Feature + agent: speckit.plan + prompt: Create a technical plan for the specification +scripts: + sh: scripts/bash/fetch-pr-feedback.sh + ps: scripts/powershell/fetch-pr-feedback.ps1 +--- + +## User Input + +```text +$ARGUMENTS +``` + +You **MUST** consider the user input before proceeding (if not empty). + +## Outline + +Goal: Capture lessons learned from PR feedback and store them for future reference by other speckit phases. + +## Execution Steps + +### Step 1: Parse Arguments + +Analyze the `$ARGUMENTS` input to determine the operation mode: + +**Mode A - Specific PR**: If `--pr ` is present, use that PR number. + +**Mode B - Auto-detect PR**: If no `--pr` flag but no inline text, find the open PR for the current branch (or most recently merged if no open PR exists). + +**Mode C - Manual Entry**: If inline text is provided (not a flag), treat it as a manual lesson entry. + +Parse these optional flags: +- `--pr `: Specific PR to extract feedback from +- `--category `: Category for manual lesson (code-quality, architecture, testing, documentation, security, performance, other) +- `--commit`: Automatically run git add/commit/push after extracting lessons + +### Step 2: Ensure Directory Structure + +Check if `memory/` directory exists (it should, as it contains `constitution.md`). Create the feedback subdirectory if needed: +- `memory/lessons.md` - Central lessons database (alongside constitution.md) +- `memory/feedback/` - Directory for PR-specific lesson files +- `memory/feedback/pr--lessons.md` - Per-PR lesson files + +### Step 3: Execute Based on Mode + +#### Mode A/B: PR Feedback Extraction + +1. **Get PR Number**: + - Mode A: Use the `--pr ` value + - Mode B: Run `{SCRIPT} --find-current` to get the open PR for current branch (falls back to most recently merged) + +2. **Check for Existing PR Lessons** (Incremental Mode): + - If `memory/feedback/pr--lessons.md` exists: + - Read existing lessons from the file + - Track existing lesson IDs for merge later + - Note: No prompt needed - we will merge new lessons with existing ones + +3. **Fetch PR Data**: + - Run `{SCRIPT} ` + - Parse the JSON output containing: + - PR metadata (title, body, author, URL) + - Review comments and suggestions + - Discussion thread comments + +4. **Handle Edge Cases**: + - If no PR found: Prompt user "No open or merged PR found for current branch. Enter a PR number or type a lesson for manual entry." + - If PR has no review comments: Report "No review feedback found in PR #. Would you like to add a manual lesson instead?" + +5. **Extract and Categorize Lessons**: + - Analyze review comments, suggestions, and PR description + - For each substantive feedback item, create a lesson: + - Determine the most appropriate category (code-quality, architecture, testing, documentation, security, performance, other) + - Extract the core lesson content + - Add relevant tags based on file paths, technologies mentioned + - Limit to top 10 lessons if PR discussion is very long (summarize themes) + - **Deduplication**: Skip lessons that match existing lessons (from step 2) + +6. **Generate/Update PR Lessons File** (Incremental Merge): + - If file exists: Merge new lessons with existing ones + - Keep all existing lessons + - Add only new lessons (those not matching existing by content) + - Update `extracted_date` to today + - Update `lesson_count` to new total + - If file is new: Create `memory/feedback/pr--lessons.md` with YAML frontmatter: + ```yaml + --- + pr_number: + pr_title: "" + pr_url: "<url>" + extracted_date: <today's date YYYY-MM-DD> + lesson_count: <count> + --- + ``` + - Add each lesson in the format from data-model.md + +7. **Update Central Database** (proceed to Step 4) + +#### Mode C: Manual Lesson Entry + +1. **Get Lesson Content**: Extract the inline text from `$ARGUMENTS` + +2. **Determine Category**: + - If `--category <category>` provided, use that category + - Otherwise, prompt user: "Select a category for this lesson: 1) code-quality, 2) architecture, 3) testing, 4) documentation, 5) security, 6) performance, 7) other" + +3. **Create Lesson Object**: + ```yaml + lesson_id: <next available L### ID> + category: <selected category> + tags: [] + source: manual + source_ref: "Manual entry <today's date>" + date: <today's date YYYY-MM-DD> + frequency: 1 + ``` + +4. **Proceed to Step 4** to add to database + +### Step 4: Update Central Lessons Database + +1. **Read Existing Database**: + - Load `memory/lessons.md` + - Parse YAML frontmatter to get `total_lessons` and `last_updated` + - Parse existing lesson entries to build lesson ID list + +2. **Duplicate Detection**: + For each new lesson: + - Compare content with existing lessons (fuzzy match - look for similar phrasing, same core concept) + - If duplicate found (>80% similarity in meaning): + - Increment the `frequency` count on the existing lesson + - Do NOT add as new entry + - If not duplicate: + - Generate next lesson_id (L001, L002, etc. based on existing IDs) + - Append to database + +3. **Update Database Metadata**: + - Update `last_updated` to today's date + - Update `total_lessons` count + +4. **Write Updated Database**: + - Preserve the header and category documentation + - Append new lessons below the `<!-- Lessons are appended below this line -->` marker + +### Step 5: Report Summary + +Output a summary to the user: + +**For PR Extraction (first run):** +``` +✅ Extracted <N> lessons from PR #<number> "<title>" + - <count> code-quality lessons + - <count> architecture lessons + - <count> testing lessons + - ... (other categories with lessons) + +📝 Created: memory/feedback/pr-<number>-lessons.md +📊 Updated: memory/lessons.md (now contains <total> total lessons) + +<If any duplicates found in central database> +â„šī¸ <M> lessons matched existing entries (frequency incremented) +``` + +**For PR Extraction (incremental run - file already existed):** +``` +✅ Merged <N> new lessons into PR #<number> lessons + - <existing> existing lessons preserved + - <new> new lessons added + - <skipped> duplicates skipped + +📝 Updated: memory/feedback/pr-<number>-lessons.md (now contains <total> lessons) +📊 Updated: memory/lessons.md (now contains <total> total lessons) +``` + +**For Manual Entry:** +``` +✅ Added manual lesson to database + Category: <category> + +📊 Updated: memory/lessons.md (now contains <total> total lessons) +``` + +### Step 6: Commit Changes (if --commit flag) or Prompt + +**IMPORTANT**: This step runs for ALL modes (PR extraction, manual entry) whenever there are changes to `memory/lessons.md` or PR lesson files. Always check for uncommitted changes and proceed with commit if `--commit` flag is present. + +Since feedback is captured before the PR is merged, the lessons files can be committed directly to the current feature branch. + +**If `--commit` flag is present**: + +First, check if there are any uncommitted changes to memory/ lesson files: +```bash +git status --short memory/lessons.md memory/feedback/ +``` + +If there are changes, run the following git commands (get current branch name first): + +```bash +git add memory/lessons.md memory/feedback/ +git commit -m "chore(feedback): <describe changes - PR lessons or manual entry>" +git push origin <current-branch> +``` + +Use appropriate commit message: +- PR extraction: `"chore(feedback): capture lessons from PR #<number>"` +- Manual entry: `"chore(feedback): add manual lesson L<id>"` +- Mixed: `"chore(feedback): update lessons database"` + +If no changes are pending, skip the commit and report: +``` +â„šī¸ No uncommitted changes to memory/ lessons - nothing to commit. +``` + +**Output** (when changes committed): +``` +✅ Changes committed and pushed! + git add memory/lessons.md memory/feedback/ + git commit -m "chore(feedback): ..." + git push origin <current-branch> + +Your lessons are now part of the PR - ready to merge! +``` + +**If no `--commit` flag** (default): + +**Output**: +``` +📝 Ready to commit! Run: + git add memory/lessons.md memory/feedback/pr-<number>-lessons.md + git commit -m "chore(feedback): capture lessons from PR #<number>" + git push origin <your-branch> + +Then merge your PR as usual - lessons will be included! +``` + +This simplified workflow means: +- No separate branch or PR needed for lessons +- Lessons are reviewed along with the original PR +- Merge includes both the feature changes and the captured lessons + +## Lesson Entry Format + +Each lesson in the central database uses a Markdown heading with a fenced YAML metadata block: + +```markdown +### L### + +```yaml +lesson_id: L### +category: <category> +tags: [tag1, tag2] +source: pr | manual +source_ref: "PR #123" | "Manual entry YYYY-MM-DD" +date: YYYY-MM-DD +frequency: 1 +``` + +<Lesson content in plain text or markdown> +``` + +**Note**: Lessons are separated by blank lines. Do NOT use `---` separators between lessons to avoid ambiguous YAML parsing. + +## Categories Reference + +| Category | Use For | +|----------|---------| +| code-quality | Code style, idioms, language-specific best practices | +| architecture | System design, patterns, module structure | +| testing | Test strategies, coverage, edge case handling | +| documentation | Comments, READMEs, API documentation | +| security | Authentication, authorization, input validation, secrets handling | +| performance | Optimization, efficiency, resource usage | +| other | Miscellaneous lessons not fitting other categories | + +## Error Handling + +- **No `gh` CLI**: "GitHub CLI (gh) is required. Install with: brew install gh" +- **Not authenticated**: "Please authenticate with GitHub: gh auth login" +- **Not in git repo**: "This command must be run from within a git repository" +- **PR not found**: "PR #<number> not found. Please verify the PR number exists." +- **Network error**: "Failed to fetch PR data. Check your network connection and try again." + +## Stop Conditions + +- Successfully created/updated lesson files +- User cancels duplicate overwrite prompt +- Fatal error (missing dependencies, authentication failure) diff --git a/templates/commands/implement.md b/templates/commands/implement.md index 39abb1e6c8..674123ba82 100644 --- a/templates/commands/implement.md +++ b/templates/commands/implement.md @@ -15,6 +15,12 @@ You **MUST** consider the user input before proceeding (if not empty). ## Outline +0. **Load Lessons Context** (if available): + - Check if `memory/lessons.md` exists in the repository + - If it exists, read the file and filter for lessons with categories: `code-quality`, `security`, `testing` + - Keep relevant lessons in mind during implementation to avoid repeating past mistakes + - Apply lessons proactively (e.g., if L007 says "check CLI dependencies", do that in your code) + 1. Run `{SCRIPT}` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot"). 2. **Check checklists status** (if FEATURE_DIR/checklists/ exists): diff --git a/templates/commands/plan.md b/templates/commands/plan.md index 147da0afa0..81842bb426 100644 --- a/templates/commands/plan.md +++ b/templates/commands/plan.md @@ -26,6 +26,12 @@ You **MUST** consider the user input before proceeding (if not empty). ## Outline +0. **Load Lessons Context** (if available): + - Check if `memory/lessons.md` exists in the repository + - If it exists, read the file and filter for lessons with categories: `architecture`, `testing`, `performance` + - Keep relevant lessons in mind when creating the technical plan to avoid repeating past mistakes + - You may reference applied lessons in the plan (e.g., "Per L003: ...") + 1. **Setup**: Run `{SCRIPT}` from repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot"). 2. **Load context**: Read FEATURE_SPEC and `/memory/constitution.md`. Load IMPL_PLAN template (already copied). diff --git a/templates/commands/specify.md b/templates/commands/specify.md index 3c952d683e..a9d2c304e4 100644 --- a/templates/commands/specify.md +++ b/templates/commands/specify.md @@ -27,6 +27,12 @@ The text the user typed after `/speckit.specify` in the triggering message **is* Given that feature description, do this: +0. **Load Lessons Context** (if available): + - Check if `memory/lessons.md` exists in the repository + - If it exists, read the file and filter for lessons with categories: `architecture`, `documentation` + - Keep relevant lessons in mind when writing the specification to avoid repeating past mistakes + - You may reference applied lessons in the spec (e.g., "Per L007: ...") + 1. **Generate a concise short name** (2-4 words) for the branch: - Analyze the feature description and extract the most meaningful keywords - Create a 2-4 word short name that captures the essence of the feature From 4b8671049197dbc1ed60d0e991e6b2c6735da502 Mon Sep 17 00:00:00 2001 From: buddhisthead <chris.tilt@gmail.com> Date: Fri, 30 Jan 2026 16:15:02 -0800 Subject: [PATCH 2/3] test: Point to buddhisthead/spec-kit for template downloads during testing --- src/specify_cli/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index b49bc4c2a6..927c6ef7f2 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -635,7 +635,7 @@ def deep_merge(base: dict, update: dict) -> dict: return merged def download_template_from_github(ai_assistant: str, download_dir: Path, *, script_type: str = "sh", verbose: bool = True, show_progress: bool = True, client: httpx.Client = None, debug: bool = False, github_token: str = None) -> Tuple[Path, dict]: - repo_owner = "github" + repo_owner = "buddhisthead" repo_name = "spec-kit" if client is None: client = httpx.Client(verify=ssl_context) @@ -1308,7 +1308,7 @@ def version(): pass # Fetch latest template release version - repo_owner = "github" + repo_owner = "buddhisthead" repo_name = "spec-kit" api_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases/latest" From 1a6327070cd841774e2aeec08bbbb572f0ce9e73 Mon Sep 17 00:00:00 2001 From: buddhisthead <chris.tilt@gmail.com> Date: Thu, 5 Feb 2026 13:31:54 -0800 Subject: [PATCH 3/3] Update the specify command to identify branch numbers from the user input if present --- README.md | 5 +++++ spec-driven.md | 2 +- templates/commands/specify.md | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 16b08cd565..6f5ed83cd1 100644 --- a/README.md +++ b/README.md @@ -441,6 +441,11 @@ see yours. You can edit any comments that you make, but you can't edit comments delete any comments that you made, but you can't delete comments anybody else made. ``` +You can mention a number of issue in your description and it will be used to generate the branch number, such as +```text +For GH issue #123, Develop BugZapper, a new fuzzy test framework". +``` + After this prompt is entered, you should see Claude Code kick off the planning and spec drafting process. Claude Code will also trigger some of the built-in scripts to set up the repository. Once this step is completed, you should have a new branch created (e.g., `001-create-taskify`), as well as a new specification in the `specs/001-create-taskify` directory. diff --git a/spec-driven.md b/spec-driven.md index 70b9789708..cd7f164f0b 100644 --- a/spec-driven.md +++ b/spec-driven.md @@ -78,7 +78,7 @@ The SDD methodology is significantly enhanced through three powerful commands th This command transforms a simple feature description (the user-prompt) into a complete, structured specification with automatic repository management: -1. **Automatic Feature Numbering**: Scans existing specs to determine the next feature number (e.g., 001, 002, 003) +1. **Automatic Feature Numbering**: Scans existing specs to determine the next feature number (e.g., 001, 002, 003) or uses any mention of a number or issue number in the description. 2. **Branch Creation**: Generates a semantic branch name from your description and creates it automatically 3. **Template-Based Generation**: Copies and customizes the feature specification template with your requirements 4. **Directory Structure**: Creates the proper `specs/[branch-name]/` structure for all related documents diff --git a/templates/commands/specify.md b/templates/commands/specify.md index a9d2c304e4..3a9607fb4d 100644 --- a/templates/commands/specify.md +++ b/templates/commands/specify.md @@ -61,7 +61,8 @@ Given that feature description, do this: c. Determine the next available number: - Extract all numbers from all three sources - Find the highest number N - - Use N+1 for the new branch number + - Use N+1 for the default new branch number + - Examine User Input for any explicit number provided by the user (e.g., "number 5", "N=5", "issue number 5", or "GH issue #5", or "issue #5") and if found, use that instead of N+1 for the new branch number d. Run the script `{SCRIPT}` with the calculated number and short-name: - Pass `--number N+1` and `--short-name "your-short-name"` along with the feature description