@kody-ade/kody-engine 0.4.14 → 0.4.16

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.4.14",
6
+ version: "0.4.16",
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",
@@ -108,6 +108,72 @@ ensure_label() {
108
108
  gh label create "$1" --color "$2" --description "$3" --force >/dev/null 2>&1 || true
109
109
  }
110
110
 
111
+ read_state_field() {
112
+ # read_state_field <key> — prints the value or empty string. Never fails.
113
+ python3 - "$state_file" "$1" <<'PY' 2>/dev/null || echo ""
114
+ import json, sys
115
+ path, key = sys.argv[1], sys.argv[2]
116
+ try:
117
+ with open(path) as f:
118
+ s = json.load(f)
119
+ v = s.get(key, "")
120
+ print("" if v is None else v)
121
+ except Exception:
122
+ print("")
123
+ PY
124
+ }
125
+
126
+ ensure_goal_issue() {
127
+ # Create the umbrella goal issue (once), label it goal:<id> + kody:building,
128
+ # and persist its number on state.json. Idempotent: if state already has
129
+ # goalIssueNumber, this is a no-op. The issue auto-closes when the final
130
+ # goal PR merges, via the `Closes #N` line we add to that PR body.
131
+ local existing
132
+ existing=$(read_state_field "goalIssueNumber")
133
+ if [ -n "$existing" ] && [ "$existing" != "0" ]; then
134
+ return 0
135
+ fi
136
+
137
+ ensure_label "$label" "0e8a16" "kody goal task: belongs to goal ${goal_id}"
138
+ ensure_label "kody:building" "1d76db" "kody: in-flight (work being assembled on a branch)"
139
+
140
+ local title body num
141
+ title="goal: ${goal_id}"
142
+ body=$(printf "Umbrella issue for goal **%s**.\n\nClosed automatically when the goal PR (\`%s\` → \`%s\`) merges.\n" \
143
+ "$goal_id" "$goal_branch" "$default_branch")
144
+
145
+ # `gh issue create` prints the new issue's URL on stdout
146
+ # (https://github.com/<owner>/<repo>/issues/<n>). It does NOT support
147
+ # --json/--jq, so parse the trailing number off the URL.
148
+ local url
149
+ url=$(gh issue create \
150
+ --title "$title" \
151
+ --body "$body" \
152
+ --label "$label" \
153
+ --label "kody:building" 2>/dev/null || echo "")
154
+
155
+ num="${url##*/}"
156
+ if [ -z "$num" ] || ! [[ "$num" =~ ^[0-9]+$ ]]; then
157
+ echo "[goal-tick] ensure_goal_issue: gh issue create failed (got '${url}') — continuing without umbrella issue"
158
+ return 0
159
+ fi
160
+
161
+ python3 - "$state_file" "$num" <<'PY'
162
+ import json, sys
163
+ from datetime import datetime, timezone
164
+ path = sys.argv[1]
165
+ n = int(sys.argv[2])
166
+ with open(path) as f:
167
+ s = json.load(f)
168
+ s["goalIssueNumber"] = n
169
+ s["updatedAt"] = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
170
+ with open(path, "w") as f:
171
+ json.dump(s, f, indent=2)
172
+ f.write("\n")
173
+ PY
174
+ echo "[goal-tick] opened umbrella issue #${num} for ${goal_id}"
175
+ }
176
+
111
177
  list_goal_issues() {
112
178
  # Up to 100 goal-labelled issues. PRs filtered out.
113
179
  gh api \
@@ -181,7 +247,16 @@ if [ "$open_count" = "0" ]; then
181
247
  existing_pr=$(gh pr list --head "$goal_branch" --state open --json number,url --jq '.[0]' 2>/dev/null || echo "")
182
248
  if [ -z "$existing_pr" ] || [ "$existing_pr" = "null" ]; then
183
249
  title="goal: ${goal_id}"
184
- body=$(printf "Final integration PR for goal **%s**.\n\nAll task issues are closed and merged into \`%s\`. Ready for review.\n" "$goal_id" "$goal_branch")
250
+ goal_issue_number=$(read_state_field "goalIssueNumber")
251
+ # `Closes #N` auto-closes the umbrella goal issue on PR merge — that's
252
+ # how the dashboard learns the goal is done. Skip the line gracefully
253
+ # if no umbrella issue was ever opened (older goals from before this
254
+ # change, or `gh issue create` failed silently during a tick).
255
+ if [ -n "$goal_issue_number" ] && [ "$goal_issue_number" != "0" ]; then
256
+ body=$(printf "Final integration PR for goal **%s**.\n\nAll task issues are closed and merged into \`%s\`. Ready for review.\n\nCloses #%s\n" "$goal_id" "$goal_branch" "$goal_issue_number")
257
+ else
258
+ body=$(printf "Final integration PR for goal **%s**.\n\nAll task issues are closed and merged into \`%s\`. Ready for review.\n" "$goal_id" "$goal_branch")
259
+ fi
185
260
  goal_pr_url=$(gh pr create \
186
261
  --head "$goal_branch" \
187
262
  --base "$default_branch" \
@@ -286,6 +361,12 @@ else
286
361
  fi
287
362
  fi
288
363
 
364
+ # The goal branch now exists (or the fallback path will be used). Open the
365
+ # umbrella goal issue if we haven't yet, so the dashboard can render the goal
366
+ # as an issue row with a "kody:building" status and the goal branch as its
367
+ # preview. Idempotent — only the first call actually creates anything.
368
+ ensure_goal_issue
369
+
289
370
  echo "[goal-tick] dispatching @kody on task #$next_issue (--base $goal_branch)"
290
371
  gh issue comment "$next_issue" --body "@kody --base ${goal_branch}"
291
372
  gh issue edit "$next_issue" --add-label "$dispatched_label"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.14",
3
+ "version": "0.4.16",
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",