From 98b579e6c710715f574a4b91fdf1439abe0133ed Mon Sep 17 00:00:00 2001 From: Cyrus Gray <144336577+graycyrus@users.noreply.github.com> Date: Sat, 16 May 2026 08:25:46 +0530 Subject: [PATCH] feat: add deep-work automation system (#1644) Co-authored-by: Claude Co-authored-by: Steven Enamakel <31011319+senamakel@users.noreply.github.com> --- package.json | 1 + scripts/deep-work/cleanup.sh | 157 ++++++++++++++ scripts/deep-work/cli.sh | 67 ++++++ scripts/deep-work/continue.sh | 291 ++++++++++++++++++++++++++ scripts/deep-work/lib.sh | 208 +++++++++++++++++++ scripts/deep-work/list.sh | 57 +++++ scripts/deep-work/pick.sh | 158 ++++++++++++++ scripts/deep-work/start.sh | 379 ++++++++++++++++++++++++++++++++++ scripts/deep-work/status.sh | 156 ++++++++++++++ 9 files changed, 1474 insertions(+) create mode 100755 scripts/deep-work/cleanup.sh create mode 100755 scripts/deep-work/cli.sh create mode 100755 scripts/deep-work/continue.sh create mode 100755 scripts/deep-work/lib.sh create mode 100755 scripts/deep-work/list.sh create mode 100755 scripts/deep-work/pick.sh create mode 100755 scripts/deep-work/start.sh create mode 100755 scripts/deep-work/status.sh diff --git a/package.json b/package.json index 526fe60de..7197947ba 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "pr:checklist": "node scripts/check-pr-checklist.mjs", "rabbit": "bash scripts/rabbit/cli.sh", "reset": "bash scripts/shortcuts/ws-reset.sh", + "deep-work": "bash scripts/deep-work/cli.sh", "review": "bash scripts/shortcuts/review/cli.sh", "work": "bash scripts/shortcuts/work/cli.sh", "agent-batch": "node scripts/agent-batch/cli.mjs", diff --git a/scripts/deep-work/cleanup.sh b/scripts/deep-work/cleanup.sh new file mode 100755 index 000000000..8427fa067 --- /dev/null +++ b/scripts/deep-work/cleanup.sh @@ -0,0 +1,157 @@ +#!/usr/bin/env bash +# cleanup.sh +# +# Clean up worktree for completed issue + +set -euo pipefail +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# shellcheck source=./lib.sh +source "$here/lib.sh" + +if [ -z "${1:-}" ]; then + echo "Usage: pnpm deep-work cleanup " >&2 + echo "" + echo "Active worktrees:" + list_deep_work_worktrees | while IFS='=' read -r key value; do + if [ "$key" = "issue" ]; then + issue_num="$value" + elif [ "$key" = "path" ]; then + echo " #$issue_num: $value" + fi + done + exit 1 +fi + +case "$1" in + ''|*[!0-9]*) + echo "[deep-work] issue-number must be numeric, got: $1" >&2 + exit 1 + ;; +esac + +issue="$1" + +echo "[deep-work] 🧹 Cleaning up worktree for issue #$issue" + +# Check if worktree exists +if ! worktree_exists "$issue"; then + echo "[deep-work] ❌ no worktree found for issue #$issue" + exit 1 +fi + +worktree_dir=$(worktree_dir_for_issue "$issue") +echo "[deep-work] worktree location: $worktree_dir" + +# Get branch info +branch="" +if [ -d "$worktree_dir" ]; then + branch=$(cd "$worktree_dir" && git branch --show-current 2>/dev/null || echo "unknown") + echo "[deep-work] branch: $branch" +fi + +# Check for uncommitted changes +if [ -d "$worktree_dir/.git" ]; then + if ! (cd "$worktree_dir" && git diff --quiet && git diff --cached --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]); then + echo "" + echo "⚠️ WARNING: Worktree has uncommitted changes!" + echo "" + echo "Files with changes:" + (cd "$worktree_dir" && git status --short) + echo "" + echo "These changes will be LOST if you proceed with cleanup." + echo "" + echo "Options:" + echo "1) Cancel cleanup and commit changes first" + echo "2) Continue anyway and lose changes" + echo "" + read -p "What would you like to do? [1/2]: " -r choice + case "$choice" in + 1) + echo "Cleanup cancelled. Please commit your changes first:" + echo " cd $worktree_dir" + echo " git add ." + echo " git commit -m 'message'" + echo " pnpm deep-work cleanup $issue" + exit 0 + ;; + 2) + echo "Proceeding with cleanup, changes will be lost..." + ;; + *) + echo "Invalid choice. Cancelling cleanup." + exit 1 + ;; + esac + fi +fi + +# Check PR status +repo=$(resolve_deep_work_repo) +pr_status="" +pr_url="" + +if [ -n "$branch" ] && [ "$branch" != "unknown" ]; then + if pr_json=$(gh pr view "$branch" -R "$repo" --json url,state,isDraft,mergeable 2>/dev/null); then + pr_url=$(echo "$pr_json" | jq -r '.url') + pr_state=$(echo "$pr_json" | jq -r '.state') + pr_draft=$(echo "$pr_json" | jq -r '.isDraft') + + if [ "$pr_state" = "MERGED" ]; then + pr_status="merged" + elif [ "$pr_draft" = "true" ]; then + pr_status="draft" + elif [ "$pr_state" = "OPEN" ]; then + pr_status="open" + else + pr_status="$pr_state" + fi + + echo "" + echo "πŸ“‹ PR Status: $pr_status" + echo "πŸ”— PR URL: $pr_url" + else + echo "" + echo "πŸ“‹ No PR found for branch $branch" + fi +fi + +# Confirm cleanup +echo "" +echo "This will:" +echo " βœ— Remove worktree directory: $worktree_dir" +echo " βœ— Delete local branch: $branch" +echo " βœ“ Keep remote branch and PR intact (if they exist)" +echo "" + +if [ "$pr_status" = "draft" ] || [ "$pr_status" = "open" ]; then + echo "⚠️ Note: PR is still $pr_status. You may want to merge it first." + echo "" +fi + +read -p "Continue with cleanup? [y/N]: " -r +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Cleanup cancelled." + exit 0 +fi + +# Perform cleanup +cleanup_worktree "$issue" "true" + +echo "" +echo "[deep-work] βœ… Cleanup complete!" + +if [ -n "$pr_url" ]; then + echo "" + echo "πŸ“‹ PR remains available: $pr_url" + + if [ "$pr_status" = "merged" ]; then + echo "" + echo "πŸŽ‰ Issue #$issue has been successfully completed and merged!" + fi +fi + +echo "" +echo "πŸš€ Ready to start your next deep-work session:" +echo " pnpm deep-work pick" +echo " pnpm deep-work start " \ No newline at end of file diff --git a/scripts/deep-work/cli.sh b/scripts/deep-work/cli.sh new file mode 100755 index 000000000..0a8744dd3 --- /dev/null +++ b/scripts/deep-work/cli.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# Dispatcher for `pnpm deep-work `. +# Commands: start, pick, continue, status, cleanup, list + +set -euo pipefail +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +usage() { + cat <<'EOF' +Usage: pnpm deep-work [args...] + +Full workflow automation for GitHub issues using worktrees and AI agents. + +Commands: + start Start full workflow for an issue + pick Smart issue selection + start workflow + continue [issue-number] Resume workflow from current step + status Show all active worktrees and progress + list List all worktrees + cleanup Clean up specific worktree (with confirmation) + +Examples: + pnpm deep-work start 1234 # Start working on issue #1234 + pnpm deep-work pick # Let AI pick and start an issue + pnpm deep-work continue # Resume current work + pnpm deep-work status # Check progress on all issues + pnpm deep-work cleanup 1234 # Clean up worktree for issue #1234 +EOF +} + +cmd="${1:-}" +if [ -z "$cmd" ] || [ "$cmd" = "-h" ] || [ "$cmd" = "--help" ]; then + usage + exit 0 +fi + +case "$cmd" in + start) + shift + exec "$here/start.sh" "$@" + ;; + pick) + shift + exec "$here/pick.sh" "$@" + ;; + continue) + shift + exec "$here/continue.sh" "$@" + ;; + status) + shift + exec "$here/status.sh" "$@" + ;; + list) + shift + exec "$here/list.sh" "$@" + ;; + cleanup) + shift + exec "$here/cleanup.sh" "$@" + ;; + *) + echo "[deep-work] unknown command: $cmd" >&2 + usage >&2 + exit 1 + ;; +esac \ No newline at end of file diff --git a/scripts/deep-work/continue.sh b/scripts/deep-work/continue.sh new file mode 100755 index 000000000..cca12304b --- /dev/null +++ b/scripts/deep-work/continue.sh @@ -0,0 +1,291 @@ +#!/usr/bin/env bash +# continue.sh [issue-number] +# +# Resume deep-work workflow from current state + +set -euo pipefail +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# shellcheck source=./lib.sh +source "$here/lib.sh" + +issue="${1:-}" + +# If no issue provided, try to detect from current directory +if [ -z "$issue" ]; then + worktree_info=$(get_current_worktree_info) + if [ "$worktree_info" = "not_in_worktree" ]; then + echo "[deep-work] not currently in a worktree and no issue specified" + echo "" + echo "Usage: pnpm deep-work continue [issue-number]" + echo "" + echo "Active worktrees:" + list_deep_work_worktrees | while IFS='=' read -r key value; do + if [ "$key" = "issue" ]; then + issue_num="$value" + elif [ "$key" = "path" ]; then + echo " #$issue_num: $value" + fi + done + exit 1 + fi + + # Extract issue from worktree info + issue=$(echo "$worktree_info" | grep -o 'issue=[0-9]*' | cut -d= -f2) + echo "[deep-work] detected issue #$issue from current directory" +fi + +# Validate issue number +case "$issue" in + ''|*[!0-9]*) + echo "[deep-work] invalid issue number: $issue" >&2 + exit 1 + ;; +esac + +# Check if worktree exists +worktree_dir=$(worktree_dir_for_issue "$issue") +if ! worktree_exists "$issue"; then + echo "[deep-work] no worktree found for issue #$issue" + echo "[deep-work] use 'pnpm deep-work start $issue' to begin work" + exit 1 +fi + +echo "[deep-work] πŸ”„ Resuming work on issue #$issue" + +# Change to worktree +cd "$worktree_dir" +echo "[deep-work] πŸ“ working in: $(pwd)" +echo "[deep-work] 🌿 branch: $(git branch --show-current)" + +# Check git status to determine next step +git_status=$(git status --porcelain) +has_staged=$(git diff --cached --quiet && echo "false" || echo "true") +has_unstaged=$(git diff --quiet && echo "false" || echo "true") +has_untracked=$([ -z "$(git ls-files --others --exclude-standard)" ] && echo "false" || echo "true") + +echo "" +echo "[deep-work] analyzing current state..." + +# Determine what step to resume from +if [ "$has_staged" = "true" ] || [ "$has_unstaged" = "true" ] || [ "$has_untracked" = "true" ]; then + echo "[deep-work] πŸ“ detected uncommitted changes" + echo "" + echo "Git status:" + git status --short + echo "" + echo "Next steps:" + echo "1) Continue implementation - call codecrusher to finish work" + echo "2) Run quality checks - typecheck, lint, format, build" + echo "3) Commit changes - stage and commit current work" + echo "4) Manual review - inspect changes yourself" + echo "" + + while true; do + read -p "What would you like to do? [1-4]: " -r choice + case "$choice" in + 1) + echo "[deep-work] πŸ’» calling codecrusher to continue implementation..." + + repo=$(resolve_deep_work_repo) + issue_json=$(gh issue view "$issue" -R "$repo" --json title,body,url) + title=$(jq -r '.title' <<<"$issue_json") + body=$(jq -r '.body // ""' <<<"$issue_json") + url=$(jq -r '.url' <<<"$issue_json") + + implementation_prompt="Continue working on GitHub issue #${issue} from previous session. + +Working branch: $(git branch --show-current) +Issue URL: ${url} +Issue title: ${title} + +Current uncommitted changes detected. Please review the current state and continue implementation: + +--- Issue body --- +${body} +--- end issue body --- + +Complete the implementation following the existing patterns and ensure all requirements are met." + + claude "$implementation_prompt" --task codecrusher + break + ;; + 2) + echo "[deep-work] πŸ” running quality checks..." + if run_quality_checks "continue"; then + echo "[deep-work] βœ… quality checks passed" + else + echo "[deep-work] ❌ quality checks failed - please fix issues" + fi + break + ;; + 3) + echo "[deep-work] πŸ“ preparing to commit..." + git add . + + repo=$(resolve_deep_work_repo) + issue_json=$(gh issue view "$issue" -R "$repo" --json title,body) + title=$(jq -r '.title' <<<"$issue_json") + body=$(jq -r '.body // ""' <<<"$issue_json") + + commit_message="fix(#${issue}): ${title} + +${body} + +πŸ€– Generated with [Claude Code](https://claude.ai/code) + +Co-Authored-By: Claude " + + git commit -m "$(cat </dev/null | grep -q .; then + echo "[deep-work] πŸ“€ found unpushed commits" + echo "" + echo "Unpushed commits:" + git log origin/"$branch"..HEAD --oneline + echo "" + echo "Next steps:" + echo "1) Push and create PR" + echo "2) Run additional quality checks first" + echo "3) Update memory with learnings" + echo "" + + while true; do + read -p "What would you like to do? [1-3]: " -r choice + case "$choice" in + 1) + echo "[deep-work] πŸ“€ pushing branch and creating PR..." + git push -u origin "$branch" + + # Create PR if it doesn't exist + repo=$(resolve_deep_work_repo) + if ! gh pr view "$branch" -R "$repo" >/dev/null 2>&1; then + issue_json=$(gh issue view "$issue" -R "$repo" --json title,body) + title=$(jq -r '.title' <<<"$issue_json") + body=$(jq -r '.body // ""' <<<"$issue_json") + + pr_body="## Summary + +Closes #${issue} + +${body} + +πŸ€– Generated with [Claude Code](https://claude.ai/code)" + + pr_url=$(gh pr create \ + --title "fix(#${issue}): ${title}" \ + --body "$pr_body" \ + --draft \ + --head "$(gh auth status 2>&1 | grep 'Logged in.*as' | sed 's/.*as //' | cut -d' ' -f1):$branch" \ + --base main \ + --repo "$repo") + + echo "[deep-work] πŸ“ PR created: $pr_url" + else + echo "[deep-work] βœ… PR already exists" + fi + break + ;; + 2) + if run_quality_checks "continue"; then + echo "[deep-work] βœ… quality checks passed" + else + echo "[deep-work] ❌ quality checks failed - please fix issues" + fi + break + ;; + 3) + echo "[deep-work] 🧠 calling memory-keeper..." + repo=$(resolve_deep_work_repo) + issue_json=$(gh issue view "$issue" -R "$repo" --json title) + title=$(jq -r '.title' <<<"$issue_json") + + memory_prompt="Update project memory with learnings from issue #${issue}: \"${title}\". + +Review the recent commits and changes to extract useful insights for future development." + + claude "$memory_prompt" --task memory-keeper + break + ;; + *) + echo "Please enter 1, 2, or 3" + continue + ;; + esac + done + else + echo "[deep-work] πŸ“‹ everything up to date" + echo "" + echo "Next steps:" + echo "1) Check PR status and reviews" + echo "2) Run final quality checks" + echo "3) Clean up worktree" + echo "" + + while true; do + read -p "What would you like to do? [1-3]: " -r choice + case "$choice" in + 1) + repo=$(resolve_deep_work_repo) + if gh pr view "$branch" -R "$repo" >/dev/null 2>&1; then + echo "[deep-work] πŸ“‹ PR status:" + gh pr view "$branch" -R "$repo" + else + echo "[deep-work] ❌ no PR found for this branch" + fi + break + ;; + 2) + if run_quality_checks "final"; then + echo "[deep-work] βœ… final quality checks passed" + else + echo "[deep-work] ❌ quality checks failed" + fi + break + ;; + 3) + echo "[deep-work] 🧹 cleaning up worktree..." + cd "$(git rev-parse --show-toplevel)/.." + cleanup_worktree "$issue" + break + ;; + *) + echo "Please enter 1, 2, or 3" + continue + ;; + esac + done + fi +fi + +echo "" +echo "[deep-work] 🎯 Continue session complete!" +echo "[deep-work] Use 'pnpm deep-work continue $issue' to resume anytime." \ No newline at end of file diff --git a/scripts/deep-work/lib.sh b/scripts/deep-work/lib.sh new file mode 100755 index 000000000..9a08ed67e --- /dev/null +++ b/scripts/deep-work/lib.sh @@ -0,0 +1,208 @@ +#!/usr/bin/env bash +# Shared utilities for deep-work scripts + +set -euo pipefail + +# Source the existing review lib for repo resolution +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd "$here/../.." && pwd)" +# shellcheck source=../review/lib.sh +source "$repo_root/scripts/review/lib.sh" + +# Worktree utilities +worktree_dir_for_issue() { + local issue="$1" + echo "../oh-$issue" +} + +worktree_branch_for_issue() { + local issue="$1" + local title="$2" + local branch_prefix="${DEEP_WORK_BRANCH_PREFIX:-fix}" + + # Create slug from title + local slug + slug=$(printf '%s' "$title" \ + | tr '[:upper:]' '[:lower:]' \ + | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//' \ + | cut -c1-30 \ + | sed -E 's/-+$//') + + if [ -z "$slug" ]; then + slug="work" + fi + + echo "${branch_prefix}/${issue}-${slug}" +} + +# Check if worktree exists for issue +worktree_exists() { + local issue="$1" + local worktree_dir + worktree_dir=$(worktree_dir_for_issue "$issue") + [ -d "$worktree_dir" ] +} + +# Get current worktree info +get_current_worktree_info() { + local current_dir + current_dir=$(pwd) + + # Check if we're in a worktree by looking for oh- pattern + if [[ "$current_dir" =~ /oh-([0-9]+)$ ]]; then + local issue="${BASH_REMATCH[1]}" + local branch + branch=$(git branch --show-current 2>/dev/null || echo "") + echo "issue=$issue branch=$branch dir=$current_dir" + else + echo "not_in_worktree" + fi +} + +# Resolve repo (with DEEP_WORK_REPO override) +resolve_deep_work_repo() { + local repo="${DEEP_WORK_REPO:-${WORK_REPO:-${REVIEW_REPO:-}}}" + if [ -z "$repo" ]; then + repo=$(REVIEW_REPO= resolve_repo) + fi + echo "$repo" +} + +# Create worktree for issue +create_worktree() { + local issue="$1" + local title="$2" + + local worktree_dir + worktree_dir=$(worktree_dir_for_issue "$issue") + + local branch + branch=$(worktree_branch_for_issue "$issue" "$title") + + if worktree_exists "$issue"; then + echo "[deep-work] worktree for issue #$issue already exists at $worktree_dir" + return 1 + fi + + echo "[deep-work] creating worktree at $worktree_dir with branch $branch..." + git worktree add "$worktree_dir" -b "$branch" + + echo "[deep-work] worktree created: $worktree_dir" + echo "[deep-work] branch: $branch" +} + +# List all deep-work worktrees +list_deep_work_worktrees() { + git worktree list --porcelain | while IFS= read -r line; do + if [[ "$line" =~ ^worktree\ (.*/oh-([0-9]+))$ ]]; then + local path="${BASH_REMATCH[1]}" + local issue="${BASH_REMATCH[2]}" + echo "issue=$issue path=$path" + fi + done +} + +# Clean up worktree +cleanup_worktree() { + local issue="$1" + local force="${2:-false}" + + local worktree_dir + worktree_dir=$(worktree_dir_for_issue "$issue") + + if ! worktree_exists "$issue"; then + echo "[deep-work] no worktree found for issue #$issue" + return 1 + fi + + if [ "$force" != "true" ]; then + echo "This will remove the worktree at $worktree_dir and delete the branch." + echo "Any uncommitted changes will be lost." + read -p "Are you sure? (y/N) " -r + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Cancelled." + return 1 + fi + fi + + # Get branch name before removing worktree + local branch + if [ -d "$worktree_dir" ]; then + branch=$(cd "$worktree_dir" && git branch --show-current 2>/dev/null || echo "") + fi + + echo "[deep-work] removing worktree $worktree_dir..." + git worktree remove "$worktree_dir" --force + + if [ -n "$branch" ] && git show-ref --verify --quiet "refs/heads/$branch"; then + echo "[deep-work] deleting branch $branch..." + git branch -D "$branch" || echo "[deep-work] failed to delete branch $branch" + fi + + echo "[deep-work] cleanup completed for issue #$issue" +} + +# Sync with upstream +sync_upstream() { + echo "[deep-work] syncing with upstream..." + + # Ensure we're on main + if [ "$(git branch --show-current)" != "main" ]; then + echo "[deep-work] switching to main branch..." + git checkout main + fi + + # Fetch and merge upstream + if git remote get-url upstream >/dev/null 2>&1; then + git fetch upstream + echo "[deep-work] merging upstream/main..." + git merge --ff-only upstream/main || git merge upstream/main + fi + + # Update from origin + if git remote get-url origin >/dev/null 2>&1; then + echo "[deep-work] pulling from origin/main..." + git pull --ff-only origin main + fi + + # Update submodules + git submodule update --init --recursive + + echo "[deep-work] upstream sync completed" +} + +# Run quality checks +run_quality_checks() { + local step="$1" + echo "[deep-work] running quality checks for $step..." + + # Typecheck + echo "[deep-work] typechecking..." + if ! pnpm typecheck; then + echo "[deep-work] ❌ typecheck failed" + return 1 + fi + + # Lint + echo "[deep-work] linting..." + if ! pnpm lint; then + echo "[deep-work] ❌ lint failed" + return 1 + fi + + # Format check + echo "[deep-work] format check..." + if ! pnpm format:check; then + echo "[deep-work] running formatter..." + pnpm format + fi + + # Build + echo "[deep-work] building..." + if ! pnpm build; then + echo "[deep-work] ❌ build failed" + return 1 + fi + + echo "[deep-work] βœ… all quality checks passed" +} \ No newline at end of file diff --git a/scripts/deep-work/list.sh b/scripts/deep-work/list.sh new file mode 100755 index 000000000..2c9ec4bac --- /dev/null +++ b/scripts/deep-work/list.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# list.sh +# +# Simple list of all deep-work worktrees + +set -euo pipefail +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# shellcheck source=./lib.sh +source "$here/lib.sh" + +echo "[deep-work] πŸ“‚ All deep-work worktrees:" +echo "" + +worktrees_found=false + +list_deep_work_worktrees | while IFS='=' read -r key value; do + if [ "$key" = "issue" ]; then + issue_num="$value" + worktrees_found=true + elif [ "$key" = "path" ]; then + path="$value" + branch="" + status="" + + if [ -d "$path" ]; then + if [ -d "$path/.git" ]; then + branch=$(cd "$path" && git branch --show-current 2>/dev/null || echo "unknown") + + # Quick status check + if cd "$path" && git diff --quiet && git diff --cached --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then + status="clean" + else + status="uncommitted changes" + fi + else + status="not a git repo" + fi + else + status="directory missing" + fi + + printf " #%-4s %s\n" "$issue_num" "$path" + printf " branch: %s\n" "$branch" + printf " status: %s\n" "$status" + echo "" + fi +done + +# Check if we found any worktrees (need to use a different approach since the while loop runs in a subshell) +if ! list_deep_work_worktrees | grep -q "issue="; then + echo " No deep-work worktrees found." + echo "" + echo " To create a new one:" + echo " pnpm deep-work start " + echo " pnpm deep-work pick" +fi \ No newline at end of file diff --git a/scripts/deep-work/pick.sh b/scripts/deep-work/pick.sh new file mode 100755 index 000000000..d08522969 --- /dev/null +++ b/scripts/deep-work/pick.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash +# pick.sh +# +# Smart issue selection based on workflow criteria: +# - Filter by complexity (easy to medium) +# - Filter by description quality (substantial content) +# - Prioritize bugs over features +# - Avoid blocked issues + +set -euo pipefail +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# shellcheck source=./lib.sh +source "$here/lib.sh" + +require gh jq + +repo=$(resolve_deep_work_repo) + +echo "[deep-work] πŸ” Smart issue picking from $repo..." + +# Get all open issues +echo "[deep-work] fetching open issues..." +issues_json=$(gh issue list -R "$repo" --state open --limit 100 \ + --json number,title,body,labels,assignees,state,url) + +if [ "$(echo "$issues_json" | jq 'length')" -eq 0 ]; then + echo "[deep-work] no open issues found in $repo" + exit 1 +fi + +echo "[deep-work] found $(echo "$issues_json" | jq 'length') open issues" + +# Filter and score issues +echo "[deep-work] analyzing issues..." + +candidates=$(echo "$issues_json" | jq -r ' + map(select( + # Must be open + .state == "OPEN" and + + # Must not be assigned (or ask user about assigned ones) + (.assignees | length == 0) and + + # Must have substantial description (>500 chars) + (.body // "" | length > 500) + )) | + + # Add scoring + map( + . + { + "score": ( + # Prefer bugs (+3) + (if (.labels | map(.name) | contains(["bug"])) then 3 else 0 end) + + + # Prefer enhancement (+2) + (if (.labels | map(.name) | contains(["enhancement"])) then 2 else 0 end) + + + # Prefer good-first-issue (+1) + (if (.labels | map(.name) | contains(["good first issue"])) then 1 else 0 end) + + + # Penalize large scope labels (-2) + (if (.labels | map(.name) | contains(["epic", "large", "major"])) then -2 else 0 end) + + + # Description quality bonus (length / 1000) + ((.body // "" | length) / 1000 | floor) + ), + "complexity": ( + if (.labels | map(.name) | contains(["epic", "large", "major"])) then "hard" + elif (.labels | map(.name) | contains(["good first issue", "easy"])) then "easy" + else "medium" + end + ) + } + ) | + + # Filter out hard complexity + map(select(.complexity != "hard")) | + + # Sort by score (descending) + sort_by(-.score) | + + # Take top 10 + .[0:10] +') + +num_candidates=$(echo "$candidates" | jq 'length') + +if [ "$num_candidates" -eq 0 ]; then + echo "[deep-work] no suitable issues found after filtering" + echo "" + echo "Criteria used:" + echo "- Open and unassigned" + echo "- Substantial description (>500 chars)" + echo "- Not labeled as epic/large/major complexity" + echo "" + echo "Try browsing issues manually: gh issue list -R $repo" + exit 1 +fi + +echo "[deep-work] found $num_candidates suitable candidates" +echo "" + +# Display top candidates +echo "πŸ† Top issue candidates:" +echo "" + +echo "$candidates" | jq -r '.[] | + "\(.number). \(.title) + 🏷️ Labels: \((.labels | map(.name) | join(", ")) // "none") + πŸ“Š Score: \(.score) | Complexity: \(.complexity) + πŸ“ Description: \(.body // "no description" | .[0:100])... + πŸ”— \(.url) + " +' + +echo "" +echo "Select an issue to work on:" +echo "1-$num_candidates) Pick by number from list above" +echo "0) Browse all issues manually" +echo "q) Quit" +echo "" + +while true; do + read -p "Your choice [1-$num_candidates/0/q]: " -r choice + + case "$choice" in + q|Q) + echo "Cancelled." + exit 0 + ;; + 0) + echo "[deep-work] opening issue browser..." + gh issue list -R "$repo" --web + exit 0 + ;; + ''|*[!0-9]*) + echo "Please enter a number between 1-$num_candidates, 0, or q" + continue + ;; + *) + if [ "$choice" -ge 1 ] && [ "$choice" -le "$num_candidates" ]; then + selected_idx=$((choice - 1)) + selected_issue=$(echo "$candidates" | jq -r ".[$selected_idx].number") + selected_title=$(echo "$candidates" | jq -r ".[$selected_idx].title") + + echo "" + echo "[deep-work] 🎯 Selected issue #$selected_issue: $selected_title" + echo "" + echo "Starting full workflow..." + exec "$here/start.sh" "$selected_issue" + else + echo "Please enter a number between 1-$num_candidates, 0, or q" + continue + fi + ;; + esac +done \ No newline at end of file diff --git a/scripts/deep-work/start.sh b/scripts/deep-work/start.sh new file mode 100755 index 000000000..5ee317555 --- /dev/null +++ b/scripts/deep-work/start.sh @@ -0,0 +1,379 @@ +#!/usr/bin/env bash +# start.sh +# +# Full workflow automation for a GitHub issue: +# 0. Worktree setup (create worktree β†’ new branch) +# 1. Issue fetching and validation +# 1.5. Context gathering (CLAUDE.md, memory.md, recent commits) +# 2. Planning with architectobot agent +# 3. Implementation with codecrusher agent +# 4. Cross-checking (typecheck, lint, format, build) +# 5. Quality checks and test runs +# 6. Memory updates with memory-keeper agent +# 7. Commit with proper message +# 8. Merge main and resolve conflicts +# 9. Push and create draft PR +# 10. Review cycle with pr-reviewer agent +# 11. Mark ready for review (with user confirmation) +# 12. Cleanup (with user confirmation) + +set -euo pipefail +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd "$here/../.." && pwd)" + +# shellcheck source=./lib.sh +source "$here/lib.sh" + +require git gh jq + +if [ -z "${1:-}" ]; then + echo "Usage: pnpm deep-work start " >&2 + exit 1 +fi + +case "$1" in + ''|*[!0-9]*) + echo "[deep-work] issue-number must be numeric, got: $1" >&2 + exit 1 + ;; +esac + +issue="$1" +shift + +repo=$(resolve_deep_work_repo) + +echo "[deep-work] πŸš€ Starting full workflow for issue #$issue from $repo" + +# Step 0: Ensure we're on main and synced +echo "" +echo "[deep-work] === Step 0: Upstream sync & worktree setup ===" +sync_upstream + +# Step 1: Fetch issue details +echo "" +echo "[deep-work] === Step 1: Fetching issue details ===" +echo "[deep-work] fetching issue #$issue from $repo..." +issue_json=$(gh issue view "$issue" -R "$repo" \ + --json number,title,body,labels,state,url,assignees) + +state=$(jq -r '.state' <<<"$issue_json") +if [ "$state" != "OPEN" ]; then + echo "[deep-work] ⚠️ issue #$issue is $state β€” continuing anyway" >&2 +fi + +title=$(jq -r '.title' <<<"$issue_json") +body=$(jq -r '.body // ""' <<<"$issue_json") +url=$(jq -r '.url' <<<"$issue_json") +labels=$(jq -r '[.labels[].name] | join(", ")' <<<"$issue_json") + +echo "[deep-work] πŸ“‹ Issue: $title" +echo "[deep-work] 🏷️ Labels: ${labels:-(none)}" +echo "[deep-work] πŸ”— URL: $url" + +# Create worktree +echo "" +echo "[deep-work] creating worktree..." +if ! create_worktree "$issue" "$title"; then + worktree_dir=$(worktree_dir_for_issue "$issue") + echo "[deep-work] using existing worktree at $worktree_dir" +fi + +# Change to worktree +worktree_dir=$(worktree_dir_for_issue "$issue") +cd "$worktree_dir" +echo "[deep-work] πŸ“ working in: $(pwd)" +echo "[deep-work] 🌿 branch: $(git branch --show-current)" + +# Step 1.5: Context gathering +echo "" +echo "[deep-work] === Step 1.5: Context gathering ===" +echo "[deep-work] reading project documentation..." + +context_info="" +if [ -f "CLAUDE.md" ]; then + echo "[deep-work] βœ… found CLAUDE.md" + context_info+="βœ… CLAUDE.md available\n" +else + echo "[deep-work] ⚠️ CLAUDE.md not found" + context_info+="⚠️ CLAUDE.md not found\n" +fi + +if [ -f ".claude/memory.md" ]; then + echo "[deep-work] βœ… found .claude/memory.md" + context_info+="βœ… .claude/memory.md available\n" +else + echo "[deep-work] ⚠️ .claude/memory.md not found" + context_info+="⚠️ .claude/memory.md not found\n" +fi + +echo "[deep-work] checking recent commits..." +recent_commits=$(git log --oneline -10 main) +context_info+="πŸ“ Recent commits:\n$recent_commits\n" + +# Step 2: Planning phase +echo "" +echo "[deep-work] === Step 2: Planning with architectobot ===" + +planning_prompt="You are starting work on GitHub issue #${issue} from ${repo}. + +Working branch: $(git branch --show-current) +Issue URL: ${url} +Issue title: ${title} +Labels: ${labels:-(none)} + +Context information: +${context_info} + +--- Issue body --- +${body} +--- end issue body --- + +Please analyze this issue and create a detailed implementation plan. Follow the workflow in CLAUDE.md and consider the existing codebase architecture. Break down the work into clear, manageable steps. + +Focus on: +1. Understanding the requirements +2. Identifying affected components +3. Planning the implementation approach +4. Considering testing strategy +5. Potential edge cases or challenges + +Output a structured plan that can guide the implementation phase." + +echo "[deep-work] 🧠 calling architectobot for planning..." + +# Call architectobot agent directly via claude with task flag +echo "$planning_prompt" | claude --task architectobot + +echo "" +echo "[deep-work] πŸ“‹ Planning complete. Press Enter to continue to implementation..." +read -r + +# Step 3: Implementation phase +echo "" +echo "[deep-work] === Step 3: Implementation with codecrusher ===" + +implementation_prompt="Continue working on GitHub issue #${issue} from the planning phase. + +Working branch: $(git branch --show-current) +Issue URL: ${url} +Issue title: ${title} + +The architectobot has analyzed the requirements and created a plan. Now implement the solution: + +--- Issue body --- +${body} +--- end issue body --- + +Follow the implementation plan and: +1. Write clean, idiomatic code following existing patterns +2. Add appropriate tests for new functionality +3. Update documentation if needed +4. Ensure all changes are minimal and focused +5. Follow the project's coding conventions in CLAUDE.md + +Keep the diff minimal and focused on the specific issue requirements." + +echo "[deep-work] πŸ’» calling codecrusher for implementation..." + +# Call codecrusher agent directly via claude with task flag +echo "$implementation_prompt" | claude --task codecrusher + +echo "" +echo "[deep-work] βœ… Implementation complete. Press Enter to continue to quality checks..." +read -r + +# Step 4 & 5: Quality checks +echo "" +echo "[deep-work] === Steps 4-5: Quality checks ===" +if ! run_quality_checks "implementation"; then + echo "[deep-work] ❌ quality checks failed. Please fix issues and re-run." + echo "[deep-work] to resume: pnpm deep-work continue $issue" + exit 1 +fi + +# Step 6: Memory updates +echo "" +echo "[deep-work] === Step 6: Memory updates ===" +echo "[deep-work] 🧠 calling memory-keeper to update project knowledge..." + +memory_prompt="I've just completed implementation work on GitHub issue #${issue}: \"${title}\". + +Please update the project memory (.claude/memory.md) with any important learnings, patterns, gotchas, or insights from this work that would help future development on this project. + +Focus on: +- New patterns or conventions established +- Technical challenges overcome +- Important gotchas or edge cases discovered +- Useful debugging techniques +- Architecture insights +- Testing approaches that worked well + +Only add genuinely useful information that would help someone working on similar issues in the future." + +# Call memory-keeper agent directly via claude with task flag +echo "$memory_prompt" | claude --task memory-keeper + +# Step 7: Commit +echo "" +echo "[deep-work] === Step 7: Commit ===" + +echo "[deep-work] staging changes for commit..." +git add . + +# Generate commit message +commit_message="fix(#${issue}): ${title} + +${body} + +πŸ€– Generated with [Claude Code](https://claude.ai/code) + +Co-Authored-By: Claude " + +echo "[deep-work] committing changes..." +git commit -m "$(cat <&1 | grep 'Logged in.*as' | sed 's/.*as //' | cut -d' ' -f1):$branch" \ + --base main \ + --repo "$repo") + +echo "[deep-work] πŸ“ draft PR created: $pr_url" + +# Step 10: Review cycle +echo "" +echo "[deep-work] === Step 10: Review cycle ===" +echo "[deep-work] πŸ” calling pr-reviewer for automated review..." + +review_prompt="Please perform a thorough CodeRabbit-style review of this PR: $pr_url + +This PR addresses issue #${issue}: \"${title}\" + +Provide: +1. Walkthrough of changes +2. Change summary table +3. Per-file analysis +4. Inline comments with concrete suggestions +5. Identify any potential issues or improvements + +After review, apply any approved suggestions, run quality checks, commit and push updates." + +# Call pr-reviewer agent directly via claude with task flag +echo "$review_prompt" | claude --task pr-reviewer + +# Step 11: Mark ready for review (user confirmation) +echo "" +echo "[deep-work] === Step 11: Ready for review ===" +echo "[deep-work] πŸŽ‰ Workflow complete! The PR has been created and auto-reviewed." +echo "" +echo "PR URL: $pr_url" +echo "Branch: $branch" +echo "Worktree: $worktree_dir" +echo "" +echo "Would you like to mark the PR as ready for review? (y/N)" +read -p "> " -r +if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "[deep-work] marking PR as ready for review..." + gh pr ready "$pr_url" + echo "[deep-work] βœ… PR marked as ready for review" +else + echo "[deep-work] PR remains in draft. You can mark it ready later with:" + echo " gh pr ready $pr_url" +fi + +# Step 12: Cleanup (user confirmation) +echo "" +echo "[deep-work] === Step 12: Cleanup ===" +echo "" +echo "The worktree for issue #$issue is at $worktree_dir (branch: $branch)." +echo "" +echo "Would you like me to clean up the worktree? This will:" +echo "- Remove the worktree directory" +echo "- Delete the local branch" +echo "- Keep the remote branch and PR intact" +echo "" +echo "Clean up worktree? (y/N)" +read -p "> " -r +if [[ $REPLY =~ ^[Yy]$ ]]; then + cd "$repo_root" + cleanup_worktree "$issue" "true" + echo "[deep-work] βœ… worktree cleaned up" +else + echo "[deep-work] worktree preserved. You can clean it up later with:" + echo " pnpm deep-work cleanup $issue" +fi + +echo "" +echo "[deep-work] 🎊 Deep work session complete!" +echo "PR: $pr_url" +echo "" +echo "Next steps:" +echo "- Monitor PR for reviewer feedback" +echo "- Address any review comments" +echo "- Merge when approved" \ No newline at end of file diff --git a/scripts/deep-work/status.sh b/scripts/deep-work/status.sh new file mode 100755 index 000000000..bca259ebc --- /dev/null +++ b/scripts/deep-work/status.sh @@ -0,0 +1,156 @@ +#!/usr/bin/env bash +# status.sh +# +# Show status of all deep-work worktrees and progress + +set -euo pipefail +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# shellcheck source=./lib.sh +source "$here/lib.sh" + +require git gh jq + +repo=$(resolve_deep_work_repo) + +echo "[deep-work] πŸ“Š Deep Work Status Report" +echo "Repository: $repo" +echo "" + +# Get current location info +current_info=$(get_current_worktree_info) +if [ "$current_info" != "not_in_worktree" ]; then + current_issue=$(echo "$current_info" | grep -o 'issue=[0-9]*' | cut -d= -f2) + current_branch=$(echo "$current_info" | grep -o 'branch=[^[:space:]]*' | cut -d= -f2) + current_dir=$(echo "$current_info" | grep -o 'dir=[^[:space:]]*' | cut -d= -f2) + echo "πŸ“ Current location: Issue #$current_issue (branch: $current_branch)" + echo " Directory: $current_dir" + echo "" +fi + +# List all deep-work worktrees +worktrees_data="" +while IFS='=' read -r key value; do + if [ "$key" = "issue" ]; then + current_issue_num="$value" + elif [ "$key" = "path" ]; then + current_path="$value" + worktrees_data+="$current_issue_num|$current_path\n" + fi +done < <(list_deep_work_worktrees) + +if [ -z "$worktrees_data" ]; then + echo "🏜️ No active deep-work sessions found." + echo "" + echo "To start working on an issue:" + echo " pnpm deep-work start " + echo " pnpm deep-work pick" + exit 0 +fi + +echo "πŸš€ Active deep-work sessions:" +echo "" + +printf "%-6s %-20s %-15s %-30s %s\n" "ISSUE" "STATUS" "BRANCH" "TITLE" "PROGRESS" +echo "────────────────────────────────────────────────────────────────────────────────────" + +echo -e "$worktrees_data" | while IFS='|' read -r issue_num path; do + if [ -z "$issue_num" ] || [ -z "$path" ]; then + continue + fi + + if [ ! -d "$path" ]; then + printf "%-6s %-20s %-15s %-30s %s\n" "#$issue_num" "❌ MISSING" "?" "?" "Worktree directory not found" + continue + fi + + # Get branch and git status from worktree + branch="" + git_clean="" + has_commits="" + pr_exists="" + + if [ -d "$path/.git" ]; then + branch=$(cd "$path" && git branch --show-current 2>/dev/null || echo "unknown") + + # Check if working tree is clean + if cd "$path" && git diff --quiet && git diff --cached --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then + git_clean="clean" + else + git_clean="dirty" + fi + + # Check for commits ahead of origin + if cd "$path" && git log origin/"$branch"..HEAD --oneline 2>/dev/null | grep -q .; then + has_commits="unpushed" + elif cd "$path" && git log --oneline -1 >/dev/null 2>&1; then + has_commits="pushed" + else + has_commits="no_commits" + fi + + # Check if PR exists + if gh pr view "$branch" -R "$repo" >/dev/null 2>&1; then + pr_status=$(gh pr view "$branch" -R "$repo" --json state,isDraft | jq -r 'if .isDraft then "draft" else .state end') + pr_exists="$pr_status" + else + pr_exists="no_pr" + fi + fi + + # Get issue title + issue_title="" + if issue_json=$(gh issue view "$issue_num" -R "$repo" --json title 2>/dev/null); then + issue_title=$(echo "$issue_json" | jq -r '.title' | cut -c1-30) + else + issue_title="(issue not found)" + fi + + # Determine overall status + status="" + progress="" + + if [ "$git_clean" = "dirty" ]; then + status="πŸ› οΈ WORKING" + progress="Implementation in progress" + elif [ "$has_commits" = "no_commits" ]; then + status="πŸ†• FRESH" + progress="Just started" + elif [ "$has_commits" = "unpushed" ]; then + status="πŸ“ COMMITTED" + progress="Ready to push" + elif [ "$pr_exists" = "no_pr" ]; then + status="πŸ“€ PUSHED" + progress="Ready for PR" + elif [ "$pr_exists" = "draft" ]; then + status="πŸ“‹ DRAFT_PR" + progress="PR created, needs review" + elif [ "$pr_exists" = "OPEN" ]; then + status="πŸ” IN_REVIEW" + progress="Under review" + elif [ "$pr_exists" = "MERGED" ]; then + status="βœ… MERGED" + progress="Complete, ready for cleanup" + else + status="❓ UNKNOWN" + progress="Unknown state" + fi + + printf "%-6s %-20s %-15s %-30s %s\n" "#$issue_num" "$status" "$branch" "$issue_title" "$progress" +done + +echo "" +echo "πŸ“‹ Legend:" +echo " πŸ†• FRESH - Just started, no commits yet" +echo " πŸ› οΈ WORKING - Implementation in progress (uncommitted changes)" +echo " πŸ“ COMMITTED - Work committed locally, ready to push" +echo " πŸ“€ PUSHED - Pushed to remote, ready to create PR" +echo " πŸ“‹ DRAFT_PR - Draft PR created, under development" +echo " πŸ” IN_REVIEW - PR open and ready for review" +echo " βœ… MERGED - PR merged, ready for cleanup" +echo "" +echo "πŸ› οΈ Commands:" +echo " pnpm deep-work continue - Resume work on specific issue" +echo " pnpm deep-work cleanup - Clean up completed worktree" +echo " pnpm deep-work start - Start new issue" +echo " pnpm deep-work pick - Pick and start new issue" \ No newline at end of file