hub-launch 1.16.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +102 -2
  3. package/dist/commands/init.d.ts +61 -0
  4. package/dist/commands/init.d.ts.map +1 -1
  5. package/dist/commands/init.js +125 -114
  6. package/dist/commands/init.js.map +1 -1
  7. package/dist/commands/instructions.d.ts +17 -0
  8. package/dist/commands/instructions.d.ts.map +1 -0
  9. package/dist/commands/instructions.js +51 -0
  10. package/dist/commands/instructions.js.map +1 -0
  11. package/dist/commands/launch.d.ts +21 -0
  12. package/dist/commands/launch.d.ts.map +1 -1
  13. package/dist/commands/launch.js +61 -0
  14. package/dist/commands/launch.js.map +1 -1
  15. package/dist/commands/script.d.ts +28 -0
  16. package/dist/commands/script.d.ts.map +1 -0
  17. package/dist/commands/script.js +69 -0
  18. package/dist/commands/script.js.map +1 -0
  19. package/dist/commands/session-hook.d.ts +26 -0
  20. package/dist/commands/session-hook.d.ts.map +1 -0
  21. package/dist/commands/session-hook.js +146 -0
  22. package/dist/commands/session-hook.js.map +1 -0
  23. package/dist/index.js +22 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/scripts/fix-commit.d.ts +2 -0
  26. package/dist/scripts/fix-commit.d.ts.map +1 -0
  27. package/dist/scripts/fix-commit.js +125 -0
  28. package/dist/scripts/fix-commit.js.map +1 -0
  29. package/dist/scripts/fix-setup.d.ts +2 -0
  30. package/dist/scripts/fix-setup.d.ts.map +1 -0
  31. package/dist/scripts/fix-setup.js +161 -0
  32. package/dist/scripts/fix-setup.js.map +1 -0
  33. package/dist/scripts/launch-run.d.ts +14 -0
  34. package/dist/scripts/launch-run.d.ts.map +1 -0
  35. package/dist/scripts/launch-run.js +117 -0
  36. package/dist/scripts/launch-run.js.map +1 -0
  37. package/dist/scripts/lib/exec.d.ts +48 -0
  38. package/dist/scripts/lib/exec.d.ts.map +1 -0
  39. package/dist/scripts/lib/exec.js +61 -0
  40. package/dist/scripts/lib/exec.js.map +1 -0
  41. package/dist/scripts/lib/io.d.ts +48 -0
  42. package/dist/scripts/lib/io.d.ts.map +1 -0
  43. package/dist/scripts/lib/io.js +72 -0
  44. package/dist/scripts/lib/io.js.map +1 -0
  45. package/dist/scripts/lib/read-config.d.ts +26 -0
  46. package/dist/scripts/lib/read-config.d.ts.map +1 -0
  47. package/dist/scripts/lib/read-config.js +65 -0
  48. package/dist/scripts/lib/read-config.js.map +1 -0
  49. package/dist/scripts/merge-local.d.ts +2 -0
  50. package/dist/scripts/merge-local.d.ts.map +1 -0
  51. package/dist/scripts/merge-local.js +146 -0
  52. package/dist/scripts/merge-local.js.map +1 -0
  53. package/dist/scripts/merge-remote.d.ts +12 -0
  54. package/dist/scripts/merge-remote.d.ts.map +1 -0
  55. package/dist/scripts/merge-remote.js +71 -0
  56. package/dist/scripts/merge-remote.js.map +1 -0
  57. package/dist/scripts/schedule-manage.d.ts +2 -0
  58. package/dist/scripts/schedule-manage.d.ts.map +1 -0
  59. package/dist/scripts/schedule-manage.js +151 -0
  60. package/dist/scripts/schedule-manage.js.map +1 -0
  61. package/dist/scripts/schedule-run.d.ts +2 -0
  62. package/dist/scripts/schedule-run.d.ts.map +1 -0
  63. package/dist/scripts/schedule-run.js +146 -0
  64. package/dist/scripts/schedule-run.js.map +1 -0
  65. package/dist/scripts/verify-gather.d.ts +2 -0
  66. package/dist/scripts/verify-gather.d.ts.map +1 -0
  67. package/dist/scripts/verify-gather.js +206 -0
  68. package/dist/scripts/verify-gather.js.map +1 -0
  69. package/dist/scripts/verify-post.d.ts +12 -0
  70. package/dist/scripts/verify-post.d.ts.map +1 -0
  71. package/dist/scripts/verify-post.js +91 -0
  72. package/dist/scripts/verify-post.js.map +1 -0
  73. package/dist/templates/planning-instructions.md +7 -7
  74. package/dist/templates/skill-creation-instructions.md +2 -2
  75. package/dist/templates/skills/hula-confirm/SKILL.md +3 -3
  76. package/dist/templates/skills/hula-fix/SKILL.md +3 -3
  77. package/dist/templates/skills/hula-help/SKILL.md +117 -0
  78. package/dist/templates/skills/hula-launch/SKILL.md +14 -8
  79. package/dist/templates/skills/hula-merge/SKILL.md +2 -2
  80. package/dist/templates/skills/hula-plan/SKILL.md +5 -5
  81. package/dist/templates/skills/hula-schedule/SKILL.md +16 -16
  82. package/dist/templates/skills/hula-verify/SKILL.md +32 -33
  83. package/dist/types/config.schema.d.ts +235 -0
  84. package/dist/types/config.schema.d.ts.map +1 -1
  85. package/dist/types/config.schema.js +66 -0
  86. package/dist/types/config.schema.js.map +1 -1
  87. package/dist/utils/client-session.d.ts +2 -2
  88. package/dist/utils/client-session.js +2 -2
  89. package/package.json +6 -5
  90. package/scripts/postinstall.mjs +102 -0
  91. package/dist/templates/scripts/hula-fix-commit.sh +0 -114
  92. package/dist/templates/scripts/hula-fix-setup.sh +0 -152
  93. package/dist/templates/scripts/hula-launch-run.sh +0 -140
  94. package/dist/templates/scripts/hula-merge-local.sh +0 -138
  95. package/dist/templates/scripts/hula-merge-remote.sh +0 -94
  96. package/dist/templates/scripts/hula-read-config.sh +0 -38
  97. package/dist/templates/scripts/hula-schedule-manage.sh +0 -159
  98. package/dist/templates/scripts/hula-schedule-run.sh +0 -148
  99. package/dist/templates/scripts/hula-session-hook.sh +0 -68
  100. package/dist/templates/scripts/hula-verify-gather.sh +0 -135
  101. package/dist/templates/scripts/hula-verify-post.sh +0 -84
