mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
179 lines
5.3 KiB
Bash
Executable File
179 lines
5.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# start.sh <issue-number> [extra-prompt] [--agent <tool>] [--no-checkout]
|
|
#
|
|
# Pick up a GitHub issue:
|
|
# 1. Sync `main` from upstream.
|
|
# 2. Create a working branch `<prefix>/<num>-<slug>` (slug from issue title).
|
|
# 3. Pull the issue (title/body/labels) via gh.
|
|
# 4. Hand off to the agent CLI with a prompt that includes the issue plus
|
|
# repo conventions (CLAUDE.md / AGENTS.md pointers).
|
|
#
|
|
# --agent picks the CLI that drives the work. Default: claude.
|
|
# `--agent codex` uses `codex exec --dangerously-bypass-approvals-and-sandbox`
|
|
# and `--agent cursor` / `cursor-agent` use `cursor-agent --yolo`, so those
|
|
# sessions start in their equivalent "yolo" mode.
|
|
# A trailing positional <extra-prompt> is appended to the agent prompt.
|
|
# --no-checkout skips git sync/branch creation (use the current branch as-is).
|
|
|
|
set -euo pipefail
|
|
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# shellcheck source=../review/lib.sh
|
|
source "$here/../review/lib.sh"
|
|
|
|
require git gh jq
|
|
|
|
if [ -z "${1:-}" ]; then
|
|
echo "Usage: pnpm work <issue-number> [extra-prompt] [--agent <tool>] [--no-checkout]" >&2
|
|
exit 1
|
|
fi
|
|
case "$1" in
|
|
''|*[!0-9]*)
|
|
echo "[work] issue-number must be numeric, got: $1" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
issue="$1"
|
|
shift
|
|
agent="claude"
|
|
extra_prompt=""
|
|
do_checkout=1
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--agent) agent="${2:?--agent requires a value}"; shift 2 ;;
|
|
--agent=*) agent="${1#*=}"; shift ;;
|
|
--no-checkout) do_checkout=0; shift ;;
|
|
*)
|
|
if [ -n "$extra_prompt" ]; then
|
|
echo "[work] unexpected extra arg: $1 (extra-prompt already set)" >&2
|
|
exit 1
|
|
fi
|
|
extra_prompt="$1"; shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
require "$agent"
|
|
|
|
# resolve_repo() lives in scripts/shortcuts/review/lib.sh; honour WORK_REPO override too.
|
|
repo="${WORK_REPO:-${REVIEW_REPO:-}}"
|
|
if [ -z "$repo" ]; then
|
|
repo=$(REVIEW_REPO= resolve_repo)
|
|
fi
|
|
branch_prefix="${WORK_BRANCH_PREFIX:-issue}"
|
|
auto_assign="${WORK_AUTO_ASSIGN:-1}"
|
|
|
|
echo "[work] fetching issue #$issue from $repo..."
|
|
issue_json=$(gh issue view "$issue" -R "$repo" \
|
|
--json number,title,body,labels,state,url,assignees)
|
|
|
|
if [ "$auto_assign" = "1" ]; then
|
|
gh_assign_self_issue "$issue" "$repo"
|
|
fi
|
|
|
|
state=$(jq -r '.state' <<<"$issue_json")
|
|
if [ "$state" != "OPEN" ]; then
|
|
echo "[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")
|
|
|
|
# Slug: lowercase, alnum + hyphens, max 40 chars, trimmed.
|
|
slug=$(printf '%s' "$title" \
|
|
| tr '[:upper:]' '[:lower:]' \
|
|
| sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//' \
|
|
| cut -c1-40 \
|
|
| sed -E 's/-+$//')
|
|
if [ -z "$slug" ]; then
|
|
slug="work"
|
|
fi
|
|
branch="${branch_prefix}/${issue}-${slug}"
|
|
|
|
if [ "$do_checkout" = "1" ]; then
|
|
echo "[work] syncing main..."
|
|
git checkout main
|
|
if git remote get-url upstream >/dev/null 2>&1; then
|
|
git fetch upstream
|
|
git merge --ff-only upstream/main || git merge upstream/main
|
|
fi
|
|
if git remote get-url origin >/dev/null 2>&1; then
|
|
git pull --ff-only origin main
|
|
fi
|
|
git submodule update --init --recursive
|
|
|
|
if git show-ref --verify --quiet "refs/heads/$branch"; then
|
|
echo "[work] branch $branch already exists — checking it out and merging main"
|
|
git checkout "$branch"
|
|
if ! git merge main; then
|
|
echo "[work] merge from main failed on branch $branch; resolve conflicts and re-run." >&2
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "[work] creating branch $branch off main"
|
|
git checkout -b "$branch"
|
|
fi
|
|
else
|
|
echo "[work] --no-checkout: staying on $(git branch --show-current)"
|
|
fi
|
|
|
|
current_branch=$(git branch --show-current)
|
|
labels_display="${labels:-(none)}"
|
|
|
|
template="$here/prompts/start.md"
|
|
if [ ! -f "$template" ]; then
|
|
echo "[work] missing prompt template: $template" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Use awk for substitution — handles multi-line values (issue body) cleanly.
|
|
# Pass values via the environment (ENVIRON[]) because BSD awk on macOS rejects
|
|
# literal newlines in `-v var=value`, and the issue body routinely has them.
|
|
# Escape backslashes and ampersands so gsub doesn't interpret them in the
|
|
# replacement text.
|
|
prompt=$(WORK_ISSUE="$issue" WORK_REPO_NAME="$repo" WORK_BRANCH="$current_branch" \
|
|
WORK_URL="$url" WORK_TITLE="$title" WORK_LABELS="$labels_display" \
|
|
WORK_BODY="$body" \
|
|
awk '
|
|
function esc(s) {
|
|
gsub(/\\/, "\\\\", s); gsub(/&/, "\\\\&", s); return s
|
|
}
|
|
BEGIN {
|
|
issue=esc(ENVIRON["WORK_ISSUE"]);
|
|
repo=esc(ENVIRON["WORK_REPO_NAME"]);
|
|
branch=esc(ENVIRON["WORK_BRANCH"]);
|
|
url=esc(ENVIRON["WORK_URL"]);
|
|
title=esc(ENVIRON["WORK_TITLE"]);
|
|
labels=esc(ENVIRON["WORK_LABELS"]);
|
|
body=esc(ENVIRON["WORK_BODY"]);
|
|
}
|
|
{
|
|
gsub(/__ISSUE__/, issue);
|
|
gsub(/__REPO__/, repo);
|
|
gsub(/__BRANCH__/, branch);
|
|
gsub(/__URL__/, url);
|
|
gsub(/__TITLE__/, title);
|
|
gsub(/__LABELS__/, labels);
|
|
gsub(/__BODY__/, body);
|
|
print
|
|
}
|
|
' "$template")
|
|
|
|
if [ -n "$extra_prompt" ]; then
|
|
prompt="${prompt}
|
|
|
|
# Additional instructions from the user
|
|
${extra_prompt}"
|
|
fi
|
|
|
|
echo "[work] handing off to ${agent} on branch ${current_branch}"
|
|
if [ "$agent" = "codex" ]; then
|
|
codex exec --dangerously-bypass-approvals-and-sandbox "$prompt"
|
|
elif [ "$agent" = "cursor" ] || [ "$agent" = "cursor-agent" ]; then
|
|
cursor-agent --yolo "$prompt"
|
|
else
|
|
"$agent" "$prompt"
|
|
fi
|