690504:1641 Update specs [skip ci]
This commit is contained in:
@@ -5,13 +5,14 @@ set -e
|
||||
JSON_MODE=false
|
||||
SHORT_NAME=""
|
||||
BRANCH_NUMBER=""
|
||||
CATEGORY=""
|
||||
ARGS=()
|
||||
i=1
|
||||
while [ $i -le $# ]; do
|
||||
arg="${!i}"
|
||||
case "$arg" in
|
||||
--json)
|
||||
JSON_MODE=true
|
||||
--json)
|
||||
JSON_MODE=true
|
||||
;;
|
||||
--short-name)
|
||||
if [ $((i + 1)) -gt $# ]; then
|
||||
@@ -40,22 +41,42 @@ while [ $i -le $# ]; do
|
||||
fi
|
||||
BRANCH_NUMBER="$next_arg"
|
||||
;;
|
||||
--help|-h)
|
||||
echo "Usage: $0 [--json] [--short-name <name>] [--number N] <feature_description>"
|
||||
--category)
|
||||
if [ $((i + 1)) -gt $# ]; then
|
||||
echo 'Error: --category requires a value' >&2
|
||||
exit 1
|
||||
fi
|
||||
i=$((i + 1))
|
||||
next_arg="${!i}"
|
||||
if [[ "$next_arg" == --* ]]; then
|
||||
echo 'Error: --category requires a value' >&2
|
||||
exit 1
|
||||
fi
|
||||
CATEGORY="$next_arg"
|
||||
;;
|
||||
--help|-h)
|
||||
echo "Usage: $0 [--json] [--short-name <name>] [--number N] [--category <cat>] <feature_description>"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --json Output in JSON format"
|
||||
echo " --short-name <name> Provide a custom short name (2-4 words) for the branch"
|
||||
echo " --number N Specify branch number manually (overrides auto-detection)"
|
||||
echo " --category <cat> Category folder (100, 200, or 300). Defaults to 200 (fullstacks)"
|
||||
echo " --help, -h Show this help message"
|
||||
echo ""
|
||||
echo "Categories:"
|
||||
echo " 100 - Infrastructure (Deployment, Monitoring, Docker Compose, Network)"
|
||||
echo " 200 - Fullstack Development (Backend + Frontend features, Workflow Engine, API)"
|
||||
echo " 300 - Others (Documentation, Research, Non-code tasks)"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 'Add user authentication system' --short-name 'user-auth'"
|
||||
echo " $0 'Implement OAuth2 integration for API' --number 5"
|
||||
echo " $0 'Docker compose hardening' --category 100"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
ARGS+=("$arg")
|
||||
*)
|
||||
ARGS+=("$arg")
|
||||
;;
|
||||
esac
|
||||
i=$((i + 1))
|
||||
@@ -83,35 +104,54 @@ find_repo_root() {
|
||||
# Function to get highest number from specs directory
|
||||
get_highest_from_specs() {
|
||||
local specs_dir="$1"
|
||||
local category="$2"
|
||||
local highest=0
|
||||
|
||||
|
||||
if [ -d "$specs_dir" ]; then
|
||||
for dir in "$specs_dir"/*; do
|
||||
[ -d "$dir" ] || continue
|
||||
dirname=$(basename "$dir")
|
||||
number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0")
|
||||
number=$((10#$number))
|
||||
if [ "$number" -gt "$highest" ]; then
|
||||
highest=$number
|
||||
# If category specified, only check that category
|
||||
if [ -n "$category" ]; then
|
||||
local category_dir="$specs_dir/$category"
|
||||
if [ -d "$category_dir" ]; then
|
||||
for dir in "$category_dir"/*; do
|
||||
[ -d "$dir" ] || continue
|
||||
dirname=$(basename "$dir")
|
||||
# Extract the last 2 digits from nXX pattern
|
||||
number=$(echo "$dirname" | grep -o '[0-9]\{2\}$' || echo "0")
|
||||
number=$((10#$number))
|
||||
if [ "$number" -gt "$highest" ]; then
|
||||
highest=$number
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
else
|
||||
# Check all directories in specs/ (old behavior for backward compatibility)
|
||||
for dir in "$specs_dir"/*; do
|
||||
[ -d "$dir" ] || continue
|
||||
dirname=$(basename "$dir")
|
||||
number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0")
|
||||
number=$((10#$number))
|
||||
if [ "$number" -gt "$highest" ]; then
|
||||
highest=$number
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
echo "$highest"
|
||||
}
|
||||
|
||||
# Function to get highest number from git branches
|
||||
get_highest_from_branches() {
|
||||
local highest=0
|
||||
|
||||
|
||||
# Get all branches (local and remote)
|
||||
branches=$(git branch -a 2>/dev/null || echo "")
|
||||
|
||||
|
||||
if [ -n "$branches" ]; then
|
||||
while IFS= read -r branch; do
|
||||
# Clean branch name: remove leading markers and remote prefixes
|
||||
clean_branch=$(echo "$branch" | sed 's/^[* ]*//; s|^remotes/[^/]*/||')
|
||||
|
||||
|
||||
# Extract feature number if branch matches pattern ###-*
|
||||
if echo "$clean_branch" | grep -q '^[0-9]\{3\}-'; then
|
||||
number=$(echo "$clean_branch" | grep -o '^[0-9]\{3\}' || echo "0")
|
||||
@@ -122,7 +162,7 @@ get_highest_from_branches() {
|
||||
fi
|
||||
done <<< "$branches"
|
||||
fi
|
||||
|
||||
|
||||
echo "$highest"
|
||||
}
|
||||
|
||||
@@ -180,19 +220,19 @@ mkdir -p "$SPECS_DIR"
|
||||
# Function to generate branch name with stop word filtering and length filtering
|
||||
generate_branch_name() {
|
||||
local description="$1"
|
||||
|
||||
|
||||
# Common stop words to filter out
|
||||
local stop_words="^(i|a|an|the|to|for|of|in|on|at|by|with|from|is|are|was|were|be|been|being|have|has|had|do|does|did|will|would|should|could|can|may|might|must|shall|this|that|these|those|my|your|our|their|want|need|add|get|set)$"
|
||||
|
||||
|
||||
# Convert to lowercase and split into words
|
||||
local clean_name=$(echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g')
|
||||
|
||||
|
||||
# Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original)
|
||||
local meaningful_words=()
|
||||
for word in $clean_name; do
|
||||
# Skip empty words
|
||||
[ -z "$word" ] && continue
|
||||
|
||||
|
||||
# Keep words that are NOT stop words AND (length >= 3 OR are potential acronyms)
|
||||
if ! echo "$word" | grep -qiE "$stop_words"; then
|
||||
if [ ${#word} -ge 3 ]; then
|
||||
@@ -203,12 +243,12 @@ generate_branch_name() {
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
# If we have meaningful words, use first 3-4 of them
|
||||
if [ ${#meaningful_words[@]} -gt 0 ]; then
|
||||
local max_words=3
|
||||
if [ ${#meaningful_words[@]} -eq 4 ]; then max_words=4; fi
|
||||
|
||||
|
||||
local result=""
|
||||
local count=0
|
||||
for word in "${meaningful_words[@]}"; do
|
||||
@@ -238,10 +278,10 @@ fi
|
||||
if [ -z "$BRANCH_NUMBER" ]; then
|
||||
if [ "$HAS_GIT" = true ]; then
|
||||
# Check existing branches on remotes
|
||||
BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR")
|
||||
BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR" "$CATEGORY")
|
||||
else
|
||||
# Fall back to local directory check
|
||||
HIGHEST=$(get_highest_from_specs "$SPECS_DIR")
|
||||
HIGHEST=$(get_highest_from_specs "$SPECS_DIR" "$CATEGORY")
|
||||
BRANCH_NUMBER=$((HIGHEST + 1))
|
||||
fi
|
||||
fi
|
||||
@@ -257,15 +297,15 @@ if [ ${#BRANCH_NAME} -gt $MAX_BRANCH_LENGTH ]; then
|
||||
# Calculate how much we need to trim from suffix
|
||||
# Account for: feature number (3) + hyphen (1) = 4 chars
|
||||
MAX_SUFFIX_LENGTH=$((MAX_BRANCH_LENGTH - 4))
|
||||
|
||||
|
||||
# Truncate suffix at word boundary if possible
|
||||
TRUNCATED_SUFFIX=$(echo "$BRANCH_SUFFIX" | cut -c1-$MAX_SUFFIX_LENGTH)
|
||||
# Remove trailing hyphen if truncation created one
|
||||
TRUNCATED_SUFFIX=$(echo "$TRUNCATED_SUFFIX" | sed 's/-$//')
|
||||
|
||||
|
||||
ORIGINAL_BRANCH_NAME="$BRANCH_NAME"
|
||||
BRANCH_NAME="${FEATURE_NUM}-${TRUNCATED_SUFFIX}"
|
||||
|
||||
|
||||
>&2 echo "[specify] Warning: Branch name exceeded GitHub's 244-byte limit"
|
||||
>&2 echo "[specify] Original: $ORIGINAL_BRANCH_NAME (${#ORIGINAL_BRANCH_NAME} bytes)"
|
||||
>&2 echo "[specify] Truncated to: $BRANCH_NAME (${#BRANCH_NAME} bytes)"
|
||||
@@ -277,7 +317,7 @@ else
|
||||
>&2 echo "[specify] Warning: Git repository not detected; skipped branch creation for $BRANCH_NAME"
|
||||
fi
|
||||
|
||||
FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME"
|
||||
FEATURE_DIR="$SPECS_DIR/${CATEGORY}-${CATEGORY_NAME}/${BRANCH_NAME}"
|
||||
mkdir -p "$FEATURE_DIR"
|
||||
|
||||
TEMPLATE="$REPO_ROOT/.specify/templates/spec-template.md"
|
||||
|
||||
@@ -12,31 +12,21 @@
|
||||
Optional manual branch number (overrides auto-detection).
|
||||
.EXAMPLE
|
||||
.\create-new-feature.ps1 -Description "Add user authentication" -ShortName "user-auth"
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory = $true, Position = 0)]
|
||||
[string]$Description,
|
||||
#
|
||||
$aram(
|
||||
[PErroeterrMandatory = $true, Position = 0)]
|
||||
[stAing]cDescrittion,onPreference = "Stop"
|
||||
|
||||
[string]$ShortName,
|
||||
[int]$Number = 0
|
||||
)
|
||||
# Validate category
|
||||
if ($Category "tgorut be 100, 200, or 300"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Load common functions
|
||||
. "$PSScriptRoot\common.ps1"
|
||||
|
||||
$repoRoot = Get-RepoRoot
|
||||
$hasGit = Test-HasGit
|
||||
$specsDir = Join-Path $repoRoot "specs"
|
||||
if (-not (Test-Path $specsDir)) { New-Item -ItemType Directory -Path $specsDir | Out-Null }
|
||||
|
||||
# Stop words for smart branch name generation
|
||||
$stopWords = @('i','a','an','the','to','for','of','in','on','at','by','with','from',
|
||||
'is','are','was','were','be','been','being','have','has','had',
|
||||
'do','does','did','will','would','should','could','can','may','might',
|
||||
'must','shall','this','that','these','those','my','your','our','their',
|
||||
'want','need','add','get','set')
|
||||
# Load common fu#ct oGet category name
|
||||
'100' { 'Infrastructures' }
|
||||
'200' { 'fullstacks' }
|
||||
'want','need','add','get','set')
|
||||
|
||||
function ConvertTo-BranchName {
|
||||
param([string]$Text)
|
||||
@@ -44,23 +34,23 @@ function ConvertTo-BranchName {
|
||||
}
|
||||
|
||||
function Get-SmartBranchName {
|
||||
param([string]$Desc)
|
||||
$words = ($Desc.ToLower() -replace '[^a-z0-9]', ' ').Split(' ', [StringSplitOptions]::RemoveEmptyEntries)
|
||||
$meaningful = $words | Where-Object { $_ -notin $stopWords -and $_.Length -ge 3 } | Select-Object -First 3
|
||||
if ($meaningful.Count -gt 0) { return ($meaningful -join '-') }
|
||||
return ConvertTo-BranchName $Desc
|
||||
}
|
||||
param(
|
||||
[string]$Des,
|
||||
[string]$Category = ""
|
||||
c)
|
||||
$words = ($D
|
||||
|
||||
if ($Category) {
|
||||
# Check specific category directory
|
||||
$categoryDir = Join-Path $Dir "$Category-$categoryName"
|
||||
if (Test-Path $categoryDir) {
|
||||
if ( if ($num -gt $highest) { $highest = $num }
|
||||
olon T
|
||||
Get-ChildItem -Path $Dir -Directory |r $Catego yForEach-Object {
|
||||
if ($_.Name -match '^(\d+)-') {
|
||||
$num = [int]$Matches[1] if ($num -gt $highest) { $highest = $num }
|
||||
}
|
||||
|
||||
function Get-HighestNumber {
|
||||
param([string]$Dir)
|
||||
$highest = 0
|
||||
if (Test-Path $Dir) {
|
||||
Get-ChildItem -Path $Dir -Directory | ForEach-Object {
|
||||
if ($_.Name -match '^(\d+)-') {
|
||||
$num = [int]$Matches[1]
|
||||
if ($num -gt $highest) { $highest = $num }
|
||||
}
|
||||
}
|
||||
}
|
||||
return $highest
|
||||
}
|
||||
@@ -78,7 +68,8 @@ if ($Number -gt 0) {
|
||||
} else {
|
||||
$highestSpec = Get-HighestNumber $specsDir
|
||||
$highestBranch = 0
|
||||
if ($hasGit) {
|
||||
# Use nXX format where n = category hundreds digit, XX = feature number
|
||||
if ($hasGit) }{1{2 $Category,
|
||||
try {
|
||||
git fetch --all --prune 2>$null | Out-Null
|
||||
$branches = git branch -a 2>$null
|
||||
@@ -122,10 +113,31 @@ if (Test-Path $templateFile) {
|
||||
Copy-Item $templateFile $specFile
|
||||
} else {
|
||||
New-Item -ItemType File -Path $specFile -Force | Out-Null
|
||||
}r "$Category-$categoyName"
|
||||
rectory -Path $featuDir -Fore | Out-Null
|
||||
|
||||
$templaeFile = Jin-Path $epoRoot ".specif" "templates" "spec-template.md"
|
||||
$specFile= Join"spec.md"
|
||||
if (TestPath $templateile) {
|
||||
Cpy-Item $templateFile $specFile
|
||||
} else {
|
||||
New-Item -ItemType File -Path $specFile -Foll
|
||||
}
|
||||
|
||||
$env:SPECIFY_FEATURE = $branchName
|
||||
|
||||
# Output
|
||||
[PSCustomObject]@{
|
||||
BranchName = $branchName
|
||||
SpecFie = $specFie
|
||||
FeatureNum = $featureNum
|
||||
}
|
||||
|
||||
Write-Host "BRANCH_NAME: $branchName"
|
||||
Write-Host "SPEC_FILE: $specFile"
|
||||
Write-Host "FEATURE_NUM: $featureNum"
|
||||
$env:SPECIFY_FEATURE = $branchName
|
||||
|
||||
# Output
|
||||
[PSCustomObject]@{
|
||||
BranchName = $branchName
|
||||
|
||||
Reference in New Issue
Block a user