@kody-ade/kody-engine 0.4.224 → 0.4.226
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/README.md +2 -2
- package/dist/bin/kody.js +825 -832
- package/dist/executables/run/profile.json +1 -1
- package/dist/executables/types.ts +3 -1
- package/package.json +1 -1
- package/dist/duties/duty-scheduler/duty.md +0 -3
- package/dist/duties/duty-scheduler/profile.json +0 -6
- package/dist/duties/duty-tick/duty.md +0 -3
- package/dist/duties/duty-tick/profile.json +0 -6
- package/dist/duties/duty-tick-scripted/duty.md +0 -3
- package/dist/duties/duty-tick-scripted/profile.json +0 -6
- package/dist/duties/fix/duty.md +0 -10
- package/dist/duties/fix/profile.json +0 -6
- package/dist/duties/fix-ci/duty.md +0 -10
- package/dist/duties/fix-ci/profile.json +0 -6
- package/dist/duties/goal-scheduler/duty.md +0 -3
- package/dist/duties/goal-scheduler/profile.json +0 -6
- package/dist/duties/goal-tick/duty.md +0 -3
- package/dist/duties/goal-tick/profile.json +0 -6
- package/dist/duties/init/duty.md +0 -10
- package/dist/duties/init/profile.json +0 -6
- package/dist/duties/job-live-verify/duty.md +0 -3
- package/dist/duties/job-live-verify/profile.json +0 -6
- package/dist/duties/merge/duty.md +0 -10
- package/dist/duties/merge/profile.json +0 -6
- package/dist/duties/npm-publish/duty.md +0 -17
- package/dist/duties/npm-publish/profile.json +0 -6
- package/dist/duties/plan-verify/duty.md +0 -3
- package/dist/duties/plan-verify/profile.json +0 -6
- package/dist/duties/preview-build/duty.md +0 -10
- package/dist/duties/preview-build/profile.json +0 -6
- package/dist/duties/probe-skill/duty.md +0 -3
- package/dist/duties/probe-skill/profile.json +0 -6
- package/dist/duties/qa-goal/duty.md +0 -3
- package/dist/duties/qa-goal/profile.json +0 -6
- package/dist/duties/release/duty.md +0 -10
- package/dist/duties/release/profile.json +0 -6
- package/dist/duties/release-deploy/duty.md +0 -10
- package/dist/duties/release-deploy/profile.json +0 -6
- package/dist/duties/release-prepare/duty.md +0 -10
- package/dist/duties/release-prepare/profile.json +0 -6
- package/dist/duties/release-publish/duty.md +0 -10
- package/dist/duties/release-publish/profile.json +0 -6
- package/dist/duties/resolve/duty.md +0 -10
- package/dist/duties/resolve/profile.json +0 -6
- package/dist/duties/revert/duty.md +0 -10
- package/dist/duties/revert/profile.json +0 -6
- package/dist/duties/sync/duty.md +0 -10
- package/dist/duties/sync/profile.json +0 -6
- package/dist/duties/task-job-fail-once/duty.md +0 -3
- package/dist/duties/task-job-fail-once/profile.json +0 -6
- package/dist/duties/task-job-pass-a/duty.md +0 -3
- package/dist/duties/task-job-pass-a/profile.json +0 -6
- package/dist/duties/task-job-pass-b/duty.md +0 -3
- package/dist/duties/task-job-pass-b/profile.json +0 -6
- package/dist/duties/task-jobs/duty.md +0 -3
- package/dist/duties/task-jobs/profile.json +0 -6
- package/dist/duties/worker-ask/duty.md +0 -10
- package/dist/duties/worker-ask/profile.json +0 -6
- package/dist/executables/duty-scheduler/profile.json +0 -57
- package/dist/executables/duty-tick/profile.json +0 -78
- package/dist/executables/duty-tick/prompt.md +0 -77
- package/dist/executables/duty-tick/prompts/locked.md +0 -50
- package/dist/executables/duty-tick-scripted/profile.json +0 -72
- package/dist/executables/fix/profile.json +0 -91
- package/dist/executables/fix/prompt.md +0 -90
- package/dist/executables/fix-ci/profile.json +0 -71
- package/dist/executables/fix-ci/prompt.md +0 -78
- package/dist/executables/goal-scheduler/profile.json +0 -53
- package/dist/executables/goal-scheduler/scheduler.sh +0 -82
- package/dist/executables/goal-tick/profile.json +0 -69
- package/dist/executables/init/profile.json +0 -36
- package/dist/executables/job-live-verify/profile.json +0 -36
- package/dist/executables/job-live-verify/prompt.md +0 -42
- package/dist/executables/merge/profile.json +0 -54
- package/dist/executables/npm-publish/profile.json +0 -73
- package/dist/executables/npm-publish/publish.sh +0 -93
- package/dist/executables/plan-verify/profile.json +0 -38
- package/dist/executables/plan-verify/prompt.md +0 -72
- package/dist/executables/preview-build/profile.json +0 -61
- package/dist/executables/preview-health/profile.json +0 -48
- package/dist/executables/preview-health/tick.sh +0 -319
- package/dist/executables/probe-skill/profile.json +0 -35
- package/dist/executables/probe-skill/prompt.md +0 -31
- package/dist/executables/probe-skill/skills/probe-skill-marker/SKILL.md +0 -18
- package/dist/executables/qa-goal/profile.json +0 -65
- package/dist/executables/release/deploy.sh +0 -157
- package/dist/executables/release/prepare.sh +0 -353
- package/dist/executables/release/profile.json +0 -95
- package/dist/executables/release/publish.sh +0 -88
- package/dist/executables/release/release.sh +0 -230
- package/dist/executables/release/wait.sh +0 -88
- package/dist/executables/release-deploy/deploy.sh +0 -222
- package/dist/executables/release-deploy/profile.json +0 -53
- package/dist/executables/release-prepare/prepare.sh +0 -368
- package/dist/executables/release-prepare/profile.json +0 -70
- package/dist/executables/release-publish/profile.json +0 -53
- package/dist/executables/release-publish/publish.sh +0 -137
- package/dist/executables/resolve/apply-prefer.sh +0 -54
- package/dist/executables/resolve/profile.json +0 -107
- package/dist/executables/resolve/prompt.md +0 -51
- package/dist/executables/revert/profile.json +0 -82
- package/dist/executables/revert/revert.sh +0 -33
- package/dist/executables/sync/profile.json +0 -48
- package/dist/executables/task-job-fail-once/profile.json +0 -44
- package/dist/executables/task-job-fail-once/prompt.md +0 -1
- package/dist/executables/task-job-pass-a/profile.json +0 -43
- package/dist/executables/task-job-pass-a/prompt.md +0 -1
- package/dist/executables/task-job-pass-b/profile.json +0 -43
- package/dist/executables/task-job-pass-b/prompt.md +0 -1
- package/dist/executables/task-jobs/profile.json +0 -41
- package/dist/executables/task-jobs/prompt.md +0 -1
- package/dist/executables/worker-ask/profile.json +0 -70
- package/dist/executables/worker-ask/prompt.md +0 -60
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# release/release.sh — single-job release driver.
|
|
4
|
-
#
|
|
5
|
-
# Replaces the orchestrator + 3-executable chain with one linear bash flow:
|
|
6
|
-
# prepare → wait CI → merge → publish → deploy → notify
|
|
7
|
-
#
|
|
8
|
-
# Inputs (env, set by the executor):
|
|
9
|
-
# KODY_ARG_ISSUE triggering issue number (required)
|
|
10
|
-
# KODY_ARG_BUMP patch|minor|major (default: patch)
|
|
11
|
-
# KODY_ARG_DRY_RUN true|false
|
|
12
|
-
# KODY_ARG_PREFER ours|theirs (optional; for branch collision)
|
|
13
|
-
#
|
|
14
|
-
# Config (env, flattened from kody.config.json):
|
|
15
|
-
# KODY_CFG_GIT_DEFAULTBRANCH e.g. dev
|
|
16
|
-
# KODY_CFG_RELEASE_RELEASEBRANCH e.g. main (unset → deploy is no-op)
|
|
17
|
-
# KODY_CFG_RELEASE_VERSIONFILES JSON array
|
|
18
|
-
# KODY_CFG_RELEASE_PUBLISHCOMMAND optional; $VERSION substituted
|
|
19
|
-
# KODY_CFG_RELEASE_NOTIFYCOMMAND optional; $VERSION + $DEPLOY_PR_URL substituted
|
|
20
|
-
# KODY_CFG_RELEASE_DRAFTRELEASE "true" → create as draft
|
|
21
|
-
# KODY_CFG_RELEASE_TIMEOUTMS per-command timeout in ms
|
|
22
|
-
#
|
|
23
|
-
# Stdout signals:
|
|
24
|
-
# KODY_PR_URL=<deploy PR url>
|
|
25
|
-
# KODY_REASON=<text>
|
|
26
|
-
# KODY_SKIP_AGENT=true
|
|
27
|
-
# RELEASE_COMPLETED=true | RELEASE_FAILED=true (consumed by recordOutcome → finishFlow)
|
|
28
|
-
|
|
29
|
-
set -euo pipefail
|
|
30
|
-
|
|
31
|
-
HERE="$(dirname "$0")"
|
|
32
|
-
# shellcheck source=prepare.sh
|
|
33
|
-
source "$HERE/prepare.sh"
|
|
34
|
-
# shellcheck source=wait.sh
|
|
35
|
-
source "$HERE/wait.sh"
|
|
36
|
-
# shellcheck source=publish.sh
|
|
37
|
-
source "$HERE/publish.sh"
|
|
38
|
-
# shellcheck source=deploy.sh
|
|
39
|
-
source "$HERE/deploy.sh"
|
|
40
|
-
|
|
41
|
-
issue="${KODY_ARG_ISSUE:?required}"
|
|
42
|
-
bump="${KODY_ARG_BUMP:-patch}"
|
|
43
|
-
dry_run="${KODY_ARG_DRY_RUN:-false}"
|
|
44
|
-
prefer="${KODY_ARG_PREFER:-}"
|
|
45
|
-
|
|
46
|
-
default_branch="${KODY_CFG_GIT_DEFAULTBRANCH:-main}"
|
|
47
|
-
release_branch="${KODY_CFG_RELEASE_RELEASEBRANCH:-}"
|
|
48
|
-
notify_cmd="${KODY_CFG_RELEASE_NOTIFYCOMMAND:-}"
|
|
49
|
-
notify_timeout_s=$(( ${KODY_CFG_RELEASE_TIMEOUTMS:-600000} / 1000 ))
|
|
50
|
-
|
|
51
|
-
# Tracks where we were when an error fired, for clearer failure messages.
|
|
52
|
-
current_step="init"
|
|
53
|
-
|
|
54
|
-
on_error() {
|
|
55
|
-
local rc=$?
|
|
56
|
-
echo "[release] FAILED during step '${current_step}' (exit ${rc})" >&2
|
|
57
|
-
echo "KODY_REASON=release failed during ${current_step}"
|
|
58
|
-
echo "RELEASE_FAILED=true"
|
|
59
|
-
echo "KODY_SKIP_AGENT=true"
|
|
60
|
-
exit "$rc"
|
|
61
|
-
}
|
|
62
|
-
trap on_error ERR
|
|
63
|
-
|
|
64
|
-
if [[ ! -f package.json ]]; then
|
|
65
|
-
echo "[release] package.json not found in $(pwd)" >&2
|
|
66
|
-
echo "KODY_REASON=release: package.json not found"
|
|
67
|
-
echo "RELEASE_FAILED=true"
|
|
68
|
-
echo "KODY_SKIP_AGENT=true"
|
|
69
|
-
exit 1
|
|
70
|
-
fi
|
|
71
|
-
|
|
72
|
-
read_pkg_version_from_ref() {
|
|
73
|
-
local ref="$1"
|
|
74
|
-
git show "${ref}:package.json" 2>/dev/null | node -e 'let s=""; process.stdin.on("data", c => s += c); process.stdin.on("end", () => { try { console.log(JSON.parse(s).version || "") } catch { process.exit(1) } })'
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
finish_release_with_deploy() {
|
|
78
|
-
local version="$1"
|
|
79
|
-
local tag="$2"
|
|
80
|
-
local release_url="${3:-}"
|
|
81
|
-
|
|
82
|
-
current_step="deploy"
|
|
83
|
-
set +e
|
|
84
|
-
deploy_pr_url=$(open_deploy_pr "$version" "$issue")
|
|
85
|
-
deploy_rc=$?
|
|
86
|
-
set -e
|
|
87
|
-
if [[ "$deploy_rc" -ne 0 ]]; then
|
|
88
|
-
echo "[release] deploy step failed (rc=${deploy_rc}) — published v${version} but ${default_branch}→${release_branch} promotion PR was not opened" >&2
|
|
89
|
-
echo "KODY_REASON=release v${version}: published, but ${default_branch}→${release_branch} deploy PR failed"
|
|
90
|
-
echo "RELEASE_TAG=${tag}"
|
|
91
|
-
[[ -n "$release_url" ]] && echo "RELEASE_URL=${release_url}"
|
|
92
|
-
echo "RELEASE_FAILED=true"
|
|
93
|
-
exit 1
|
|
94
|
-
fi
|
|
95
|
-
if [[ -z "$deploy_pr_url" ]]; then
|
|
96
|
-
echo " (deploy: no-op — single-branch repo)"
|
|
97
|
-
else
|
|
98
|
-
echo "✓ deploy: ${deploy_pr_url}"
|
|
99
|
-
fi
|
|
100
|
-
|
|
101
|
-
current_step="notify"
|
|
102
|
-
notify_status="skipped"
|
|
103
|
-
if [[ -n "$notify_cmd" ]]; then
|
|
104
|
-
cmd="${notify_cmd//\$VERSION/$version}"
|
|
105
|
-
cmd="${cmd//\$DEPLOY_PR_URL/${deploy_pr_url:-}}"
|
|
106
|
-
echo " notify: ${cmd}"
|
|
107
|
-
if timeout "$notify_timeout_s" bash -c "$cmd"; then
|
|
108
|
-
notify_status="ok"
|
|
109
|
-
else
|
|
110
|
-
notify_status="failed"
|
|
111
|
-
echo "[release] notifyCommand failed (non-fatal)" >&2
|
|
112
|
-
fi
|
|
113
|
-
fi
|
|
114
|
-
|
|
115
|
-
current_step="done"
|
|
116
|
-
[[ -n "$deploy_pr_url" ]] && echo "KODY_PR_URL=${deploy_pr_url}"
|
|
117
|
-
echo "RELEASE_TAG=${tag}"
|
|
118
|
-
[[ -n "$release_url" ]] && echo "RELEASE_URL=${release_url}"
|
|
119
|
-
[[ -n "$deploy_pr_url" ]] && echo "RELEASE_DEPLOY_PR=${deploy_pr_url}"
|
|
120
|
-
echo "KODY_REASON=release v${version} complete (notify=${notify_status})"
|
|
121
|
-
echo "RELEASE_COMPLETED=true"
|
|
122
|
-
echo "KODY_SKIP_AGENT=true"
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
resume_prepared_release_if_needed() {
|
|
126
|
-
[[ -n "$release_branch" && "$release_branch" != "$default_branch" ]] || return 1
|
|
127
|
-
|
|
128
|
-
current_step="resume-check"
|
|
129
|
-
git fetch origin "$default_branch" "$release_branch" --tags
|
|
130
|
-
|
|
131
|
-
local default_version release_version
|
|
132
|
-
default_version=$(read_pkg_version_from_ref "origin/${default_branch}" || echo "")
|
|
133
|
-
release_version=$(read_pkg_version_from_ref "origin/${release_branch}" || echo "")
|
|
134
|
-
[[ -n "$default_version" && -n "$release_version" ]] || return 1
|
|
135
|
-
[[ "$default_version" != "$release_version" ]] || return 1
|
|
136
|
-
|
|
137
|
-
local version="$default_version"
|
|
138
|
-
local tag="v${version}"
|
|
139
|
-
echo "→ release: resuming prepared ${tag}; ${default_branch} is not promoted to ${release_branch}"
|
|
140
|
-
|
|
141
|
-
current_step="publish"
|
|
142
|
-
git checkout "$default_branch"
|
|
143
|
-
git reset --hard "origin/$default_branch"
|
|
144
|
-
|
|
145
|
-
local publish_status release_url
|
|
146
|
-
publish_status=$(tag_and_publish "$version")
|
|
147
|
-
release_url=$(create_gh_release "$tag" || echo "")
|
|
148
|
-
echo "✓ publish: tag=${tag} status=${publish_status} release_url=${release_url:-<none>}"
|
|
149
|
-
if [[ "$publish_status" == "failed" ]]; then
|
|
150
|
-
echo "[release] publishCommand failed but tag + GH release exist" >&2
|
|
151
|
-
echo "KODY_REASON=tag + GH release created, but publishCommand failed"
|
|
152
|
-
echo "RELEASE_FAILED=true"
|
|
153
|
-
echo "KODY_SKIP_AGENT=true"
|
|
154
|
-
exit 1
|
|
155
|
-
fi
|
|
156
|
-
|
|
157
|
-
finish_release_with_deploy "$version" "$tag" "$release_url"
|
|
158
|
-
exit 0
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
resume_prepared_release_if_needed
|
|
162
|
-
|
|
163
|
-
# ── 1. Prepare ────────────────────────────────────────────────────────────
|
|
164
|
-
current_step="prepare"
|
|
165
|
-
old_version=$(read_pkg_version)
|
|
166
|
-
new_version=$(bump_version "$old_version" "$bump")
|
|
167
|
-
tag="v${new_version}"
|
|
168
|
-
echo "→ release: issue=#${issue} bump=${bump} ${old_version} → ${new_version}"
|
|
169
|
-
|
|
170
|
-
if [[ "$dry_run" == "true" ]]; then
|
|
171
|
-
echo "RELEASE_PLAN=bump=${new_version} tag=${tag}"
|
|
172
|
-
echo "KODY_REASON=dry-run — would bump to ${new_version}${prefer:+ (--prefer ${prefer})}"
|
|
173
|
-
echo "RELEASE_COMPLETED=true"
|
|
174
|
-
echo "KODY_SKIP_AGENT=true"
|
|
175
|
-
exit 0
|
|
176
|
-
fi
|
|
177
|
-
|
|
178
|
-
prep_pr_url=$(open_prepare_pr "$new_version" "$issue" "$prefer")
|
|
179
|
-
if [[ -z "$prep_pr_url" ]]; then
|
|
180
|
-
echo "[release] prepare returned no PR URL" >&2
|
|
181
|
-
exit 1
|
|
182
|
-
fi
|
|
183
|
-
echo "✓ prepare: ${prep_pr_url}"
|
|
184
|
-
set_kody_release_pr_marker "$issue" "$prep_pr_url"
|
|
185
|
-
|
|
186
|
-
# ── 2. Wait for prepare PR CI ─────────────────────────────────────────────
|
|
187
|
-
current_step="wait_prepare_ci"
|
|
188
|
-
prep_pr_num="${prep_pr_url##*/}"
|
|
189
|
-
wait_for_ci "$prep_pr_num" 60 || {
|
|
190
|
-
echo "[release] CI failed/timeout on prepare PR #${prep_pr_num}" >&2
|
|
191
|
-
exit 1
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
# ── 3. Merge prepare PR ───────────────────────────────────────────────────
|
|
195
|
-
current_step="merge"
|
|
196
|
-
if gh pr merge "$prep_pr_num" --merge --admin 2>&1; then
|
|
197
|
-
echo "✓ merged: PR #${prep_pr_num}"
|
|
198
|
-
elif gh pr merge "$prep_pr_num" --merge 2>&1 | grep -qi "already merged"; then
|
|
199
|
-
echo " (already merged)"
|
|
200
|
-
else
|
|
201
|
-
echo "[release] gh pr merge failed for PR #${prep_pr_num}" >&2
|
|
202
|
-
exit 1
|
|
203
|
-
fi
|
|
204
|
-
|
|
205
|
-
# ── 4. Publish (tag + GH release) ─────────────────────────────────────────
|
|
206
|
-
current_step="publish"
|
|
207
|
-
git fetch origin "$default_branch" --tags
|
|
208
|
-
git checkout "$default_branch"
|
|
209
|
-
git reset --hard "origin/$default_branch"
|
|
210
|
-
|
|
211
|
-
# Sanity: confirm the bump landed on the integration branch.
|
|
212
|
-
landed_version=$(read_pkg_version)
|
|
213
|
-
if [[ "$landed_version" != "$new_version" ]]; then
|
|
214
|
-
echo "[release] WARN: package.json on ${default_branch} is ${landed_version}, expected ${new_version} after merge" >&2
|
|
215
|
-
fi
|
|
216
|
-
|
|
217
|
-
publish_status=$(tag_and_publish "$new_version")
|
|
218
|
-
release_url=$(create_gh_release "$tag" || echo "")
|
|
219
|
-
echo "✓ publish: tag=${tag} status=${publish_status} release_url=${release_url:-<none>}"
|
|
220
|
-
|
|
221
|
-
if [[ "$publish_status" == "failed" ]]; then
|
|
222
|
-
echo "[release] publishCommand failed but tag + GH release exist" >&2
|
|
223
|
-
echo "KODY_REASON=tag + GH release created, but publishCommand failed"
|
|
224
|
-
echo "RELEASE_FAILED=true"
|
|
225
|
-
echo "KODY_SKIP_AGENT=true"
|
|
226
|
-
exit 1
|
|
227
|
-
fi
|
|
228
|
-
|
|
229
|
-
finish_release_with_deploy "$new_version" "$tag" "$release_url"
|
|
230
|
-
exit 0
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# release/wait.sh — wait_for_ci function: poll a PR's check rollup
|
|
4
|
-
# until all non-skipped checks pass, or timeout.
|
|
5
|
-
#
|
|
6
|
-
# Function: wait_for_ci <pr_number> <timeout_minutes> [poll_seconds] [initial_wait]
|
|
7
|
-
# Returns 0 on CI passed, 1 on CI failed/timeout.
|
|
8
|
-
#
|
|
9
|
-
# Special-case: if a PR has zero registered checks (no CI configured),
|
|
10
|
-
# treats that as PASSED after a short stabilization window — so a Tester
|
|
11
|
-
# repo without checks doesn't loop forever.
|
|
12
|
-
|
|
13
|
-
# shellcheck disable=SC2148
|
|
14
|
-
|
|
15
|
-
wait_for_ci() {
|
|
16
|
-
local pr_number="$1"
|
|
17
|
-
local timeout_minutes="${2:-60}"
|
|
18
|
-
local poll_seconds="${3:-30}"
|
|
19
|
-
local initial_wait="${4:-15}"
|
|
20
|
-
|
|
21
|
-
if [[ -z "$pr_number" || ! "$pr_number" =~ ^[0-9]+$ ]]; then
|
|
22
|
-
echo "[wait_for_ci] invalid pr_number: '$pr_number'" >&2
|
|
23
|
-
return 1
|
|
24
|
-
fi
|
|
25
|
-
|
|
26
|
-
local deadline=$(( $(date +%s) + timeout_minutes * 60 ))
|
|
27
|
-
echo "→ wait_for_ci: PR #${pr_number}, timeout=${timeout_minutes}m"
|
|
28
|
-
|
|
29
|
-
sleep "$initial_wait"
|
|
30
|
-
|
|
31
|
-
# Track consecutive "no checks" results so we don't bail prematurely
|
|
32
|
-
# on a PR that's just slow to register checks.
|
|
33
|
-
local empty_count=0
|
|
34
|
-
local empty_threshold=4 # ~2 minutes (4 * poll_seconds=30s) before treating as no-CI
|
|
35
|
-
|
|
36
|
-
while (( $(date +%s) < deadline )); do
|
|
37
|
-
local raw
|
|
38
|
-
if ! raw=$(gh pr checks "$pr_number" --json state 2>/dev/null); then
|
|
39
|
-
# gh exits non-zero when there are no checks — treat as no-CI.
|
|
40
|
-
empty_count=$((empty_count + 1))
|
|
41
|
-
echo " [wait_for_ci] gh pr checks returned non-zero (count=${empty_count})"
|
|
42
|
-
if (( empty_count >= empty_threshold )); then
|
|
43
|
-
echo "→ wait_for_ci: PR #${pr_number} has no CI checks configured — treating as passed"
|
|
44
|
-
return 0
|
|
45
|
-
fi
|
|
46
|
-
sleep "$poll_seconds"
|
|
47
|
-
continue
|
|
48
|
-
fi
|
|
49
|
-
|
|
50
|
-
# Empty array? Same path.
|
|
51
|
-
local total
|
|
52
|
-
total=$(printf '%s' "$raw" | jq 'length' 2>/dev/null || echo "0")
|
|
53
|
-
if [[ "$total" == "0" ]]; then
|
|
54
|
-
empty_count=$((empty_count + 1))
|
|
55
|
-
echo " [wait_for_ci] no checks registered yet (count=${empty_count})"
|
|
56
|
-
if (( empty_count >= empty_threshold )); then
|
|
57
|
-
echo "→ wait_for_ci: PR #${pr_number} has no CI checks configured — treating as passed"
|
|
58
|
-
return 0
|
|
59
|
-
fi
|
|
60
|
-
sleep "$poll_seconds"
|
|
61
|
-
continue
|
|
62
|
-
fi
|
|
63
|
-
empty_count=0
|
|
64
|
-
|
|
65
|
-
# Tally states via jq.
|
|
66
|
-
local pending failed passed failed_names
|
|
67
|
-
pending=$(printf '%s' "$raw" | jq '[.[] | select((.state // "") | IN("PENDING","IN_PROGRESS","QUEUED",""))] | length')
|
|
68
|
-
failed=$(printf '%s' "$raw" | jq '[.[] | select((.state // "") | IN("FAILURE","CANCELLED","TIMED_OUT","ACTION_REQUIRED","STARTUP_FAILURE"))] | length')
|
|
69
|
-
passed=$(printf '%s' "$raw" | jq '[.[] | select((.state // "") | IN("SUCCESS","SKIPPED","NEUTRAL"))] | length')
|
|
70
|
-
failed_names=$(printf '%s' "$raw" | jq -r '[.[] | select((.state // "") | IN("FAILURE","CANCELLED","TIMED_OUT","ACTION_REQUIRED","STARTUP_FAILURE")) | .name] | join(",")')
|
|
71
|
-
|
|
72
|
-
echo " [wait_for_ci] pending=${pending} passed=${passed} failed=${failed} (total=${total})"
|
|
73
|
-
|
|
74
|
-
if [[ "$failed" -gt 0 ]]; then
|
|
75
|
-
echo "[wait_for_ci] CI failed on PR #${pr_number}: ${failed_names}" >&2
|
|
76
|
-
return 1
|
|
77
|
-
fi
|
|
78
|
-
if [[ "$pending" -eq 0 && "$passed" -gt 0 ]]; then
|
|
79
|
-
echo "→ wait_for_ci: all checks passed (${passed}) on PR #${pr_number}"
|
|
80
|
-
return 0
|
|
81
|
-
fi
|
|
82
|
-
|
|
83
|
-
sleep "$poll_seconds"
|
|
84
|
-
done
|
|
85
|
-
|
|
86
|
-
echo "[wait_for_ci] timeout after ${timeout_minutes}m on PR #${pr_number}" >&2
|
|
87
|
-
return 1
|
|
88
|
-
}
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# release-deploy: open a PR from the integration branch (git.defaultBranch)
|
|
4
|
-
# into the production target (release.releaseBranch) — the human gate for
|
|
5
|
-
# production deploy. The orchestrator's chain ENDS with this PR opened;
|
|
6
|
-
# merging it is a manual step. No agent.
|
|
7
|
-
#
|
|
8
|
-
# Behavior:
|
|
9
|
-
# - If release.releaseBranch is unset OR equals git.defaultBranch:
|
|
10
|
-
# no-op success (single-branch repos have nothing to deploy).
|
|
11
|
-
# - Else: idempotently open PR `defaultBranch` → `releaseBranch`. If an
|
|
12
|
-
# open PR for that pair already exists, reuse its URL.
|
|
13
|
-
#
|
|
14
|
-
# After the PR is opened, runs `release.notifyCommand` (if set) as a
|
|
15
|
-
# best-effort post-deploy hook.
|
|
16
|
-
#
|
|
17
|
-
# Inputs (env):
|
|
18
|
-
# KODY_ARG_DRY_RUN true|false
|
|
19
|
-
# KODY_ARG_ISSUE triggering issue/PR number (optional)
|
|
20
|
-
#
|
|
21
|
-
# Config (env):
|
|
22
|
-
# KODY_CFG_GIT_DEFAULTBRANCH e.g. dev
|
|
23
|
-
# KODY_CFG_RELEASE_RELEASEBRANCH e.g. main (unset → no-op)
|
|
24
|
-
# KODY_CFG_RELEASE_NOTIFYCOMMAND optional; $VERSION substituted
|
|
25
|
-
# KODY_CFG_RELEASE_TIMEOUTMS per-command timeout in ms (default 600000)
|
|
26
|
-
#
|
|
27
|
-
# Stdout signals:
|
|
28
|
-
# KODY_PR_URL=<deploy PR url>
|
|
29
|
-
# KODY_REASON=<text>
|
|
30
|
-
# KODY_SKIP_AGENT=true
|
|
31
|
-
|
|
32
|
-
set -euo pipefail
|
|
33
|
-
|
|
34
|
-
dry_run="${KODY_ARG_DRY_RUN:-false}"
|
|
35
|
-
default_branch="${KODY_CFG_GIT_DEFAULTBRANCH:-main}"
|
|
36
|
-
release_branch="${KODY_CFG_RELEASE_RELEASEBRANCH:-}"
|
|
37
|
-
notify_cmd="${KODY_CFG_RELEASE_NOTIFYCOMMAND:-}"
|
|
38
|
-
timeout_ms="${KODY_CFG_RELEASE_TIMEOUTMS:-600000}"
|
|
39
|
-
timeout_s=$((timeout_ms / 1000))
|
|
40
|
-
|
|
41
|
-
# Read version from the integration branch tip (where the bump commit lives),
|
|
42
|
-
# not from the local working tree (the workflow may have checked out another
|
|
43
|
-
# branch). Falls back to local package.json if the fetch fails.
|
|
44
|
-
read_version() {
|
|
45
|
-
local branch="$1"
|
|
46
|
-
if git fetch origin "$branch" --quiet 2>/dev/null; then
|
|
47
|
-
if pkg=$(git show "origin/${branch}:package.json" 2>/dev/null); then
|
|
48
|
-
echo "$pkg" | python3 -c "import json,sys; print(json.load(sys.stdin)['version'])" 2>/dev/null && return
|
|
49
|
-
fi
|
|
50
|
-
fi
|
|
51
|
-
python3 -c "import json; print(json.load(open('package.json'))['version'])" 2>/dev/null || echo "unknown"
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
version=$(read_version "$default_branch")
|
|
55
|
-
echo "→ release deploy: v${version}"
|
|
56
|
-
|
|
57
|
-
# Read the CHANGELOG section for $version from the integration branch
|
|
58
|
-
# (where release-prepare just committed it). Handles both header shapes
|
|
59
|
-
# observed in the wild: `## [0.25.0] - 2026-04-15` and `## 0.22.0 (...)`.
|
|
60
|
-
# Prints the body lines (without the matched header). Empty stdout =
|
|
61
|
-
# fall back to the minimal PR body — never break the release.
|
|
62
|
-
read_changelog_section() {
|
|
63
|
-
local branch="$1" ver="$2" raw=""
|
|
64
|
-
if ! raw=$(git show "origin/${branch}:CHANGELOG.md" 2>/dev/null); then
|
|
65
|
-
return 0
|
|
66
|
-
fi
|
|
67
|
-
printf '%s' "$raw" | awk -v ver="$ver" '
|
|
68
|
-
BEGIN { capture = 0 }
|
|
69
|
-
# Match all observed header shapes:
|
|
70
|
-
# "## [0.25.0] - 2026-04-15" (bracketed, dash separator)
|
|
71
|
-
# "## 0.22.0 (2026-03-25)" (bare, parenthesized date)
|
|
72
|
-
# "## v0.25.5 — 2026-05-05" (v-prefixed, em-dash, kody release-prepare style)
|
|
73
|
-
/^##[[:space:]]/ {
|
|
74
|
-
if (capture) { exit }
|
|
75
|
-
header = $0
|
|
76
|
-
sub(/^##[[:space:]]+/, "", header)
|
|
77
|
-
sub(/^\[/, "", header); sub(/\].*/, "", header)
|
|
78
|
-
sub(/[[:space:]].*/, "", header)
|
|
79
|
-
sub(/[(].*/, "", header)
|
|
80
|
-
sub(/^v/, "", header)
|
|
81
|
-
if (header == ver) { capture = 1; next }
|
|
82
|
-
}
|
|
83
|
-
capture { print }
|
|
84
|
-
' | awk '
|
|
85
|
-
# Trim leading and trailing blank lines from the captured block.
|
|
86
|
-
NF { if (!started) started = 1; out[++n] = $0; last = n; next }
|
|
87
|
-
started { out[++n] = $0 }
|
|
88
|
-
END { for (i = 1; i <= last; i++) print out[i] }
|
|
89
|
-
'
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
changelog_section=$(read_changelog_section "$default_branch" "$version" || true)
|
|
93
|
-
if [[ -z "$changelog_section" ]]; then
|
|
94
|
-
echo " no CHANGELOG section for v${version} on origin/${default_branch} — using minimal PR body"
|
|
95
|
-
fi
|
|
96
|
-
|
|
97
|
-
# Build the kody-managed body block. A marker pair lets us update the
|
|
98
|
-
# section idempotently on re-runs without clobbering anything a human
|
|
99
|
-
# pasted outside the markers.
|
|
100
|
-
build_pr_body() {
|
|
101
|
-
local tracking_line="$1"
|
|
102
|
-
printf 'Automated deploy PR opened by kody — promotes `%s` to `%s` for release **v%s**.\n\n' \
|
|
103
|
-
"$default_branch" "$release_branch" "$version"
|
|
104
|
-
if [[ -n "$changelog_section" ]]; then
|
|
105
|
-
printf '<!-- kody-changelog-start -->\n## What'\''s changing in v%s\n\n%s\n<!-- kody-changelog-end -->\n\n' \
|
|
106
|
-
"$version" "$changelog_section"
|
|
107
|
-
fi
|
|
108
|
-
printf 'Merge this PR to deploy v%s to `%s`.%s\n' \
|
|
109
|
-
"$version" "$release_branch" "$tracking_line"
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
# Single-branch repos: nothing to deploy.
|
|
113
|
-
if [[ -z "$release_branch" || "$release_branch" == "$default_branch" ]]; then
|
|
114
|
-
echo "KODY_REASON=no releaseBranch configured (or equals defaultBranch) — nothing to deploy"
|
|
115
|
-
echo "KODY_SKIP_AGENT=true"
|
|
116
|
-
exit 0
|
|
117
|
-
fi
|
|
118
|
-
|
|
119
|
-
if [[ "$dry_run" == "true" ]]; then
|
|
120
|
-
echo "KODY_REASON=dry-run — would open PR ${default_branch} → ${release_branch}"
|
|
121
|
-
echo "KODY_SKIP_AGENT=true"
|
|
122
|
-
exit 0
|
|
123
|
-
fi
|
|
124
|
-
|
|
125
|
-
export HUSKY=0 SKIP_HOOKS=1 CI="${CI:-1}"
|
|
126
|
-
|
|
127
|
-
# Idempotency: reuse an open PR for this branch pair if one exists.
|
|
128
|
-
existing=$(gh pr list --head "$default_branch" --base "$release_branch" --state open --json url --limit 1 2>/dev/null \
|
|
129
|
-
| python3 -c 'import json,sys; data=json.load(sys.stdin); print(data[0]["url"] if data else "")' 2>/dev/null \
|
|
130
|
-
|| echo "")
|
|
131
|
-
|
|
132
|
-
# Hoisted so the kody-release-pr marker write below also runs in the
|
|
133
|
-
# reuse-existing-PR path.
|
|
134
|
-
issue_arg="${KODY_ARG_ISSUE:-}"
|
|
135
|
-
|
|
136
|
-
# Same Tracking-Issue marker as release-prepare — non-closing reference
|
|
137
|
-
# so the originating release issue stays open through the deploy step
|
|
138
|
-
# while the Kody Dashboard can still link this PR to the task for preview.
|
|
139
|
-
tracking_line=""
|
|
140
|
-
if [[ "$issue_arg" =~ ^[0-9]+$ && "$issue_arg" != "0" ]]; then
|
|
141
|
-
tracking_line=$'\n\nTracking-Issue: #'"${issue_arg}"
|
|
142
|
-
fi
|
|
143
|
-
body=$(build_pr_body "$tracking_line")
|
|
144
|
-
|
|
145
|
-
# GitHub rejects a PR body over 65536 chars (GraphQL createPullRequest). A
|
|
146
|
-
# large accumulated CHANGELOG section can blow past it, so clamp: drop the
|
|
147
|
-
# changelog to a budget and rebuild, then hard-truncate as a final guard.
|
|
148
|
-
body_max=65000
|
|
149
|
-
if (( ${#body} > body_max )); then
|
|
150
|
-
echo "[kody release-deploy] PR body ${#body} chars > ${body_max} — truncating changelog" >&2
|
|
151
|
-
budget=$(( body_max - 2000 ))
|
|
152
|
-
changelog_section="${changelog_section:0:budget}"$'\n\n_…changelog truncated; see CHANGELOG.md on the branch._'
|
|
153
|
-
body=$(build_pr_body "$tracking_line")
|
|
154
|
-
(( ${#body} > body_max )) && body="${body:0:body_max}"
|
|
155
|
-
fi
|
|
156
|
-
|
|
157
|
-
if [[ -n "$existing" ]]; then
|
|
158
|
-
echo " reusing existing deploy PR: ${existing}"
|
|
159
|
-
pr_url="$existing"
|
|
160
|
-
# Refresh the body so re-runs converge on the current changelog. Best-
|
|
161
|
-
# effort: a failed edit (e.g. permission-denied) shouldn't fail the
|
|
162
|
-
# release — the PR already exists and its title/branch are unchanged.
|
|
163
|
-
if ! printf '%s' "$body" | gh pr edit "$pr_url" --body-file - >/dev/null 2>&1; then
|
|
164
|
-
echo "[kody release-deploy] WARN: failed to refresh deploy PR body" >&2
|
|
165
|
-
fi
|
|
166
|
-
else
|
|
167
|
-
if ! pr_url=$(printf '%s' "$body" | gh pr create --head "$default_branch" --base "$release_branch" --title "deploy: ${default_branch} → ${release_branch} (v${version})" --body-file -); then
|
|
168
|
-
echo "KODY_REASON=release deploy: gh pr create failed"
|
|
169
|
-
echo "KODY_SKIP_AGENT=true"
|
|
170
|
-
exit 1
|
|
171
|
-
fi
|
|
172
|
-
fi
|
|
173
|
-
|
|
174
|
-
if [[ -z "$pr_url" ]]; then
|
|
175
|
-
echo "KODY_REASON=release deploy: empty PR URL after gh pr create"
|
|
176
|
-
echo "KODY_SKIP_AGENT=true"
|
|
177
|
-
exit 1
|
|
178
|
-
fi
|
|
179
|
-
|
|
180
|
-
# Persist the deploy-PR marker on the originating issue body. Mirrors the
|
|
181
|
-
# release-prepare path — the issue body is owned by the orchestrator, so
|
|
182
|
-
# this signal survives any @kody fix that overwrites the PR body. The
|
|
183
|
-
# marker replaces the prepare-PR ref so the dashboard pivots to the deploy
|
|
184
|
-
# PR (the now-current task) automatically.
|
|
185
|
-
if [[ "${issue_arg:-}" =~ ^[0-9]+$ && "${issue_arg:-0}" != "0" ]]; then
|
|
186
|
-
pr_number="${pr_url##*/}"
|
|
187
|
-
if [[ "$pr_number" =~ ^[0-9]+$ ]]; then
|
|
188
|
-
cur_body=$(gh issue view "$issue_arg" --json body -q .body 2>/dev/null || echo "")
|
|
189
|
-
cleaned_body=$(printf '%s' "$cur_body" | sed -E '/<!-- kody-release-pr:[^>]*-->/d')
|
|
190
|
-
{
|
|
191
|
-
printf '%s' "$cleaned_body"
|
|
192
|
-
printf '\n\n<!-- kody-release-pr: #%s -->\n' "$pr_number"
|
|
193
|
-
} | gh issue edit "$issue_arg" --body-file - >/dev/null 2>&1 || \
|
|
194
|
-
echo "[kody release-deploy] WARN: failed to write kody-release-pr marker to issue #${issue_arg}"
|
|
195
|
-
fi
|
|
196
|
-
fi
|
|
197
|
-
|
|
198
|
-
echo "RELEASE_DEPLOY_PR=${pr_url}"
|
|
199
|
-
echo "KODY_PR_URL=${pr_url}"
|
|
200
|
-
|
|
201
|
-
# Optional post-deploy notification (e.g. Slack ping that a deploy PR is up).
|
|
202
|
-
# Substituted placeholders in the configured command:
|
|
203
|
-
# $VERSION — release version (e.g. 0.25.4)
|
|
204
|
-
# $DEPLOY_PR_URL — URL of the deploy PR just opened/reused
|
|
205
|
-
# The notifyCommand can use $DEPLOY_PR_URL to pull real content (e.g.
|
|
206
|
-
# `gh pr view $DEPLOY_PR_URL --json body --jq .body`) instead of
|
|
207
|
-
# rendering a hardcoded one-liner.
|
|
208
|
-
notify_status="skipped"
|
|
209
|
-
if [[ -n "$notify_cmd" ]]; then
|
|
210
|
-
cmd="${notify_cmd//\$VERSION/$version}"
|
|
211
|
-
cmd="${cmd//\$DEPLOY_PR_URL/$pr_url}"
|
|
212
|
-
echo " notify: ${cmd}"
|
|
213
|
-
if timeout "${timeout_s}" bash -c "$cmd"; then
|
|
214
|
-
notify_status="ok"
|
|
215
|
-
else
|
|
216
|
-
notify_status="failed"
|
|
217
|
-
echo "[kody release-deploy] notifyCommand failed (non-fatal)" >&2
|
|
218
|
-
fi
|
|
219
|
-
fi
|
|
220
|
-
|
|
221
|
-
echo "KODY_REASON=opened deploy PR ${default_branch} → ${release_branch} (notify=${notify_status})"
|
|
222
|
-
echo "KODY_SKIP_AGENT=true"
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "release-deploy",
|
|
3
|
-
"role": "utility",
|
|
4
|
-
"phase": "shipped",
|
|
5
|
-
"describe": "Run the configured deployCommand and notifyCommand after release-publish has tagged + published. No agent.",
|
|
6
|
-
"inputs": [
|
|
7
|
-
{
|
|
8
|
-
"name": "dry-run",
|
|
9
|
-
"flag": "--dry-run",
|
|
10
|
-
"type": "bool",
|
|
11
|
-
"required": false,
|
|
12
|
-
"describe": "Print plan without running deploy/notify commands."
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"name": "issue",
|
|
16
|
-
"flag": "--issue",
|
|
17
|
-
"type": "int",
|
|
18
|
-
"required": false,
|
|
19
|
-
"describe": "Issue/PR number to post the terminal notice on. Auto-injected by dispatch."
|
|
20
|
-
}
|
|
21
|
-
],
|
|
22
|
-
"claudeCode": {
|
|
23
|
-
"model": "inherit",
|
|
24
|
-
"permissionMode": "acceptEdits",
|
|
25
|
-
"maxTurns": 0,
|
|
26
|
-
"maxThinkingTokens": null,
|
|
27
|
-
"systemPromptAppend": null,
|
|
28
|
-
"tools": [],
|
|
29
|
-
"hooks": [],
|
|
30
|
-
"skills": [],
|
|
31
|
-
"commands": [],
|
|
32
|
-
"subagents": [],
|
|
33
|
-
"plugins": [],
|
|
34
|
-
"mcpServers": []
|
|
35
|
-
},
|
|
36
|
-
"cliTools": [],
|
|
37
|
-
"inputArtifacts": [],
|
|
38
|
-
"outputArtifacts": [],
|
|
39
|
-
"scripts": {
|
|
40
|
-
"preflight": [
|
|
41
|
-
{ "script": "setCommentTarget", "with": { "type": "issue" } },
|
|
42
|
-
{ "script": "loadTaskState" },
|
|
43
|
-
{ "shell": "deploy.sh" },
|
|
44
|
-
{ "script": "skipAgent" }
|
|
45
|
-
],
|
|
46
|
-
"postflight": [
|
|
47
|
-
{ "script": "recordOutcome" },
|
|
48
|
-
{ "script": "saveTaskState" },
|
|
49
|
-
{ "script": "notifyTerminal", "with": { "label": "release deploy" } },
|
|
50
|
-
{ "script": "advanceFlow" }
|
|
51
|
-
]
|
|
52
|
-
}
|
|
53
|
-
}
|