@kody-ade/kody-engine 0.4.224 → 0.4.225
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 +779 -831
- 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,368 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# release-prepare: bump version files, generate CHANGELOG.md, commit on a
|
|
4
|
-
# release branch, open the release PR. Pure mechanical work — no agent.
|
|
5
|
-
#
|
|
6
|
-
# Inputs (env, set by the executor):
|
|
7
|
-
# KODY_ARG_BUMP patch|minor|major (default: patch)
|
|
8
|
-
# KODY_ARG_DRY_RUN true|false
|
|
9
|
-
# KODY_ARG_PREFER ours|theirs (optional)
|
|
10
|
-
# KODY_ARG_ISSUE triggering issue/PR number (optional)
|
|
11
|
-
#
|
|
12
|
-
# Config (env, flattened from kody.config.json):
|
|
13
|
-
# KODY_CFG_GIT_DEFAULTBRANCH e.g. main
|
|
14
|
-
# KODY_CFG_RELEASE_VERSIONFILES JSON array, e.g. ["package.json"]
|
|
15
|
-
#
|
|
16
|
-
# Stdout signals to the executor:
|
|
17
|
-
# KODY_PR_URL=<url> — set ctx.output.prUrl (final PR URL)
|
|
18
|
-
# KODY_REASON=<text> — set ctx.output.reason (failure or dry-run note)
|
|
19
|
-
# KODY_SKIP_AGENT=true — bypass the agent (always; this is a no-agent flow)
|
|
20
|
-
|
|
21
|
-
set -euo pipefail
|
|
22
|
-
|
|
23
|
-
# ── Helpers ────────────────────────────────────────────────────────────────
|
|
24
|
-
|
|
25
|
-
bump="${KODY_ARG_BUMP:-patch}"
|
|
26
|
-
dry_run="${KODY_ARG_DRY_RUN:-false}"
|
|
27
|
-
prefer="${KODY_ARG_PREFER:-}"
|
|
28
|
-
default_branch="${KODY_CFG_GIT_DEFAULTBRANCH:-main}"
|
|
29
|
-
version_files_json="${KODY_CFG_RELEASE_VERSIONFILES:-}"
|
|
30
|
-
|
|
31
|
-
fail() {
|
|
32
|
-
local reason="$1"
|
|
33
|
-
echo "KODY_REASON=$reason"
|
|
34
|
-
echo "KODY_SKIP_AGENT=true"
|
|
35
|
-
exit "${2:-1}"
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
bump_version() {
|
|
39
|
-
local cur="$1" kind="$2"
|
|
40
|
-
local rest="${cur#*-}"
|
|
41
|
-
local core="${cur%%-*}"
|
|
42
|
-
if ! [[ "$core" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
|
43
|
-
fail "release prepare: cannot parse version '$cur' (expected x.y.z[-suffix])" 99
|
|
44
|
-
fi
|
|
45
|
-
local maj="${BASH_REMATCH[1]}" min="${BASH_REMATCH[2]}" pat="${BASH_REMATCH[3]}"
|
|
46
|
-
case "$kind" in
|
|
47
|
-
major) maj=$((maj + 1)); min=0; pat=0 ;;
|
|
48
|
-
minor) min=$((min + 1)); pat=0 ;;
|
|
49
|
-
patch|*) pat=$((pat + 1)) ;;
|
|
50
|
-
esac
|
|
51
|
-
echo "${maj}.${min}.${pat}"
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
read_pkg_version() {
|
|
55
|
-
python3 -c "import json,sys; print(json.load(open('package.json'))['version'])"
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
write_pkg_version() {
|
|
59
|
-
local file="$1" new="$2"
|
|
60
|
-
python3 - "$file" "$new" <<'PY'
|
|
61
|
-
import json, sys
|
|
62
|
-
path, new = sys.argv[1], sys.argv[2]
|
|
63
|
-
try:
|
|
64
|
-
with open(path) as f:
|
|
65
|
-
text = f.read()
|
|
66
|
-
except FileNotFoundError:
|
|
67
|
-
print("MISSING")
|
|
68
|
-
sys.exit(0)
|
|
69
|
-
try:
|
|
70
|
-
data = json.loads(text)
|
|
71
|
-
except Exception:
|
|
72
|
-
print("UNCHANGED")
|
|
73
|
-
sys.exit(0)
|
|
74
|
-
if data.get("version") == new:
|
|
75
|
-
print("UNCHANGED")
|
|
76
|
-
sys.exit(0)
|
|
77
|
-
data["version"] = new
|
|
78
|
-
indent = 2
|
|
79
|
-
with open(path, "w") as f:
|
|
80
|
-
f.write(json.dumps(data, indent=indent) + "\n")
|
|
81
|
-
print("WROTE")
|
|
82
|
-
PY
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
resolve_version_files() {
|
|
86
|
-
if [[ -z "$version_files_json" ]]; then
|
|
87
|
-
echo "package.json"
|
|
88
|
-
return
|
|
89
|
-
fi
|
|
90
|
-
python3 - <<PY
|
|
91
|
-
import json, os, sys
|
|
92
|
-
raw = os.environ.get("KODY_CFG_RELEASE_VERSIONFILES", "")
|
|
93
|
-
try:
|
|
94
|
-
arr = json.loads(raw)
|
|
95
|
-
except Exception:
|
|
96
|
-
print("package.json")
|
|
97
|
-
sys.exit(0)
|
|
98
|
-
if isinstance(arr, list) and arr:
|
|
99
|
-
for f in arr:
|
|
100
|
-
if isinstance(f, str) and f:
|
|
101
|
-
print(f)
|
|
102
|
-
else:
|
|
103
|
-
print("package.json")
|
|
104
|
-
PY
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
generate_changelog() {
|
|
108
|
-
local new_version="$1"
|
|
109
|
-
local last_tag count
|
|
110
|
-
# actions/checkout@v4 with fetch-depth: 0 still does NOT fetch tags by
|
|
111
|
-
# default — operators have to opt in via `fetch-tags: true`. When tags
|
|
112
|
-
# are absent, `git describe` returns empty and the else-branch's
|
|
113
|
-
# 100-commit window can over- or under-shoot. Try once to backfill
|
|
114
|
-
# tags before describing, ignoring failures (offline/sandbox runs).
|
|
115
|
-
if ! last_tag=$(git describe --tags --abbrev=0 --match 'v*' 2>/dev/null); then
|
|
116
|
-
git fetch --tags --quiet 2>/dev/null || true
|
|
117
|
-
last_tag=$(git describe --tags --abbrev=0 --match 'v*' 2>/dev/null || true)
|
|
118
|
-
fi
|
|
119
|
-
if [[ -n "$last_tag" ]]; then
|
|
120
|
-
count=$(git rev-list --count "${last_tag}..HEAD" --no-merges 2>/dev/null || echo "?")
|
|
121
|
-
echo " changelog: ${count} commits since ${last_tag}" >&2
|
|
122
|
-
git log "${last_tag}..HEAD" --pretty=format:'%s||%h' --no-merges 2>/dev/null || true
|
|
123
|
-
else
|
|
124
|
-
echo " changelog: no previous v* tag found — using last 100 commits" >&2
|
|
125
|
-
git log -n100 HEAD --pretty=format:'%s||%h' --no-merges 2>/dev/null || true
|
|
126
|
-
fi
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
format_changelog() {
|
|
130
|
-
local new_version="$1"
|
|
131
|
-
local date_str
|
|
132
|
-
date_str=$(date -u +%Y-%m-%d)
|
|
133
|
-
python3 - "$new_version" "$date_str" <<PY
|
|
134
|
-
import sys, re
|
|
135
|
-
new_version, date_str = sys.argv[1], sys.argv[2]
|
|
136
|
-
raw = sys.stdin.read()
|
|
137
|
-
buckets = {k: [] for k in ("feat", "fix", "perf", "refactor", "docs", "chore", "other")}
|
|
138
|
-
for line in raw.splitlines():
|
|
139
|
-
line = line.strip()
|
|
140
|
-
if not line:
|
|
141
|
-
continue
|
|
142
|
-
if "||" not in line:
|
|
143
|
-
continue
|
|
144
|
-
subject, sha = line.split("||", 1)
|
|
145
|
-
if re.match(r"(?i)^chore:\s*release\s+v\d", subject):
|
|
146
|
-
continue
|
|
147
|
-
m = re.match(r"^(\w+)(?:\(.*?\))?\s*:\s*(.+)$", subject)
|
|
148
|
-
if m:
|
|
149
|
-
kind = m.group(1).lower()
|
|
150
|
-
msg = m.group(2)
|
|
151
|
-
else:
|
|
152
|
-
kind = "other"
|
|
153
|
-
msg = subject
|
|
154
|
-
buckets.setdefault(kind, buckets["other"]).append(f"- {msg} ({sha})")
|
|
155
|
-
labels = [
|
|
156
|
-
("feat", "Features"),
|
|
157
|
-
("fix", "Fixes"),
|
|
158
|
-
("perf", "Performance"),
|
|
159
|
-
("refactor", "Refactoring"),
|
|
160
|
-
("docs", "Docs"),
|
|
161
|
-
("chore", "Chores"),
|
|
162
|
-
("other", "Other"),
|
|
163
|
-
]
|
|
164
|
-
parts = [f"## v{new_version} — {date_str}", ""]
|
|
165
|
-
emitted = False
|
|
166
|
-
for key, label in labels:
|
|
167
|
-
items = buckets.get(key) or []
|
|
168
|
-
if not items:
|
|
169
|
-
continue
|
|
170
|
-
parts.append(f"### {label}")
|
|
171
|
-
parts.extend(items)
|
|
172
|
-
parts.append("")
|
|
173
|
-
emitted = True
|
|
174
|
-
if not emitted:
|
|
175
|
-
parts.append("_No notable commits since the last release._")
|
|
176
|
-
parts.append("")
|
|
177
|
-
sys.stdout.write("\n".join(parts))
|
|
178
|
-
PY
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
prepend_changelog() {
|
|
182
|
-
local entry="$1"
|
|
183
|
-
local header='# Changelog
|
|
184
|
-
|
|
185
|
-
All notable changes to this project will be documented in this file.
|
|
186
|
-
|
|
187
|
-
'
|
|
188
|
-
if [[ -f CHANGELOG.md ]]; then
|
|
189
|
-
if grep -qE '^#\s*Changelog\b' CHANGELOG.md; then
|
|
190
|
-
python3 - "$entry" <<'PY'
|
|
191
|
-
import sys
|
|
192
|
-
entry = sys.argv[1]
|
|
193
|
-
with open("CHANGELOG.md") as f:
|
|
194
|
-
prior = f.read()
|
|
195
|
-
idx = prior.index("\n", prior.index("# Changelog"))
|
|
196
|
-
new = prior[: idx + 1] + "\n" + entry + prior[idx + 1 :]
|
|
197
|
-
with open("CHANGELOG.md", "w") as f:
|
|
198
|
-
f.write(new)
|
|
199
|
-
PY
|
|
200
|
-
else
|
|
201
|
-
python3 - "$entry" <<'PY'
|
|
202
|
-
import sys
|
|
203
|
-
entry = sys.argv[1]
|
|
204
|
-
header = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n"
|
|
205
|
-
with open("CHANGELOG.md") as f:
|
|
206
|
-
prior = f.read()
|
|
207
|
-
with open("CHANGELOG.md", "w") as f:
|
|
208
|
-
f.write(header + entry + prior)
|
|
209
|
-
PY
|
|
210
|
-
fi
|
|
211
|
-
else
|
|
212
|
-
python3 - "$entry" <<'PY'
|
|
213
|
-
import sys
|
|
214
|
-
entry = sys.argv[1]
|
|
215
|
-
header = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n"
|
|
216
|
-
with open("CHANGELOG.md", "w") as f:
|
|
217
|
-
f.write(header + entry)
|
|
218
|
-
PY
|
|
219
|
-
fi
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
remote_branch_exists() {
|
|
223
|
-
local branch="$1"
|
|
224
|
-
git ls-remote --heads origin "$branch" 2>/dev/null | grep -q .
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
find_open_pr() {
|
|
228
|
-
local branch="$1"
|
|
229
|
-
gh pr list --head "$branch" --state open --json url --limit 1 2>/dev/null \
|
|
230
|
-
| python3 -c 'import json,sys; data=json.load(sys.stdin); print(data[0]["url"] if data else "")' 2>/dev/null \
|
|
231
|
-
|| echo ""
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
# ── Flow ───────────────────────────────────────────────────────────────────
|
|
235
|
-
|
|
236
|
-
if [[ ! -f package.json ]]; then
|
|
237
|
-
fail "release prepare: package.json not found" 99
|
|
238
|
-
fi
|
|
239
|
-
|
|
240
|
-
old_version=$(read_pkg_version)
|
|
241
|
-
new_version=$(bump_version "$old_version" "$bump")
|
|
242
|
-
tag="v${new_version}"
|
|
243
|
-
release_branch="release/${tag}"
|
|
244
|
-
|
|
245
|
-
echo "→ release prepare: ${old_version} → ${new_version} (${bump})"
|
|
246
|
-
|
|
247
|
-
if [[ "$dry_run" == "true" ]]; then
|
|
248
|
-
echo "RELEASE_PLAN=bump=${new_version} tag=${tag}"
|
|
249
|
-
echo "KODY_REASON=dry-run — would bump to ${new_version}${prefer:+ (--prefer ${prefer})}"
|
|
250
|
-
echo "KODY_SKIP_AGENT=true"
|
|
251
|
-
exit 0
|
|
252
|
-
fi
|
|
253
|
-
|
|
254
|
-
# Branch-collision gate.
|
|
255
|
-
collides=false
|
|
256
|
-
if remote_branch_exists "$release_branch"; then
|
|
257
|
-
collides=true
|
|
258
|
-
case "$prefer" in
|
|
259
|
-
theirs)
|
|
260
|
-
existing=$(find_open_pr "$release_branch")
|
|
261
|
-
if [[ -n "$existing" ]]; then
|
|
262
|
-
echo " reusing existing PR (--prefer theirs): ${existing}"
|
|
263
|
-
echo "KODY_PR_URL=${existing}"
|
|
264
|
-
echo "KODY_REASON=reused existing release PR"
|
|
265
|
-
echo "KODY_SKIP_AGENT=true"
|
|
266
|
-
exit 0
|
|
267
|
-
fi
|
|
268
|
-
fail "release prepare --prefer theirs: ${release_branch} exists on remote but has no open PR — nothing to reuse" 4
|
|
269
|
-
;;
|
|
270
|
-
ours)
|
|
271
|
-
echo " branch ${release_branch} exists on remote — will force-push (--prefer ours)"
|
|
272
|
-
;;
|
|
273
|
-
*)
|
|
274
|
-
fail "release prepare: branch ${release_branch} already exists on remote. Use --prefer ours to force-push, or --prefer theirs to reuse the existing PR." 4
|
|
275
|
-
;;
|
|
276
|
-
esac
|
|
277
|
-
fi
|
|
278
|
-
|
|
279
|
-
# Bump version files.
|
|
280
|
-
mapfile -t files < <(resolve_version_files)
|
|
281
|
-
touched=()
|
|
282
|
-
for f in "${files[@]}"; do
|
|
283
|
-
res=$(write_pkg_version "$f" "$new_version")
|
|
284
|
-
if [[ "$res" == "WROTE" ]]; then
|
|
285
|
-
touched+=("$f")
|
|
286
|
-
fi
|
|
287
|
-
done
|
|
288
|
-
if [[ ${#touched[@]} -eq 0 ]]; then
|
|
289
|
-
fail "release prepare: no version strings updated (files: ${files[*]})"
|
|
290
|
-
fi
|
|
291
|
-
echo " wrote ${touched[*]}"
|
|
292
|
-
|
|
293
|
-
# Changelog.
|
|
294
|
-
raw_log=$(generate_changelog "$new_version") || raw_log=""
|
|
295
|
-
entry=$(printf '%s' "$raw_log" | format_changelog "$new_version")
|
|
296
|
-
prepend_changelog "$entry"
|
|
297
|
-
echo " wrote CHANGELOG.md"
|
|
298
|
-
|
|
299
|
-
# Commit + push.
|
|
300
|
-
export HUSKY=0 SKIP_HOOKS=1
|
|
301
|
-
git checkout -b "$release_branch"
|
|
302
|
-
for f in "${touched[@]}" CHANGELOG.md; do
|
|
303
|
-
git add -- "$f"
|
|
304
|
-
done
|
|
305
|
-
git -c commit.gpgsign=false commit -m "chore: release ${tag}"
|
|
306
|
-
if [[ "$collides" == "true" && "$prefer" == "ours" ]]; then
|
|
307
|
-
git push -u --force-with-lease origin "$release_branch"
|
|
308
|
-
else
|
|
309
|
-
git push -u origin "$release_branch"
|
|
310
|
-
fi
|
|
311
|
-
|
|
312
|
-
# Open PR (or link to existing one if --prefer ours collided).
|
|
313
|
-
pr_url=""
|
|
314
|
-
if [[ "$collides" == "true" && "$prefer" == "ours" ]]; then
|
|
315
|
-
pr_url=$(find_open_pr "$release_branch")
|
|
316
|
-
fi
|
|
317
|
-
|
|
318
|
-
if [[ -z "$pr_url" ]]; then
|
|
319
|
-
body_max=60000
|
|
320
|
-
if [[ ${#entry} -gt $body_max ]]; then
|
|
321
|
-
body_entry="${entry:0:$body_max}
|
|
322
|
-
|
|
323
|
-
_… truncated; see CHANGELOG.md_"
|
|
324
|
-
else
|
|
325
|
-
body_entry="$entry"
|
|
326
|
-
fi
|
|
327
|
-
# Link the PR back to the originating release issue WITHOUT auto-closing
|
|
328
|
-
# it. The orchestrator continues to publish + deploy after merge using the
|
|
329
|
-
# issue as the dispatch target, so the issue must stay open for the
|
|
330
|
-
# dashboard to keep showing the task as in-progress until finishFlow
|
|
331
|
-
# applies kody:done. We use `Tracking-Issue: #N` (not `Closes #N`) — GitHub
|
|
332
|
-
# treats this as plain text, the dashboard parses it as a fallback link
|
|
333
|
-
# signal, and no auto-close fires on PR merge.
|
|
334
|
-
issue_arg="${KODY_ARG_ISSUE:-}"
|
|
335
|
-
tracking_line=""
|
|
336
|
-
if [[ "$issue_arg" =~ ^[0-9]+$ && "$issue_arg" != "0" ]]; then
|
|
337
|
-
tracking_line=$'\n\nTracking-Issue: #'"${issue_arg}"
|
|
338
|
-
fi
|
|
339
|
-
body=$'Automated release PR opened by kody.\n\n'"$body_entry"$'\n\nThe release orchestrator will merge this into `'"${default_branch}"$'` and continue to publish + deploy.'"${tracking_line}"
|
|
340
|
-
pr_url=$(printf '%s' "$body" | gh pr create --head "$release_branch" --base "$default_branch" --title "chore: release ${tag}" --body-file -)
|
|
341
|
-
fi
|
|
342
|
-
|
|
343
|
-
if [[ -z "$pr_url" ]]; then
|
|
344
|
-
fail "release prepare: gh pr create returned empty URL" 4
|
|
345
|
-
fi
|
|
346
|
-
|
|
347
|
-
# Persist a release-PR marker on the originating issue body so the Kody
|
|
348
|
-
# Dashboard can link the issue → PR even if @kody fix later overwrites the
|
|
349
|
-
# PR body. Idempotent: any existing marker line is stripped before append,
|
|
350
|
-
# so re-runs replace the previous PR ref. The issue body is owned by the
|
|
351
|
-
# orchestrator (no @kody fix touches it), so this signal is durable.
|
|
352
|
-
if [[ "${issue_arg:-}" =~ ^[0-9]+$ && "${issue_arg:-0}" != "0" ]]; then
|
|
353
|
-
pr_number="${pr_url##*/}"
|
|
354
|
-
if [[ "$pr_number" =~ ^[0-9]+$ ]]; then
|
|
355
|
-
cur_body=$(gh issue view "$issue_arg" --json body -q .body 2>/dev/null || echo "")
|
|
356
|
-
cleaned_body=$(printf '%s' "$cur_body" | sed -E '/<!-- kody-release-pr:[^>]*-->/d')
|
|
357
|
-
{
|
|
358
|
-
printf '%s' "$cleaned_body"
|
|
359
|
-
printf '\n\n<!-- kody-release-pr: #%s -->\n' "$pr_number"
|
|
360
|
-
} | gh issue edit "$issue_arg" --body-file - >/dev/null 2>&1 || \
|
|
361
|
-
echo "[kody release-prepare] WARN: failed to write kody-release-pr marker to issue #${issue_arg}"
|
|
362
|
-
fi
|
|
363
|
-
fi
|
|
364
|
-
|
|
365
|
-
echo "RELEASE_PR=${pr_url}"
|
|
366
|
-
echo "KODY_PR_URL=${pr_url}"
|
|
367
|
-
echo "KODY_REASON=opened release PR for ${tag}"
|
|
368
|
-
echo "KODY_SKIP_AGENT=true"
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "release-prepare",
|
|
3
|
-
"role": "utility",
|
|
4
|
-
"phase": "shipped",
|
|
5
|
-
"describe": "Bump version files, generate CHANGELOG.md, commit on a release branch, open the release PR. No agent.",
|
|
6
|
-
"inputs": [
|
|
7
|
-
{
|
|
8
|
-
"name": "bump",
|
|
9
|
-
"flag": "--bump",
|
|
10
|
-
"type": "enum",
|
|
11
|
-
"values": ["patch", "minor", "major"],
|
|
12
|
-
"required": false,
|
|
13
|
-
"describe": "Version bump increment. Default patch."
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"name": "dry-run",
|
|
17
|
-
"flag": "--dry-run",
|
|
18
|
-
"type": "bool",
|
|
19
|
-
"required": false,
|
|
20
|
-
"describe": "Print plan without writing files, committing, or opening a PR."
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"name": "prefer",
|
|
24
|
-
"flag": "--prefer",
|
|
25
|
-
"type": "enum",
|
|
26
|
-
"values": ["ours", "theirs"],
|
|
27
|
-
"required": false,
|
|
28
|
-
"describe": "On release/vX.Y.Z branch collision: 'ours' force-pushes; 'theirs' reuses the existing PR. Default refuses non-ff."
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"name": "issue",
|
|
32
|
-
"flag": "--issue",
|
|
33
|
-
"type": "int",
|
|
34
|
-
"required": false,
|
|
35
|
-
"describe": "Issue/PR number to post the terminal notice on. Auto-injected by dispatch."
|
|
36
|
-
}
|
|
37
|
-
],
|
|
38
|
-
"claudeCode": {
|
|
39
|
-
"model": "inherit",
|
|
40
|
-
"permissionMode": "acceptEdits",
|
|
41
|
-
"maxTurns": 0,
|
|
42
|
-
"maxThinkingTokens": null,
|
|
43
|
-
"systemPromptAppend": null,
|
|
44
|
-
"tools": [],
|
|
45
|
-
"hooks": [],
|
|
46
|
-
"skills": [],
|
|
47
|
-
"commands": [],
|
|
48
|
-
"subagents": [],
|
|
49
|
-
"plugins": [],
|
|
50
|
-
"mcpServers": []
|
|
51
|
-
},
|
|
52
|
-
"cliTools": [],
|
|
53
|
-
"inputArtifacts": [],
|
|
54
|
-
"outputArtifacts": [],
|
|
55
|
-
"scripts": {
|
|
56
|
-
"preflight": [
|
|
57
|
-
{ "script": "setCommentTarget", "with": { "type": "issue" } },
|
|
58
|
-
{ "script": "loadTaskState" },
|
|
59
|
-
{ "shell": "prepare.sh" },
|
|
60
|
-
{ "script": "skipAgent" }
|
|
61
|
-
],
|
|
62
|
-
"postflight": [
|
|
63
|
-
{ "script": "recordOutcome" },
|
|
64
|
-
{ "script": "saveTaskState" },
|
|
65
|
-
{ "script": "mirrorStateToPr" },
|
|
66
|
-
{ "script": "notifyTerminal", "with": { "label": "release prepare" } },
|
|
67
|
-
{ "script": "advanceFlow" }
|
|
68
|
-
]
|
|
69
|
-
}
|
|
70
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "release-publish",
|
|
3
|
-
"role": "utility",
|
|
4
|
-
"phase": "shipped",
|
|
5
|
-
"describe": "Tag the merged release commit, run the configured publishCommand, create the GitHub release. No agent.",
|
|
6
|
-
"inputs": [
|
|
7
|
-
{
|
|
8
|
-
"name": "dry-run",
|
|
9
|
-
"flag": "--dry-run",
|
|
10
|
-
"type": "bool",
|
|
11
|
-
"required": false,
|
|
12
|
-
"describe": "Print plan without tagging or publishing."
|
|
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": "publish.sh" },
|
|
44
|
-
{ "script": "skipAgent" }
|
|
45
|
-
],
|
|
46
|
-
"postflight": [
|
|
47
|
-
{ "script": "recordOutcome" },
|
|
48
|
-
{ "script": "saveTaskState" },
|
|
49
|
-
{ "script": "notifyTerminal", "with": { "label": "release publish" } },
|
|
50
|
-
{ "script": "advanceFlow" }
|
|
51
|
-
]
|
|
52
|
-
}
|
|
53
|
-
}
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# release-publish: tag the current package version, push the tag, run the
|
|
4
|
-
# configured publishCommand (e.g. `pnpm publish --access public`), create
|
|
5
|
-
# the GitHub release. Runs AFTER the orchestrator has merged the release
|
|
6
|
-
# PR into the default branch. No agent.
|
|
7
|
-
#
|
|
8
|
-
# Inputs (env):
|
|
9
|
-
# KODY_ARG_DRY_RUN true|false
|
|
10
|
-
# KODY_ARG_ISSUE triggering issue/PR number (optional)
|
|
11
|
-
#
|
|
12
|
-
# Config (env):
|
|
13
|
-
# KODY_CFG_GIT_DEFAULTBRANCH e.g. main
|
|
14
|
-
# KODY_CFG_RELEASE_PUBLISHCOMMAND optional; $VERSION is substituted
|
|
15
|
-
# KODY_CFG_RELEASE_DRAFTRELEASE "true" → create as draft
|
|
16
|
-
# KODY_CFG_RELEASE_TIMEOUTMS publish timeout (default 600000ms)
|
|
17
|
-
#
|
|
18
|
-
# Stdout signals:
|
|
19
|
-
# KODY_REASON=<text>
|
|
20
|
-
# KODY_PR_URL=<release URL> — gh release create URL (used as the "PR" link)
|
|
21
|
-
# KODY_SKIP_AGENT=true
|
|
22
|
-
|
|
23
|
-
set -euo pipefail
|
|
24
|
-
|
|
25
|
-
dry_run="${KODY_ARG_DRY_RUN:-false}"
|
|
26
|
-
default_branch="${KODY_CFG_GIT_DEFAULTBRANCH:-main}"
|
|
27
|
-
publish_cmd="${KODY_CFG_RELEASE_PUBLISHCOMMAND:-}"
|
|
28
|
-
draft="${KODY_CFG_RELEASE_DRAFTRELEASE:-false}"
|
|
29
|
-
timeout_ms="${KODY_CFG_RELEASE_TIMEOUTMS:-600000}"
|
|
30
|
-
timeout_s=$((timeout_ms / 1000))
|
|
31
|
-
|
|
32
|
-
fail() {
|
|
33
|
-
echo "KODY_REASON=$1"
|
|
34
|
-
echo "KODY_SKIP_AGENT=true"
|
|
35
|
-
exit "${2:-1}"
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
read_pkg_version() {
|
|
39
|
-
python3 -c "import json; print(json.load(open('package.json'))['version'])"
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if [[ ! -f package.json ]]; then
|
|
43
|
-
fail "release publish: package.json not found" 99
|
|
44
|
-
fi
|
|
45
|
-
|
|
46
|
-
export HUSKY=0 SKIP_HOOKS=1 CI="${CI:-1}"
|
|
47
|
-
|
|
48
|
-
# Make sure we're on the merged commit. The orchestrator merged the release
|
|
49
|
-
# PR into default_branch; pull so the local tree has the bump commit.
|
|
50
|
-
git fetch origin "$default_branch" --tags
|
|
51
|
-
git checkout "$default_branch"
|
|
52
|
-
git reset --hard "origin/$default_branch"
|
|
53
|
-
|
|
54
|
-
version=$(read_pkg_version)
|
|
55
|
-
tag="v${version}"
|
|
56
|
-
|
|
57
|
-
echo "→ release publish: ${tag}"
|
|
58
|
-
|
|
59
|
-
# Recovery is resume-not-overwrite: a release is three steps (push tag, run
|
|
60
|
-
# publishCommand, create GH release) and any of them may already be done from
|
|
61
|
-
# a prior partial run. We probe each step's state and perform only the missing
|
|
62
|
-
# parts. An already-fully-released version is therefore a no-op success, and we
|
|
63
|
-
# never re-point or force an existing tag (that would rewrite a shipped version).
|
|
64
|
-
tag_local=false
|
|
65
|
-
tag_remote=false
|
|
66
|
-
release_exists=false
|
|
67
|
-
if git rev-parse --verify "$tag" >/dev/null 2>&1; then tag_local=true; fi
|
|
68
|
-
if git ls-remote --exit-code --tags origin "refs/tags/$tag" >/dev/null 2>&1; then tag_remote=true; fi
|
|
69
|
-
if gh release view "$tag" >/dev/null 2>&1; then release_exists=true; fi
|
|
70
|
-
|
|
71
|
-
if [[ "$tag_local" == "true" || "$tag_remote" == "true" || "$release_exists" == "true" ]]; then
|
|
72
|
-
echo " resuming: tag exists (local=${tag_local} remote=${tag_remote}), gh-release=${release_exists}"
|
|
73
|
-
fi
|
|
74
|
-
|
|
75
|
-
if [[ "$dry_run" == "true" ]]; then
|
|
76
|
-
echo "KODY_REASON=dry-run — would tag + publish ${tag} (tag_remote=${tag_remote}, gh-release=${release_exists})"
|
|
77
|
-
echo "KODY_SKIP_AGENT=true"
|
|
78
|
-
exit 0
|
|
79
|
-
fi
|
|
80
|
-
|
|
81
|
-
# Tag + push (skip whichever half already exists).
|
|
82
|
-
if [[ "$tag_local" == "false" && "$tag_remote" == "false" ]]; then
|
|
83
|
-
git tag -a "$tag" -m "Release ${tag}"
|
|
84
|
-
fi
|
|
85
|
-
if [[ "$tag_remote" == "false" ]]; then
|
|
86
|
-
git push origin "$tag"
|
|
87
|
-
fi
|
|
88
|
-
|
|
89
|
-
# publishCommand (optional). A version that's already on the registry is the
|
|
90
|
-
# expected recovery case, not an error: treat "already published" as done.
|
|
91
|
-
# Any other non-zero is a real failure (recorded, but we still create the GH
|
|
92
|
-
# release so the tag is discoverable).
|
|
93
|
-
publish_status="skipped"
|
|
94
|
-
if [[ -n "$publish_cmd" ]]; then
|
|
95
|
-
cmd="${publish_cmd//\$VERSION/$version}"
|
|
96
|
-
echo " publish: ${cmd}"
|
|
97
|
-
publish_out=""
|
|
98
|
-
if publish_out=$(timeout "${timeout_s}" bash -c "$cmd" 2>&1); then
|
|
99
|
-
publish_status="ok"
|
|
100
|
-
elif echo "$publish_out" | grep -qiE "already exists|cannot publish over|previously published|403 Forbidden"; then
|
|
101
|
-
publish_status="already-published"
|
|
102
|
-
echo "[kody release-publish] version ${version} already on registry — treating as published"
|
|
103
|
-
else
|
|
104
|
-
publish_status="failed"
|
|
105
|
-
echo "[kody release-publish] publishCommand failed (continuing to create GH release)" >&2
|
|
106
|
-
fi
|
|
107
|
-
echo "$publish_out"
|
|
108
|
-
fi
|
|
109
|
-
|
|
110
|
-
# GitHub release (create only if missing).
|
|
111
|
-
release_url=""
|
|
112
|
-
if [[ "$release_exists" == "true" ]]; then
|
|
113
|
-
release_url=$(gh release view "$tag" --json url --jq .url 2>/dev/null || echo "")
|
|
114
|
-
else
|
|
115
|
-
draft_flag=""
|
|
116
|
-
[[ "$draft" == "true" ]] && draft_flag="--draft"
|
|
117
|
-
if release_url=$(gh release create "$tag" --title "$tag" --notes "Release ${tag} — automated by kody." $draft_flag 2>&1); then
|
|
118
|
-
:
|
|
119
|
-
else
|
|
120
|
-
echo "[kody release-publish] gh release create failed: $release_url" >&2
|
|
121
|
-
release_url=""
|
|
122
|
-
fi
|
|
123
|
-
fi
|
|
124
|
-
|
|
125
|
-
echo "RELEASE_TAG=${tag}"
|
|
126
|
-
[[ -n "$release_url" ]] && echo "RELEASE_URL=${release_url}"
|
|
127
|
-
|
|
128
|
-
if [[ "$publish_status" == "failed" ]]; then
|
|
129
|
-
echo "KODY_REASON=tag + GH release created, but publishCommand failed"
|
|
130
|
-
echo "KODY_SKIP_AGENT=true"
|
|
131
|
-
exit 1
|
|
132
|
-
fi
|
|
133
|
-
|
|
134
|
-
[[ -n "$release_url" ]] && echo "KODY_PR_URL=${release_url}"
|
|
135
|
-
echo "KODY_REASON=tagged ${tag}, published${publish_status:+ ($publish_status)}"
|
|
136
|
-
echo "KODY_SKIP_AGENT=true"
|
|
137
|
-
exit 0
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# Deterministic conflict resolution for `kody resolve --prefer ours|theirs`.
|
|
4
|
-
#
|
|
5
|
-
# Runs as a preflight shell entry (see src/executables/resolve/profile.json).
|
|
6
|
-
# Gated by runWhen on args.prefer, so it only fires when the user requested
|
|
7
|
-
# a side. Reads the side from $KODY_ARG_PREFER (the executor exposes every
|
|
8
|
-
# ctx.args.<key> as an env var).
|
|
9
|
-
#
|
|
10
|
-
# Preconditions set by the prior `resolveFlow` preflight:
|
|
11
|
-
# - cwd is on the PR branch.
|
|
12
|
-
# - `git merge origin/<base> --no-edit --no-ff` already ran and conflicted.
|
|
13
|
-
# - Working tree has unmerged paths.
|
|
14
|
-
#
|
|
15
|
-
# Behavior: for each unmerged file, git checkout --ours (or --theirs), add,
|
|
16
|
-
# commit the merge, push the branch. Prints `KODY_SKIP_AGENT=true` on
|
|
17
|
-
# success so the executor bypasses the agent entirely.
|
|
18
|
-
#
|
|
19
|
-
# Exits:
|
|
20
|
-
# 0 — resolved + pushed (or nothing to resolve)
|
|
21
|
-
# 64 — invalid side value
|
|
22
|
-
# 1+ — git operation failed (executor will surface stderr)
|
|
23
|
-
|
|
24
|
-
set -euo pipefail
|
|
25
|
-
|
|
26
|
-
side="${KODY_ARG_PREFER:-}"
|
|
27
|
-
if [[ "$side" != "ours" && "$side" != "theirs" ]]; then
|
|
28
|
-
echo "apply-prefer: expected KODY_ARG_PREFER=ours|theirs, got '$side'" >&2
|
|
29
|
-
exit 64
|
|
30
|
-
fi
|
|
31
|
-
|
|
32
|
-
unmerged=$(git diff --name-only --diff-filter=U)
|
|
33
|
-
if [[ -z "$unmerged" ]]; then
|
|
34
|
-
echo "apply-prefer: no unmerged paths — nothing to resolve"
|
|
35
|
-
echo "KODY_SKIP_AGENT=true"
|
|
36
|
-
exit 0
|
|
37
|
-
fi
|
|
38
|
-
|
|
39
|
-
count=0
|
|
40
|
-
while IFS= read -r f; do
|
|
41
|
-
[[ -z "$f" ]] && continue
|
|
42
|
-
git checkout "--$side" -- "$f"
|
|
43
|
-
git add -- "$f"
|
|
44
|
-
count=$((count + 1))
|
|
45
|
-
done <<< "$unmerged"
|
|
46
|
-
echo "apply-prefer: resolved $count file(s) via --$side"
|
|
47
|
-
|
|
48
|
-
# Complete the merge. git merge left MERGE_MSG in place; --no-edit uses it.
|
|
49
|
-
HUSKY=0 SKIP_HOOKS=1 git -c commit.gpgsign=false commit --no-edit
|
|
50
|
-
|
|
51
|
-
branch=$(git rev-parse --abbrev-ref HEAD)
|
|
52
|
-
git push origin "$branch"
|
|
53
|
-
echo "apply-prefer: pushed $branch"
|
|
54
|
-
echo "KODY_SKIP_AGENT=true"
|