@@ -1,114 +0,0 @@
1
- #!/usr/bin/env bash
2
- # hula-fix-commit.sh — Step 9 of the hula.fix workflow
3
- # Usage: bash .github/scripts/hula-fix-commit.sh <issue-number> <commit-message> [--worktree-path <path>]
4
- #
5
- # Performs: git add -A → git commit → git push from within the fix worktree
6
- # Outputs structured JSON to stdout; all other output goes to stderr
7
- # Exit codes: 0=success, 1=user error, 2=tool error
8
-
9
- set -euo pipefail
10
-
11
- # ── helpers ────────────────────────────────────────────────────────────────────
12
-
13
- json_escape() {
14
- printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/$/\\n/g' | tr -d '\n' | sed 's/\\n$//'
15
- }
16
-
17
- die() {
18
- local code="$1"; shift
19
- local safe_msg
20
- safe_msg=$(json_escape "$*")
21
- printf '{"status":"error","code":%d,"message":"%s"}\n' "$code" "$safe_msg" >&1
22
- exit "$code"
23
- }
24
-
25
- # ── argument parsing ────────────────────────────────────────────────────────────
26
-
27
- ISSUE_NUMBER=''
28
- COMMIT_MSG=''
29
- WORKTREE_PATH=''
30
-
31
- while [[ $# -gt 0 ]]; do
32
- case "$1" in
33
- --worktree-path)
34
- WORKTREE_PATH="$2"; shift 2 ;;
35
- --worktree-path=*)
36
- WORKTREE_PATH="${1#--worktree-path=}"; shift ;;
37
- *)
38
- if [[ -z "$ISSUE_NUMBER" ]]; then
39
- ISSUE_NUMBER="$1"
40
- elif [[ -z "$COMMIT_MSG" ]]; then
41
- COMMIT_MSG="$1"
42
- else
43
- die 1 "Unexpected argument: $1"
44
- fi
45
- shift ;;
46
- esac
47
- done
48
-
49
- if [[ -z "$ISSUE_NUMBER" ]]; then
50
- die 1 "Usage: bash .github/scripts/hula-fix-commit.sh <issue-number> <commit-message> [--worktree-path <path>]"
51
- fi
52
-
53
- if [[ -z "$COMMIT_MSG" ]]; then
54
- die 1 "Commit message is required."
55
- fi
56
-
57
- # ── Resolve worktree path ─────────────────────────────────────────────────────
58
-
59
- if [[ -z "$WORKTREE_PATH" ]]; then
60
- # Try to read from session file (use absolute path so it works from any CWD)
61
- REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || REPO_ROOT="$(pwd)"
62
- SESSION_FILE="${REPO_ROOT}/.hublaunch/.fix-sessions/issue-${ISSUE_NUMBER}.json"
63
- if [[ -f "$SESSION_FILE" ]]; then
64
- # Extract worktreePath from JSON (handles both compact and pretty-printed formats).
65
- # Pattern .*"worktreePath" *: *"\([^"]*\)".* tolerates optional whitespace, matching:
66
- # - Compact: "worktreePath":"..."
67
- # - Pretty: "worktreePath": "..."
68
- WORKTREE_PATH=$(grep '"worktreePath"' "$SESSION_FILE" | sed 's/.*"worktreePath" *: *"\([^"]*\)".*/\1/')
69
- fi
70
- fi
71
-
72
- if [[ -z "$WORKTREE_PATH" ]]; then
73
- # Last resort: infer from repo name using config
74
- REPO_NAME=$(gh repo view --json name -q '.name' 2>/dev/null) || die 2 "Failed to determine repo name."
75
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
76
- source "${SCRIPT_DIR}/hula-read-config.sh"
77
- WORKTREE_PATH="${HULA_WORKTREE_BASE_PATH}/hula/${REPO_NAME}/fix-${ISSUE_NUMBER}"
78
- fi
79
-
80
- if [[ ! -d "$WORKTREE_PATH" ]]; then
81
- die 1 "Worktree not found at ${WORKTREE_PATH}. Run hula-fix-setup.sh first."
82
- fi
83
-
84
- # ── Check for changes ─────────────────────────────────────────────────────────
85
-
86
- cd "$WORKTREE_PATH"
87
-
88
- PORCELAIN=$(git status --porcelain 2>/dev/null)
89
-
90
- if [[ -z "$PORCELAIN" ]]; then
91
- printf '{"status":"success","message":"Nothing to commit","filesChanged":0,"worktreePath":"%s"}\n' \
92
- "$(json_escape "$WORKTREE_PATH")"
93
- exit 0
94
- fi
95
-
96
- FILES_CHANGED=$(printf '%s' "$PORCELAIN" | grep -c '' || true)
97
-
98
- # ── Stage, commit, push ───────────────────────────────────────────────────────
99
-
100
- git add -A >&2 2>&1 || die 2 "git add failed"
101
-
102
- git commit -m "$COMMIT_MSG" >&2 2>&1 \
103
- || die 2 "Failed to commit changes. Ensure git user.name and user.email are configured."
104
-
105
- git push >&2 2>&1 \
106
- || die 2 "Push failed. Try: cd ${WORKTREE_PATH} && git push"
107
-
108
- # ── Output result ─────────────────────────────────────────────────────────────
109
-
110
- SAFE_MSG=$(json_escape "$COMMIT_MSG")
111
- SAFE_PATH=$(json_escape "$WORKTREE_PATH")
112
-
113
- printf '{"status":"success","commitMessage":"%s","filesChanged":%d,"worktreePath":"%s"}\n' \
114
- "$SAFE_MSG" "$FILES_CHANGED" "$SAFE_PATH"
@@ -1,152 +0,0 @@
1
- #!/usr/bin/env bash
2
- # hula-fix-setup.sh — Steps 2-6 of the hula.fix workflow
3
- # Usage: bash .github/scripts/hula-fix-setup.sh <issue-number> [--repo-root <path>]
4
- #
5
- # Performs: issue lookup → worktree creation → session file writing
6
- # Outputs structured JSON to stdout; all other output goes to stderr
7
- # Exit codes: 0=success, 1=user error, 2=tool error
8
-
9
- set -euo pipefail
10
-
11
- # ── helpers ────────────────────────────────────────────────────────────────────
12
-
13
- json_escape() {
14
- # Escape a string for safe embedding inside a JSON double-quoted value
15
- printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/$/\\n/g' | tr -d '\n' | sed 's/\\n$//'
16
- }
17
-
18
- die() {
19
- local code="$1"; shift
20
- local safe_msg
21
- safe_msg=$(json_escape "$*")
22
- printf '{"status":"error","code":%d,"message":"%s"}\n' "$code" "$safe_msg" >&1
23
- exit "$code"
24
- }
25
-
26
- emit_json() {
27
- # emit_json key value [key value ...]
28
- # Outputs compact single-line JSON: {"key":"value",...}
29
- # All values must already be JSON-safe (escaped via json_escape).
30
- local out='{'
31
- local sep=''
32
- while [[ $# -ge 2 ]]; do
33
- local k="$1" v="$2"; shift 2
34
- out="${out}${sep}\"${k}\":\"${v}\""
35
- sep=','
36
- done
37
- printf '%s}\n' "$out"
38
- }
39
-
40
- # ── argument parsing ────────────────────────────────────────────────────────────
41
-
42
- ISSUE_NUMBER=''
43
- REPO_ROOT=''
44
-
45
- while [[ $# -gt 0 ]]; do
46
- case "$1" in
47
- --repo-root)
48
- REPO_ROOT="$2"; shift 2 ;;
49
- --repo-root=*)
50
- REPO_ROOT="${1#--repo-root=}"; shift ;;
51
- [0-9]*)
52
- ISSUE_NUMBER="$1"; shift ;;
53
- *)
54
- die 1 "Unknown argument: $1" ;;
55
- esac
56
- done
57
-
58
- if [[ -z "$ISSUE_NUMBER" ]]; then
59
- die 1 "Usage: bash .github/scripts/hula-fix-setup.sh <issue-number> [--repo-root <path>]"
60
- fi
61
-
62
- if [[ -n "$REPO_ROOT" ]]; then
63
- cd "$REPO_ROOT"
64
- fi
65
-
66
- # ── Step 1: Infer owner/repo and repo name from git remote ─────────────────────
67
-
68
- NAME_WITH_OWNER=$(gh repo view --json nameWithOwner -q '.nameWithOwner' 2>/dev/null) \
69
- || die 2 "Failed to determine repo from git remote. Is this a GitHub repo?"
70
-
71
- REPO_NAME=$(gh repo view --json name -q '.name' 2>/dev/null) \
72
- || die 2 "Failed to determine repo name."
73
-
74
- # ── Step 2: Fetch issue details ────────────────────────────────────────────────
75
-
76
- ISSUE_JSON=$(gh issue view "$ISSUE_NUMBER" --json title,body,labels 2>/dev/null) \
77
- || die 1 "Issue #${ISSUE_NUMBER} not found or not accessible."
78
-
79
- ISSUE_TITLE=$(printf '%s' "$ISSUE_JSON" | grep -o '"title":"[^"]*"' | head -1 | sed 's/"title":"//;s/"$//')
80
-
81
- # ── Step 3: Validate issue is tracked and has a PR (via hula view) ─────────────
82
-
83
- HULA_VIEW=$(hula view 2>&1) || true
84
-
85
- if printf '%s' "$HULA_VIEW" | grep -qi "not tracked\|no issues"; then
86
- die 1 "Issue #${ISSUE_NUMBER} is not tracked. Run: hula track ${ISSUE_NUMBER}"
87
- fi
88
-
89
- # ── Step 4: Find PR branch for the issue ──────────────────────────────────────
90
-
91
- PR_BRANCH=$(gh pr list --search "linked:${ISSUE_NUMBER}" --json headRefName -q '.[0].headRefName' 2>/dev/null) || true
92
-
93
- if [[ -z "$PR_BRANCH" ]]; then
94
- # Fallback: search PR bodies for issue references
95
- # Handles both short format (#N) and full-path format (owner/repo#N)
96
- # Uses gh's built-in jq (-q) — no external jq required
97
- JQ_EXPR='.[] | select(.body != null) | select(.body | test("(Fixes|Closes|Resolves)[ ]+([A-Za-z0-9._-]+/[A-Za-z0-9._-]+)?#'"${ISSUE_NUMBER}"'([^0-9]|$)|(^|[^0-9])#'"${ISSUE_NUMBER}"'([^0-9]|$)"; "i")) | .headRefName'
98
- PR_BRANCH=$(gh pr list --json headRefName,body --limit 100 -q "$JQ_EXPR" 2>/dev/null | head -1) || true
99
- fi
100
-
101
- if [[ -z "$PR_BRANCH" ]]; then
102
- die 1 "No PR found for issue #${ISSUE_NUMBER}. Create a PR first with: hula checkout ${ISSUE_NUMBER}"
103
- fi
104
-
105
- # ── Step 5: Determine worktree path ───────────────────────────────────────────
106
-
107
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
108
- source "${SCRIPT_DIR}/hula-read-config.sh"
109
- WORKTREE_PATH="${HULA_WORKTREE_BASE_PATH}/hula/${REPO_NAME}/fix-${ISSUE_NUMBER}"
110
-
111
- # ── Step 6: Create or reuse the worktree ─────────────────────────────────────
112
-
113
- WORKTREE_STATUS='reused'
114
-
115
- if git worktree list --porcelain 2>/dev/null | grep -qxF "worktree ${WORKTREE_PATH}"; then
116
- printf 'ℹ️ Resuming existing fix worktree at %s\n' "$WORKTREE_PATH" >&2
117
- else
118
- printf '🌳 Creating fix worktree at: %s\n' "$WORKTREE_PATH" >&2
119
- git fetch origin >&2 2>&1 || die 2 "git fetch origin failed"
120
- git worktree add "$WORKTREE_PATH" "$PR_BRANCH" >&2 2>&1 \
121
- || die 2 "Failed to create worktree at ${WORKTREE_PATH}. Check that branch ${PR_BRANCH} exists."
122
- WORKTREE_STATUS='created'
123
- fi
124
-
125
- # ── Step 7: Write session file ────────────────────────────────────────────────
126
-
127
- REPO_ROOT_SESSION=$(git rev-parse --show-toplevel 2>/dev/null) || REPO_ROOT_SESSION="$(pwd)"
128
- SESSION_DIR="${REPO_ROOT_SESSION}/.hublaunch/.fix-sessions"
129
- mkdir -p "$SESSION_DIR"
130
-
131
- STARTED_AT=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z" 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
132
-
133
- SESSION_FILE="${SESSION_DIR}/issue-${ISSUE_NUMBER}.json"
134
- SAFE_WT_SESSION=$(json_escape "$WORKTREE_PATH")
135
- SAFE_BR_SESSION=$(json_escape "$PR_BRANCH")
136
- # Write compact single-line JSON via emit_json so the session file format matches
137
- # what readers (hula-fix-commit.sh) expect. Avoids the pretty-printed/compact mismatch.
138
- emit_json \
139
- "issueNumber" "$ISSUE_NUMBER" \
140
- "worktreePath" "$SAFE_WT_SESSION" \
141
- "prBranch" "$SAFE_BR_SESSION" \
142
- "startedAt" "$STARTED_AT" > "$SESSION_FILE"
143
-
144
- # ── Output result ─────────────────────────────────────────────────────────────
145
-
146
- SAFE_TITLE=$(json_escape "$ISSUE_TITLE")
147
- SAFE_BRANCH=$(json_escape "$PR_BRANCH")
148
- SAFE_WORKTREE=$(json_escape "$WORKTREE_PATH")
149
- SAFE_SESSION=$(json_escape "$SESSION_FILE")
150
-
151
- printf '{"status":"success","issueNumber":%s,"issueTitle":"%s","prBranch":"%s","worktreePath":"%s","sessionFile":"%s","worktreeStatus":"%s"}\n' \
152
- "$ISSUE_NUMBER" "$SAFE_TITLE" "$SAFE_BRANCH" "$SAFE_WORKTREE" "$SAFE_SESSION" "$WORKTREE_STATUS"
@@ -1,140 +0,0 @@
1
- #!/usr/bin/env bash
2
- # hula-launch-run.sh — Step 4 of the hula.launch workflow
3
- # Usage: bash .github/scripts/hula-launch-run.sh <plan-path> <branch-name> [--handoff <username>] [--test] [--kill] [--kill-and-relaunch]
4
- # bash .github/scripts/hula-launch-run.sh <branch-name> --kill # cancel only; no plan path required
5
- #
6
- # Performs: hula launch (which handles plan upload automatically)
7
- # Outputs structured JSON to stdout; all other output goes to stderr.
8
- # Exit codes: 0=success, 1=user error, 2=tool error
9
-
10
- set -euo pipefail
11
-
12
- # ── helpers ────────────────────────────────────────────────────────────────────
13
-
14
- json_escape() {
15
- if command -v jq &>/dev/null; then
16
- # jq -Rs . reads stdin as a raw string and outputs a JSON string (with surrounding quotes).
17
- # Strip the surrounding quotes; the escaped content is safe to embed in JSON.
18
- printf '%s' "$1" | jq -Rs . | sed 's/^"//;s/"$//'
19
- else
20
- # Fallback: escape backslashes and double quotes, collapse control chars to spaces
21
- printf '%s' "$1" \
22
- | sed 's/\\/\\\\/g; s/"/\\"/g' \
23
- | tr '\n\r\t' ' '
24
- fi
25
- }
26
-
27
- die() {
28
- local code="$1"; shift
29
- local safe_msg
30
- safe_msg=$(json_escape "$*")
31
- printf '{"status":"error","code":%d,"message":"%s"}\n' "$code" "$safe_msg" >&1
32
- exit "$code"
33
- }
34
-
35
- # ── argument parsing ────────────────────────────────────────────────────────────
36
-
37
- PLAN_PATH=''
38
- BRANCH_NAME=''
39
- HANDOFF=''
40
- TEST=''
41
- KILL=''
42
- KILL_AND_RELAUNCH=''
43
-
44
- while [[ $# -gt 0 ]]; do
45
- case "$1" in
46
- --handoff)
47
- HANDOFF="$2"; shift 2 ;;
48
- --handoff=*)
49
- HANDOFF="${1#--handoff=}"; shift ;;
50
- --test)
51
- TEST=1; shift ;;
52
- --kill)
53
- KILL=1; shift ;;
54
- --kill-and-relaunch)
55
- KILL_AND_RELAUNCH=1; shift ;;
56
- *)
57
- if [[ -z "$PLAN_PATH" ]]; then
58
- PLAN_PATH="$1"
59
- elif [[ -z "$BRANCH_NAME" ]]; then
60
- BRANCH_NAME="$1"
61
- else
62
- die 1 "Unexpected argument: $1"
63
- fi
64
- shift ;;
65
- esac
66
- done
67
-
68
- if [[ -n "$KILL" && -n "$KILL_AND_RELAUNCH" ]]; then
69
- die 1 "--kill and --kill-and-relaunch cannot be used together"
70
- fi
71
-
72
- if [[ -n "$KILL" ]]; then
73
- # kill needs ONLY the branch name. Accept either "<branch-name> --kill" (a
74
- # single positional) or "<plan-path> <branch-name> --kill" (the plan path is
75
- # ignored — kill sends none). When only one positional was given it lands in
76
- # PLAN_PATH, so re-home it as the branch name.
77
- if [[ -z "$BRANCH_NAME" ]]; then
78
- BRANCH_NAME="$PLAN_PATH"
79
- PLAN_PATH=''
80
- fi
81
- if [[ -z "$BRANCH_NAME" ]]; then
82
- die 1 "Usage: bash .github/scripts/hula-launch-run.sh <branch-name> --kill"
83
- fi
84
- else
85
- if [[ -z "$PLAN_PATH" || -z "$BRANCH_NAME" ]]; then
86
- die 1 "Usage: bash .github/scripts/hula-launch-run.sh <plan-path> <branch-name> [--handoff <username>] [--test] [--kill-and-relaunch]"
87
- fi
88
- fi
89
-
90
- # Reject paths with directory traversal sequences (only when a plan is in play)
91
- if [[ -n "$PLAN_PATH" ]] && printf '%s' "$PLAN_PATH" | grep -q '\.\.'; then
92
- die 1 "Invalid plan path (path traversal not allowed): ${PLAN_PATH}"
93
- fi
94
-
95
- # ── Step 4: Run hula launch ─────────────────────────────────────────────────
96
-
97
- if [[ -n "$KILL" ]]; then
98
- printf '🛑 Stopping in-flight task for branch: %s\n' "$BRANCH_NAME" >&2
99
- else
100
- printf '🚀 Launching issue from plan: %s\n' "$PLAN_PATH" >&2
101
- fi
102
-
103
- # Build the launch invocation as an args array so the flags compose without
104
- # combinatorial if/else branching. Branch name is always first; the plan path is
105
- # a positional appended only when present (a bare --kill sends no plan path).
106
- LAUNCH_ARGS=("$BRANCH_NAME")
107
- if [[ -n "$PLAN_PATH" && -z "$KILL" ]]; then
108
- LAUNCH_ARGS+=("$PLAN_PATH")
109
- fi
110
- if [[ -n "$HANDOFF" ]]; then
111
- LAUNCH_ARGS+=(--handoff "$HANDOFF")
112
- fi
113
- if [[ -n "$TEST" ]]; then
114
- LAUNCH_ARGS+=(--test)
115
- fi
116
- if [[ -n "$KILL" ]]; then
117
- LAUNCH_ARGS+=(--kill)
118
- fi
119
- if [[ -n "$KILL_AND_RELAUNCH" ]]; then
120
- LAUNCH_ARGS+=(--kill-and-relaunch)
121
- fi
122
- LAUNCH_OUTPUT=$(hula launch "${LAUNCH_ARGS[@]}" 2>&1) \
123
- || die 2 "Launch failed: $LAUNCH_OUTPUT"
124
-
125
- # Extract issue number from CLI output (e.g. "Created issue #42" or "issue #42")
126
- ISSUE_NUMBER=$(printf '%s' "$LAUNCH_OUTPUT" | grep -oE '#[0-9]+' | head -1 | tr -d '#') || true
127
-
128
- # ── Output result ─────────────────────────────────────────────────────────────
129
-
130
- SAFE_PLAN=$(json_escape "$PLAN_PATH")
131
- SAFE_BRANCH=$(json_escape "$BRANCH_NAME")
132
- SAFE_HANDOFF=$(json_escape "$HANDOFF")
133
- SAFE_OUTPUT=$(json_escape "$LAUNCH_OUTPUT")
134
-
135
- printf '{"status":"success","issueNumber":"%s","branchName":"%s","planPath":"%s","handoff":"%s","cliOutput":"%s"}\n' \
136
- "${ISSUE_NUMBER:-}" \
137
- "$SAFE_BRANCH" \
138
- "$SAFE_PLAN" \
139
- "$SAFE_HANDOFF" \
140
- "$SAFE_OUTPUT"
@@ -1,138 +0,0 @@
1
- #!/usr/bin/env bash
2
- # hula-merge-local.sh — Path A of the hula.merge workflow (local session merge)
3
- # Usage: bash .github/scripts/hula-merge-local.sh <issue-number> <worktree-path> <commit-message> [--skip-tests] [--dry-run]
4
- #
5
- # Performs: commit + push from worktree → hula merge → remove worktree → delete session file
6
- # Outputs structured JSON to stdout; all other output goes to stderr.
7
- # Exit codes: 0=success, 1=user error, 2=tool error
8
-
9
- set -euo pipefail
10
-
11
- # ── helpers ────────────────────────────────────────────────────────────────────
12
-
13
- json_escape() {
14
- printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/$/\\n/g' | tr -d '\n' | sed 's/\\n$//'
15
- }
16
-
17
- die() {
18
- local code="$1"; shift
19
- local safe_msg
20
- safe_msg=$(json_escape "$*")
21
- printf '{"status":"error","code":%d,"message":"%s"}\n' "$code" "$safe_msg" >&1
22
- exit "$code"
23
- }
24
-
25
- # ── argument parsing ────────────────────────────────────────────────────────────
26
-
27
- ISSUE_NUMBER=''
28
- WORKTREE_PATH=''
29
- COMMIT_MSG=''
30
- SKIP_TESTS=false
31
- DRY_RUN=false
32
-
33
- while [[ $# -gt 0 ]]; do
34
- case "$1" in
35
- --skip-tests) SKIP_TESTS=true; shift ;;
36
- --dry-run) DRY_RUN=true; shift ;;
37
- *)
38
- if [[ -z "$ISSUE_NUMBER" ]]; then
39
- ISSUE_NUMBER="$1"
40
- elif [[ -z "$WORKTREE_PATH" ]]; then
41
- WORKTREE_PATH="$1"
42
- elif [[ -z "$COMMIT_MSG" ]]; then
43
- COMMIT_MSG="$1"
44
- else
45
- die 1 "Unexpected argument: $1"
46
- fi
47
- shift ;;
48
- esac
49
- done
50
-
51
- if [[ -z "$ISSUE_NUMBER" || -z "$WORKTREE_PATH" || -z "$COMMIT_MSG" ]]; then
52
- die 1 "Usage: bash .github/scripts/hula-merge-local.sh <issue-number> <worktree-path> <commit-message> [--skip-tests] [--dry-run]"
53
- fi
54
-
55
- if [[ ! -d "$WORKTREE_PATH" ]]; then
56
- die 1 "Worktree not found at ${WORKTREE_PATH}."
57
- fi
58
-
59
- # Compute absolute session file path before cd (so it resolves correctly later)
60
- REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || REPO_ROOT="$(pwd)"
61
- SESSION_FILE="${REPO_ROOT}/.hublaunch/.fix-sessions/issue-${ISSUE_NUMBER}.json"
62
-
63
- # ── Step A3: Commit and push from worktree ────────────────────────────────────
64
-
65
- cd "$WORKTREE_PATH"
66
-
67
- PORCELAIN=$(git status --porcelain 2>/dev/null)
68
- COMMITTED=false
69
- FILES_CHANGED=0
70
-
71
- if [[ -n "$PORCELAIN" ]]; then
72
- FILES_CHANGED=$(printf '%s' "$PORCELAIN" | grep -c '' || true)
73
-
74
- if [[ "$DRY_RUN" == false ]]; then
75
- git add -A >&2 2>&1 \
76
- || die 2 "git add failed in worktree ${WORKTREE_PATH}"
77
-
78
- git commit -m "$COMMIT_MSG" >&2 2>&1 \
79
- || die 2 "Failed to commit changes. Ensure git user.name and user.email are configured."
80
-
81
- git push >&2 2>&1 \
82
- || die 2 "Push failed. Try: cd ${WORKTREE_PATH} && git push"
83
-
84
- COMMITTED=true
85
- else
86
- printf '🔍 Dry run: would commit %d file(s) with message: %s\n' "$FILES_CHANGED" "$COMMIT_MSG" >&2
87
- COMMITTED=false
88
- fi
89
- else
90
- printf 'ℹ️ No changes to commit, proceeding to merge...\n' >&2
91
- fi
92
-
93
- # ── Step A4: Merge the PR ─────────────────────────────────────────────────────
94
-
95
- # Return to the main repo root (parent of worktree if possible)
96
- cd - > /dev/null 2>&1 || true
97
-
98
- if [[ "$DRY_RUN" == false ]]; then
99
- printf '🔀 Merging PR for issue #%s...\n' "$ISSUE_NUMBER" >&2
100
- hula merge "$ISSUE_NUMBER" >&2 2>&1 \
101
- || die 2 "Failed to merge PR for issue #${ISSUE_NUMBER}. Session preserved — run /hula-merge again after resolving."
102
- fi
103
-
104
- # ── Step A5: Remove fix worktree ──────────────────────────────────────────────
105
-
106
- if [[ "$DRY_RUN" == false ]]; then
107
- printf '🧹 Removing fix worktree: %s\n' "$WORKTREE_PATH" >&2
108
- git worktree remove "$WORKTREE_PATH" --force >&2 2>&1 \
109
- || printf '⚠️ Could not remove worktree at %s (may already be gone)\n' "$WORKTREE_PATH" >&2
110
- fi
111
-
112
- # ── Step A6: Clean up session file ───────────────────────────────────────────
113
-
114
- SESSION_CLEANED=false
115
-
116
- if [[ -f "$SESSION_FILE" ]]; then
117
- if [[ "$DRY_RUN" == false ]]; then
118
- rm -f "$SESSION_FILE"
119
- SESSION_CLEANED=true
120
- printf '🧹 Cleaned up session file\n' >&2
121
- else
122
- printf '🔍 Dry run: would delete session file %s\n' "$SESSION_FILE" >&2
123
- fi
124
- fi
125
-
126
- # ── Output result ─────────────────────────────────────────────────────────────
127
-
128
- SAFE_MSG=$(json_escape "$COMMIT_MSG")
129
- SAFE_PATH=$(json_escape "$WORKTREE_PATH")
130
-
131
- printf '{"status":"success","issueNumber":%s,"committed":%s,"filesChanged":%d,"worktreeRemoved":%s,"sessionCleaned":%s,"commitMessage":"%s","worktreePath":"%s"}\n' \
132
- "$ISSUE_NUMBER" \
133
- "$([ "$COMMITTED" == true ] && echo 'true' || echo 'false')" \
134
- "$FILES_CHANGED" \
135
- "$([ "$DRY_RUN" == false ] && echo 'true' || echo 'false')" \
136
- "$([ "$SESSION_CLEANED" == true ] && echo 'true' || echo 'false')" \
137
- "$SAFE_MSG" \
138
- "$SAFE_PATH"
@@ -1,94 +0,0 @@
1
- #!/usr/bin/env bash
2
- # hula-merge-remote.sh — Path B of the hula.merge workflow (remote-only merge)
3
- # Usage: bash .github/scripts/hula-merge-remote.sh <issue-number> [--session-path <path>]
4
- #
5
- # Merges the PR directly on GitHub, then optionally cleans up a stale session file.
6
- # Outputs structured JSON to stdout; all other output goes to stderr.
7
- # Exit codes: 0=success, 1=user error, 2=tool error
8
-
9
- set -euo pipefail
10
-
11
- # ── helpers ────────────────────────────────────────────────────────────────────
12
-
13
- json_escape() {
14
- printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/$/\\n/g' | tr -d '\n' | sed 's/\\n$//'
15
- }
16
-
17
- die() {
18
- local code="$1"; shift
19
- local safe_msg
20
- safe_msg=$(json_escape "$*")
21
- printf '{"status":"error","code":%d,"message":"%s"}\n' "$code" "$safe_msg" >&1
22
- exit "$code"
23
- }
24
-
25
- # ── argument parsing ────────────────────────────────────────────────────────────
26
-
27
- ISSUE_NUMBER=''
28
- SESSION_PATH=''
29
-
30
- while [[ $# -gt 0 ]]; do
31
- case "$1" in
32
- --session-path)
33
- SESSION_PATH="$2"; shift 2 ;;
34
- --session-path=*)
35
- SESSION_PATH="${1#--session-path=}"; shift ;;
36
- [0-9]*)
37
- ISSUE_NUMBER="$1"; shift ;;
38
- *)
39
- die 1 "Unknown argument: $1" ;;
40
- esac
41
- done
42
-
43
- if [[ -z "$ISSUE_NUMBER" ]]; then
44
- die 1 "Usage: bash .github/scripts/hula-merge-remote.sh <issue-number> [--session-path <path>]"
45
- fi
46
-
47
- # ── Step B1: Merge the PR via hula CLI ───────────────────────────────────────
48
-
49
- printf '🔀 Merging PR for issue #%s (remote-only)...\n' "$ISSUE_NUMBER" >&2
50
-
51
- # Capture the CLI output so we can detect whether the local default branch was
52
- # fast-forwarded, while still surfacing it live to the user (on stderr — stdout
53
- # is reserved for the structured JSON result).
54
- set +e
55
- MERGE_OUTPUT=$(hula merge "$ISSUE_NUMBER" 2>&1)
56
- MERGE_EXIT=$?
57
- set -e
58
-
59
- printf '%s\n' "$MERGE_OUTPUT" >&2
60
-
61
- if [[ $MERGE_EXIT -ne 0 ]]; then
62
- die 2 "Failed to merge PR for issue #${ISSUE_NUMBER}. Check the PR on GitHub."
63
- fi
64
-
65
- # Detect whether `hula merge` actually fast-forwarded the local default branch.
66
- # The CLI prints "✓ Local <branch> fast-forwarded to origin/<branch>" on success
67
- # and a specific warning (with remediation) on each skip/failure mode.
68
- LOCAL_UPDATED=false
69
- if printf '%s' "$MERGE_OUTPUT" | grep -q "Local.*fast-forwarded"; then
70
- LOCAL_UPDATED=true
71
- fi
72
-
73
- # ── Step B2: Clean up stale session file (if applicable) ─────────────────────
74
-
75
- SESSION_CLEANED=false
76
-
77
- # Default session file path if not provided (use absolute path so it works from any CWD)
78
- if [[ -z "$SESSION_PATH" ]]; then
79
- REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || REPO_ROOT="$(pwd)"
80
- SESSION_PATH="${REPO_ROOT}/.hublaunch/.fix-sessions/issue-${ISSUE_NUMBER}.json"
81
- fi
82
-
83
- if [[ -f "$SESSION_PATH" ]]; then
84
- rm -f "$SESSION_PATH"
85
- SESSION_CLEANED=true
86
- printf '🧹 Cleaned up stale session file\n' >&2
87
- fi
88
-
89
- # ── Output result ─────────────────────────────────────────────────────────────
90
-
91
- printf '{"status":"success","issueNumber":%s,"sessionCleaned":%s,"localUpdated":%s}\n' \
92
- "$ISSUE_NUMBER" \
93
- "$([ "$SESSION_CLEANED" == true ] && echo 'true' || echo 'false')" \
94
- "$([ "$LOCAL_UPDATED" == true ] && echo 'true' || echo 'false')"
@@ -1,38 +0,0 @@
1
- #!/usr/bin/env bash
2
- # hula-read-config.sh — Shared config reader for hula shell scripts
3
- # Source this file; do NOT execute directly.
4
- #
5
- # After sourcing, the following variables are set:
6
- # HULA_WORKTREE_BASE_PATH — absolute resolved worktreeBasePath
7
- #
8
- # Reads worktreeBasePath from .hublaunch/hublaunch.config.js.
9
- # Falls back to ".hula-worktrees" (resolved relative to repo root).
10
-
11
- _hula_read_worktree_base_path() {
12
- local repo_root
13
- repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || {
14
- printf 'Warning: could not determine repo root\n' >&2
15
- repo_root="$(pwd)"
16
- }
17
- local config_file="${repo_root}/.hublaunch/hublaunch.config.js"
18
- local base_path=""
19
-
20
- if [[ -f "$config_file" ]]; then
21
- base_path=$(grep 'worktreeBasePath' "$config_file" \
22
- | grep -v '^\s*//' \
23
- | sed "s/.*worktreeBasePath[^'\"]*['\"]\\([^'\"]*\\)['\"].*/\\1/" \
24
- | head -1) || true
25
- fi
26
-
27
- # Default to .hula-worktrees if not found or empty
28
- base_path="${base_path:-.hula-worktrees}"
29
-
30
- # Resolve relative paths against repo root
31
- if [[ "$base_path" != /* ]]; then
32
- base_path="${repo_root}/${base_path}"
33
- fi
34
-
35
- printf '%s' "$base_path"
36
- }
37
-
38
- HULA_WORKTREE_BASE_PATH=$(_hula_read_worktree_base_path)