@kody-ade/kody-engine 0.4.14 → 0.4.15

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.15",
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,68 @@ 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
+ num=$(gh issue create \
146
+ --title "$title" \
147
+ --body "$body" \
148
+ --label "$label" \
149
+ --label "kody:building" \
150
+ --json number --jq '.number' 2>/dev/null || echo "")
151
+
152
+ if [ -z "$num" ]; then
153
+ echo "[goal-tick] ensure_goal_issue: gh issue create failed — continuing without umbrella issue"
154
+ return 0
155
+ fi
156
+
157
+ python3 - "$state_file" "$num" <<'PY'
158
+ import json, sys
159
+ from datetime import datetime, timezone
160
+ path = sys.argv[1]
161
+ n = int(sys.argv[2])
162
+ with open(path) as f:
163
+ s = json.load(f)
164
+ s["goalIssueNumber"] = n
165
+ s["updatedAt"] = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
166
+ with open(path, "w") as f:
167
+ json.dump(s, f, indent=2)
168
+ f.write("\n")
169
+ PY
170
+ echo "[goal-tick] opened umbrella issue #${num} for ${goal_id}"
171
+ }
172
+
111
173
  list_goal_issues() {
112
174
  # Up to 100 goal-labelled issues. PRs filtered out.
113
175
  gh api \
@@ -181,7 +243,16 @@ if [ "$open_count" = "0" ]; then
181
243
  existing_pr=$(gh pr list --head "$goal_branch" --state open --json number,url --jq '.[0]' 2>/dev/null || echo "")
182
244
  if [ -z "$existing_pr" ] || [ "$existing_pr" = "null" ]; then
183
245
  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")
246
+ goal_issue_number=$(read_state_field "goalIssueNumber")
247
+ # `Closes #N` auto-closes the umbrella goal issue on PR merge — that's
248
+ # how the dashboard learns the goal is done. Skip the line gracefully
249
+ # if no umbrella issue was ever opened (older goals from before this
250
+ # change, or `gh issue create` failed silently during a tick).
251
+ if [ -n "$goal_issue_number" ] && [ "$goal_issue_number" != "0" ]; then
252
+ 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")
253
+ else
254
+ 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")
255
+ fi
185
256
  goal_pr_url=$(gh pr create \
186
257
  --head "$goal_branch" \
187
258
  --base "$default_branch" \
@@ -286,6 +357,12 @@ else
286
357
  fi
287
358
  fi
288
359
 
360
+ # The goal branch now exists (or the fallback path will be used). Open the
361
+ # umbrella goal issue if we haven't yet, so the dashboard can render the goal
362
+ # as an issue row with a "kody:building" status and the goal branch as its
363
+ # preview. Idempotent — only the first call actually creates anything.
364
+ ensure_goal_issue
365
+
289
366
  echo "[goal-tick] dispatching @kody on task #$next_issue (--base $goal_branch)"
290
367
  gh issue comment "$next_issue" --body "@kody --base ${goal_branch}"
291
368
  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.15",
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",