From 465e1c3d5297bbe9fa483705d4b96b8f11464bcf Mon Sep 17 00:00:00 2001 From: Victor Zhang Date: Sat, 13 Jun 2026 03:57:11 -0700 Subject: [PATCH 1/2] feat: add version, doctor, and migrate commands --- scripts/wtcraft | 151 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/scripts/wtcraft b/scripts/wtcraft index 01558c7..23c7629 100755 --- a/scripts/wtcraft +++ b/scripts/wtcraft @@ -28,6 +28,8 @@ Usage: wtcraft check [--json] [--repo ] wtcraft verify [--json] [--repo ] wtcraft capabilities --json + wtcraft doctor + wtcraft migrate [--yes] wtcraft --version wtcraft help [command] EOF @@ -215,6 +217,37 @@ wtcraft capabilities --json EOF } +usage_doctor() { + cat <<'EOF' +wtcraft doctor + + Diagnose the current wtcraft installation and repository scaffold. + + Reports: + - CLI path and version + - other wtcraft installations on PATH (shadow detection) + - scaffold file completeness (when inside a git repository) + - legacy file detection (.worktree-task.template.md) + - template directory status + - git credential helper +EOF +} + +usage_migrate() { + cat <<'EOF' +wtcraft migrate [--yes] + + Ensure all scaffold files are present and remove legacy files. + + Runs `wtcraft init` to fill in any missing scaffold, then offers to + delete known legacy files such as .worktree-task.template.md. + + Options: + --yes Skip the interactive confirmation prompt and delete legacy + files automatically. Useful for CI and scripted usage. +EOF +} + cmd_help() { if [ $# -eq 0 ]; then usage @@ -230,6 +263,9 @@ cmd_help() { check) usage_check ;; verify) usage_verify ;; capabilities) usage_capabilities ;; + doctor) usage_doctor ;; + migrate) usage_migrate ;; + version) usage ;; help) usage ;; *) die "no help topic for: $1" ;; esac @@ -513,6 +549,14 @@ EOF echo echo "Local mode enabled: wtcraft scaffold is ignored via .git/info/exclude in this clone only." fi + + echo + echo "Initialized wtcraft harness using wtcraft $(read_cli_version)" + if [ -f "${repo_root}/.worktree-task.template.md" ]; then + echo + echo "Legacy scaffold detected: .worktree-task.template.md" + echo "Run: wtcraft migrate" + fi } cmd_patch() { @@ -1342,6 +1386,111 @@ cmd_capabilities() { "$(json_field "$(read_cli_version)")" } +cmd_doctor() { + local cli_path cli_version + cli_path="$(command -v wtcraft || echo "$0")" + cli_version="$(read_cli_version)" + echo "CLI path: $cli_path" + echo "Version: ${cli_version}" + + local other_installs + other_installs="$(type -a wtcraft 2>/dev/null | grep -v " is $cli_path" | awk '{print $3}' | sort -u || true)" + if [ -n "$other_installs" ]; then + echo "Other installations:" + for inst in $other_installs; do + local inst_ver + inst_ver="$("$inst" --version 2>/dev/null || echo "unknown")" + if [ -z "$inst_ver" ]; then inst_ver="unknown"; fi + echo " $inst ($inst_ver)" + done + + if echo "$other_installs" | grep -iq "homebrew"; then + if echo "$cli_path" | grep -iq -e "pipx" -e "\.local/bin"; then + echo "Warning: a pipx installation shadows Homebrew" + fi + fi + fi + + echo "" + echo "Checking repository context..." + if git rev-parse --show-toplevel >/dev/null 2>&1; then + local repo_root + repo_root="$(git rev-parse --show-toplevel)" + + local missing=0 + for file in .agent-harness/planner.md .agent-harness/executor.md .agent-harness/finisher.md .agent-harness/role-models.yml .claude/commands/planwt.md; do + if [ ! -f "$repo_root/$file" ]; then + echo "Missing scaffold: $file" + missing=$((missing + 1)) + fi + done + if [ "$missing" -eq 0 ]; then + echo "Scaffold files: Complete" + fi + + if [ -f "$repo_root/.worktree-task.template.md" ]; then + echo "Legacy file detected: .worktree-task.template.md (run 'wtcraft migrate' to fix)" + fi + else + echo "Not inside a git repository." + fi + + echo "" + echo "Checking environment..." + local cred_helper + cred_helper="$(git config --get credential.helper || echo "None")" + echo "Git credential helper: $cred_helper" + + if [ ! -d "$TEMPLATE_DIR" ] || [ -z "$(ls -A "$TEMPLATE_DIR" 2>/dev/null)" ]; then + echo "Warning: Template directory '$TEMPLATE_DIR' is missing or empty." + else + echo "Template directory: Complete ($TEMPLATE_DIR)" + fi +} + +cmd_migrate() { + local auto_yes=false + local repo_arg="." + while [ $# -gt 0 ]; do + case "$1" in + --yes) auto_yes=true ;; + --repo) shift; repo_arg="${1:?--repo requires a path}" ;; + *) die "unknown migrate option: $1" ;; + esac + shift + done + ensure_git_path "$repo_arg" + local repo_root + repo_root="$(current_worktree_root "$repo_arg")" + + echo "Running wtcraft init to ensure all scaffolds are present..." + cmd_init --repo "$repo_root" + + if [ -f "$repo_root/.worktree-task.template.md" ]; then + if [ "$auto_yes" = true ]; then + rm -f "$repo_root/.worktree-task.template.md" + echo "Deleted .worktree-task.template.md" + elif [ -t 0 ]; then + echo "" + read -r -p "Delete legacy file '.worktree-task.template.md'? [y/N] " response + case "$response" in + [yY][eE][sS]|[yY]) + rm -f "$repo_root/.worktree-task.template.md" + echo "Deleted .worktree-task.template.md" + ;; + *) + echo "Skipped deletion." + ;; + esac + else + echo "Legacy file detected: .worktree-task.template.md" + echo "Run with --yes to delete automatically." + fi + else + echo "No legacy files to migrate." + fi +} + main() { [ $# -ge 1 ] || { usage; exit 1; } local cmd="$1" @@ -1357,6 +1506,8 @@ main() { check) cmd_check "$@" ;; verify) cmd_verify "$@" ;; capabilities) cmd_capabilities "$@" ;; + doctor) cmd_doctor "$@" ;; + migrate) cmd_migrate "$@" ;; help) cmd_help "$@" ;; version|--version) printf '%s\n' "$(read_cli_version)" ;; -h|--help) usage ;; From 2633c24a71106a20bb634ee99d5c588fb3bc66ff Mon Sep 17 00:00:00 2001 From: Victor Zhang Date: Sat, 13 Jun 2026 05:59:02 -0700 Subject: [PATCH 2/2] feat: improve doctor/migrate and add E2E tests - Remove hardcoded VERSION; use read_cli_version() from PR #33 - Add --yes flag to migrate for non-interactive usage - Detect non-interactive stdin and skip prompt gracefully - Add dedicated usage_doctor and usage_migrate help text - Add E2E tests for version, doctor, migrate --yes, and help Co-Authored-By: Claude Opus 4.6 --- tests/e2e_doctor_migrate.sh | 123 ++++++++++++++++++++++++++++++++++++ tests/run_all.sh | 1 + 2 files changed, 124 insertions(+) create mode 100755 tests/e2e_doctor_migrate.sh diff --git a/tests/e2e_doctor_migrate.sh b/tests/e2e_doctor_migrate.sh new file mode 100755 index 0000000..0b3571e --- /dev/null +++ b/tests/e2e_doctor_migrate.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env bash +source "$(dirname "${BASH_SOURCE[0]}")/framework.sh" + +test_version_prints_semver() { + local repo="$1" + cd "$repo" + local ver + ver="$("$CLI" --version)" + echo "$ver" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+' + # "version" subcommand produces the same output + [ "$("$CLI" version)" = "$ver" ] +} + +test_doctor_outside_repo() { + local repo="$1" + local tmpdir + tmpdir="$(mktemp -d)" + ( + cd "$tmpdir" + # Not a git repo, doctor should still run and report it + output="$("$CLI" doctor 2>&1)" + echo "$output" | grep -q "Not inside a git repository" + echo "$output" | grep -q "Version:" + echo "$output" | grep -q "CLI path:" + ) + rm -rf "$tmpdir" +} + +test_doctor_inside_repo() { + local repo="$1" + cd "$repo" + git config user.name "wtcraft-smoke" + git config user.email "wtcraft-smoke@example.com" + + # Before init: doctor should report missing scaffolds + output="$("$CLI" doctor 2>&1)" + echo "$output" | grep -q "Missing scaffold:" + + # After init: doctor should report complete + "$CLI" init + git add -A && git commit -q -m "init" + output="$("$CLI" doctor 2>&1)" + echo "$output" | grep -q "Scaffold files: Complete" +} + +test_doctor_detects_legacy_file() { + local repo="$1" + cd "$repo" + git config user.name "wtcraft-smoke" + git config user.email "wtcraft-smoke@example.com" + "$CLI" init + git add -A && git commit -q -m "init" + + # Create a legacy file + echo "legacy" > .worktree-task.template.md + output="$("$CLI" doctor 2>&1)" + echo "$output" | grep -q "Legacy file detected" + echo "$output" | grep -q "wtcraft migrate" +} + +test_migrate_yes_deletes_legacy() { + local repo="$1" + cd "$repo" + git config user.name "wtcraft-smoke" + git config user.email "wtcraft-smoke@example.com" + "$CLI" init + git add -A && git commit -q -m "init" + + # Create legacy file + echo "legacy" > .worktree-task.template.md + test -f .worktree-task.template.md + + # migrate --yes should delete it without prompting + "$CLI" migrate --yes + test ! -f .worktree-task.template.md +} + +test_migrate_yes_no_legacy() { + local repo="$1" + cd "$repo" + git config user.name "wtcraft-smoke" + git config user.email "wtcraft-smoke@example.com" + "$CLI" init + git add -A && git commit -q -m "init" + + # No legacy file: migrate should report nothing to do + output="$("$CLI" migrate --yes 2>&1)" + echo "$output" | grep -q "No legacy files to migrate" +} + +test_migrate_non_interactive_skips_without_yes() { + local repo="$1" + cd "$repo" + git config user.name "wtcraft-smoke" + git config user.email "wtcraft-smoke@example.com" + "$CLI" init + git add -A && git commit -q -m "init" + + echo "legacy" > .worktree-task.template.md + + # Pipe stdin to make it non-interactive; without --yes it should not delete + output="$(echo "" | "$CLI" migrate 2>&1)" + echo "$output" | grep -q "Run with --yes to delete automatically" + test -f .worktree-task.template.md +} + +test_help_doctor_migrate() { + local repo="$1" + cd "$repo" + "$CLI" help doctor | grep -q "Diagnose" + "$CLI" help migrate | grep -q "scaffold" +} + +run_in_temp_repo test_version_prints_semver +test_doctor_outside_repo "" +run_in_temp_repo test_doctor_inside_repo +run_in_temp_repo test_doctor_detects_legacy_file +run_in_temp_repo test_migrate_yes_deletes_legacy +run_in_temp_repo test_migrate_yes_no_legacy +run_in_temp_repo test_migrate_non_interactive_skips_without_yes +run_in_temp_repo test_help_doctor_migrate + +echo "[PASS] e2e_doctor_migrate" diff --git a/tests/run_all.sh b/tests/run_all.sh index a0bf19c..97a2760 100755 --- a/tests/run_all.sh +++ b/tests/run_all.sh @@ -9,6 +9,7 @@ bash "${SCRIPT_DIR}/unit_codegen.sh" bash "${SCRIPT_DIR}/unit_awk.sh" bash "${SCRIPT_DIR}/e2e_init_patch.sh" bash "${SCRIPT_DIR}/e2e_lifecycle.sh" +bash "${SCRIPT_DIR}/e2e_doctor_migrate.sh" # Integration tests require npm and python3 (skip gracefully if unavailable) if command -v npm >/dev/null 2>&1 && command -v python3 >/dev/null 2>&1; then