Skip to content

Commit 42243ef

Browse files
committed
feat(scripts): add --dry-run flag to create-new-feature scripts
Add a --dry-run / -DryRun flag to both bash and PowerShell create-new-feature scripts that computes the next branch name, spec file path, and feature number without creating any branches, directories, or files. This enables external tools to query the next available name before running the full specify workflow. When combined with --json, the output includes a DRY_RUN field. Without --dry-run, behavior is completely unchanged. Closes #1931 Assisted-By: 🤖 Claude Code
1 parent f8da535 commit 42243ef

File tree

3 files changed

+322
-98
lines changed

3 files changed

+322
-98
lines changed

scripts/bash/create-new-feature.sh

Lines changed: 75 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
set -e
44

55
JSON_MODE=false
6+
DRY_RUN=false
67
ALLOW_EXISTING=false
78
SHORT_NAME=""
89
BRANCH_NUMBER=""
@@ -15,6 +16,9 @@ while [ $i -le $# ]; do
1516
--json)
1617
JSON_MODE=true
1718
;;
19+
--dry-run)
20+
DRY_RUN=true
21+
;;
1822
--allow-existing-branch)
1923
ALLOW_EXISTING=true
2024
;;
@@ -49,10 +53,11 @@ while [ $i -le $# ]; do
4953
USE_TIMESTAMP=true
5054
;;
5155
--help|-h)
52-
echo "Usage: $0 [--json] [--allow-existing-branch] [--short-name <name>] [--number N] [--timestamp] <feature_description>"
56+
echo "Usage: $0 [--json] [--dry-run] [--allow-existing-branch] [--short-name <name>] [--number N] [--timestamp] <feature_description>"
5357
echo ""
5458
echo "Options:"
5559
echo " --json Output in JSON format"
60+
echo " --dry-run Compute branch name and paths without creating branches, directories, or files"
5661
echo " --allow-existing-branch Switch to branch if it already exists instead of failing"
5762
echo " --short-name <name> Provide a custom short name (2-4 words) for the branch"
5863
echo " --number N Specify branch number manually (overrides auto-detection)"
@@ -74,7 +79,7 @@ done
7479

7580
FEATURE_DESCRIPTION="${ARGS[*]}"
7681
if [ -z "$FEATURE_DESCRIPTION" ]; then
77-
echo "Usage: $0 [--json] [--allow-existing-branch] [--short-name <name>] [--number N] [--timestamp] <feature_description>" >&2
82+
echo "Usage: $0 [--json] [--dry-run] [--allow-existing-branch] [--short-name <name>] [--number N] [--timestamp] <feature_description>" >&2
7883
exit 1
7984
fi
8085

