hub-launch 1.15.0 → 1.17.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.
- package/CHANGELOG.md +12 -0
- package/README.md +103 -2
- package/dist/commands/init.d.ts +55 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +93 -103
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/instructions.d.ts +17 -0
- package/dist/commands/instructions.d.ts.map +1 -0
- package/dist/commands/instructions.js +51 -0
- package/dist/commands/instructions.js.map +1 -0
- package/dist/commands/launch.d.ts +21 -0
- package/dist/commands/launch.d.ts.map +1 -1
- package/dist/commands/launch.js +61 -0
- package/dist/commands/launch.js.map +1 -1
- package/dist/commands/script.d.ts +28 -0
- package/dist/commands/script.d.ts.map +1 -0
- package/dist/commands/script.js +69 -0
- package/dist/commands/script.js.map +1 -0
- package/dist/commands/session-hook.d.ts +26 -0
- package/dist/commands/session-hook.d.ts.map +1 -0
- package/dist/commands/session-hook.js +146 -0
- package/dist/commands/session-hook.js.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -1
- package/dist/scripts/fix-commit.d.ts +2 -0
- package/dist/scripts/fix-commit.d.ts.map +1 -0
- package/dist/scripts/fix-commit.js +125 -0
- package/dist/scripts/fix-commit.js.map +1 -0
- package/dist/scripts/fix-setup.d.ts +2 -0
- package/dist/scripts/fix-setup.d.ts.map +1 -0
- package/dist/scripts/fix-setup.js +161 -0
- package/dist/scripts/fix-setup.js.map +1 -0
- package/dist/scripts/launch-run.d.ts +14 -0
- package/dist/scripts/launch-run.d.ts.map +1 -0
- package/dist/scripts/launch-run.js +117 -0
- package/dist/scripts/launch-run.js.map +1 -0
- package/dist/scripts/lib/exec.d.ts +48 -0
- package/dist/scripts/lib/exec.d.ts.map +1 -0
- package/dist/scripts/lib/exec.js +61 -0
- package/dist/scripts/lib/exec.js.map +1 -0
- package/dist/scripts/lib/io.d.ts +48 -0
- package/dist/scripts/lib/io.d.ts.map +1 -0
- package/dist/scripts/lib/io.js +72 -0
- package/dist/scripts/lib/io.js.map +1 -0
- package/dist/scripts/lib/read-config.d.ts +26 -0
- package/dist/scripts/lib/read-config.d.ts.map +1 -0
- package/dist/scripts/lib/read-config.js +65 -0
- package/dist/scripts/lib/read-config.js.map +1 -0
- package/dist/scripts/merge-local.d.ts +2 -0
- package/dist/scripts/merge-local.d.ts.map +1 -0
- package/dist/scripts/merge-local.js +146 -0
- package/dist/scripts/merge-local.js.map +1 -0
- package/dist/scripts/merge-remote.d.ts +12 -0
- package/dist/scripts/merge-remote.d.ts.map +1 -0
- package/dist/scripts/merge-remote.js +71 -0
- package/dist/scripts/merge-remote.js.map +1 -0
- package/dist/scripts/schedule-manage.d.ts +2 -0
- package/dist/scripts/schedule-manage.d.ts.map +1 -0
- package/dist/scripts/schedule-manage.js +151 -0
- package/dist/scripts/schedule-manage.js.map +1 -0
- package/dist/scripts/schedule-run.d.ts +2 -0
- package/dist/scripts/schedule-run.d.ts.map +1 -0
- package/dist/scripts/schedule-run.js +146 -0
- package/dist/scripts/schedule-run.js.map +1 -0
- package/dist/scripts/verify-gather.d.ts +2 -0
- package/dist/scripts/verify-gather.d.ts.map +1 -0
- package/dist/scripts/verify-gather.js +210 -0
- package/dist/scripts/verify-gather.js.map +1 -0
- package/dist/scripts/verify-post.d.ts +12 -0
- package/dist/scripts/verify-post.d.ts.map +1 -0
- package/dist/scripts/verify-post.js +72 -0
- package/dist/scripts/verify-post.js.map +1 -0
- package/dist/services/git/FilePublishService.d.ts +6 -0
- package/dist/services/git/FilePublishService.d.ts.map +1 -1
- package/dist/services/git/FilePublishService.js +79 -2
- package/dist/services/git/FilePublishService.js.map +1 -1
- package/dist/services/git/WorktreeService.d.ts +17 -0
- package/dist/services/git/WorktreeService.d.ts.map +1 -1
- package/dist/services/git/WorktreeService.js +40 -0
- package/dist/services/git/WorktreeService.js.map +1 -1
- package/dist/templates/planning-instructions.md +7 -7
- package/dist/templates/skill-creation-instructions.md +2 -2
- package/dist/templates/skills/hula-confirm/SKILL.md +3 -3
- package/dist/templates/skills/hula-fix/SKILL.md +3 -3
- package/dist/templates/skills/hula-launch/SKILL.md +14 -8
- package/dist/templates/skills/hula-merge/SKILL.md +2 -2
- package/dist/templates/skills/hula-plan/SKILL.md +5 -5
- package/dist/templates/skills/hula-schedule/SKILL.md +16 -16
- package/dist/templates/skills/hula-verify/SKILL.md +2 -2
- package/dist/types/config.schema.d.ts +235 -0
- package/dist/types/config.schema.d.ts.map +1 -1
- package/dist/types/config.schema.js +66 -0
- package/dist/types/config.schema.js.map +1 -1
- package/dist/utils/client-session.d.ts +2 -2
- package/dist/utils/client-session.js +2 -2
- package/package.json +6 -5
- package/scripts/postinstall.mjs +101 -0
- package/dist/templates/scripts/hula-fix-commit.sh +0 -114
- package/dist/templates/scripts/hula-fix-setup.sh +0 -152
- package/dist/templates/scripts/hula-launch-run.sh +0 -140
- package/dist/templates/scripts/hula-merge-local.sh +0 -138
- package/dist/templates/scripts/hula-merge-remote.sh +0 -94
- package/dist/templates/scripts/hula-read-config.sh +0 -38
- package/dist/templates/scripts/hula-schedule-manage.sh +0 -159
- package/dist/templates/scripts/hula-schedule-run.sh +0 -148
- package/dist/templates/scripts/hula-session-hook.sh +0 -68
- package/dist/templates/scripts/hula-verify-gather.sh +0 -135
- package/dist/templates/scripts/hula-verify-post.sh +0 -84
|
@@ -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)
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# hula-schedule-manage.sh — management surface for the /hula-schedule skill
|
|
3
|
-
# Usage (exactly one action per call):
|
|
4
|
-
# bash .github/scripts/hula-schedule-manage.sh --list
|
|
5
|
-
# bash .github/scripts/hula-schedule-manage.sh --list-schedules
|
|
6
|
-
# bash .github/scripts/hula-schedule-manage.sh --show <runId>
|
|
7
|
-
# bash .github/scripts/hula-schedule-manage.sh --run-now <scheduleId>
|
|
8
|
-
# bash .github/scripts/hula-schedule-manage.sh --cancel-schedule <scheduleId>
|
|
9
|
-
# bash .github/scripts/hula-schedule-manage.sh --publish-skill <repo-relative-path>
|
|
10
|
-
# bash .github/scripts/hula-schedule-manage.sh --delete-skill <repo-relative-path>
|
|
11
|
-
#
|
|
12
|
-
# Each invocation maps to the corresponding `hula schedule …` call, captures its
|
|
13
|
-
# output, and emits a single structured JSON object to stdout; all other output
|
|
14
|
-
# goes to stderr.
|
|
15
|
-
#
|
|
16
|
-
# JSON contract (stdout):
|
|
17
|
-
# read verbs : {"status":"success","kind":"list|list-schedules|show","cliOutput":"…"}
|
|
18
|
-
# run-now : {"status":"success","kind":"run-now","runId":"…","cliOutput":"…"}
|
|
19
|
-
# cancel : {"status":"success","kind":"cancel-schedule","scheduleId":"…","cliOutput":"…"}
|
|
20
|
-
# publish-skill : {"status":"success","kind":"publish-skill","path":"…","cliOutput":"…"}
|
|
21
|
-
# delete-skill : {"status":"success","kind":"delete-skill","path":"…","cliOutput":"…"}
|
|
22
|
-
# failure : {"status":"error","code":<n>,"message":"…"}
|
|
23
|
-
#
|
|
24
|
-
# Exit codes: 0=success, 1=user error, 2=tool error
|
|
25
|
-
#
|
|
26
|
-
# Security: never prints secrets. Credentials are resolved inside the CLI from
|
|
27
|
-
# flags/config/env — they are NOT passed by this script. --publish-skill and
|
|
28
|
-
# --delete-skill reject path traversal and require a .hublaunch/skills/ path.
|
|
29
|
-
|
|
30
|
-
set -euo pipefail
|
|
31
|
-
|
|
32
|
-
# ── helpers ────────────────────────────────────────────────────────────────────
|
|
33
|
-
|
|
34
|
-
json_escape() {
|
|
35
|
-
if command -v jq &>/dev/null; then
|
|
36
|
-
# jq -Rs . reads stdin as a raw string and outputs a JSON string (with surrounding quotes).
|
|
37
|
-
# Strip the surrounding quotes; the escaped content is safe to embed in JSON.
|
|
38
|
-
printf '%s' "$1" | jq -Rs . | sed 's/^"//;s/"$//'
|
|
39
|
-
else
|
|
40
|
-
# Fallback: escape backslashes and double quotes, collapse control chars to spaces
|
|
41
|
-
printf '%s' "$1" \
|
|
42
|
-
| sed 's/\\/\\\\/g; s/"/\\"/g' \
|
|
43
|
-
| tr '\n\r\t' ' '
|
|
44
|
-
fi
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
die() {
|
|
48
|
-
local code="$1"; shift
|
|
49
|
-
local safe_msg
|
|
50
|
-
safe_msg=$(json_escape "$*")
|
|
51
|
-
printf '{"status":"error","code":%d,"message":"%s"}\n' "$code" "$safe_msg" >&1
|
|
52
|
-
exit "$code"
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
# Validate a skill path: must be repo-relative, under .hublaunch/skills/, no traversal.
|
|
56
|
-
validate_skill_path() {
|
|
57
|
-
local p="$1"
|
|
58
|
-
case "$p" in
|
|
59
|
-
/*) die 1 "Skill path must be repo-relative, not absolute: ${p}" ;;
|
|
60
|
-
esac
|
|
61
|
-
if printf '%s' "$p" | grep -q '\.\.'; then
|
|
62
|
-
die 1 "Invalid skill path (path traversal not allowed): ${p}"
|
|
63
|
-
fi
|
|
64
|
-
case "$p" in
|
|
65
|
-
.hublaunch/skills/*) : ;;
|
|
66
|
-
*) die 1 "Skill path must be under .hublaunch/skills/ — got ${p}" ;;
|
|
67
|
-
esac
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
# ── argument parsing ────────────────────────────────────────────────────────────
|
|
71
|
-
# Exactly one action flag is allowed per call.
|
|
72
|
-
|
|
73
|
-
ACTION=''
|
|
74
|
-
ARG=''
|
|
75
|
-
|
|
76
|
-
set_action() {
|
|
77
|
-
if [[ -n "$ACTION" ]]; then
|
|
78
|
-
die 1 "Provide exactly one action (got --$ACTION and $1)."
|
|
79
|
-
fi
|
|
80
|
-
ACTION="$1"
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
while [[ $# -gt 0 ]]; do
|
|
84
|
-
case "$1" in
|
|
85
|
-
--list) set_action 'list'; shift ;;
|
|
86
|
-
--list-schedules) set_action 'list-schedules'; shift ;;
|
|
87
|
-
--show) set_action 'show'; ARG="$2"; shift 2 ;;
|
|
88
|
-
--show=*) set_action 'show'; ARG="${1#--show=}"; shift ;;
|
|
89
|
-
--run-now) set_action 'run-now'; ARG="$2"; shift 2 ;;
|
|
90
|
-
--run-now=*) set_action 'run-now'; ARG="${1#--run-now=}"; shift ;;
|
|
91
|
-
--cancel-schedule) set_action 'cancel-schedule'; ARG="$2"; shift 2 ;;
|
|
92
|
-
--cancel-schedule=*) set_action 'cancel-schedule'; ARG="${1#--cancel-schedule=}"; shift ;;
|
|
93
|
-
--publish-skill) set_action 'publish-skill'; ARG="$2"; shift 2 ;;
|
|
94
|
-
--publish-skill=*) set_action 'publish-skill'; ARG="${1#--publish-skill=}"; shift ;;
|
|
95
|
-
--delete-skill) set_action 'delete-skill'; ARG="$2"; shift 2 ;;
|
|
96
|
-
--delete-skill=*) set_action 'delete-skill'; ARG="${1#--delete-skill=}"; shift ;;
|
|
97
|
-
*) die 1 "Unexpected argument: $1" ;;
|
|
98
|
-
esac
|
|
99
|
-
done
|
|
100
|
-
|
|
101
|
-
if [[ -z "$ACTION" ]]; then
|
|
102
|
-
die 1 "Usage: bash .github/scripts/hula-schedule-manage.sh (--list | --list-schedules | --show <id> | --run-now <id> | --cancel-schedule <id> | --publish-skill <path> | --delete-skill <path>)"
|
|
103
|
-
fi
|
|
104
|
-
|
|
105
|
-
# Actions that require an argument.
|
|
106
|
-
case "$ACTION" in
|
|
107
|
-
show|run-now|cancel-schedule|publish-skill|delete-skill)
|
|
108
|
-
[[ -n "$ARG" ]] || die 1 "--$ACTION requires an argument." ;;
|
|
109
|
-
esac
|
|
110
|
-
|
|
111
|
-
# Path validation for skill file actions.
|
|
112
|
-
case "$ACTION" in
|
|
113
|
-
publish-skill|delete-skill) validate_skill_path "$ARG" ;;
|
|
114
|
-
esac
|
|
115
|
-
|
|
116
|
-
# ── Build and run the hula schedule invocation ────────────────────────────────
|
|
117
|
-
|
|
118
|
-
CLI_ARGS=()
|
|
119
|
-
case "$ACTION" in
|
|
120
|
-
list) CLI_ARGS=(--list) ;;
|
|
121
|
-
list-schedules) CLI_ARGS=(--list-schedules) ;;
|
|
122
|
-
show) CLI_ARGS=(--show "$ARG") ;;
|
|
123
|
-
run-now) CLI_ARGS=(--run-now "$ARG") ;;
|
|
124
|
-
cancel-schedule) CLI_ARGS=(--cancel-schedule "$ARG") ;;
|
|
125
|
-
publish-skill) CLI_ARGS=(--publish-skill "$ARG") ;;
|
|
126
|
-
delete-skill) CLI_ARGS=(--delete-skill "$ARG") ;;
|
|
127
|
-
esac
|
|
128
|
-
|
|
129
|
-
printf '⚙️ hula schedule --%s...\n' "$ACTION" >&2
|
|
130
|
-
|
|
131
|
-
OUTPUT=$(hula schedule "${CLI_ARGS[@]}" 2>&1) || die 2 "Management action failed: $OUTPUT"
|
|
132
|
-
|
|
133
|
-
SAFE_OUTPUT=$(json_escape "$OUTPUT")
|
|
134
|
-
|
|
135
|
-
# ── Emit the structured JSON result ──────────────────────────────────────────
|
|
136
|
-
|
|
137
|
-
case "$ACTION" in
|
|
138
|
-
list|list-schedules|show)
|
|
139
|
-
printf '{"status":"success","kind":"%s","cliOutput":"%s"}\n' \
|
|
140
|
-
"$ACTION" "$SAFE_OUTPUT"
|
|
141
|
-
;;
|
|
142
|
-
run-now)
|
|
143
|
-
# The CLI prints " ID: <id>" for the new run.
|
|
144
|
-
RUN_ID=$(printf '%s' "$OUTPUT" | grep -E '^\s*ID:' | head -1 | sed -E 's/^\s*ID:\s*//') || true
|
|
145
|
-
SAFE_ID=$(json_escape "$RUN_ID")
|
|
146
|
-
printf '{"status":"success","kind":"run-now","runId":"%s","cliOutput":"%s"}\n' \
|
|
147
|
-
"$SAFE_ID" "$SAFE_OUTPUT"
|
|
148
|
-
;;
|
|
149
|
-
cancel-schedule)
|
|
150
|
-
SAFE_ID=$(json_escape "$ARG")
|
|
151
|
-
printf '{"status":"success","kind":"cancel-schedule","scheduleId":"%s","cliOutput":"%s"}\n' \
|
|
152
|
-
"$SAFE_ID" "$SAFE_OUTPUT"
|
|
153
|
-
;;
|
|
154
|
-
publish-skill|delete-skill)
|
|
155
|
-
SAFE_PATH=$(json_escape "$ARG")
|
|
156
|
-
printf '{"status":"success","kind":"%s","path":"%s","cliOutput":"%s"}\n' \
|
|
157
|
-
"$ACTION" "$SAFE_PATH" "$SAFE_OUTPUT"
|
|
158
|
-
;;
|
|
159
|
-
esac
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# hula-schedule-run.sh — runs the hula schedule workflow for the /hula-schedule skill
|
|
3
|
-
# Usage:
|
|
4
|
-
# bash .github/scripts/hula-schedule-run.sh \
|
|
5
|
-
# (--built-in <name> | --action-path <path>) \
|
|
6
|
-
# [--entry-point <path>] [--outcome-type <pr|plan|feedback>] [--schedule "<cron>"] \
|
|
7
|
-
# [--pr-policy <always|skip-if-open|close-previous>] [--update-notification-url <url>]
|
|
8
|
-
#
|
|
9
|
-
# Performs: hula schedule (one-off run, or a recurring schedule when --schedule is given).
|
|
10
|
-
# Outputs a single structured JSON object to stdout; all other output goes to stderr.
|
|
11
|
-
#
|
|
12
|
-
# JSON contract (stdout):
|
|
13
|
-
# one-off run : {"status":"success","kind":"run","runId":"…","prUrl":"…","cliOutput":"…"}
|
|
14
|
-
# schedule : {"status":"success","kind":"schedule","scheduleId":"…","cronExpr":"…","cliOutput":"…"}
|
|
15
|
-
# failure : {"status":"error","code":<n>,"message":"…"}
|
|
16
|
-
#
|
|
17
|
-
# Exit codes: 0=success, 1=user error, 2=tool error
|
|
18
|
-
#
|
|
19
|
-
# Security: never prints secrets. Credentials are resolved inside the CLI from
|
|
20
|
-
# flags/config/env — they are NOT passed by this script.
|
|
21
|
-
|
|
22
|
-
set -euo pipefail
|
|
23
|
-
|
|
24
|
-
# ── helpers ────────────────────────────────────────────────────────────────────
|
|
25
|
-
|
|
26
|
-
json_escape() {
|
|
27
|
-
if command -v jq &>/dev/null; then
|
|
28
|
-
# jq -Rs . reads stdin as a raw string and outputs a JSON string (with surrounding quotes).
|
|
29
|
-
# Strip the surrounding quotes; the escaped content is safe to embed in JSON.
|
|
30
|
-
printf '%s' "$1" | jq -Rs . | sed 's/^"//;s/"$//'
|
|
31
|
-
else
|
|
32
|
-
# Fallback: escape backslashes and double quotes, collapse control chars to spaces
|
|
33
|
-
printf '%s' "$1" \
|
|
34
|
-
| sed 's/\\/\\\\/g; s/"/\\"/g' \
|
|
35
|
-
| tr '\n\r\t' ' '
|
|
36
|
-
fi
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
die() {
|
|
40
|
-
local code="$1"; shift
|
|
41
|
-
local safe_msg
|
|
42
|
-
safe_msg=$(json_escape "$*")
|
|
43
|
-
printf '{"status":"error","code":%d,"message":"%s"}\n' "$code" "$safe_msg" >&1
|
|
44
|
-
exit "$code"
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
# ── argument parsing ────────────────────────────────────────────────────────────
|
|
48
|
-
|
|
49
|
-
BUILT_IN=''
|
|
50
|
-
ACTION_PATH=''
|
|
51
|
-
ENTRY_POINT=''
|
|
52
|
-
OUTCOME_TYPE=''
|
|
53
|
-
SCHEDULE=''
|
|
54
|
-
PR_POLICY=''
|
|
55
|
-
UPDATE_NOTIFICATION_URL=''
|
|
56
|
-
|
|
57
|
-
while [[ $# -gt 0 ]]; do
|
|
58
|
-
case "$1" in
|
|
59
|
-
--built-in) BUILT_IN="$2"; shift 2 ;;
|
|
60
|
-
--built-in=*) BUILT_IN="${1#--built-in=}"; shift ;;
|
|
61
|
-
--action-path) ACTION_PATH="$2"; shift 2 ;;
|
|
62
|
-
--action-path=*) ACTION_PATH="${1#--action-path=}"; shift ;;
|
|
63
|
-
--entry-point) ENTRY_POINT="$2"; shift 2 ;;
|
|
64
|
-
--entry-point=*) ENTRY_POINT="${1#--entry-point=}"; shift ;;
|
|
65
|
-
--outcome-type) OUTCOME_TYPE="$2"; shift 2 ;;
|
|
66
|
-
--outcome-type=*) OUTCOME_TYPE="${1#--outcome-type=}"; shift ;;
|
|
67
|
-
--schedule) SCHEDULE="$2"; shift 2 ;;
|
|
68
|
-
--schedule=*) SCHEDULE="${1#--schedule=}"; shift ;;
|
|
69
|
-
--pr-policy) PR_POLICY="$2"; shift 2 ;;
|
|
70
|
-
--pr-policy=*) PR_POLICY="${1#--pr-policy=}"; shift ;;
|
|
71
|
-
--update-notification-url) UPDATE_NOTIFICATION_URL="$2"; shift 2 ;;
|
|
72
|
-
--update-notification-url=*) UPDATE_NOTIFICATION_URL="${1#--update-notification-url=}"; shift ;;
|
|
73
|
-
*) die 1 "Unexpected argument: $1" ;;
|
|
74
|
-
esac
|
|
75
|
-
done
|
|
76
|
-
|
|
77
|
-
# Exactly one of --built-in / --action-path is required.
|
|
78
|
-
if [[ -n "$BUILT_IN" && -n "$ACTION_PATH" ]]; then
|
|
79
|
-
die 1 "Provide either --built-in or --action-path, not both."
|
|
80
|
-
fi
|
|
81
|
-
if [[ -z "$BUILT_IN" && -z "$ACTION_PATH" ]]; then
|
|
82
|
-
die 1 "Usage: bash .github/scripts/hula-schedule-run.sh (--built-in <name> | --action-path <path>) [--entry-point <path>] [--outcome-type <pr|plan|feedback>] [--schedule \"<cron>\"] [--pr-policy <always|skip-if-open|close-previous>]"
|
|
83
|
-
fi
|
|
84
|
-
|
|
85
|
-
# Reject directory-traversal in --action-path, except for https:// URLs.
|
|
86
|
-
if [[ -n "$ACTION_PATH" ]] && [[ "$ACTION_PATH" != https://* ]]; then
|
|
87
|
-
if printf '%s' "$ACTION_PATH" | grep -q '\.\.'; then
|
|
88
|
-
die 1 "Invalid action path (path traversal not allowed): ${ACTION_PATH}"
|
|
89
|
-
fi
|
|
90
|
-
fi
|
|
91
|
-
|
|
92
|
-
# ── Run hula schedule ─────────────────────────────────────────────────────────
|
|
93
|
-
|
|
94
|
-
ARGS=()
|
|
95
|
-
if [[ -n "$BUILT_IN" ]]; then
|
|
96
|
-
ARGS+=(--built-in "$BUILT_IN")
|
|
97
|
-
else
|
|
98
|
-
ARGS+=(--action-path "$ACTION_PATH")
|
|
99
|
-
fi
|
|
100
|
-
[[ -n "$ENTRY_POINT" ]] && ARGS+=(--entry-point "$ENTRY_POINT")
|
|
101
|
-
[[ -n "$OUTCOME_TYPE" ]] && ARGS+=(--outcome-type "$OUTCOME_TYPE")
|
|
102
|
-
[[ -n "$SCHEDULE" ]] && ARGS+=(--schedule "$SCHEDULE")
|
|
103
|
-
[[ -n "$PR_POLICY" ]] && ARGS+=(--pr-policy "$PR_POLICY")
|
|
104
|
-
[[ -n "$UPDATE_NOTIFICATION_URL" ]] && ARGS+=(--update-notification-url "$UPDATE_NOTIFICATION_URL")
|
|
105
|
-
|
|
106
|
-
if [[ -n "$SCHEDULE" ]]; then
|
|
107
|
-
printf '⏰ Scheduling action (%s)...\n' "$SCHEDULE" >&2
|
|
108
|
-
else
|
|
109
|
-
printf '🚀 Running action...\n' >&2
|
|
110
|
-
fi
|
|
111
|
-
|
|
112
|
-
OUTPUT=$(hula schedule "${ARGS[@]}" 2>&1) || die 2 "Execute failed: $OUTPUT"
|
|
113
|
-
|
|
114
|
-
# ── Parse identifiers from the human-readable CLI output ─────────────────────
|
|
115
|
-
# Parsing is intentionally tolerant: a missing identifier becomes an empty
|
|
116
|
-
# string and we still report success, falling back to the raw cliOutput.
|
|
117
|
-
|
|
118
|
-
# Distinguish a schedule from a one-off run by the CLI's success banner.
|
|
119
|
-
if printf '%s' "$OUTPUT" | grep -q 'Schedule created successfully'; then
|
|
120
|
-
KIND='schedule'
|
|
121
|
-
elif printf '%s' "$OUTPUT" | grep -q 'Execute action queued successfully'; then
|
|
122
|
-
KIND='run'
|
|
123
|
-
elif [[ -n "$SCHEDULE" ]]; then
|
|
124
|
-
KIND='schedule'
|
|
125
|
-
else
|
|
126
|
-
KIND='run'
|
|
127
|
-
fi
|
|
128
|
-
|
|
129
|
-
# The CLI prints " ID: <id>" for the run/schedule identifier.
|
|
130
|
-
PARSED_ID=$(printf '%s' "$OUTPUT" | grep -E '^\s*ID:' | head -1 | sed -E 's/^\s*ID:\s*//') || true
|
|
131
|
-
SAFE_OUTPUT=$(json_escape "$OUTPUT")
|
|
132
|
-
|
|
133
|
-
if [[ "$KIND" == 'schedule' ]]; then
|
|
134
|
-
# " Cron: <expr>" — fall back to the schedule we sent if the label is absent.
|
|
135
|
-
CRON_EXPR=$(printf '%s' "$OUTPUT" | grep -E '^\s*Cron:' | head -1 | sed -E 's/^\s*Cron:\s*//') || true
|
|
136
|
-
[[ -z "$CRON_EXPR" ]] && CRON_EXPR="$SCHEDULE"
|
|
137
|
-
SAFE_ID=$(json_escape "$PARSED_ID")
|
|
138
|
-
SAFE_CRON=$(json_escape "$CRON_EXPR")
|
|
139
|
-
printf '{"status":"success","kind":"schedule","scheduleId":"%s","cronExpr":"%s","cliOutput":"%s"}\n' \
|
|
140
|
-
"$SAFE_ID" "$SAFE_CRON" "$SAFE_OUTPUT"
|
|
141
|
-
else
|
|
142
|
-
# " PR: <url>"
|
|
143
|
-
PR_URL=$(printf '%s' "$OUTPUT" | grep -E '^\s*PR:' | head -1 | sed -E 's/^\s*PR:\s*//') || true
|
|
144
|
-
SAFE_ID=$(json_escape "$PARSED_ID")
|
|
145
|
-
SAFE_PR=$(json_escape "$PR_URL")
|
|
146
|
-
printf '{"status":"success","kind":"run","runId":"%s","prUrl":"%s","cliOutput":"%s"}\n' \
|
|
147
|
-
"$SAFE_ID" "$SAFE_PR" "$SAFE_OUTPUT"
|
|
148
|
-
fi
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# hula-session-hook.sh — Claude Code PreToolUse hook (matcher: Bash).
|
|
3
|
-
#
|
|
4
|
-
# Captures the Claude Code session id of the chat session that runs
|
|
5
|
-
# /hula-launch, so the hula CLI can send it to hula-server as
|
|
6
|
-
# `clientSessionId` (launch-session provenance; hula-server PR #419).
|
|
7
|
-
#
|
|
8
|
-
# Why a hook: bash subprocesses cannot learn the session id any other way —
|
|
9
|
-
# CLAUDE_SESSION_ID is not exported to the Bash tool, and the model does not
|
|
10
|
-
# know its own id. Hooks are the one place Claude Code provides it.
|
|
11
|
-
#
|
|
12
|
-
# Installed to .github/scripts/ and registered under hooks.PreToolUse in the
|
|
13
|
-
# consumer repo's .claude/settings.json by `hula init`. Costs nothing: it is
|
|
14
|
-
# a local shell script (no tokens, no API calls) that no-ops in ~5ms for any
|
|
15
|
-
# Bash call that is not a hula-launch-run.sh invocation.
|
|
16
|
-
#
|
|
17
|
-
# Input (stdin): hook JSON with .session_id, .cwd and .tool_input.command
|
|
18
|
-
# Output: $HOME/.hublaunch-sessions/<repo-dir-name>/<branch>.json
|
|
19
|
-
# Always exits 0 — a capture failure must never block the tool call.
|
|
20
|
-
|
|
21
|
-
INPUT="$(cat)"
|
|
22
|
-
|
|
23
|
-
CMD="$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)"
|
|
24
|
-
case "$CMD" in
|
|
25
|
-
*hula-launch-run.sh*) ;; # our target — continue
|
|
26
|
-
*) exit 0 ;; # any other Bash call — ignore
|
|
27
|
-
esac
|
|
28
|
-
|
|
29
|
-
SESSION_ID="$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)"
|
|
30
|
-
CWD="$(printf '%s' "$INPUT" | jq -r '.cwd // empty' 2>/dev/null)"
|
|
31
|
-
[ -z "$SESSION_ID" ] && exit 0
|
|
32
|
-
|
|
33
|
-
# --- extract the branch from the launch command ------------------------------
|
|
34
|
-
# Forms (see hula-launch-run.sh usage):
|
|
35
|
-
# bash .github/scripts/hula-launch-run.sh <plan-path> <branch> [--handoff u] [--test] [--kill-and-relaunch]
|
|
36
|
-
# bash .github/scripts/hula-launch-run.sh <branch> --kill (no new task -> skip)
|
|
37
|
-
AFTER="${CMD#*hula-launch-run.sh}"
|
|
38
|
-
# shellcheck disable=SC2086
|
|
39
|
-
set -- $AFTER
|
|
40
|
-
|
|
41
|
-
POSITIONAL=()
|
|
42
|
-
KILL_ONLY=false
|
|
43
|
-
while [ $# -gt 0 ]; do
|
|
44
|
-
case "$1" in
|
|
45
|
-
--handoff) shift 2 || break ;;
|
|
46
|
-
--kill) KILL_ONLY=true; shift ;;
|
|
47
|
-
--test|--kill-and-relaunch) shift ;;
|
|
48
|
-
--*) shift ;;
|
|
49
|
-
*) POSITIONAL+=("$1"); shift ;;
|
|
50
|
-
esac
|
|
51
|
-
done
|
|
52
|
-
|
|
53
|
-
$KILL_ONLY && exit 0 # pure kill: no new task launched
|
|
54
|
-
[ "${#POSITIONAL[@]}" -lt 2 ] && exit 0 # need <plan-path> <branch>
|
|
55
|
-
BRANCH="${POSITIONAL[1]}"
|
|
56
|
-
# Keep only filename-safe chars; fold any path separator into '-'.
|
|
57
|
-
BRANCH="$(printf '%s' "$BRANCH" | tr -cd 'A-Za-z0-9._/-' | tr '/' '-')"
|
|
58
|
-
[ -z "$BRANCH" ] && exit 0
|
|
59
|
-
|
|
60
|
-
REPO_NAME="$(basename "${CWD:-$PWD}")"
|
|
61
|
-
OUT_DIR="$HOME/.hublaunch-sessions/$REPO_NAME"
|
|
62
|
-
mkdir -p "$OUT_DIR"
|
|
63
|
-
|
|
64
|
-
jq -n --arg sid "$SESSION_ID" --arg branch "$BRANCH" --arg cwd "$CWD" \
|
|
65
|
-
'{sessionId:$sid, branch:$branch, cwd:$cwd, recordedAt:(now|todate)}' \
|
|
66
|
-
> "$OUT_DIR/$BRANCH.json" 2>/dev/null
|
|
67
|
-
|
|
68
|
-
exit 0
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# hula-verify-gather.sh — Steps 2-5 of the hula.verify workflow
|
|
3
|
-
# Usage: bash .github/scripts/hula-verify-gather.sh <issue-number>
|
|
4
|
-
#
|
|
5
|
-
# Fetches issue details, finds associated PR, gathers PR files and diff.
|
|
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
|
-
|
|
29
|
-
while [[ $# -gt 0 ]]; do
|
|
30
|
-
case "$1" in
|
|
31
|
-
[0-9]*)
|
|
32
|
-
ISSUE_NUMBER="$1"; shift ;;
|
|
33
|
-
*)
|
|
34
|
-
die 1 "Unknown argument: $1" ;;
|
|
35
|
-
esac
|
|
36
|
-
done
|
|
37
|
-
|
|
38
|
-
if [[ -z "$ISSUE_NUMBER" ]]; then
|
|
39
|
-
die 1 "Usage: bash .github/scripts/hula-verify-gather.sh <issue-number>"
|
|
40
|
-
fi
|
|
41
|
-
|
|
42
|
-
# ── Step 2: Fetch issue details ────────────────────────────────────────────────
|
|
43
|
-
|
|
44
|
-
printf '🔍 Fetching issue #%s...\n' "$ISSUE_NUMBER" >&2
|
|
45
|
-
|
|
46
|
-
ISSUE_JSON=$(gh issue view "$ISSUE_NUMBER" --json title,body,number,url,state 2>/dev/null) \
|
|
47
|
-
|| die 1 "Issue #${ISSUE_NUMBER} not found or not accessible."
|
|
48
|
-
|
|
49
|
-
ISSUE_TITLE=$(printf '%s' "$ISSUE_JSON" | grep -o '"title":"[^"]*"' | head -1 | sed 's/"title":"//;s/"$//')
|
|
50
|
-
ISSUE_STATE=$(printf '%s' "$ISSUE_JSON" | grep -o '"state":"[^"]*"' | head -1 | sed 's/"state":"//;s/"$//')
|
|
51
|
-
ISSUE_URL=$(printf '%s' "$ISSUE_JSON" | grep -o '"url":"[^"]*"' | head -1 | sed 's/"url":"//;s/"$//')
|
|
52
|
-
|
|
53
|
-
# ── Step 3: Find associated PR ────────────────────────────────────────────────
|
|
54
|
-
|
|
55
|
-
printf '🔗 Looking for PR linked to issue #%s...\n' "$ISSUE_NUMBER" >&2
|
|
56
|
-
|
|
57
|
-
# Try linked search first (works when PR is formally linked via GitHub)
|
|
58
|
-
PR_NUMBER=$(gh pr list --search "linked:${ISSUE_NUMBER}" --json number -q '.[0].number' 2>/dev/null) || true
|
|
59
|
-
|
|
60
|
-
if [[ -z "$PR_NUMBER" ]]; then
|
|
61
|
-
# Fallback: search PR bodies for issue references
|
|
62
|
-
# Handles both short format (#N) and full-path format (owner/repo#N)
|
|
63
|
-
# Uses gh's built-in jq (-q) — no external jq required
|
|
64
|
-
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")) | .number'
|
|
65
|
-
PR_NUMBER=$(gh pr list --json number,body --limit 100 -q "$JQ_EXPR" 2>/dev/null | head -1) || true
|
|
66
|
-
fi
|
|
67
|
-
|
|
68
|
-
if [[ -z "$PR_NUMBER" ]]; then
|
|
69
|
-
die 1 "No PR found for issue #${ISSUE_NUMBER}. The implementation may not have started yet."
|
|
70
|
-
fi
|
|
71
|
-
|
|
72
|
-
# Fetch full PR details via pr view (reliable single-object JSON)
|
|
73
|
-
PR_DETAILS=$(gh pr view "$PR_NUMBER" --json number,title,url,state,headRefName,isDraft 2>/dev/null) \
|
|
74
|
-
|| die 2 "Failed to fetch details for PR #${PR_NUMBER}."
|
|
75
|
-
|
|
76
|
-
PR_TITLE=$(printf '%s' "$PR_DETAILS" | grep -o '"title":"[^"]*"' | head -1 | sed 's/"title":"//;s/"$//')
|
|
77
|
-
PR_URL=$(printf '%s' "$PR_DETAILS" | grep -o '"url":"[^"]*"' | head -1 | sed 's/"url":"//;s/"$//')
|
|
78
|
-
PR_STATE=$(printf '%s' "$PR_DETAILS" | grep -o '"state":"[^"]*"' | head -1 | sed 's/"state":"//;s/"$//')
|
|
79
|
-
PR_BRANCH=$(printf '%s' "$PR_DETAILS" | grep -o '"headRefName":"[^"]*"' | head -1 | sed 's/"headRefName":"//;s/"$//')
|
|
80
|
-
PR_DRAFT=$(printf '%s' "$PR_DETAILS" | grep -o '"isDraft":[a-z]*' | head -1 | sed 's/"isDraft"://')
|
|
81
|
-
|
|
82
|
-
# ── Step 4: Fetch PR file changes ─────────────────────────────────────────────
|
|
83
|
-
|
|
84
|
-
printf '📊 Fetching changed files for PR #%s...\n' "$PR_NUMBER" >&2
|
|
85
|
-
|
|
86
|
-
PR_FILES_JSON=$(gh pr view "$PR_NUMBER" --json files,additions,deletions 2>/dev/null) \
|
|
87
|
-
|| die 2 "Failed to fetch PR files for PR #${PR_NUMBER}."
|
|
88
|
-
|
|
89
|
-
TOTAL_ADDITIONS=$(printf '%s' "$PR_FILES_JSON" | grep -o '"additions":[0-9]*' | tail -1 | sed 's/"additions"://')
|
|
90
|
-
TOTAL_DELETIONS=$(printf '%s' "$PR_FILES_JSON" | grep -o '"deletions":[0-9]*' | tail -1 | sed 's/"deletions"://')
|
|
91
|
-
|
|
92
|
-
# Count files by category (rough heuristic)
|
|
93
|
-
FILES_LIST=$(printf '%s' "$PR_FILES_JSON" | grep -o '"path":"[^"]*"' | sed 's/"path":"//;s/"$//')
|
|
94
|
-
|
|
95
|
-
CODE_COUNT=0
|
|
96
|
-
TEST_COUNT=0
|
|
97
|
-
DOC_COUNT=0
|
|
98
|
-
CONFIG_COUNT=0
|
|
99
|
-
TOTAL_COUNT=0
|
|
100
|
-
|
|
101
|
-
while IFS= read -r fpath; do
|
|
102
|
-
[[ -z "$fpath" ]] && continue
|
|
103
|
-
TOTAL_COUNT=$((TOTAL_COUNT + 1))
|
|
104
|
-
case "$fpath" in
|
|
105
|
-
*.test.*|*.spec.*|*/__tests__/*|*/test/*|*/tests/*) TEST_COUNT=$((TEST_COUNT + 1)) ;;
|
|
106
|
-
*.md|docs/*|README*|CHANGELOG*|DOCUMENTATION*) DOC_COUNT=$((DOC_COUNT + 1)) ;;
|
|
107
|
-
package.json|package-lock.json|tsconfig*|*.config.*|*.json|*.yaml|*.yml) CONFIG_COUNT=$((CONFIG_COUNT + 1)) ;;
|
|
108
|
-
*) CODE_COUNT=$((CODE_COUNT + 1)) ;;
|
|
109
|
-
esac
|
|
110
|
-
done <<< "$FILES_LIST"
|
|
111
|
-
|
|
112
|
-
# ── Step 5: Fetch PR diff ─────────────────────────────────────────────────────
|
|
113
|
-
|
|
114
|
-
printf '📄 Fetching PR diff...\n' >&2
|
|
115
|
-
|
|
116
|
-
DIFF_FILE="/tmp/hula-verify-diff-${PR_NUMBER}.patch"
|
|
117
|
-
gh pr diff "$PR_NUMBER" > "$DIFF_FILE" 2>/dev/null \
|
|
118
|
-
|| printf '⚠️ Could not fetch PR diff (PR may be too large or not accessible)\n' >&2
|
|
119
|
-
|
|
120
|
-
# ── Output result ─────────────────────────────────────────────────────────────
|
|
121
|
-
|
|
122
|
-
SAFE_ISSUE_TITLE=$(json_escape "$ISSUE_TITLE")
|
|
123
|
-
SAFE_PR_TITLE=$(json_escape "$PR_TITLE")
|
|
124
|
-
SAFE_PR_URL=$(json_escape "$PR_URL")
|
|
125
|
-
SAFE_ISSUE_URL=$(json_escape "$ISSUE_URL")
|
|
126
|
-
SAFE_PR_BRANCH=$(json_escape "$PR_BRANCH")
|
|
127
|
-
SAFE_DIFF_FILE=$(json_escape "$DIFF_FILE")
|
|
128
|
-
PR_DRAFT_VAL="${PR_DRAFT:-false}"
|
|
129
|
-
|
|
130
|
-
printf '{"status":"success","issueNumber":%s,"issueTitle":"%s","issueState":"%s","issueUrl":"%s","prNumber":%s,"prTitle":"%s","prUrl":"%s","prState":"%s","prBranch":"%s","prIsDraft":%s,"diffFile":"%s","filesChanged":{"total":%d,"code":%d,"test":%d,"doc":%d,"config":%d},"lines":{"additions":%s,"deletions":%s}}\n' \
|
|
131
|
-
"$ISSUE_NUMBER" "$SAFE_ISSUE_TITLE" "$ISSUE_STATE" "$SAFE_ISSUE_URL" \
|
|
132
|
-
"$PR_NUMBER" "$SAFE_PR_TITLE" "$SAFE_PR_URL" "$PR_STATE" "$SAFE_PR_BRANCH" "$PR_DRAFT_VAL" \
|
|
133
|
-
"$SAFE_DIFF_FILE" \
|
|
134
|
-
"$TOTAL_COUNT" "$CODE_COUNT" "$TEST_COUNT" "$DOC_COUNT" "$CONFIG_COUNT" \
|
|
135
|
-
"${TOTAL_ADDITIONS:-0}" "${TOTAL_DELETIONS:-0}"
|