Skip to content

Commit bf7c1ad

Browse files
MrFlounderclaude
andcommitted
feat(wip): improve save/restore experience
Save improvements: - Custom names: `crab save "my feature"` or `crab wip save "name"` - Save untracked file contents (not just listing) - Add `crab save` shortcut (same as `crab wip save`) Restore improvements: - Restore untracked files from WIP - Don't show "run crab N to open" when already in workspace Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 166ddfb commit bf7c1ad

1 file changed

Lines changed: 122 additions & 30 deletions

File tree

src/crabcode

Lines changed: 122 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1912,6 +1912,7 @@ get_wip_dir() {
19121912
wip_save() {
19131913
local num=$1
19141914
local do_restart=$2
1915+
local custom_name="${3:-}"
19151916
local dir="$WORKSPACE_BASE/$WORKSPACE_PREFIX-$num"
19161917
local wip_dir=$(get_wip_dir "$num")
19171918
local timestamp=$(date +%Y%m%d-%H%M%S)
@@ -1930,6 +1931,9 @@ wip_save() {
19301931
local status=$(git status --porcelain 2>/dev/null || echo "")
19311932
local branch=$(git branch --show-current 2>/dev/null || echo "unknown")
19321933

1934+
# Get untracked files
1935+
local untracked_files=$(git ls-files --others --exclude-standard 2>/dev/null || echo "")
1936+
19331937
git fetch origin main --quiet 2>/dev/null || true
19341938
local commits_ahead=$(git rev-list --count origin/main..HEAD 2>/dev/null || echo "0")
19351939
local commit_log=""
@@ -1956,7 +1960,7 @@ wip_save() {
19561960

19571961
# Check if there's anything to save
19581962
local has_changes="false"
1959-
if [ -n "$diff" ] || [ -n "$staged" ] || [ -n "$status" ] || [ -n "$submodule_diffs" ]; then
1963+
if [ -n "$diff" ] || [ -n "$staged" ] || [ -n "$status" ] || [ -n "$submodule_diffs" ] || [ -n "$untracked_files" ]; then
19601964
has_changes="true"
19611965
elif [ "$commits_ahead" -gt 0 ] 2>/dev/null; then
19621966
has_changes="true"
@@ -1969,28 +1973,57 @@ wip_save() {
19691973
return 0
19701974
fi
19711975

1972-
local slug="wip-$(date +%H%M)"
1973-
local summary="Work in progress"
1976+
local slug=""
1977+
local summary=""
19741978

1975-
# Try to generate AI summary if claude is available
1976-
if command_exists claude; then
1977-
local all_diffs="$diff$staged$submodule_diffs"
1978-
local summary_json=$(generate_wip_summary "$all_diffs")
1979-
local parsed_slug=$(echo "$summary_json" | sed -n 's/.*"slug"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
1980-
local parsed_summary=$(echo "$summary_json" | sed -n 's/.*"summary"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
1981-
[ -n "$parsed_slug" ] && slug="$parsed_slug"
1982-
[ -n "$parsed_summary" ] && summary="$parsed_summary"
1979+
# Use custom name if provided
1980+
if [ -n "$custom_name" ]; then
1981+
slug=$(echo "$custom_name" | tr ' ' '-' | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]//g')
1982+
summary="$custom_name"
1983+
else
1984+
# Try to generate AI summary if claude is available
1985+
slug="wip-$(date +%H%M)"
1986+
summary="Work in progress"
1987+
if command_exists claude; then
1988+
local all_diffs="$diff$staged$submodule_diffs"
1989+
local summary_json=$(generate_wip_summary "$all_diffs")
1990+
local parsed_slug=$(echo "$summary_json" | sed -n 's/.*"slug"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
1991+
local parsed_summary=$(echo "$summary_json" | sed -n 's/.*"summary"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
1992+
[ -n "$parsed_slug" ] && slug="$parsed_slug"
1993+
[ -n "$parsed_summary" ] && summary="$parsed_summary"
1994+
fi
19831995
fi
19841996

19851997
local wip_path="$wip_dir/$timestamp-$slug"
19861998
mkdir -p "$wip_path"
19871999

1988-
echo " Saving patches..."
1989-
[ -n "$diff" ] && echo "$diff" > "$wip_path/main-unstaged.patch"
1990-
[ -n "$staged" ] && echo "$staged" > "$wip_path/main-staged.patch"
2000+
local saved_count=0
2001+
2002+
# Save patches
2003+
if [ -n "$diff" ]; then
2004+
echo "$diff" > "$wip_path/main-unstaged.patch"
2005+
((saved_count++))
2006+
fi
2007+
if [ -n "$staged" ]; then
2008+
echo "$staged" > "$wip_path/main-staged.patch"
2009+
((saved_count++))
2010+
fi
19912011
[ -n "$status" ] && echo "$status" > "$wip_path/main-status.txt"
19922012
[ -n "$commit_log" ] && echo "$commit_log" > "$wip_path/main-commits.txt"
19932013

2014+
# Save untracked files (copy actual content)
2015+
if [ -n "$untracked_files" ]; then
2016+
local untracked_dir="$wip_path/untracked"
2017+
mkdir -p "$untracked_dir"
2018+
echo "$untracked_files" | while read -r file; do
2019+
[ -z "$file" ] && continue
2020+
local file_dir=$(dirname "$file")
2021+
mkdir -p "$untracked_dir/$file_dir"
2022+
cp "$dir/$file" "$untracked_dir/$file" 2>/dev/null && ((saved_count++))
2023+
done
2024+
echo "$untracked_files" > "$wip_path/untracked-files.txt"
2025+
fi
2026+
19942027
# Save submodule patches
19952028
for ((i=0; i<submodules_count; i++)); do
19962029
local sub_path=$(yq -r ".submodules[$i].path" "$CONFIG_FILE" 2>/dev/null)
@@ -2025,8 +2058,7 @@ EOF
20252058
mood_record_event "wip_save" "$slug"
20262059

20272060
success "WIP saved: $slug"
2028-
echo -e " ${BLUE}$summary${NC}"
2029-
echo " Location: $wip_path"
2061+
echo -e " ${GRAY}$summary${NC}"
20302062

20312063
if [ "$do_restart" = "true" ]; then
20322064
echo ""
@@ -2600,16 +2632,26 @@ _restore_wip() {
26002632
fi
26012633
done
26022634

2635+
# Restore untracked files
2636+
if [ -d "$wip_path/untracked" ]; then
2637+
echo " Restoring untracked files..."
2638+
cp -r "$wip_path/untracked/." "$dir/" 2>/dev/null || true
2639+
fi
2640+
26032641
success "WIP restored: $wip_name"
26042642

2605-
if [ "$open_after" = "true" ]; then
2643+
# Check if user is already in the workspace
2644+
local current_dir=$(pwd)
2645+
if [[ "$current_dir" == "$dir"* ]]; then
2646+
# Already in workspace, no need to tell them to open it
2647+
:
2648+
elif [ "$open_after" = "true" ]; then
26062649
echo ""
26072650
echo " Opening workspace $num..."
26082651
open_workspace "$num"
26092652
else
26102653
echo ""
26112654
echo " Run 'crab $num' to open the workspace"
2612-
echo " Or 'crab wip restore <N> --open' to restore and open"
26132655
fi
26142656
}
26152657

@@ -6400,11 +6442,22 @@ handle_wip_for_workspace() {
64006442

64016443
case "${1:-}" in
64026444
"save")
6403-
if [ "${2:-}" = "--restart" ] || [ "${2:-}" = "-r" ]; then
6404-
wip_save "$num" "true"
6405-
else
6406-
wip_save "$num" "false"
6407-
fi
6445+
shift # Remove "save"
6446+
local do_restart="false"
6447+
local custom_name=""
6448+
while [ $# -gt 0 ]; do
6449+
case "$1" in
6450+
--restart|-r)
6451+
do_restart="true"
6452+
shift
6453+
;;
6454+
*)
6455+
custom_name="$1"
6456+
shift
6457+
;;
6458+
esac
6459+
done
6460+
wip_save "$num" "$do_restart" "$custom_name"
64086461
;;
64096462
"ls"|"list")
64106463
wip_list "$num"
@@ -6420,7 +6473,7 @@ handle_wip_for_workspace() {
64206473
;;
64216474
*)
64226475
echo -e "${CYAN}WIP Commands for workspace $num:${NC}"
6423-
echo " crab ws $num wip save [--restart]"
6476+
echo " crab ws $num wip save [\"name\"] [--restart]"
64246477
echo " crab ws $num wip ls"
64256478
echo " crab ws $num wip --continue"
64266479
echo " crab ws $num wip --resume"
@@ -7373,7 +7426,7 @@ main() {
73737426

73747427
# For project-aware commands, resolve project (cwd-first, then default)
73757428
case "${1:-}" in
7376-
""|"ws"|"workspace"|"restart"|"reset"|"refresh"|"continue"|"resume"|"cleanup"|"clean"|"destroy"|"rm"|"remove"|"new"|"create"|"wip"|"config"|"doctor"|"ports"|"shared"|"status"|"snapshot"|"receive"|"handoff"|"rewind"|"timetravel"|"tt"|"pair"|"join"|"spectate"|"watch"|"mood"|"mobile"|"slack"|"tk"|"toolkit"|"pf"|"promptfoo"|"session"|"review")
7429+
""|"ws"|"workspace"|"restart"|"reset"|"refresh"|"continue"|"resume"|"cleanup"|"clean"|"destroy"|"rm"|"remove"|"new"|"create"|"wip"|"save"|"config"|"doctor"|"ports"|"shared"|"status"|"snapshot"|"receive"|"handoff"|"rewind"|"timetravel"|"tt"|"pair"|"join"|"spectate"|"watch"|"mood"|"mobile"|"slack"|"tk"|"toolkit"|"pf"|"promptfoo"|"session"|"review")
73777430
if [ -z "$PROJECT_ALIAS" ]; then
73787431
# Legacy migration check
73797432
if is_legacy_config; then
@@ -7481,6 +7534,32 @@ main() {
74817534
fi
74827535
cleanup_workspace "$num"
74837536
;;
7537+
"save")
7538+
# Shortcut: crab save ["name"] [--restart]
7539+
load_config
7540+
validate_config
7541+
num=$(detect_workspace)
7542+
if [ -z "$num" ]; then
7543+
error "Not in a workspace directory. Run from inside a workspace."
7544+
exit 1
7545+
fi
7546+
local do_restart="false"
7547+
local custom_name=""
7548+
shift # Remove "save"
7549+
while [ $# -gt 0 ]; do
7550+
case "$1" in
7551+
--restart|-r)
7552+
do_restart="true"
7553+
shift
7554+
;;
7555+
*)
7556+
custom_name="$1"
7557+
shift
7558+
;;
7559+
esac
7560+
done
7561+
wip_save "$num" "$do_restart" "$custom_name"
7562+
;;
74847563
"destroy"|"rm"|"remove")
74857564
load_config
74867565
validate_config
@@ -7507,11 +7586,24 @@ main() {
75077586
error "Not in a workspace directory. Use: crab ws <N> wip save"
75087587
exit 1
75097588
fi
7510-
if [ "${3:-}" = "--restart" ] || [ "${3:-}" = "-r" ]; then
7511-
wip_save "$num" "true"
7512-
else
7513-
wip_save "$num" "false"
7514-
fi
7589+
# Parse: crab wip save ["name"] [--restart]
7590+
local do_restart="false"
7591+
local custom_name=""
7592+
shift 2 # Remove "wip" and "save"
7593+
while [ $# -gt 0 ]; do
7594+
case "$1" in
7595+
--restart|-r)
7596+
do_restart="true"
7597+
shift
7598+
;;
7599+
*)
7600+
# Anything else is the custom name
7601+
custom_name="$1"
7602+
shift
7603+
;;
7604+
esac
7605+
done
7606+
wip_save "$num" "$do_restart" "$custom_name"
75157607
;;
75167608
"ls"|"list")
75177609
# List is global by default, unless --ws flag or physically in workspace

0 commit comments

Comments
 (0)