@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.
Files changed (114) hide show
  1. package/README.md +2 -2
  2. package/dist/bin/kody.js +825 -832
  3. package/dist/executables/run/profile.json +1 -1
  4. package/dist/executables/types.ts +3 -1
  5. package/package.json +1 -1
  6. package/dist/duties/duty-scheduler/duty.md +0 -3
  7. package/dist/duties/duty-scheduler/profile.json +0 -6
  8. package/dist/duties/duty-tick/duty.md +0 -3
  9. package/dist/duties/duty-tick/profile.json +0 -6
  10. package/dist/duties/duty-tick-scripted/duty.md +0 -3
  11. package/dist/duties/duty-tick-scripted/profile.json +0 -6
  12. package/dist/duties/fix/duty.md +0 -10
  13. package/dist/duties/fix/profile.json +0 -6
  14. package/dist/duties/fix-ci/duty.md +0 -10
  15. package/dist/duties/fix-ci/profile.json +0 -6
  16. package/dist/duties/goal-scheduler/duty.md +0 -3
  17. package/dist/duties/goal-scheduler/profile.json +0 -6
  18. package/dist/duties/goal-tick/duty.md +0 -3
  19. package/dist/duties/goal-tick/profile.json +0 -6
  20. package/dist/duties/init/duty.md +0 -10
  21. package/dist/duties/init/profile.json +0 -6
  22. package/dist/duties/job-live-verify/duty.md +0 -3
  23. package/dist/duties/job-live-verify/profile.json +0 -6
  24. package/dist/duties/merge/duty.md +0 -10
  25. package/dist/duties/merge/profile.json +0 -6
  26. package/dist/duties/npm-publish/duty.md +0 -17
  27. package/dist/duties/npm-publish/profile.json +0 -6
  28. package/dist/duties/plan-verify/duty.md +0 -3
  29. package/dist/duties/plan-verify/profile.json +0 -6
  30. package/dist/duties/preview-build/duty.md +0 -10
  31. package/dist/duties/preview-build/profile.json +0 -6
  32. package/dist/duties/probe-skill/duty.md +0 -3
  33. package/dist/duties/probe-skill/profile.json +0 -6
  34. package/dist/duties/qa-goal/duty.md +0 -3
  35. package/dist/duties/qa-goal/profile.json +0 -6
  36. package/dist/duties/release/duty.md +0 -10
  37. package/dist/duties/release/profile.json +0 -6
  38. package/dist/duties/release-deploy/duty.md +0 -10
  39. package/dist/duties/release-deploy/profile.json +0 -6
  40. package/dist/duties/release-prepare/duty.md +0 -10
  41. package/dist/duties/release-prepare/profile.json +0 -6
  42. package/dist/duties/release-publish/duty.md +0 -10
  43. package/dist/duties/release-publish/profile.json +0 -6
  44. package/dist/duties/resolve/duty.md +0 -10
  45. package/dist/duties/resolve/profile.json +0 -6
  46. package/dist/duties/revert/duty.md +0 -10
  47. package/dist/duties/revert/profile.json +0 -6
  48. package/dist/duties/sync/duty.md +0 -10
  49. package/dist/duties/sync/profile.json +0 -6
  50. package/dist/duties/task-job-fail-once/duty.md +0 -3
  51. package/dist/duties/task-job-fail-once/profile.json +0 -6
  52. package/dist/duties/task-job-pass-a/duty.md +0 -3
  53. package/dist/duties/task-job-pass-a/profile.json +0 -6
  54. package/dist/duties/task-job-pass-b/duty.md +0 -3
  55. package/dist/duties/task-job-pass-b/profile.json +0 -6
  56. package/dist/duties/task-jobs/duty.md +0 -3
  57. package/dist/duties/task-jobs/profile.json +0 -6
  58. package/dist/duties/worker-ask/duty.md +0 -10
  59. package/dist/duties/worker-ask/profile.json +0 -6
  60. package/dist/executables/duty-scheduler/profile.json +0 -57
  61. package/dist/executables/duty-tick/profile.json +0 -78
  62. package/dist/executables/duty-tick/prompt.md +0 -77
  63. package/dist/executables/duty-tick/prompts/locked.md +0 -50
  64. package/dist/executables/duty-tick-scripted/profile.json +0 -72
  65. package/dist/executables/fix/profile.json +0 -91
  66. package/dist/executables/fix/prompt.md +0 -90
  67. package/dist/executables/fix-ci/profile.json +0 -71
  68. package/dist/executables/fix-ci/prompt.md +0 -78
  69. package/dist/executables/goal-scheduler/profile.json +0 -53
  70. package/dist/executables/goal-scheduler/scheduler.sh +0 -82
  71. package/dist/executables/goal-tick/profile.json +0 -69
  72. package/dist/executables/init/profile.json +0 -36
  73. package/dist/executables/job-live-verify/profile.json +0 -36
  74. package/dist/executables/job-live-verify/prompt.md +0 -42
  75. package/dist/executables/merge/profile.json +0 -54
  76. package/dist/executables/npm-publish/profile.json +0 -73
  77. package/dist/executables/npm-publish/publish.sh +0 -93
  78. package/dist/executables/plan-verify/profile.json +0 -38
  79. package/dist/executables/plan-verify/prompt.md +0 -72
  80. package/dist/executables/preview-build/profile.json +0 -61
  81. package/dist/executables/preview-health/profile.json +0 -48
  82. package/dist/executables/preview-health/tick.sh +0 -319
  83. package/dist/executables/probe-skill/profile.json +0 -35
  84. package/dist/executables/probe-skill/prompt.md +0 -31
  85. package/dist/executables/probe-skill/skills/probe-skill-marker/SKILL.md +0 -18
  86. package/dist/executables/qa-goal/profile.json +0 -65
  87. package/dist/executables/release/deploy.sh +0 -157
  88. package/dist/executables/release/prepare.sh +0 -353
  89. package/dist/executables/release/profile.json +0 -95
  90. package/dist/executables/release/publish.sh +0 -88
  91. package/dist/executables/release/release.sh +0 -230
  92. package/dist/executables/release/wait.sh +0 -88
  93. package/dist/executables/release-deploy/deploy.sh +0 -222
  94. package/dist/executables/release-deploy/profile.json +0 -53
  95. package/dist/executables/release-prepare/prepare.sh +0 -368
  96. package/dist/executables/release-prepare/profile.json +0 -70
  97. package/dist/executables/release-publish/profile.json +0 -53
  98. package/dist/executables/release-publish/publish.sh +0 -137
  99. package/dist/executables/resolve/apply-prefer.sh +0 -54
  100. package/dist/executables/resolve/profile.json +0 -107
  101. package/dist/executables/resolve/prompt.md +0 -51
  102. package/dist/executables/revert/profile.json +0 -82
  103. package/dist/executables/revert/revert.sh +0 -33
  104. package/dist/executables/sync/profile.json +0 -48
  105. package/dist/executables/task-job-fail-once/profile.json +0 -44
  106. package/dist/executables/task-job-fail-once/prompt.md +0 -1
  107. package/dist/executables/task-job-pass-a/profile.json +0 -43
  108. package/dist/executables/task-job-pass-a/prompt.md +0 -1
  109. package/dist/executables/task-job-pass-b/profile.json +0 -43
  110. package/dist/executables/task-job-pass-b/prompt.md +0 -1
  111. package/dist/executables/task-jobs/profile.json +0 -41
  112. package/dist/executables/task-jobs/prompt.md +0 -1
  113. package/dist/executables/worker-ask/profile.json +0 -70
  114. 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
- }