@kody-ade/kody-engine 0.3.70-beta.6 → 0.3.70

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/dist/bin/kody.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@kody-ade/kody-engine",
6
- version: "0.3.70-beta.6",
6
+ version: "0.3.70",
7
7
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
8
8
  license: "MIT",
9
9
  type: "module",
@@ -91,23 +91,8 @@ open_deploy_pr() {
91
91
  if [[ -n "$existing" ]]; then
92
92
  echo " reusing existing deploy PR: ${existing}" >&2
93
93
  pr_url="$existing"
94
- # Refresh body via REST API instead of `gh pr edit` gh's edit path
95
- # uses GraphQL which requires read:org scope on KODY_TOKEN. REST PATCH
96
- # works with plain `repo` scope.
97
- local pr_num="${pr_url##*/}"
98
- local owner="${KODY_CFG_GITHUB_OWNER:-}"
99
- local repo="${KODY_CFG_GITHUB_REPO:-}"
100
- if [[ -z "$owner" || -z "$repo" ]]; then
101
- # Fall back to extracting from the URL if config missing.
102
- local stripped="${pr_url#https://github.com/}"
103
- owner="${stripped%%/*}"
104
- repo=$(echo "$stripped" | cut -d/ -f2)
105
- fi
106
- local edit_err
107
- if ! edit_err=$(gh api --method PATCH "repos/${owner}/${repo}/pulls/${pr_num}" -f body="$body" 2>&1 >/dev/null); then
108
- echo "[deploy] WARN: failed to refresh deploy PR body for ${pr_url}: ${edit_err}" >&2
109
- else
110
- echo " refreshed deploy PR body via REST" >&2
94
+ if ! printf '%s' "$body" | gh pr edit "$pr_url" --body-file - >/dev/null 2>&1; then
95
+ echo "[deploy] WARN: failed to refresh deploy PR body" >&2
111
96
  fi
112
97
  else
113
98
  if ! pr_url=$(printf '%s' "$body" | gh pr create --head "$default_branch" --base "$release_branch" --title "deploy: ${default_branch} → ${release_branch} (v${new_version})" --body-file -); then
@@ -107,18 +107,12 @@ generate_changelog() {
107
107
 
108
108
  format_changelog() {
109
109
  local new_version="$1"
110
- local date_str raw
110
+ local date_str
111
111
  date_str=$(date -u +%Y-%m-%d)
112
- # Capture stdin BEFORE invoking python — the python heredoc below
113
- # redirects sys.stdin to the heredoc text itself, so we can't rely on
114
- # `sys.stdin.read()` to see the piped `subject||sha` lines. Pass the
115
- # raw log through an env var instead.
116
- raw=$(cat)
117
- RAW_CHANGELOG="$raw" NEW_VER="$new_version" DATE_STR="$date_str" python3 - <<'PY'
118
- import os, re, sys
119
- new_version = os.environ["NEW_VER"]
120
- date_str = os.environ["DATE_STR"]
121
- raw = os.environ.get("RAW_CHANGELOG", "")
112
+ python3 - "$new_version" "$date_str" <<'PY'
113
+ import sys, re
114
+ new_version, date_str = sys.argv[1], sys.argv[2]
115
+ raw = sys.stdin.read()
122
116
  buckets = {k: [] for k in ("feat", "fix", "perf", "refactor", "docs", "chore", "other")}
123
117
  for line in raw.splitlines():
124
118
  line = line.strip()
@@ -64,7 +64,7 @@
64
64
  },
65
65
  { "script": "loadIssueContext" },
66
66
  { "script": "loadTaskState" },
67
- { "shell": "release.sh", "timeoutSec": 5400 },
67
+ { "shell": "release.sh" },
68
68
  { "script": "skipAgent" }
69
69
  ],
70
70
  "postflight": [
@@ -15,33 +15,15 @@ tag_and_publish() {
15
15
  local timeout_s=$((timeout_ms / 1000))
16
16
  local tag="v${new_version}"
17
17
 
18
- # Idempotent tagging: if the tag already exists and points at HEAD,
19
- # treat it as already-published. If it points elsewhere, fail loudly —
20
- # something is inconsistent and a human should look.
21
- local remote_sha local_sha head_sha
22
- head_sha=$(git rev-parse HEAD)
23
- if local_sha=$(git rev-parse --verify "$tag" 2>/dev/null); then
24
- if [[ "$local_sha" == "$head_sha" ]]; then
25
- echo " tag ${tag} already exists locally at HEAD — skipping create" >&2
26
- else
27
- echo "[publish] tag ${tag} exists locally at ${local_sha} but HEAD is ${head_sha}" >&2
28
- return 1
29
- fi
30
- else
31
- git tag -a "$tag" -m "Release ${tag}"
32
- fi
33
- # Push the tag if it isn't already on origin (or push always; gh will
34
- # no-op on existing remote tag at the same sha).
35
- if remote_sha=$(git ls-remote --tags origin "refs/tags/${tag}" 2>/dev/null | awk '{print $1}'); then
36
- if [[ -z "$remote_sha" ]]; then
37
- git push origin "$tag"
38
- elif [[ "$remote_sha" != "$head_sha" ]]; then
39
- echo " WARN: remote tag ${tag} points at ${remote_sha}, HEAD is ${head_sha}" >&2
40
- fi
41
- else
42
- git push origin "$tag" || true
18
+ # Refuse if the tag already exists locally (left over from a prior failed run).
19
+ if git rev-parse --verify "$tag" >/dev/null 2>&1; then
20
+ echo "[publish] tag ${tag} already exists locally" >&2
21
+ return 1
43
22
  fi
44
23
 
24
+ git tag -a "$tag" -m "Release ${tag}"
25
+ git push origin "$tag"
26
+
45
27
  # publishCommand (optional). Failure here is recorded but does not abort —
46
28
  # we still want the GH release entry so the tag is discoverable.
47
29
  local publish_status="skipped"
@@ -66,16 +48,6 @@ create_gh_release() {
66
48
  local draft_flag=""
67
49
  [[ "$draft" == "true" ]] && draft_flag="--draft"
68
50
 
69
- # Idempotent: if a release for this tag already exists, reuse it.
70
- local existing_url
71
- if existing_url=$(gh release view "$tag" --json url -q .url 2>/dev/null); then
72
- if [[ -n "$existing_url" ]]; then
73
- echo " GH release for ${tag} already exists: ${existing_url}" >&2
74
- echo "$existing_url"
75
- return 0
76
- fi
77
- fi
78
-
79
51
  local release_url=""
80
52
  if release_url=$(gh release create "$tag" --title "$tag" --notes "Release ${tag} — automated by kody." $draft_flag 2>&1); then
81
53
  echo "$release_url"
@@ -3,12 +3,10 @@
3
3
  # release/wait.sh — wait_for_ci function: poll a PR's check rollup
4
4
  # until all non-skipped checks pass, or timeout.
5
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.
6
+ # Function: wait_for_ci <pr_number> <timeout_minutes>
7
+ # Exits 0 on CI_PASSED, exits 1 on CI_FAILED/CI_TIMEOUT.
8
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.
9
+ # Reads gh pr checks <N> output. Treats SKIPPED as pass.
12
10
 
13
11
  # shellcheck disable=SC2148
14
12
 
@@ -26,50 +24,43 @@ wait_for_ci() {
26
24
  local deadline=$(( $(date +%s) + timeout_minutes * 60 ))
27
25
  echo "→ wait_for_ci: PR #${pr_number}, timeout=${timeout_minutes}m"
28
26
 
27
+ # Initial wait — gives GHA time to register checks.
29
28
  sleep "$initial_wait"
30
29
 
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
30
  while (( $(date +%s) < deadline )); do
31
+ # gh pr checks <N> --json prints an array of {state, name, ...}.
37
32
  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
33
+ if ! raw=$(gh pr checks "$pr_number" --json state,name 2>/dev/null); then
34
+ echo " [wait_for_ci] gh pr checks failed; retrying in ${poll_seconds}s" >&2
46
35
  sleep "$poll_seconds"
47
36
  continue
48
37
  fi
49
38
 
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
39
+ # Tally states. Anything that's still PENDING/IN_PROGRESS/QUEUED → keep waiting.
40
+ # Anything FAILURE/CANCELLED/TIMED_OUT/ACTION_REQUIRED → fail fast.
41
+ # SUCCESS/SKIPPED/NEUTRAL pass.
42
+ local summary
43
+ summary=$(printf '%s' "$raw" | python3 -c '
44
+ import json, sys
45
+ data = json.load(sys.stdin)
46
+ buckets = {"pending": 0, "passed": 0, "failed": 0, "failed_names": []}
47
+ for r in data:
48
+ s = (r.get("state") or "").upper()
49
+ name = r.get("name") or "?"
50
+ if s in ("PENDING", "IN_PROGRESS", "QUEUED", ""):
51
+ buckets["pending"] += 1
52
+ elif s in ("FAILURE", "CANCELLED", "TIMED_OUT", "ACTION_REQUIRED", "STARTUP_FAILURE"):
53
+ buckets["failed"] += 1
54
+ buckets["failed_names"].append(name)
55
+ else: # SUCCESS, SKIPPED, NEUTRAL, etc.
56
+ buckets["passed"] += 1
57
+ print(f"{buckets[\"pending\"]}|{buckets[\"passed\"]}|{buckets[\"failed\"]}|{','.join(buckets[\"failed_names\"][:5])}")
58
+ ')
64
59
 
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(",")')
60
+ local pending passed failed failed_names
61
+ IFS='|' read -r pending passed failed failed_names <<< "$summary"
71
62
 
72
- echo " [wait_for_ci] pending=${pending} passed=${passed} failed=${failed} (total=${total})"
63
+ echo " [wait_for_ci] pending=${pending} passed=${passed} failed=${failed}"
73
64
 
74
65
  if [[ "$failed" -gt 0 ]]; then
75
66
  echo "[wait_for_ci] CI failed on PR #${pr_number}: ${failed_names}" >&2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.3.70-beta.6",
3
+ "version": "0.3.70",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",