@@ -251,7 +256,19 @@ if [ "$USE_TIMESTAMP" = true ]; then
251256
else
252257
# Determine branch number
253258
if [ -z "$BRANCH_NUMBER" ]; then
254-
if [ "$HAS_GIT" = true ]; then
259+
if [ "$DRY_RUN" = true ]; then
260+
# Dry-run: use locally available data only (skip git fetch)
261+
_highest_branch=0
262+
if [ "$HAS_GIT" = true ]; then
263+
_highest_branch=$(get_highest_from_branches)
264+
fi
265+
_highest_spec=$(get_highest_from_specs "$SPECS_DIR")
266+
_max_num=$_highest_branch
267+
if [ "$_highest_spec" -gt "$_max_num" ]; then
268+
_max_num=$_highest_spec
269+
fi
270+
BRANCH_NUMBER=$((_max_num + 1))
271+
elif [ "$HAS_GIT" = true ]; then
255272
# Check existing branches on remotes
256273
BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR")
257274
else
@@ -288,62 +305,79 @@ if [ ${#BRANCH_NAME} -gt $MAX_BRANCH_LENGTH ]; then
288305
>&2 echo "[specify] Truncated to: $BRANCH_NAME (${#BRANCH_NAME} bytes)"
289306
fi
290307

291-
if [ "$HAS_GIT" = true ]; then
292-
if ! git checkout -b "$BRANCH_NAME" 2>/dev/null; then
293-
# Check if branch already exists
294-
if git branch --list "$BRANCH_NAME" | grep -q .; then
295-
if [ "$ALLOW_EXISTING" = true ]; then
296-
# Switch to the existing branch instead of failing
297-
if ! git checkout "$BRANCH_NAME" 2>/dev/null; then
298-
>&2 echo "Error: Failed to switch to existing branch '$BRANCH_NAME'. Please resolve any local changes or conflicts and try again."
308+
FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME"
309+
SPEC_FILE="$FEATURE_DIR/spec.md"
310+
311+
if [ "$DRY_RUN" != true ]; then
312+
if [ "$HAS_GIT" = true ]; then
313+
if ! git checkout -b "$BRANCH_NAME" 2>/dev/null; then
314+
# Check if branch already exists
315+
if git branch --list "$BRANCH_NAME" | grep -q .; then
316+
if [ "$ALLOW_EXISTING" = true ]; then
317+
# Switch to the existing branch instead of failing
318+
if ! git checkout "$BRANCH_NAME" 2>/dev/null; then
319+
>&2 echo "Error: Failed to switch to existing branch '$BRANCH_NAME'. Please resolve any local changes or conflicts and try again."
320+
exit 1
321+
fi
322+
elif [ "$USE_TIMESTAMP" = true ]; then
323+
>&2 echo "Error: Branch '$BRANCH_NAME' already exists. Rerun to get a new timestamp or use a different --short-name."
324+
exit 1
325+
else
326+
>&2 echo "Error: Branch '$BRANCH_NAME' already exists. Please use a different feature name or specify a different number with --number."
299327
exit 1
300328
fi
301-
elif [ "$USE_TIMESTAMP" = true ]; then
302-
>&2 echo "Error: Branch '$BRANCH_NAME' already exists. Rerun to get a new timestamp or use a different --short-name."
303-
exit 1
304329
else
305-
>&2 echo "Error: Branch '$BRANCH_NAME' already exists. Please use a different feature name or specify a different number with --number."
330+
>&2 echo "Error: Failed to create git branch '$BRANCH_NAME'. Please check your git configuration and try again."
306331
exit 1
307332
fi
308-
else
309-
>&2 echo "Error: Failed to create git branch '$BRANCH_NAME'. Please check your git configuration and try again."
310-
exit 1
311333
fi
334+
else
335+
>&2 echo "[specify] Warning: Git repository not detected; skipped branch creation for $BRANCH_NAME"
312336
fi
313-
else
314-
>&2 echo "[specify] Warning: Git repository not detected; skipped branch creation for $BRANCH_NAME"
315-
fi
316337

317-
FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME"
318-
mkdir -p "$FEATURE_DIR"
338+
mkdir -p "$FEATURE_DIR"
319339

320-
SPEC_FILE="$FEATURE_DIR/spec.md"
321-
if [ ! -f "$SPEC_FILE" ]; then
322-
TEMPLATE=$(resolve_template "spec-template" "$REPO_ROOT") || true
323-
if [ -n "$TEMPLATE" ] && [ -f "$TEMPLATE" ]; then
324-
cp "$TEMPLATE" "$SPEC_FILE"
325-
else
326-
echo "Warning: Spec template not found; created empty spec file" >&2
327-
touch "$SPEC_FILE"
340+
if [ ! -f "$SPEC_FILE" ]; then
341+
TEMPLATE=$(resolve_template "spec-template" "$REPO_ROOT") || true
342+
if [ -n "$TEMPLATE" ] && [ -f "$TEMPLATE" ]; then
343+
cp "$TEMPLATE" "$SPEC_FILE"
344+
else
345+
echo "Warning: Spec template not found; created empty spec file" >&2
346+
touch "$SPEC_FILE"
347+
fi
328348
fi
329-
fi
330349

331-
# Inform the user how to persist the feature variable in their own shell
332-
printf '# To persist: export SPECIFY_FEATURE=%q\n' "$BRANCH_NAME" >&2
350+
# Inform the user how to persist the feature variable in their own shell
351+
printf '# To persist: export SPECIFY_FEATURE=%q\n' "$BRANCH_NAME" >&2
352+
fi
333353

334354
if $JSON_MODE; then
335355
if command -v jq >/dev/null 2>&1; then
336-
jq -cn \
337-
--arg branch_name "$BRANCH_NAME" \
338-
--arg spec_file "$SPEC_FILE" \
339-
--arg feature_num "$FEATURE_NUM" \
340-
'{BRANCH_NAME:$branch_name,SPEC_FILE:$spec_file,FEATURE_NUM:$feature_num}'
356+
if [ "$DRY_RUN" = true ]; then
357+
jq -cn \
358+
--arg branch_name "$BRANCH_NAME" \
359+
--arg spec_file "$SPEC_FILE" \
360+
--arg feature_num "$FEATURE_NUM" \
361+
'{BRANCH_NAME:$branch_name,SPEC_FILE:$spec_file,FEATURE_NUM:$feature_num,DRY_RUN:true}'
362+
else
363+
jq -cn \
364+
--arg branch_name "$BRANCH_NAME" \
365+
--arg spec_file "$SPEC_FILE" \
366+
--arg feature_num "$FEATURE_NUM" \
367+
'{BRANCH_NAME:$branch_name,SPEC_FILE:$spec_file,FEATURE_NUM:$feature_num}'
368+
fi
341369
else
342-
printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$(json_escape "$BRANCH_NAME")" "$(json_escape "$SPEC_FILE")" "$(json_escape "$FEATURE_NUM")"
370+
if [ "$DRY_RUN" = true ]; then
371+
printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s","DRY_RUN":true}\n' "$(json_escape "$BRANCH_NAME")" "$(json_escape "$SPEC_FILE")" "$(json_escape "$FEATURE_NUM")"
372+
else
373+
printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$(json_escape "$BRANCH_NAME")" "$(json_escape "$SPEC_FILE")" "$(json_escape "$FEATURE_NUM")"
374+
fi
343375
fi
344376
else
345377
echo "BRANCH_NAME: $BRANCH_NAME"
346378
echo "SPEC_FILE: $SPEC_FILE"
347379
echo "FEATURE_NUM: $FEATURE_NUM"
348-
printf '# To persist in your shell: export SPECIFY_FEATURE=%q\n' "$BRANCH_NAME"
380+
if [ "$DRY_RUN" != true ]; then
381+
printf '# To persist in your shell: export SPECIFY_FEATURE=%q\n' "$BRANCH_NAME"
382+
fi
349383
fi

0 commit comments

Comments
 (0)