|
#!/usr/bin/env bash |
|
set -euo pipefail |
|
|
|
issue_identifier="" |
|
space="" |
|
window="" |
|
focus="--focus" |
|
|
|
while [[ $# -gt 0 ]]; do |
|
case "$1" in |
|
--space) |
|
[[ $# -ge 2 ]] || { |
|
echo "missing value for --space" >&2 |
|
exit 1 |
|
} |
|
space="$2" |
|
shift 2 |
|
;; |
|
--window) |
|
[[ $# -ge 2 ]] || { |
|
echo "missing value for --window" >&2 |
|
exit 1 |
|
} |
|
window="$2" |
|
shift 2 |
|
;; |
|
--focus) |
|
focus="--focus" |
|
shift |
|
;; |
|
--no-focus) |
|
focus="--no-focus" |
|
shift |
|
;; |
|
-h|--help) |
|
echo "usage: $(basename "$0") <ISSUE-ID> [--space N] [--window N] [--focus|--no-focus]" |
|
exit 0 |
|
;; |
|
-*) |
|
echo "unknown option: $1" >&2 |
|
exit 1 |
|
;; |
|
*) |
|
[[ -z "$issue_identifier" ]] || { |
|
echo "unexpected argument: $1" >&2 |
|
exit 1 |
|
} |
|
issue_identifier="$1" |
|
shift |
|
;; |
|
esac |
|
done |
|
|
|
[[ -n "$issue_identifier" ]] || { |
|
echo "issue identifier is required" >&2 |
|
exit 1 |
|
} |
|
|
|
[[ -z "$window" || -n "$space" ]] || { |
|
echo "--window requires --space" >&2 |
|
exit 1 |
|
} |
|
|
|
for tool in git jq linear sp codex; do |
|
command -v "$tool" >/dev/null || { |
|
echo "missing required tool: $tool" >&2 |
|
exit 1 |
|
} |
|
done |
|
|
|
sp ping >/dev/null |
|
|
|
if [[ -z "${SUPATERM_SURFACE_ID:-}" && -z "${SUPATERM_TAB_ID:-}" && -z "$space" ]]; then |
|
echo "outside Supaterm, pass --space <n>" >&2 |
|
exit 1 |
|
fi |
|
|
|
repo_root=$(git rev-parse --show-toplevel) |
|
issue_json=$(linear issue view "$issue_identifier" --json) |
|
issue_identifier=$(jq -r '.identifier' <<<"$issue_json") |
|
issue_title=$(jq -r '.title' <<<"$issue_json") |
|
branch_name=$(jq -r '.branchName // empty' <<<"$issue_json") |
|
|
|
if [[ -z "$branch_name" || "$branch_name" == "null" ]]; then |
|
issue_slug=$(printf '%s' "$issue_title" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//') |
|
branch_name="${issue_identifier,,}" |
|
[[ -z "$issue_slug" ]] || branch_name="$branch_name-$issue_slug" |
|
fi |
|
|
|
existing_worktree=$( |
|
git worktree list --porcelain | awk -v branch="refs/heads/$branch_name" ' |
|
$1 == "worktree" { worktree = $2 } |
|
$1 == "branch" && $2 == branch { print worktree; exit } |
|
' |
|
) |
|
|
|
created_worktree=false |
|
|
|
if [[ -n "$existing_worktree" ]]; then |
|
worktree_path="$existing_worktree" |
|
else |
|
worktree_root="$repo_root/.worktrees" |
|
worktree_name=$(printf '%s' "$branch_name" | tr '/' '-') |
|
worktree_path="$worktree_root/$worktree_name" |
|
|
|
mkdir -p "$worktree_root" |
|
|
|
[[ ! -e "$worktree_path" ]] || { |
|
echo "worktree path already exists but is not registered: $worktree_path" >&2 |
|
exit 1 |
|
} |
|
|
|
if git show-ref --verify --quiet "refs/heads/$branch_name"; then |
|
git worktree add "$worktree_path" "$branch_name" |
|
else |
|
if git show-ref --verify --quiet refs/remotes/origin/HEAD; then |
|
base_ref=$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD) |
|
elif git show-ref --verify --quiet refs/heads/main; then |
|
base_ref="main" |
|
else |
|
base_ref="HEAD" |
|
fi |
|
|
|
git worktree add -b "$branch_name" "$worktree_path" "$base_ref" |
|
fi |
|
|
|
created_worktree=true |
|
fi |
|
|
|
prompt=$(cat <<EOF |
|
You are working on a Linear ticket $issue_identifier under a dedicated worktree |
|
|
|
- Fetch the ticket information and evaluate it and cross check with the code |
|
- Come up with a plan to address it |
|
EOF |
|
) |
|
|
|
escaped_prompt=${prompt//\\/\\\\} |
|
escaped_prompt=${escaped_prompt//\"/\\\"} |
|
escaped_prompt=${escaped_prompt//\$/\\$} |
|
printf -v tab_script 'codex "%s"\n' "$escaped_prompt" |
|
|
|
sp_args=(new-tab --json "$focus" --cwd "$worktree_path") |
|
|
|
if [[ -n "$space" ]]; then |
|
sp_args+=(--space "$space") |
|
fi |
|
|
|
if [[ -n "$window" ]]; then |
|
sp_args+=(--window "$window") |
|
fi |
|
|
|
sp_args+=(--script "$tab_script") |
|
tab_json=$(sp "${sp_args[@]}") |
|
|
|
jq -n \ |
|
--arg issue "$issue_identifier" \ |
|
--arg title "$issue_title" \ |
|
--arg branch "$branch_name" \ |
|
--arg worktree "$worktree_path" \ |
|
--argjson createdWorktree "$created_worktree" \ |
|
--argjson tab "$tab_json" \ |
|
'{ |
|
issue: $issue, |
|
title: $title, |
|
branch: $branch, |
|
worktree: $worktree, |
|
createdWorktree: $createdWorktree, |
|
tab: $tab |
|
}' |