Initial commit
This commit is contained in:
161
skills/specimin-spec/SKILL.md
Normal file
161
skills/specimin-spec/SKILL.md
Normal file
@@ -0,0 +1,161 @@
|
||||
---
|
||||
name: "specimin-spec"
|
||||
description: "Create or update the feature specification from a natural language feature description. Only invoke when user explicitly requests to create a specification, spec, or feature specification."
|
||||
allowed-tools:
|
||||
- run_terminal_cmd
|
||||
- write
|
||||
- read_file
|
||||
---
|
||||
|
||||
**ALWAYS WAIT for user input before generating spec.**
|
||||
Ask the user for:
|
||||
1. A brief description of the spec they want to create
|
||||
2. (Optional) A GitHub issue link associated with this feature
|
||||
|
||||
BEFORE generating the spec.
|
||||
|
||||
# Interactive Specification Generator
|
||||
|
||||
## Role
|
||||
Senior product requirements analyst translating feature requests into clear, actionable specifications. Define WHAT and WHY, not HOW.
|
||||
|
||||
## Process Flow
|
||||
|
||||
### Stage 0: Branch Name (FIRST)
|
||||
If the user provided a **GitHub issue link**, extract the issue number from it:
|
||||
- Parse URLs like: `https://github.com/owner/repo/issues/123` → Extract `123`
|
||||
- Store as `$ISSUE_NUMBER` for Stage 4
|
||||
|
||||
Otherwise, generate a **2-3 word, kebab-case branch name** from the user's requirement:
|
||||
- **Good**: `user-auth`, `pdf-export`, `real-time-sync`
|
||||
- **Bad**: `authentication-system-with-jwt`, `feature`, `new-feature`
|
||||
|
||||
Store as `$BRANCH_NAME` for Stage 4.
|
||||
|
||||
### Stage 1: Analyze & Clarify
|
||||
|
||||
1. Identify critical ambiguities: scope > security/privacy > UX > technical
|
||||
2. Ask 2-5 focused questions with concrete options
|
||||
3. Show impact of each option
|
||||
4. Wait for responses
|
||||
|
||||
**Question Template:**
|
||||
```
|
||||
## Q[N]: [Topic]
|
||||
**Need to know**: [Specific question]
|
||||
|
||||
**Options**:
|
||||
- A: [Description] → Impact: [Consequence]
|
||||
- B: [Description] → Impact: [Consequence]
|
||||
- Custom: [Your preference]
|
||||
```
|
||||
|
||||
### Stage 2: Generate Draft
|
||||
Create specification using Output Format (below) based on user answers.
|
||||
|
||||
### Stage 3: Iterate
|
||||
Ask: "Does this capture what you need? What should I adjust?"
|
||||
Refine until approved.
|
||||
|
||||
### Stage 4: Finalize
|
||||
**ONLY after user approval:**
|
||||
|
||||
1. Write approved spec to temporary file `/tmp/spec-draft.md`
|
||||
|
||||
2. Execute save script:
|
||||
- **If GitHub issue link was provided:**
|
||||
```bash
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/.claude-plugin/skills/specimin-spec/scripts/save-spec.sh "$USER_REQUIREMENT" "$BRANCH_NAME" /tmp/spec-draft.md "$ISSUE_NUMBER"
|
||||
```
|
||||
- **Otherwise:**
|
||||
```bash
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/.claude-plugin/skills/specimin-spec/scripts/save-spec.sh "$USER_REQUIREMENT" "$BRANCH_NAME" /tmp/spec-draft.md
|
||||
```
|
||||
|
||||
3. Parse JSON output and confirm to user:
|
||||
"✓ Specification saved to `[spec_path]` on branch `[branch_name]`"
|
||||
|
||||
## Output Format
|
||||
|
||||
**Objective**: [What needs accomplishing]
|
||||
|
||||
**Context**: [Why needed, business impact]
|
||||
|
||||
**Assumptions**: [Reasonable defaults]
|
||||
|
||||
**Constraints**: [Technical and business limitations]
|
||||
|
||||
**Acceptance Criteria**: [Verifiable, testable conditions]
|
||||
|
||||
**User Scenarios**: [Step-by-step flows with expected outcomes]
|
||||
|
||||
**Edge Cases**: [Boundary conditions]
|
||||
|
||||
**Dependencies** *(if applicable)*: [External requirements]
|
||||
|
||||
**Out of Scope**: [Explicitly excluded]
|
||||
|
||||
## Requirements
|
||||
|
||||
**Include:**
|
||||
- Clear objectives and constraints
|
||||
- Testable acceptance criteria (measurable, technology-agnostic)
|
||||
- Realistic user scenarios
|
||||
- Explicit scope boundaries
|
||||
- Documented assumptions
|
||||
|
||||
**Exclude:**
|
||||
- Technology choices (databases, frameworks, languages)
|
||||
- API designs or code structure
|
||||
- Implementation algorithms
|
||||
|
||||
**Good**: "Users complete checkout in under 3 minutes"
|
||||
**Bad**: "API response time under 200ms" (too technical)
|
||||
|
||||
## Example
|
||||
|
||||
**User**: "Users should stay logged in when they close and reopen the browser"
|
||||
|
||||
**Objective**
|
||||
Implement persistent user authentication across browser sessions.
|
||||
|
||||
**Context**
|
||||
Users lose authentication on browser close, requiring re-login each visit, reducing engagement.
|
||||
|
||||
**Assumptions**
|
||||
- Standard web security practices apply
|
||||
- Session duration configurable by administrators
|
||||
- Users expect multi-day persistence unless explicitly logging out
|
||||
- Browser storage mechanisms available
|
||||
|
||||
**Constraints**
|
||||
- Must integrate with existing authentication system
|
||||
- Must follow security best practices for credential storage
|
||||
- Session duration must be configurable
|
||||
- Must handle expiration gracefully
|
||||
|
||||
**Acceptance Criteria**
|
||||
- User remains authenticated after browser close/reopen
|
||||
- User prompted to re-authenticate after session expires
|
||||
- User can explicitly log out to end session
|
||||
- Works across major browsers (Chrome, Firefox, Safari, Edge)
|
||||
|
||||
**User Scenarios**
|
||||
1. Returning user: Login → Close browser → Reopen → Still authenticated
|
||||
2. Session expiration: Login → Wait past duration → Prompted to re-login
|
||||
3. Explicit logout: Authenticated → Logout → Close/reopen → Must login
|
||||
|
||||
**Edge Cases**
|
||||
- Multiple simultaneous sessions (different devices/windows)
|
||||
- Session expiration during active use
|
||||
- Browser storage unavailable or cleared
|
||||
- User switches between devices
|
||||
|
||||
**Dependencies**
|
||||
- Existing authentication system must expose session management APIs
|
||||
|
||||
**Out of Scope**
|
||||
- Cross-device session synchronization
|
||||
- "Remember this device" functionality
|
||||
- Biometric authentication
|
||||
|
||||
58
skills/specimin-spec/scripts/save-spec.sh
Executable file
58
skills/specimin-spec/scripts/save-spec.sh
Executable file
@@ -0,0 +1,58 @@
|
||||
#!/bin/bash
|
||||
# save-spec.sh - Saves approved specification and commits to feature branch
|
||||
# Usage: save-spec.sh <user_requirement> <branch_name> <spec_file_path> [issue_number]
|
||||
|
||||
set -e
|
||||
|
||||
USER_REQUIREMENT="$1"
|
||||
BRANCH_NAME="$2"
|
||||
SPEC_FILE_PATH="$3"
|
||||
ISSUE_NUMBER="${4:-}"
|
||||
|
||||
if [ -z "$USER_REQUIREMENT" ] || [ -z "$BRANCH_NAME" ] || [ -z "$SPEC_FILE_PATH" ]; then
|
||||
echo "Error: Missing required arguments"
|
||||
echo "Usage: save-spec.sh <user_requirement> <branch_name> <spec_file_path> [issue_number]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify initialization
|
||||
if [ ! -d ".specimin/plans/" ]; then
|
||||
echo "Error: Specimin not initialized. Please run /init first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify spec file exists
|
||||
if [ ! -f "$SPEC_FILE_PATH" ]; then
|
||||
echo "Error: Spec file not found: $SPEC_FILE_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get script directory
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Run setup script
|
||||
if [ -n "$ISSUE_NUMBER" ]; then
|
||||
SETUP_OUTPUT=$(bash "$SCRIPT_DIR/setup.feature.sh" "$USER_REQUIREMENT" --json --no-commit --branch-name "$BRANCH_NAME" --issue-number "$ISSUE_NUMBER")
|
||||
else
|
||||
SETUP_OUTPUT=$(bash "$SCRIPT_DIR/setup.feature.sh" "$USER_REQUIREMENT" --json --no-commit --branch-name "$BRANCH_NAME")
|
||||
fi
|
||||
|
||||
# Parse output
|
||||
FEATURE_DIR=$(echo "$SETUP_OUTPUT" | jq -r '.feature_dir')
|
||||
BRANCH_NAME=$(echo "$SETUP_OUTPUT" | jq -r '.branch_name')
|
||||
|
||||
if [ -z "$FEATURE_DIR" ] || [ "$FEATURE_DIR" = "null" ]; then
|
||||
echo "Error: Failed to create feature directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy spec file to feature directory
|
||||
SPEC_DEST="$FEATURE_DIR/spec.md"
|
||||
cp "$SPEC_FILE_PATH" "$SPEC_DEST"
|
||||
|
||||
# Commit
|
||||
git add "$SPEC_DEST"
|
||||
git commit -m "Add specification: $USER_REQUIREMENT"
|
||||
|
||||
# Output success message with JSON
|
||||
echo "{\"feature_dir\": \"$FEATURE_DIR\", \"branch_name\": \"$BRANCH_NAME\", \"spec_path\": \"$SPEC_DEST\"}"
|
||||
172
skills/specimin-spec/scripts/setup.feature.sh
Executable file
172
skills/specimin-spec/scripts/setup.feature.sh
Executable file
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Script: setup.feature.sh
|
||||
# Description: Create feature branch and planning directory
|
||||
# Usage: ./setup.feature.sh "feature description" [--json] [--no-commit] [--branch-name "custom-name"] [--issue-number "123"]
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
print_error() { echo -e "${RED}ERROR: $1${NC}" >&2; }
|
||||
print_success() { echo -e "${GREEN}SUCCESS: $1${NC}"; }
|
||||
print_info() { echo -e "${YELLOW}INFO: $1${NC}"; }
|
||||
|
||||
get_next_branch_number() {
|
||||
# Get all branches matching the pattern and extract the highest number
|
||||
local max_num=$(git branch -a | \
|
||||
grep -oE '[0-9]{3}-' | \
|
||||
grep -oE '[0-9]{3}' | \
|
||||
sort -n | \
|
||||
tail -1)
|
||||
|
||||
if [[ -z "$max_num" ]]; then
|
||||
echo "001"
|
||||
else
|
||||
printf "%03d" $((10#$max_num + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
generate_branch_name() {
|
||||
local description="$1"
|
||||
|
||||
# Extract first two words and clean them
|
||||
local two_words=$(echo "$description" | \
|
||||
tr '[:upper:]' '[:lower:]' | \
|
||||
sed 's/[^a-z0-9 -]//g' | \
|
||||
tr -s ' ' | \
|
||||
awk '{print $1"-"$2}' | \
|
||||
sed 's/^-//;s/-$//')
|
||||
|
||||
# Get next branch number
|
||||
local branch_num=$(get_next_branch_number)
|
||||
|
||||
echo "${branch_num}-${two_words}"
|
||||
}
|
||||
|
||||
check_git_repo() {
|
||||
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
||||
print_error "Not a git repository"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_working_tree() {
|
||||
if ! git diff-index --quiet HEAD -- 2>/dev/null; then
|
||||
print_error "Uncommitted changes exist. Commit or stash first."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_branch_exists() {
|
||||
local branch_name="$1"
|
||||
if git rev-parse --verify "$branch_name" > /dev/null 2>&1; then
|
||||
print_error "Branch '$branch_name' already exists"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
local feature_description="${1:-}"
|
||||
local json_output=false
|
||||
local no_commit=false
|
||||
local custom_branch_name=""
|
||||
local issue_number=""
|
||||
|
||||
# Parse flags
|
||||
local i=2
|
||||
while [[ $i -le $# ]]; do
|
||||
case "${!i}" in
|
||||
--json) json_output=true ;;
|
||||
--no-commit) no_commit=true ;;
|
||||
--branch-name)
|
||||
((i++))
|
||||
custom_branch_name="${!i}"
|
||||
;;
|
||||
--issue-number)
|
||||
((i++))
|
||||
issue_number="${!i}"
|
||||
;;
|
||||
esac
|
||||
((i++))
|
||||
done
|
||||
|
||||
if [[ -z "$feature_description" ]]; then
|
||||
print_error "Feature description required"
|
||||
echo "Usage: $0 \"feature description\" [--json] [--no-commit] [--branch-name \"custom-name\"] [--issue-number \"123\"]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
check_git_repo
|
||||
check_working_tree
|
||||
|
||||
local branch_name
|
||||
local branch_num
|
||||
|
||||
# Determine branch number
|
||||
if [[ -n "$issue_number" ]]; then
|
||||
# Use issue number (padded to 3 digits)
|
||||
branch_num=$(printf "%03d" "$issue_number")
|
||||
else
|
||||
# Auto-increment (backward compatible)
|
||||
branch_num=$(get_next_branch_number)
|
||||
fi
|
||||
|
||||
if [[ -n "$custom_branch_name" ]]; then
|
||||
# Use custom branch name with branch number
|
||||
branch_name="${branch_num}-${custom_branch_name}"
|
||||
else
|
||||
# Generate from description (backward compatible)
|
||||
# Extract first two words and clean them
|
||||
local two_words=$(echo "$feature_description" | \
|
||||
tr '[:upper:]' '[:lower:]' | \
|
||||
sed 's/[^a-z0-9 -]//g' | \
|
||||
tr -s ' ' | \
|
||||
awk '{print $1"-"$2}' | \
|
||||
sed 's/^-//;s/-$//')
|
||||
branch_name="${branch_num}-${two_words}"
|
||||
fi
|
||||
|
||||
check_branch_exists "$branch_name"
|
||||
|
||||
local feature_dir=".specimin/plans/${branch_name}"
|
||||
|
||||
# Create directory
|
||||
mkdir -p "$feature_dir"
|
||||
|
||||
# Create and checkout branch
|
||||
git checkout -b "$branch_name" --quiet
|
||||
|
||||
if [[ "$no_commit" == false ]]; then
|
||||
# Commit empty directory structure (backward compatible behavior)
|
||||
touch "$feature_dir/.gitkeep"
|
||||
git add "$feature_dir"
|
||||
git commit -m "Initialize feature: $feature_description" --quiet
|
||||
fi
|
||||
|
||||
if [[ "$json_output" == true ]]; then
|
||||
cat << EOF
|
||||
{
|
||||
"branch_name": "$branch_name",
|
||||
"feature_dir": "$feature_dir",
|
||||
"absolute_path": "$(pwd)/$feature_dir",
|
||||
"status": "success"
|
||||
}
|
||||
EOF
|
||||
else
|
||||
print_success "Feature setup complete!"
|
||||
echo ""
|
||||
echo "Branch: $branch_name"
|
||||
echo "Directory: $feature_dir"
|
||||
if [[ "$no_commit" == false ]]; then
|
||||
echo ""
|
||||
echo "Next: Use spec template to create $feature_dir/spec.md"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
Reference in New Issue
Block a user