Initial commit
This commit is contained in:
197
skills/spec-author/scripts/check-completeness.sh
Executable file
197
skills/spec-author/scripts/check-completeness.sh
Executable file
@@ -0,0 +1,197 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Check Spec Completeness Script
|
||||
# Shows detailed TODO items and missing sections
|
||||
|
||||
set -o pipefail
|
||||
|
||||
SPEC_FILE="${1:-.}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Color codes
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
GRAY='\033[0;37m'
|
||||
NC='\033[0m'
|
||||
|
||||
if [ ! -f "$SPEC_FILE" ]; then
|
||||
echo -e "${RED}✗ Error: File not found: $SPEC_FILE${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract and display all TODO items
|
||||
show_todos() {
|
||||
echo -e "${BLUE}📝 TODO ITEMS (Incomplete):${NC}"
|
||||
echo "================================"
|
||||
echo ""
|
||||
|
||||
local todo_count=0
|
||||
local line_num=0
|
||||
|
||||
while IFS= read -r line; do
|
||||
((line_num++))
|
||||
if [[ $line =~ TODO|FIXME ]]; then
|
||||
((todo_count++))
|
||||
# Extract TODO content
|
||||
if [[ $line =~ \[TODO:(.*)\] ]]; then
|
||||
echo -e "${YELLOW}[$todo_count]${NC} Line $line_num: ${BASH_REMATCH[1]}"
|
||||
elif [[ $line =~ TODO:(.*) ]]; then
|
||||
echo -e "${YELLOW}[$todo_count]${NC} Line $line_num: ${BASH_REMATCH[1]}"
|
||||
else
|
||||
# Clean up the line for display
|
||||
clean_line=$(echo "$line" | sed 's/^[[:space:]]*//' | sed 's/^<!--[[:space:]]*//' | sed 's/[[:space:]]*-->$//')
|
||||
echo -e "${YELLOW}[$todo_count]${NC} Line $line_num: $clean_line"
|
||||
fi
|
||||
fi
|
||||
done < "$SPEC_FILE"
|
||||
|
||||
if [ $todo_count -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ No TODO items found!${NC}"
|
||||
else
|
||||
echo ""
|
||||
echo -e "Total TODOs: ${RED}$todo_count${NC}"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Show missing sections
|
||||
show_missing_sections() {
|
||||
echo -e "${BLUE}📋 SECTION ANALYSIS:${NC}"
|
||||
echo "================================"
|
||||
echo ""
|
||||
|
||||
local found_sections=()
|
||||
local all_possible_sections=(
|
||||
"Description"
|
||||
"Overview"
|
||||
"Executive Summary"
|
||||
"Problem Statement"
|
||||
"Goals & Success Criteria"
|
||||
"Business Value"
|
||||
"Stakeholders"
|
||||
"User Stories"
|
||||
"Acceptance Criteria"
|
||||
"Proposed Solution"
|
||||
"Architecture"
|
||||
"Implementation Plan"
|
||||
"Risk & Mitigation"
|
||||
"Security Considerations"
|
||||
"Testing Strategy"
|
||||
"Rollout & Deployment Strategy"
|
||||
"Dependencies & Assumptions"
|
||||
"References"
|
||||
"Related Resources"
|
||||
)
|
||||
|
||||
for section in "${all_possible_sections[@]}"; do
|
||||
if grep -qE "^## $section" "$SPEC_FILE"; then
|
||||
found_sections+=("$section")
|
||||
fi
|
||||
done
|
||||
|
||||
echo -e "${GREEN}✓ Found Sections (${#found_sections[@]}):${NC}"
|
||||
for section in "${found_sections[@]}"; do
|
||||
echo " ✓ $section"
|
||||
done
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Show file statistics
|
||||
show_statistics() {
|
||||
echo -e "${BLUE}📊 FILE STATISTICS:${NC}"
|
||||
echo "================================"
|
||||
echo ""
|
||||
|
||||
local total_lines=$(wc -l < "$SPEC_FILE")
|
||||
local blank_lines
|
||||
blank_lines=$(grep -c '^[[:space:]]*$' "$SPEC_FILE" 2>/dev/null)
|
||||
blank_lines=${blank_lines:-0}
|
||||
local code_lines
|
||||
code_lines=$(grep -c '```' "$SPEC_FILE" 2>/dev/null)
|
||||
code_lines=${code_lines:-0}
|
||||
local comment_lines
|
||||
comment_lines=$(grep -c '<!--' "$SPEC_FILE" 2>/dev/null)
|
||||
comment_lines=${comment_lines:-0}
|
||||
local has_frontmatter
|
||||
has_frontmatter=$(grep -c '^---$' "$SPEC_FILE" 2>/dev/null)
|
||||
has_frontmatter=${has_frontmatter:-0}
|
||||
|
||||
echo "Total Lines: $total_lines"
|
||||
echo "Blank Lines: $blank_lines"
|
||||
echo "Code Blocks: $((code_lines / 2))"
|
||||
echo "Comments: $((comment_lines / 2))"
|
||||
frontmatter_status="No"
|
||||
[ "$has_frontmatter" -gt 0 ] && frontmatter_status="Yes"
|
||||
echo "Has YAML Frontmatter: $frontmatter_status"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Show completion percentage
|
||||
show_completion() {
|
||||
local completion=$(grep -c "^##" "$SPEC_FILE" || echo 0)
|
||||
completion=$((completion * 100 / 15)) # Assume ~15 sections for full spec
|
||||
[ $completion -gt 100 ] && completion=100
|
||||
|
||||
echo -e "${BLUE}📈 COMPLETION:${NC}"
|
||||
echo "================================"
|
||||
echo ""
|
||||
|
||||
# Simple progress bar
|
||||
local filled=$((completion / 5))
|
||||
local empty=$((20 - filled))
|
||||
local bar=""
|
||||
for i in $(seq 1 $filled); do bar+="█"; done
|
||||
for i in $(seq 1 $empty); do bar+="░"; done
|
||||
|
||||
echo -n "Progress: [$bar] "
|
||||
if [ $completion -lt 50 ]; then
|
||||
echo -e "${RED}$completion%${NC}"
|
||||
elif [ $completion -lt 80 ]; then
|
||||
echo -e "${YELLOW}$completion%${NC}"
|
||||
else
|
||||
echo -e "${GREEN}$completion%${NC}"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Show references/links
|
||||
show_references() {
|
||||
echo -e "${BLUE}🔗 EXTERNAL REFERENCES:${NC}"
|
||||
echo "================================"
|
||||
echo ""
|
||||
|
||||
local refs=$(grep -oE '\[([^\]]+)\]\(([^)]+)\)' "$SPEC_FILE" | cut -d'(' -f2 | tr -d ')' | sort -u || echo "")
|
||||
|
||||
if [ -z "$refs" ]; then
|
||||
echo "No external references found"
|
||||
else
|
||||
echo "$refs" | while read -r ref; do
|
||||
# Check if link is broken (relative and file doesn't exist)
|
||||
if [[ ! "$ref" =~ ^http ]] && [ ! -f "$ref" ]; then
|
||||
echo -e " ${YELLOW}⚠${NC} $ref (file not found)"
|
||||
else
|
||||
echo " ${GREEN}✓${NC} $ref"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Main
|
||||
echo ""
|
||||
echo -e "${BLUE}📋 SPEC COMPLETENESS ANALYSIS${NC}"
|
||||
echo "========================================"
|
||||
echo "File: $(basename "$SPEC_FILE")"
|
||||
echo ""
|
||||
|
||||
show_todos
|
||||
show_missing_sections
|
||||
show_statistics
|
||||
show_completion
|
||||
show_references
|
||||
|
||||
echo -e "${GRAY}---${NC}"
|
||||
echo "Run 'validate-spec.sh' for full validation"
|
||||
186
skills/spec-author/scripts/generate-spec.sh
Executable file
186
skills/spec-author/scripts/generate-spec.sh
Executable file
@@ -0,0 +1,186 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Generate Spec from Template
|
||||
# Creates a new spec file in docs/specs/ from a template
|
||||
|
||||
set -o pipefail
|
||||
|
||||
TEMPLATE_NAME="${1:-.}"
|
||||
SPEC_ID="${2:-.}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Find git repo root for reliable path resolution
|
||||
# This allows scripts to work when called from any directory in the repo
|
||||
if git -C "$SCRIPT_DIR" rev-parse --git-dir > /dev/null 2>&1; then
|
||||
PROJECT_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel)"
|
||||
else
|
||||
# Fallback to relative path traversal (4 levels up from scripts/)
|
||||
# From: project-root/project-basics/skills/spec-author/scripts/
|
||||
# To: project-root/
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||
fi
|
||||
|
||||
# Specs are always stored in the git repo root under docs/specs/
|
||||
SPECS_DIR="$PROJECT_ROOT/docs/specs"
|
||||
|
||||
# Function to get folder name for spec type (same as template name)
|
||||
get_spec_folder() {
|
||||
local template=$1
|
||||
case "$template" in
|
||||
business-requirement|technical-requirement|design-document|api-contract|data-model|component|plan|milestone|flow-schematic|deployment-procedure|configuration-schema)
|
||||
echo "$template"
|
||||
;;
|
||||
*)
|
||||
echo ""
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Color codes
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Print usage
|
||||
usage() {
|
||||
echo "Usage: generate-spec.sh <template-type> [<spec-id> | --next <slug>]"
|
||||
echo ""
|
||||
echo "Template types:"
|
||||
echo " - business-requirement (brd-XXX-slug)"
|
||||
echo " - technical-requirement (prd-XXX-slug)"
|
||||
echo " - design-document (des-XXX-slug)"
|
||||
echo " - api-contract (api-XXX-slug)"
|
||||
echo " - data-model (data-XXX-slug)"
|
||||
echo " - component (cmp-XXX-slug)"
|
||||
echo " - plan (pln-XXX-slug)"
|
||||
echo " - milestone (mls-XXX-slug)"
|
||||
echo " - flow-schematic (flow-XXX-slug)"
|
||||
echo " - deployment-procedure (deploy-XXX-slug)"
|
||||
echo " - configuration-schema (config-XXX-slug)"
|
||||
echo ""
|
||||
echo "Spec ID: Use the full ID (e.g., brd-001-user-export)"
|
||||
echo " OR use --next <slug> to auto-generate next ID"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " generate-spec.sh business-requirement brd-001-user-export"
|
||||
echo " Creates: docs/specs/business-requirement/brd-001-user-export.md"
|
||||
echo ""
|
||||
echo " generate-spec.sh business-requirement --next user-export"
|
||||
echo " Auto-determines next ID (e.g., brd-002-user-export)"
|
||||
echo ""
|
||||
echo " generate-spec.sh api-contract --next"
|
||||
echo " Auto-determines next ID without slug (e.g., api-001)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ "$TEMPLATE_NAME" = "-h" ] || [ "$TEMPLATE_NAME" = "--help" ] || [ -z "$TEMPLATE_NAME" ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
# Handle --next flag for auto-generating next ID
|
||||
if [ "$SPEC_ID" = "--next" ]; then
|
||||
# Check if slug is provided as third argument
|
||||
SLUG="${3:-}"
|
||||
|
||||
if [ -n "$SLUG" ]; then
|
||||
SPEC_ID=$("$SCRIPT_DIR/next-id.sh" "$TEMPLATE_NAME" --with-slug "$SLUG" --quiet)
|
||||
else
|
||||
SPEC_ID=$("$SCRIPT_DIR/next-id.sh" "$TEMPLATE_NAME" --quiet)
|
||||
fi
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}✗ Error: Failed to determine next ID${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}Auto-generated ID: $SPEC_ID${NC}"
|
||||
echo ""
|
||||
elif [ -z "$SPEC_ID" ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
# Check if template exists (try multiple locations in order of preference)
|
||||
# 1. project-basics/skills/spec-author/templates/ (plugin subdirectory in project)
|
||||
# 2. skills/spec-author/templates/ (plugin in root of project)
|
||||
# 3. templates/ (root-level templates directory)
|
||||
# 4. Current script directory/../templates (relative to script)
|
||||
TEMPLATE_FILE=""
|
||||
TEMPLATE_SEARCH_PATHS=(
|
||||
"$PROJECT_ROOT/project-basics/skills/spec-author/templates/$TEMPLATE_NAME.md"
|
||||
"$PROJECT_ROOT/skills/spec-author/templates/$TEMPLATE_NAME.md"
|
||||
"$PROJECT_ROOT/templates/$TEMPLATE_NAME.md"
|
||||
"$SCRIPT_DIR/../templates/$TEMPLATE_NAME.md"
|
||||
)
|
||||
|
||||
for path in "${TEMPLATE_SEARCH_PATHS[@]}"; do
|
||||
if [ -f "$path" ]; then
|
||||
TEMPLATE_FILE="$path"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$TEMPLATE_FILE" ] || [ ! -f "$TEMPLATE_FILE" ]; then
|
||||
echo -e "${RED}✗ Error: Template not found${NC}"
|
||||
echo "Looked in:"
|
||||
for path in "${TEMPLATE_SEARCH_PATHS[@]}"; do
|
||||
echo " - $path"
|
||||
done
|
||||
echo ""
|
||||
echo "Run 'list-templates.sh' to see available templates"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the folder name for this spec type
|
||||
SPEC_FOLDER=$(get_spec_folder "$TEMPLATE_NAME")
|
||||
if [ -z "$SPEC_FOLDER" ]; then
|
||||
echo -e "${RED}✗ Error: Unknown template type: $TEMPLATE_NAME${NC}"
|
||||
echo "Run 'list-templates.sh' to see available templates"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure specs type directory exists
|
||||
SPEC_TYPE_DIR="$SPECS_DIR/$SPEC_FOLDER"
|
||||
mkdir -p "$SPEC_TYPE_DIR"
|
||||
|
||||
# Build output path
|
||||
OUTPUT_PATH="$SPEC_TYPE_DIR/$SPEC_ID.md"
|
||||
|
||||
# Check if output file already exists
|
||||
if [ -f "$OUTPUT_PATH" ]; then
|
||||
echo -e "${YELLOW}⚠ File already exists: $OUTPUT_PATH${NC}"
|
||||
read -p "Overwrite? (y/n) " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Cancelled"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Copy template to output location
|
||||
cp "$TEMPLATE_FILE" "$OUTPUT_PATH"
|
||||
|
||||
# Get current date
|
||||
CURRENT_DATE=$(date +%Y-%m-%d)
|
||||
|
||||
# Fill in some default values in the new spec
|
||||
sed -i '' "s/\[Date\]/$CURRENT_DATE/g" "$OUTPUT_PATH" 2>/dev/null || sed -i "s/\[Date\]/$CURRENT_DATE/g" "$OUTPUT_PATH"
|
||||
|
||||
echo -e "${GREEN}✓ Spec created successfully!${NC}"
|
||||
echo ""
|
||||
echo "File: $OUTPUT_PATH"
|
||||
echo "Template: $(basename "$TEMPLATE_FILE")"
|
||||
echo "ID: $SPEC_ID"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo "1. Open the file: $OUTPUT_PATH"
|
||||
echo "2. Fill in the ID and descriptive information"
|
||||
echo "3. Complete all TODO sections (marked with TODO)"
|
||||
echo "4. Remove placeholder text in [brackets]"
|
||||
echo ""
|
||||
echo "To check what needs to be completed:"
|
||||
echo " ./scripts/check-completeness.sh $OUTPUT_PATH"
|
||||
echo ""
|
||||
echo "To validate the spec:"
|
||||
echo " ./scripts/validate-spec.sh $OUTPUT_PATH"
|
||||
114
skills/spec-author/scripts/list-templates.sh
Executable file
114
skills/spec-author/scripts/list-templates.sh
Executable file
@@ -0,0 +1,114 @@
|
||||
#!/bin/bash
|
||||
|
||||
# List Available Spec Templates
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TEMPLATES_DIR="$SCRIPT_DIR/../templates"
|
||||
|
||||
# Color codes
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}📚 AVAILABLE SPECIFICATION TEMPLATES${NC}"
|
||||
echo "====================================="
|
||||
echo ""
|
||||
|
||||
if [ ! -d "$TEMPLATES_DIR" ]; then
|
||||
echo "Note: Templates will be synced from project templates directory"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Display templates by category
|
||||
echo -e "${BLUE}📋 BY CATEGORY${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}Requirements & Business:${NC}"
|
||||
echo " • business-requirement"
|
||||
echo " Document business needs and customer problems"
|
||||
echo ""
|
||||
echo " • technical-requirement"
|
||||
echo " How to build what business requires"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}Architecture & Design:${NC}"
|
||||
echo " • design-document"
|
||||
echo " Architecture and technical design decisions"
|
||||
echo ""
|
||||
echo " • api-contract"
|
||||
echo " API endpoint specifications and contracts"
|
||||
echo ""
|
||||
echo " • data-model"
|
||||
echo " Entity definitions and database schemas"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}Implementation & Operations:${NC}"
|
||||
echo " • component"
|
||||
echo " System component specifications"
|
||||
echo ""
|
||||
echo " • plan"
|
||||
echo " Implementation roadmap and phases"
|
||||
echo ""
|
||||
echo " • milestone"
|
||||
echo " Delivery milestones and checkpoints"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}Process & Deployment:${NC}"
|
||||
echo " • flow-schematic"
|
||||
echo " Business process flows and workflows"
|
||||
echo ""
|
||||
echo " • deployment-procedure"
|
||||
echo " Production deployment steps"
|
||||
echo ""
|
||||
echo " • configuration-schema"
|
||||
echo " Configuration specifications"
|
||||
echo ""
|
||||
|
||||
echo -e "${BLUE}📝 USAGE${NC}"
|
||||
echo "--------"
|
||||
echo ""
|
||||
echo "To create a new spec from a template, use:"
|
||||
echo -e " ${GREEN}./scripts/generate-spec.sh <template-name> <output-path>${NC}"
|
||||
echo ""
|
||||
echo "Example:"
|
||||
echo -e " ${GREEN}./scripts/generate-spec.sh business-requirement specs/user-export.md${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${BLUE}🎯 ID CONVENTIONS${NC}"
|
||||
echo "------------------"
|
||||
echo ""
|
||||
echo "• Business Requirement: brd-XXX-descriptive-slug"
|
||||
echo " Example: brd-001-user-export-pdf"
|
||||
echo ""
|
||||
echo "• Technical Requirement: prd-XXX-descriptive-slug"
|
||||
echo " Example: prd-001-export-service"
|
||||
echo ""
|
||||
echo "• Design Document: des-XXX-descriptive-slug"
|
||||
echo " Example: des-001-microservices-architecture"
|
||||
echo ""
|
||||
echo "• API Contract: api-XXX-descriptive-slug"
|
||||
echo " Example: api-001-user-service"
|
||||
echo ""
|
||||
echo "• Data Model: data-XXX-descriptive-slug"
|
||||
echo " Example: data-001-user-schema"
|
||||
echo ""
|
||||
echo "• Component: cmp-XXX-descriptive-slug"
|
||||
echo " Example: cmp-001-auth-service"
|
||||
echo ""
|
||||
echo "• Plan: pln-XXX-descriptive-slug"
|
||||
echo " Example: pln-001-migration-roadmap"
|
||||
echo ""
|
||||
echo "• Milestone: mls-XXX-descriptive-slug"
|
||||
echo " Example: mls-001-phase-1-delivery"
|
||||
echo ""
|
||||
echo "• Flow Schematic: flow-XXX-descriptive-slug"
|
||||
echo " Example: flow-001-user-signup"
|
||||
echo ""
|
||||
echo "• Deployment Procedure: deploy-XXX-descriptive-slug"
|
||||
echo " Example: deploy-001-staging-release"
|
||||
echo ""
|
||||
echo "• Configuration Schema: config-XXX-descriptive-slug"
|
||||
echo " Example: config-001-app-settings"
|
||||
echo ""
|
||||
195
skills/spec-author/scripts/next-id.sh
Executable file
195
skills/spec-author/scripts/next-id.sh
Executable file
@@ -0,0 +1,195 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Next Spec ID Script
|
||||
# Determines the next available ID number for a spec type
|
||||
|
||||
set -o pipefail
|
||||
|
||||
SPEC_TYPE="${1:-.}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Find git repo root for reliable path resolution
|
||||
# This allows scripts to work when called from any directory in the repo
|
||||
if git -C "$SCRIPT_DIR" rev-parse --git-dir > /dev/null 2>&1; then
|
||||
PROJECT_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel)"
|
||||
else
|
||||
# Fallback to relative path traversal (4 levels up from scripts/)
|
||||
# From: project-root/project-basics/skills/spec-author/scripts/
|
||||
# To: project-root/
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||
fi
|
||||
|
||||
# Specs are always stored in the git repo root under docs/specs/
|
||||
SPECS_DIR="$PROJECT_ROOT/docs/specs"
|
||||
|
||||
# Color codes
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Map spec types to their prefixes and folder names
|
||||
get_spec_info() {
|
||||
local type=$1
|
||||
case "$type" in
|
||||
business-requirement)
|
||||
echo "brd:business-requirement"
|
||||
;;
|
||||
technical-requirement)
|
||||
echo "prd:technical-requirement"
|
||||
;;
|
||||
design-document)
|
||||
echo "des:design-document"
|
||||
;;
|
||||
api-contract)
|
||||
echo "api:api-contract"
|
||||
;;
|
||||
data-model)
|
||||
echo "data:data-model"
|
||||
;;
|
||||
component)
|
||||
echo "cmp:component"
|
||||
;;
|
||||
plan)
|
||||
echo "pln:plan"
|
||||
;;
|
||||
milestone)
|
||||
echo "mls:milestone"
|
||||
;;
|
||||
flow-schematic)
|
||||
echo "flow:flow-schematic"
|
||||
;;
|
||||
deployment-procedure)
|
||||
echo "deploy:deployment-procedure"
|
||||
;;
|
||||
configuration-schema)
|
||||
echo "config:configuration-schema"
|
||||
;;
|
||||
*)
|
||||
echo ""
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Print usage
|
||||
usage() {
|
||||
echo "Usage: next-id.sh <spec-type> [--with-slug <slug>]"
|
||||
echo ""
|
||||
echo "Determines the next available ID number for a spec type."
|
||||
echo ""
|
||||
echo "Spec types:"
|
||||
echo " - business-requirement"
|
||||
echo " - technical-requirement"
|
||||
echo " - design-document"
|
||||
echo " - api-contract"
|
||||
echo " - data-model"
|
||||
echo " - component"
|
||||
echo " - plan"
|
||||
echo " - milestone"
|
||||
echo " - flow-schematic"
|
||||
echo " - deployment-procedure"
|
||||
echo " - configuration-schema"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --with-slug <slug> Include slug in output (e.g., brd-001-my-feature)"
|
||||
echo " --quiet Only output the ID, no formatting"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " next-id.sh business-requirement"
|
||||
echo " Output: brd-001"
|
||||
echo ""
|
||||
echo " next-id.sh business-requirement --with-slug user-export"
|
||||
echo " Output: brd-001-user-export"
|
||||
echo ""
|
||||
echo " next-id.sh api-contract --quiet"
|
||||
echo " Output: api-001"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
SLUG=""
|
||||
QUIET=false
|
||||
|
||||
if [ "$SPEC_TYPE" = "-h" ] || [ "$SPEC_TYPE" = "--help" ] || [ -z "$SPEC_TYPE" ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
shift
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--with-slug)
|
||||
SLUG="$2"
|
||||
shift 2
|
||||
;;
|
||||
--quiet)
|
||||
QUIET=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}✗ Unknown option: $1${NC}" >&2
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Get spec info
|
||||
SPEC_INFO=$(get_spec_info "$SPEC_TYPE")
|
||||
if [ -z "$SPEC_INFO" ]; then
|
||||
echo -e "${RED}✗ Error: Unknown spec type: $SPEC_TYPE${NC}" >&2
|
||||
echo "Run 'next-id.sh --help' to see available types" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PREFIX=$(echo "$SPEC_INFO" | cut -d: -f1)
|
||||
FOLDER=$(echo "$SPEC_INFO" | cut -d: -f2)
|
||||
SPEC_TYPE_DIR="$SPECS_DIR/$FOLDER"
|
||||
|
||||
# Find highest existing ID number
|
||||
MAX_ID=0
|
||||
|
||||
if [ -d "$SPEC_TYPE_DIR" ]; then
|
||||
# Look for files matching the pattern: prefix-NNN-*.md
|
||||
for file in "$SPEC_TYPE_DIR/$PREFIX"-*.md; do
|
||||
if [ -f "$file" ]; then
|
||||
# Extract the number part (NNN)
|
||||
filename=$(basename "$file" .md)
|
||||
# Remove prefix and leading dash
|
||||
rest="${filename#$PREFIX-}"
|
||||
# Extract just the number (first field before next dash)
|
||||
num=$(echo "$rest" | cut -d- -f1)
|
||||
|
||||
# Check if it's a valid number
|
||||
if [[ "$num" =~ ^[0-9]+$ ]]; then
|
||||
if [ "$num" -gt "$MAX_ID" ]; then
|
||||
MAX_ID=$num
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Calculate next ID
|
||||
NEXT_NUM=$((MAX_ID + 1))
|
||||
NEXT_ID=$(printf "%s-%03d" "$PREFIX" "$NEXT_NUM")
|
||||
|
||||
# Add slug if provided
|
||||
if [ -n "$SLUG" ]; then
|
||||
FULL_ID="$NEXT_ID-$SLUG"
|
||||
else
|
||||
FULL_ID="$NEXT_ID"
|
||||
fi
|
||||
|
||||
# Output
|
||||
if [ "$QUIET" = true ]; then
|
||||
echo "$FULL_ID"
|
||||
else
|
||||
echo -e "${GREEN}Next available ID for $SPEC_TYPE:${NC}"
|
||||
echo -e "${BLUE}$FULL_ID${NC}"
|
||||
echo ""
|
||||
if [ "$MAX_ID" -gt 0 ]; then
|
||||
echo "Last ID: $PREFIX-$(printf "%03d" "$MAX_ID")"
|
||||
else
|
||||
echo "No existing specs found (this will be the first)"
|
||||
fi
|
||||
fi
|
||||
182
skills/spec-author/scripts/task.sh
Executable file
182
skills/spec-author/scripts/task.sh
Executable file
@@ -0,0 +1,182 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Task Manager - Main CLI Orchestrator
|
||||
# Compose smaller scripts to create a unified task management system
|
||||
|
||||
set -o pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
LIB_DIR="$SCRIPT_DIR/lib"
|
||||
|
||||
source "$LIB_DIR/task-common.sh"
|
||||
|
||||
# Check if jq is available
|
||||
check_jq() {
|
||||
if ! command -v jq &> /dev/null; then
|
||||
log_warn "jq not found - some features will have limited functionality"
|
||||
fi
|
||||
}
|
||||
|
||||
# Print help
|
||||
show_help() {
|
||||
cat <<'EOF'
|
||||
|
||||
Task Manager - Unified Task Tracking System
|
||||
|
||||
USAGE:
|
||||
task.sh <command> [options]
|
||||
|
||||
COMMANDS:
|
||||
|
||||
parse <file|dir> Extract tasks from Markdown files as JSON
|
||||
Options:
|
||||
--json Output as JSON (default)
|
||||
--table Output as formatted table
|
||||
|
||||
query <criteria> Query and filter tasks from JSON input (via pipe)
|
||||
Options:
|
||||
--status <status> Filter by status (Draft|Planning|In Progress|Completed|Blocked)
|
||||
--priority <priority> Filter by priority (critical|high|medium|low)
|
||||
--owner <owner> Filter by owner
|
||||
--id <id> Find specific task by ID
|
||||
--search <text> Search in title/description
|
||||
--unassigned Find unassigned tasks
|
||||
--blockers Find tasks with dependencies
|
||||
--dependents <id> Find tasks that depend on this task
|
||||
--count <field> Count tasks by field
|
||||
--sort <field> Sort by field
|
||||
--table Format as table
|
||||
|
||||
update <file> <id> [options] Update task properties in place
|
||||
Options:
|
||||
--status <status> Set status
|
||||
--priority <priority> Set priority
|
||||
--owner <owner> Set owner
|
||||
--effort <effort> Set effort estimate
|
||||
|
||||
report [type] Generate reports from task JSON (via pipe)
|
||||
Types:
|
||||
--summary Summary statistics
|
||||
--board Kanban board view
|
||||
--priority Tasks grouped by priority
|
||||
--owner Tasks grouped by owner
|
||||
--dependencies Dependency graph
|
||||
--burndown Progress/burndown chart
|
||||
--csv Export as CSV
|
||||
--all All reports
|
||||
|
||||
validate [type] Validate task consistency (via pipe)
|
||||
Types:
|
||||
--schema Validate schema compliance
|
||||
--dependencies Validate dependencies exist
|
||||
--uniqueness Check for duplicate IDs
|
||||
--all Full validation (default)
|
||||
|
||||
list Quick task listing with filtering
|
||||
Options:
|
||||
(same as query)
|
||||
|
||||
EXAMPLES:
|
||||
|
||||
# Parse all tasks from a plan file
|
||||
./task.sh parse pln-001-roadmap.md
|
||||
|
||||
# Parse all tasks in a directory
|
||||
./task.sh parse ./specs/
|
||||
|
||||
# Find all high-priority tasks
|
||||
./task.sh parse . | ./task.sh query --priority high
|
||||
|
||||
# Find tasks assigned to Alice
|
||||
./task.sh parse . | ./task.sh query --owner "Alice" --table
|
||||
|
||||
# Find unassigned tasks across all plans
|
||||
./task.sh parse specs/ | ./task.sh query --unassigned --table
|
||||
|
||||
# Generate a kanban board
|
||||
./task.sh parse . | ./task.sh report --board
|
||||
|
||||
# Export tasks as CSV
|
||||
./task.sh parse . | ./task.sh report --csv
|
||||
|
||||
# Validate all tasks
|
||||
./task.sh parse . | ./task.sh validate --all
|
||||
|
||||
# Update task status
|
||||
./task.sh update pln-001-roadmap.md tsk-001 --status "In Progress"
|
||||
|
||||
# Update multiple fields
|
||||
./task.sh update pln-001-roadmap.md tsk-001 --status "Completed" --priority "high"
|
||||
|
||||
PIPING EXAMPLES:
|
||||
|
||||
# Find completed tasks by priority
|
||||
./task.sh parse . | ./task.sh query --status "Completed" | ./task.sh query --priority "high" | ./task.sh report --summary
|
||||
|
||||
# Find all blockers assigned to a person
|
||||
./task.sh parse . | ./task.sh query --owner "Bob" | ./task.sh query --blockers | ./task.sh report --board
|
||||
|
||||
# Generate CSV of all in-progress tasks
|
||||
./task.sh parse . | ./task.sh query --status "In Progress" | ./task.sh report --csv > tasks.csv
|
||||
|
||||
NOTES:
|
||||
- JSON piping allows composing commands together
|
||||
- Update operations modify files in place with automatic backups
|
||||
- All text output uses colors (disable with NO_COLOR env var)
|
||||
- Requires jq for full functionality (graceful degradation without it)
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# Main command routing
|
||||
main() {
|
||||
check_jq
|
||||
|
||||
if [[ $# -eq 0 ]] || [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then
|
||||
show_help
|
||||
return 0
|
||||
fi
|
||||
|
||||
local command="$1"
|
||||
shift
|
||||
|
||||
case "$command" in
|
||||
parse)
|
||||
bash "$LIB_DIR/task-parse.sh" "$@"
|
||||
;;
|
||||
query)
|
||||
bash "$LIB_DIR/task-query.sh" "$@"
|
||||
;;
|
||||
update)
|
||||
bash "$LIB_DIR/task-update.sh" "$@"
|
||||
;;
|
||||
report)
|
||||
bash "$LIB_DIR/task-report.sh" "$@"
|
||||
;;
|
||||
validate)
|
||||
bash "$LIB_DIR/task-validate.sh" "$@"
|
||||
;;
|
||||
list)
|
||||
# Convenience command: parse + query
|
||||
if [[ $# -eq 0 ]]; then
|
||||
log_error "Usage: task.sh list <file|dir> [query options]"
|
||||
return 1
|
||||
fi
|
||||
local target="$1"
|
||||
shift
|
||||
bash "$LIB_DIR/task-parse.sh" "$target" | bash "$LIB_DIR/task-query.sh" "$@"
|
||||
;;
|
||||
--version)
|
||||
echo "Task Manager v0.1.0"
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown command: $command"
|
||||
echo ""
|
||||
show_help
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Execute main
|
||||
main "$@"
|
||||
385
skills/spec-author/scripts/validate-spec.sh
Executable file
385
skills/spec-author/scripts/validate-spec.sh
Executable file
@@ -0,0 +1,385 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Spec Author Validation Script
|
||||
# Validates specification documents against required templates and standards
|
||||
|
||||
set -o pipefail
|
||||
|
||||
SPEC_FILE="${1:-.}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Find git repo root for reliable path resolution
|
||||
# This allows scripts to work when called from any directory in the repo
|
||||
if git -C "$SCRIPT_DIR" rev-parse --git-dir > /dev/null 2>&1; then
|
||||
PROJECT_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel)"
|
||||
else
|
||||
# Fallback to relative path traversal (4 levels up from scripts/)
|
||||
# From: project-root/project-basics/skills/spec-author/scripts/
|
||||
# To: project-root/
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||
fi
|
||||
|
||||
# Templates are located in the spec-author plugin directory
|
||||
TEMPLATES_DIR="$SCRIPT_DIR/../templates"
|
||||
|
||||
# Check if file is a template file
|
||||
is_template_file() {
|
||||
local basename=$(basename "$SPEC_FILE")
|
||||
# Check if file matches any template filename
|
||||
[[ "$basename" == "api-contract.md" ]] ||
|
||||
[[ "$basename" == "business-requirement.md" ]] ||
|
||||
[[ "$basename" == "component.md" ]] ||
|
||||
[[ "$basename" == "configuration-schema.md" ]] ||
|
||||
[[ "$basename" == "constitution.md" ]] ||
|
||||
[[ "$basename" == "data-model.md" ]] ||
|
||||
[[ "$basename" == "deployment-procedure.md" ]] ||
|
||||
[[ "$basename" == "design-document.md" ]] ||
|
||||
[[ "$basename" == "flow-schematic.md" ]] ||
|
||||
[[ "$basename" == "milestone.md" ]] ||
|
||||
[[ "$basename" == "plan.md" ]] ||
|
||||
[[ "$basename" == "technical-requirement.md" ]]
|
||||
}
|
||||
|
||||
# Color codes
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Check if file exists
|
||||
if [ ! -f "$SPEC_FILE" ]; then
|
||||
echo -e "${RED}✗ Error: Spec file not found: $SPEC_FILE${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Skip validation for template files themselves
|
||||
if is_template_file; then
|
||||
echo -e "${BLUE}📋 SPEC VALIDATION REPORT${NC}"
|
||||
echo "===================="
|
||||
echo ""
|
||||
echo "Spec: $(basename "$SPEC_FILE")"
|
||||
echo "Status: ${GREEN}✓ TEMPLATE${NC} (templates are not validated, they are used as references)"
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Determine spec type from filename or spec ID
|
||||
determine_spec_type() {
|
||||
local filename=$(basename "$SPEC_FILE" .md)
|
||||
|
||||
# First check by spec ID prefix in filename
|
||||
case "$filename" in
|
||||
brd-*) echo "business"; return ;;
|
||||
prd-*) echo "technical"; return ;;
|
||||
des-*) echo "design"; return ;;
|
||||
api-*) echo "api"; return ;;
|
||||
data-*) echo "data"; return ;;
|
||||
cmp-*) echo "component"; return ;;
|
||||
pln-*) echo "plan"; return ;;
|
||||
mls-*) echo "milestone"; return ;;
|
||||
flow-*) echo "flow"; return ;;
|
||||
deploy-*) echo "deployment"; return ;;
|
||||
config-*) echo "config"; return ;;
|
||||
esac
|
||||
|
||||
# Then check by full template name
|
||||
if [[ "$filename" == "design-document" ]] || [[ "$SPEC_FILE" =~ design-document ]]; then
|
||||
echo "design"
|
||||
elif [[ "$filename" == "technical-requirement" ]] || [[ "$SPEC_FILE" =~ technical-requirement ]]; then
|
||||
echo "technical"
|
||||
elif [[ "$filename" == "business-requirement" ]] || [[ "$SPEC_FILE" =~ business-requirement ]]; then
|
||||
echo "business"
|
||||
elif [[ "$filename" == "api-contract" ]] || [[ "$SPEC_FILE" =~ api-contract ]]; then
|
||||
echo "api"
|
||||
elif [[ "$filename" == "data-model" ]] || [[ "$SPEC_FILE" =~ data-model ]]; then
|
||||
echo "data"
|
||||
elif [[ "$filename" == "component" ]] || [[ "$SPEC_FILE" =~ component ]]; then
|
||||
echo "component"
|
||||
elif [[ "$filename" == "plan" ]] || [[ "$SPEC_FILE" =~ plan ]]; then
|
||||
echo "plan"
|
||||
elif [[ "$filename" == "milestone" ]] || [[ "$SPEC_FILE" =~ milestone ]]; then
|
||||
echo "milestone"
|
||||
elif [[ "$filename" == "flow-schematic" ]] || [[ "$SPEC_FILE" =~ flow-schematic ]]; then
|
||||
echo "flow"
|
||||
elif [[ "$filename" == "deployment-procedure" ]] || [[ "$SPEC_FILE" =~ deployment-procedure ]]; then
|
||||
echo "deployment"
|
||||
elif [[ "$filename" == "configuration-schema" ]] || [[ "$SPEC_FILE" =~ configuration-schema ]]; then
|
||||
echo "config"
|
||||
else
|
||||
echo "unknown"
|
||||
fi
|
||||
}
|
||||
|
||||
# Get required sections for spec type
|
||||
get_required_sections() {
|
||||
local spec_type=$1
|
||||
case $spec_type in
|
||||
design)
|
||||
echo "Executive Summary|Problem Statement|Goals & Success Criteria|Proposed Solution|Implementation Plan|Risk & Mitigation"
|
||||
;;
|
||||
technical)
|
||||
echo "Overview|Requirements|Constraints|Success Criteria"
|
||||
;;
|
||||
business)
|
||||
echo "Description|Business Value|Stakeholders|User Stories|Acceptance Criteria"
|
||||
;;
|
||||
api)
|
||||
echo "Endpoints|Request/Response|Authentication|Error Handling"
|
||||
;;
|
||||
data)
|
||||
echo "Entities|Fields|Relationships|Constraints"
|
||||
;;
|
||||
component)
|
||||
echo "Description|Responsibilities|Interfaces|Configuration"
|
||||
;;
|
||||
plan)
|
||||
echo "Overview|Phases|Deliverables|Success Metrics"
|
||||
;;
|
||||
milestone)
|
||||
echo "Milestone|Deliverables|Success Criteria"
|
||||
;;
|
||||
flow)
|
||||
echo "Overview|Process Flow|Steps|Decision Points"
|
||||
;;
|
||||
deployment)
|
||||
echo "Prerequisites|Instructions|Rollback|Monitoring"
|
||||
;;
|
||||
config)
|
||||
echo "Schema|Fields|Validation|Examples"
|
||||
;;
|
||||
*)
|
||||
echo "Title"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Count lines and TODOs
|
||||
count_todos() {
|
||||
grep -o "TODO" "$SPEC_FILE" | wc -l
|
||||
}
|
||||
|
||||
# Count TODO items more accurately
|
||||
count_todo_items() {
|
||||
local count
|
||||
count=$(grep -cE "(TODO|<!-- TODO|<!-- \[TODO)" "$SPEC_FILE" 2>/dev/null)
|
||||
count=${count:-0}
|
||||
echo "$count"
|
||||
}
|
||||
|
||||
# Check for sections with only template guidance (not filled in by user)
|
||||
check_template_only_sections() {
|
||||
local spec_type=$(determine_spec_type)
|
||||
local required_sections=$(get_required_sections "$spec_type")
|
||||
local template_only_sections=()
|
||||
|
||||
while IFS= read -r section; do
|
||||
if [ -z "$section" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Escape special regex characters in section name
|
||||
local escaped_section=$(echo "$section" | sed 's/[[\.*^$/]/\\&/g')
|
||||
|
||||
# Find section content between the section header and the next section
|
||||
local section_content=$(awk "/^## $escaped_section/{found=1; next} /^## /{found=0} found" "$SPEC_FILE")
|
||||
|
||||
# Remove only leading/trailing whitespace, keep all content
|
||||
local trimmed_content=$(echo "$section_content" | sed 's/^[[:space:]]*//g' | sed 's/[[:space:]]*$//g')
|
||||
|
||||
# Check if section is completely empty or ONLY contains HTML comments
|
||||
# (i.e., user hasn't written any actual content)
|
||||
local non_comment_content=$(echo "$trimmed_content" | grep -v '^<!--' | grep -v '^-->' | grep -v '^$' | head -1)
|
||||
|
||||
if [ -z "$non_comment_content" ]; then
|
||||
template_only_sections+=("$section")
|
||||
fi
|
||||
done < <(echo "$required_sections" | tr '|' '\n')
|
||||
|
||||
printf '%s\n' "${template_only_sections[@]}"
|
||||
}
|
||||
|
||||
# Calculate completion percentage
|
||||
calculate_completion() {
|
||||
local spec_type=$(determine_spec_type)
|
||||
local required_sections=$(get_required_sections "$spec_type")
|
||||
local total_required=$(echo "$required_sections" | tr '|' '\n' | wc -l)
|
||||
|
||||
local found_sections=0
|
||||
while IFS= read -r section; do
|
||||
if grep -qE "^## $section" "$SPEC_FILE"; then
|
||||
((found_sections++))
|
||||
fi
|
||||
done < <(echo "$required_sections" | tr '|' '\n')
|
||||
|
||||
# Check for TODO items
|
||||
local todo_count
|
||||
todo_count=$(count_todo_items)
|
||||
local completed_percentage=$((found_sections * 100 / total_required))
|
||||
|
||||
# Reduce percentage based on TODO count (max reduction of 25%)
|
||||
if [ "$todo_count" -gt 0 ] 2>/dev/null; then
|
||||
local todo_penalty=$((todo_count * 2))
|
||||
[ $todo_penalty -gt 25 ] && todo_penalty=25
|
||||
completed_percentage=$((completed_percentage - todo_penalty))
|
||||
fi
|
||||
|
||||
[ $completed_percentage -lt 0 ] && completed_percentage=0
|
||||
|
||||
echo "$completed_percentage"
|
||||
}
|
||||
|
||||
# Validate spec structure
|
||||
validate_spec() {
|
||||
local spec_type=$(determine_spec_type)
|
||||
local required_sections=$(get_required_sections "$spec_type")
|
||||
local content=$(cat "$SPEC_FILE")
|
||||
|
||||
local pass_count=0
|
||||
local warn_count=0
|
||||
local error_count=0
|
||||
local pass_items=()
|
||||
local warn_items=()
|
||||
local error_items=()
|
||||
|
||||
# Check for proper formatting
|
||||
if grep -qE "^# " "$SPEC_FILE"; then
|
||||
pass_items+=("Title present")
|
||||
((pass_count++))
|
||||
else
|
||||
error_items+=("Missing title (# Title)")
|
||||
((error_count++))
|
||||
fi
|
||||
|
||||
# Check required sections
|
||||
while IFS= read -r section; do
|
||||
if [ -z "$section" ]; then
|
||||
continue
|
||||
fi
|
||||
if grep -qE "^## $section" "$SPEC_FILE"; then
|
||||
pass_items+=("Section '$section' present")
|
||||
((pass_count++))
|
||||
else
|
||||
error_items+=("Missing required section: '$section'")
|
||||
((error_count++))
|
||||
fi
|
||||
done < <(echo "$required_sections" | tr '|' '\n')
|
||||
|
||||
# Check for TODO items - only flag non-comment TODOs
|
||||
# TODOs inside HTML comments <!-- ... --> are just guidance and are acceptable
|
||||
local todo_count
|
||||
todo_count=$(grep -v "^<!--" "$SPEC_FILE" | grep -cE "TODO" 2>/dev/null || echo 0)
|
||||
if [ "$todo_count" -gt 0 ] 2>/dev/null; then
|
||||
error_items+=("Contains $todo_count TODO items that must be resolved")
|
||||
((error_count++))
|
||||
else
|
||||
pass_items+=("No TODO items remaining")
|
||||
((pass_count++))
|
||||
fi
|
||||
|
||||
# Check for sections that still contain only template guidance (not user-written content)
|
||||
local template_only_list=$(check_template_only_sections)
|
||||
if [ -n "$template_only_list" ]; then
|
||||
while IFS= read -r section; do
|
||||
if [ -n "$section" ]; then
|
||||
error_items+=("Section '$section' still contains only template guidance - must write your own content")
|
||||
((error_count++))
|
||||
fi
|
||||
done < <(echo "$template_only_list")
|
||||
else
|
||||
pass_items+=("All required sections contain user-written content (not template placeholders)")
|
||||
((pass_count++))
|
||||
fi
|
||||
|
||||
# Check for placeholder text - only flag outside of HTML comments
|
||||
# Brackets inside <!-- ... --> comments are just guidance templates and are acceptable
|
||||
local placeholder_count
|
||||
placeholder_count=$(grep -v "^<!--" "$SPEC_FILE" | grep -cE "FIXME|XXX" 2>/dev/null || echo 0)
|
||||
if [ "$placeholder_count" -gt 0 ] 2>/dev/null; then
|
||||
warn_items+=("Contains $placeholder_count placeholder markers (FIXME/XXX)")
|
||||
((warn_count++))
|
||||
else
|
||||
pass_items+=("No placeholder text detected")
|
||||
((pass_count++))
|
||||
fi
|
||||
|
||||
# Check for empty code blocks
|
||||
if grep -qE "^\`\`\`\s*$" "$SPEC_FILE"; then
|
||||
warn_items+=("Contains empty code blocks")
|
||||
((warn_count++))
|
||||
fi
|
||||
|
||||
# Check for broken links
|
||||
local broken_links=$(grep -o '\[[^]]*\]([^)]*)' "$SPEC_FILE" 2>/dev/null | grep -c '()$' 2>/dev/null || echo 0)
|
||||
broken_links=${broken_links:-0}
|
||||
if [ "$broken_links" -gt 0 ] 2>/dev/null; then
|
||||
warn_items+=("Found $broken_links potentially broken links")
|
||||
((warn_count++))
|
||||
fi
|
||||
|
||||
# Output results
|
||||
local completion=$(calculate_completion)
|
||||
local status_emoji="✓"
|
||||
local status_text="PASS"
|
||||
|
||||
if [ "$error_count" -gt 0 ]; then
|
||||
status_emoji="✗"
|
||||
status_text="INVALID"
|
||||
elif [ "$warn_count" -gt 0 ]; then
|
||||
status_emoji="⚠"
|
||||
status_text="INCOMPLETE"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}📋 SPEC VALIDATION REPORT${NC}"
|
||||
echo "===================="
|
||||
echo ""
|
||||
echo "Spec: $(basename "$SPEC_FILE")"
|
||||
echo "Type: $spec_type"
|
||||
echo "Status: $status_emoji $status_text ($completion% complete)"
|
||||
echo ""
|
||||
|
||||
if [ "$pass_count" -gt 0 ]; then
|
||||
echo -e "${GREEN}✓ PASS ($pass_count items)${NC}"
|
||||
for item in "${pass_items[@]}"; do
|
||||
echo " - $item"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ "$warn_count" -gt 0 ]; then
|
||||
echo -e "${YELLOW}⚠ WARNINGS ($warn_count items)${NC}"
|
||||
for item in "${warn_items[@]}"; do
|
||||
echo " - $item"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ "$error_count" -gt 0 ]; then
|
||||
echo -e "${RED}✗ ERRORS ($error_count items)${NC}"
|
||||
for item in "${error_items[@]}"; do
|
||||
echo " - $item"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Return appropriate exit code
|
||||
if [ "$error_count" -gt 0 ]; then
|
||||
return 1
|
||||
elif [ "$warn_count" -gt 0 ]; then
|
||||
return 2
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Main execution
|
||||
validate_spec
|
||||
exit_code=$?
|
||||
|
||||
# Return proper exit codes
|
||||
case $exit_code in
|
||||
0) exit 0 ;; # All pass
|
||||
2) exit 0 ;; # Warnings (still acceptable)
|
||||
1) exit 1 ;; # Errors
|
||||
esac
|
||||
Reference in New Issue
Block a user