@@ -89,7 +89,10 @@ get_highest_from_specs() {
8989 for dir in " $specs_dir " /* ; do
9090 [ -d " $dir " ] || continue
9191 dirname=$( basename " $dir " )
92- number=$( echo " $dirname " | grep -o ' ^[0-9]\+' || echo " 0" )
92+ # Strip optional acronym prefix (e.g., "URA-" from "URA-001-name")
93+ local stripped_dirname
94+ stripped_dirname=$( echo " $dirname " | sed ' s/^[A-Z]\{2,5\}-//' )
95+ number=$( echo " $stripped_dirname " | grep -o ' ^[0-9]\+' || echo " 0" )
9396 number=$(( 10 #$number ))
9497 if [ " $number " -gt " $highest " ]; then
9598 highest=$number
@@ -112,9 +115,15 @@ get_highest_from_branches() {
112115 # Clean branch name: remove leading markers and remote prefixes
113116 clean_branch=$( echo " $branch " | sed ' s/^[* ]*//; s|^remotes/[^/]*/||' )
114117
118+ # Strip feature/ prefix if present
119+ clean_branch=" ${clean_branch# feature/ } "
120+ # Strip optional acronym prefix (e.g., "URA-" from "URA-001-name")
121+ local stripped_branch
122+ stripped_branch=$( echo " $clean_branch " | sed ' s/^[A-Z]\{2,5\}-//' )
123+
115124 # Extract feature number if branch matches pattern ###-*
116- if echo " $clean_branch " | grep -q ' ^[0-9]\{3\}-' ; then
117- number=$( echo " $clean_branch " | grep -o ' ^[0-9]\{3\}' || echo " 0" )
125+ if echo " $stripped_branch " | grep -q ' ^[0-9]\{3\}-' ; then
126+ number=$( echo " $stripped_branch " | grep -o ' ^[0-9]\{3\}' || echo " 0" )
118127 number=$(( 10 #$number ))
119128 if [ " $number " -gt " $highest " ]; then
120129 highest=$number
@@ -155,6 +164,60 @@ clean_branch_name() {
155164 echo " $name " | tr ' [:upper:]' ' [:lower:]' | sed ' s/[^a-z0-9]/-/g' | sed ' s/-\+/-/g' | sed ' s/^-//' | sed ' s/-$//'
156165}
157166
167+ # Function to extract project acronym from constitution.md
168+ get_project_acronym () {
169+ local repo_root=" $1 "
170+ local constitution=" $repo_root /.specify/memory/constitution.md"
171+
172+ if [ ! -f " $constitution " ]; then
173+ echo " "
174+ return
175+ fi
176+
177+ # Try to extract project_acronym from YAML front matter
178+ local acronym=" "
179+ if head -1 " $constitution " | grep -q ' ^---$' ; then
180+ acronym=$( awk ' /^---$/{n++; next} n==1 && /^project_acronym:/{sub(/^project_acronym:[[:space:]]*/,""); gsub(/^["' " '" ' ]|["' " '" ' ]$/,""); print; exit}' " $constitution " )
181+ fi
182+
183+ # Skip if placeholder or empty
184+ if [ -n " $acronym " ] && [ " $acronym " != " [PROJECT_ACRONYM]" ]; then
185+ echo " $acronym "
186+ return
187+ fi
188+
189+ # Fallback: derive from H1 heading (e.g., "# Upwork Routine Automation Constitution")
190+ local heading
191+ heading=$( grep -m1 ' ^# ' " $constitution " | sed ' s/^# //' )
192+ if [ -z " $heading " ]; then
193+ echo " "
194+ return
195+ fi
196+
197+ # Skip if heading is still a placeholder
198+ if echo " $heading " | grep -q ' \[PROJECT_NAME\]' ; then
199+ echo " "
200+ return
201+ fi
202+
203+ # Remove trailing "Constitution" if present
204+ heading=$( echo " $heading " | sed ' s/[[:space:]]*Constitution[[:space:]]*$//' )
205+
206+ # Count words
207+ local word_count
208+ word_count=$( echo " $heading " | wc -w | tr -d ' ' )
209+
210+ if [ " $word_count " -eq 1 ]; then
211+ # Single word: first 3 letters uppercased
212+ echo " $heading " | tr ' [:lower:]' ' [:upper:]' | cut -c1-3
213+ elif [ " $word_count " -ge 2 ]; then
214+ # Multiple words: first letter of each word
215+ echo " $heading " | tr ' [:lower:]' ' [:upper:]' | sed ' s/[[:space:]]\+/ /g' | sed ' s/\([A-Z]\)[^ ]*/\1/g' | tr -d ' '
216+ else
217+ echo " "
218+ fi
219+ }
220+
158221# Resolve repository root. Prefer git information when available, but fall back
159222# to searching for repository markers so the workflow still functions in repositories that
160223# were initialised with --no-git.
248311
249312# Force base-10 interpretation to prevent octal conversion (e.g., 010 → 8 in octal, but should be 10 in decimal)
250313FEATURE_NUM=$( printf " %03d" " $(( 10 #$BRANCH_NUMBER )) " )
251- BRANCH_NAME=" ${FEATURE_NUM} -${BRANCH_SUFFIX} "
314+
315+ # Get project acronym from constitution
316+ PROJECT_ACRONYM=$( get_project_acronym " $REPO_ROOT " )
317+
318+ # If no acronym found, ask the user
319+ if [ -z " $PROJECT_ACRONYM " ]; then
320+ CONSTITUTION_FILE=" $REPO_ROOT /.specify/memory/constitution.md"
321+ >&2 echo " "
322+ >&2 printf " [specify] Enter PROJECT_ACRONYM (2-5 uppercase letters, or press Enter to skip): "
323+ read -r user_acronym || user_acronym=" "
324+ # Uppercase and trim
325+ user_acronym=$( echo " $user_acronym " | tr ' [:lower:]' ' [:upper:]' | tr -d ' [:space:]' )
326+ if [[ " $user_acronym " =~ ^[A-Z]{2,5}$ ]]; then
327+ PROJECT_ACRONYM=" $user_acronym "
328+ # Persist to constitution if file exists
329+ if [ -f " $CONSTITUTION_FILE " ]; then
330+ if head -1 " $CONSTITUTION_FILE " | grep -q ' ^---$' ; then
331+ if grep -q ' ^project_acronym:' " $CONSTITUTION_FILE " ; then
332+ sed -i.bak " s/^project_acronym:.*$/project_acronym: \" $PROJECT_ACRONYM \" /" " $CONSTITUTION_FILE "
333+ rm -f " $CONSTITUTION_FILE .bak"
334+ else
335+ sed -i.bak " 1a\\
336+ project_acronym: \" $PROJECT_ACRONYM \" " " $CONSTITUTION_FILE "
337+ rm -f " $CONSTITUTION_FILE .bak"
338+ fi
339+ >&2 echo " [specify] Saved PROJECT_ACRONYM=$PROJECT_ACRONYM to constitution."
340+ fi
341+ fi
342+ elif [ -n " $user_acronym " ]; then
343+ >&2 echo " [specify] Invalid acronym (must be 2-5 uppercase letters). Skipping."
344+ fi
345+ fi
346+
347+ if [ -n " $PROJECT_ACRONYM " ]; then
348+ BRANCH_NAME=" feature/${PROJECT_ACRONYM} -${FEATURE_NUM} -${BRANCH_SUFFIX} "
349+ else
350+ BRANCH_NAME=" feature/${FEATURE_NUM} -${BRANCH_SUFFIX} "
351+ fi
252352
253353# GitHub enforces a 244-byte limit on branch names
254354# Validate and truncate if necessary
255355MAX_BRANCH_LENGTH=244
256356if [ ${# BRANCH_NAME} -gt $MAX_BRANCH_LENGTH ]; then
257- # Calculate how much we need to trim from suffix
258- # Account for: feature number (3) + hyphen (1) = 4 chars
259- MAX_SUFFIX_LENGTH=$(( MAX_BRANCH_LENGTH - 4 ))
260-
357+ # Calculate prefix length: "feature/" (8) + optional acronym + hyphen + feature number (3) + hyphen (1)
358+ if [ -n " $PROJECT_ACRONYM " ]; then
359+ PREFIX_LENGTH=$(( 8 + ${# PROJECT_ACRONYM} + 1 + 3 + 1 ))
360+ else
361+ PREFIX_LENGTH=$(( 8 + 3 + 1 ))
362+ fi
363+ MAX_SUFFIX_LENGTH=$(( MAX_BRANCH_LENGTH - PREFIX_LENGTH))
364+
261365 # Truncate suffix at word boundary if possible
262366 TRUNCATED_SUFFIX=$( echo " $BRANCH_SUFFIX " | cut -c1-$MAX_SUFFIX_LENGTH )
263367 # Remove trailing hyphen if truncation created one
264368 TRUNCATED_SUFFIX=$( echo " $TRUNCATED_SUFFIX " | sed ' s/-$//' )
265-
369+
266370 ORIGINAL_BRANCH_NAME=" $BRANCH_NAME "
267- BRANCH_NAME=" ${FEATURE_NUM} -${TRUNCATED_SUFFIX} "
268-
371+ if [ -n " $PROJECT_ACRONYM " ]; then
372+ BRANCH_NAME=" feature/${PROJECT_ACRONYM} -${FEATURE_NUM} -${TRUNCATED_SUFFIX} "
373+ else
374+ BRANCH_NAME=" feature/${FEATURE_NUM} -${TRUNCATED_SUFFIX} "
375+ fi
376+
269377 >&2 echo " [specify] Warning: Branch name exceeded GitHub's 244-byte limit"
270378 >&2 echo " [specify] Original: $ORIGINAL_BRANCH_NAME (${# ORIGINAL_BRANCH_NAME} bytes)"
271379 >&2 echo " [specify] Truncated to: $BRANCH_NAME (${# BRANCH_NAME} bytes)"
277385 >&2 echo " [specify] Warning: Git repository not detected; skipped branch creation for $BRANCH_NAME "
278386fi
279387
280- FEATURE_DIR=" $SPECS_DIR /$BRANCH_NAME "
388+ # Strip feature/ prefix for spec directory name (avoids specs/feature/ nesting)
389+ SPEC_DIR_NAME=" ${BRANCH_NAME# feature/ } "
390+ FEATURE_DIR=" $SPECS_DIR /$SPEC_DIR_NAME "
281391mkdir -p " $FEATURE_DIR "
282392
283393TEMPLATE=" $REPO_ROOT /.specify/templates/spec-template.md"
@@ -288,10 +398,11 @@ if [ -f "$TEMPLATE" ]; then cp "$TEMPLATE" "$SPEC_FILE"; else touch "$SPEC_FILE"
288398export SPECIFY_FEATURE=" $BRANCH_NAME "
289399
290400if $JSON_MODE ; then
291- printf ' {"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' " $BRANCH_NAME " " $SPEC_FILE " " $FEATURE_NUM "
401+ printf ' {"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s","PROJECT_ACRONYM":"%s" }\n' " $BRANCH_NAME " " $SPEC_FILE " " $FEATURE_NUM " " $PROJECT_ACRONYM "
292402else
293403 echo " BRANCH_NAME: $BRANCH_NAME "
294404 echo " SPEC_FILE: $SPEC_FILE "
295405 echo " FEATURE_NUM: $FEATURE_NUM "
406+ echo " PROJECT_ACRONYM: $PROJECT_ACRONYM "
296407 echo " SPECIFY_FEATURE environment variable set to: $BRANCH_NAME "
297408fi
0 commit comments