@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.
Files changed (114) hide show
  1. package/README.md +2 -2
  2. package/dist/bin/kody.js +779 -831
  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,48 +0,0 @@
1
- {
2
- "name": "preview-health",
3
- "role": "watch",
4
- "kind": "oneshot",
5
- "describe": "Scan open PRs and dispatch mechanical preview repairs.",
6
- "inputs": [],
7
- "claudeCode": {
8
- "model": "inherit",
9
- "permissionMode": "default",
10
- "maxTurns": 0,
11
- "maxThinkingTokens": null,
12
- "systemPromptAppend": null,
13
- "tools": [],
14
- "hooks": [],
15
- "skills": [],
16
- "commands": [],
17
- "subagents": [],
18
- "plugins": [],
19
- "mcpServers": []
20
- },
21
- "cliTools": [
22
- {
23
- "name": "gh",
24
- "install": {
25
- "required": true,
26
- "checkCommand": "command -v gh"
27
- },
28
- "verify": "gh auth status",
29
- "usage": "Reads open PRs, reads the CTO trust ledger, dispatches Kody workflows, and posts PR audit/recommendation comments.",
30
- "allowedUses": ["pr", "api", "issue", "workflow"]
31
- }
32
- ],
33
- "inputArtifacts": [],
34
- "outputArtifacts": [],
35
- "scripts": {
36
- "preflight": [
37
- {
38
- "shell": "tick.sh",
39
- "with": {
40
- "jobsDir": ".kody/duties",
41
- "duty": "preview-health"
42
- },
43
- "timeoutSec": 300
44
- }
45
- ],
46
- "postflight": []
47
- }
48
- }
@@ -1,319 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- jobs_dir="${1:-.kody/duties}"
5
- duty="${2:-preview-health}"
6
-
7
- export KODY_PREVIEW_HEALTH_JOBS_DIR="$jobs_dir"
8
- export KODY_PREVIEW_HEALTH_DUTY="$duty"
9
-
10
- python3 <<'PY'
11
- import base64
12
- import json
13
- import os
14
- import re
15
- import subprocess
16
- import sys
17
- from datetime import datetime, timezone
18
-
19
- DECISIONS_LABEL = "kody:cto-decisions"
20
- LEDGER_START = "<!-- kody-cto-decisions:start -->"
21
- LEDGER_END = "<!-- kody-cto-decisions:end -->"
22
- VERBS = ("fix-ci", "sync", "resolve")
23
- AUTO_VERBS = {"resolve"}
24
- MAX_ACTIONS_PER_TICK = 5
25
- FAIL_CONCLUSIONS = {"FAILURE", "TIMED_OUT", "ACTION_REQUIRED", "STARTUP_FAILURE"}
26
- RUNNING_STATUSES = {"IN_PROGRESS", "QUEUED"}
27
- STALE_THRESHOLD = 10
28
- STATE_BRANCH = "kody-state"
29
-
30
-
31
- def log(message):
32
- print(f"[preview-health] {message}", file=sys.stderr)
33
-
34
-
35
- def now_iso():
36
- return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
37
-
38
-
39
- def run_gh(args, input_text=None, check=True):
40
- result = subprocess.run(
41
- ["gh", *args],
42
- input=input_text,
43
- text=True,
44
- stdout=subprocess.PIPE,
45
- stderr=subprocess.PIPE,
46
- cwd=os.getcwd(),
47
- )
48
- if check and result.returncode != 0:
49
- raise RuntimeError(result.stderr.strip() or result.stdout.strip() or f"gh {' '.join(args)} failed")
50
- return result.stdout
51
-
52
-
53
- def repo_slug():
54
- owner = os.environ.get("KODY_CFG_GITHUB_OWNER", "").strip()
55
- repo = os.environ.get("KODY_CFG_GITHUB_REPO", "").strip()
56
- if owner and repo:
57
- return f"{owner}/{repo}"
58
- return run_gh(["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"]).strip()
59
-
60
-
61
- def default_branch(slug):
62
- configured = os.environ.get("KODY_CFG_GIT_DEFAULTBRANCH", "").strip()
63
- if configured:
64
- return configured
65
- return run_gh(["repo", "view", slug, "--json", "defaultBranchRef", "--jq", ".defaultBranchRef.name"]).strip()
66
-
67
-
68
- def ensure_state_branch(slug):
69
- if run_gh(["api", f"/repos/{slug}/git/ref/heads/{STATE_BRANCH}"], check=False).strip():
70
- return
71
- branch = default_branch(slug)
72
- sha = run_gh(["api", f"/repos/{slug}/git/ref/heads/{branch}", "--jq", ".object.sha"]).strip()
73
- payload = json.dumps({"ref": f"refs/heads/{STATE_BRANCH}", "sha": sha})
74
- run_gh(["api", "--method", "POST", f"/repos/{slug}/git/refs", "--input", "-"], input_text=payload)
75
-
76
-
77
- def initial_state():
78
- return {"version": 1, "rev": 0, "cursor": "seed", "data": {}, "done": False}
79
-
80
-
81
- def load_state(slug, path):
82
- result = run_gh(
83
- ["api", f"/repos/{slug}/contents/{path}?ref={STATE_BRANCH}"],
84
- check=False,
85
- )
86
- if not result.strip():
87
- return {"path": path, "sha": None, "state": initial_state(), "created": True}
88
- obj = json.loads(result)
89
- raw = base64.b64decode(obj["content"]).decode("utf-8")
90
- return {"path": path, "sha": obj.get("sha"), "state": json.loads(raw), "created": False}
91
-
92
-
93
- def state_unchanged(prev, next_state):
94
- return (
95
- prev.get("cursor") == next_state.get("cursor")
96
- and prev.get("done") == next_state.get("done")
97
- and prev.get("data") == next_state.get("data")
98
- )
99
-
100
-
101
- def save_state(slug, loaded, next_state):
102
- if not loaded["created"] and state_unchanged(loaded["state"], next_state):
103
- return False
104
- ensure_state_branch(slug)
105
- body = json.dumps(next_state, indent=2) + "\n"
106
- payload = {
107
- "message": f"chore(jobs): update state for {os.environ['KODY_PREVIEW_HEALTH_DUTY']} (rev {next_state['rev']})",
108
- "content": base64.b64encode(body.encode("utf-8")).decode("ascii"),
109
- "branch": STATE_BRANCH,
110
- }
111
- if loaded.get("sha"):
112
- payload["sha"] = loaded["sha"]
113
- result = run_gh(
114
- ["api", "--method", "PUT", f"/repos/{slug}/contents/{loaded['path']}", "--input", "-"],
115
- input_text=json.dumps(payload),
116
- check=False,
117
- )
118
- if result.strip():
119
- return True
120
- reloaded = load_state(slug, loaded["path"])
121
- if not reloaded["created"] and state_unchanged(reloaded["state"], next_state):
122
- return False
123
- if reloaded.get("sha"):
124
- payload["sha"] = reloaded["sha"]
125
- else:
126
- payload.pop("sha", None)
127
- run_gh(["api", "--method", "PUT", f"/repos/{slug}/contents/{loaded['path']}", "--input", "-"], input_text=json.dumps(payload))
128
- return True
129
-
130
-
131
- def read_ledger_modes():
132
- modes = {verb: "ask" for verb in VERBS}
133
- try:
134
- raw = run_gh(["issue", "list", "--label", DECISIONS_LABEL, "--state", "open", "--limit", "20", "--json", "number,body"])
135
- issues = json.loads(raw or "[]")
136
- if not issues:
137
- return modes
138
- body = sorted(issues, key=lambda x: x.get("number", 10**9))[0].get("body", "")
139
- if LEDGER_START not in body or LEDGER_END not in body:
140
- return modes
141
- inner = body.split(LEDGER_START, 1)[1].split(LEDGER_END, 1)[0]
142
- match = re.search(r"```(?:json)?\s*(\{[\s\S]*?\})\s*```", inner)
143
- if not match:
144
- return modes
145
- ledger = json.loads(match.group(1))
146
- cto = ledger.get("staff", {}).get("cto", {})
147
- for verb in VERBS:
148
- if cto.get(verb, {}).get("mode") == "auto":
149
- modes[verb] = "auto"
150
- except Exception as err:
151
- log(f"ledger read failed; treating all verbs as ask: {err}")
152
- return modes
153
-
154
-
155
- def ci_failing(rollup):
156
- if not isinstance(rollup, list):
157
- return False
158
- has_fail = any(str(check.get("conclusion", "")) in FAIL_CONCLUSIONS for check in rollup if isinstance(check, dict))
159
- running = any(str(check.get("status", "")) in RUNNING_STATUSES for check in rollup if isinstance(check, dict))
160
- return has_fail and not running
161
-
162
-
163
- def behind_by(slug, base, head):
164
- try:
165
- raw = run_gh(["api", f"repos/{slug}/compare/{base}...{head}", "--jq", ".behind_by"])
166
- return int(raw.strip() or "0")
167
- except Exception as err:
168
- log(f"compare {base}...{head} failed: {err}")
169
- return 0
170
-
171
-
172
- def detect_repair(slug, pr):
173
- mergeable = str(pr.get("mergeable") or "").upper()
174
- if mergeable == "UNKNOWN":
175
- return ("defer", f"PR #{pr['number']} mergeability still UNKNOWN; retry next tick.")
176
- if mergeable == "CONFLICTING":
177
- return ("resolve", f"PR #{pr['number']} has merge conflicts with `{pr['baseRefName']}`.")
178
- if ci_failing(pr.get("statusCheckRollup")):
179
- return ("fix-ci", f"PR #{pr['number']} has failing CI checks.")
180
- drift = behind_by(slug, pr["baseRefName"], pr["headRefName"])
181
- if drift >= STALE_THRESHOLD:
182
- return ("sync", f"PR #{pr['number']}'s branch is {drift} commits behind `{pr['baseRefName']}`.")
183
- return None
184
-
185
-
186
- def duty_operator(jobs_dir, duty):
187
- try:
188
- with open(os.path.join(os.getcwd(), jobs_dir, duty, "profile.json"), "r", encoding="utf-8") as f:
189
- profile = json.load(f)
190
- mentions = profile.get("mentions", [])
191
- return mentions[0] if isinstance(mentions, list) and mentions else ""
192
- except Exception:
193
- return ""
194
-
195
-
196
- def post_comment(pr_number, body):
197
- if os.environ.get("KODY_DRY_RUN") == "1":
198
- log(f"[dry-run] would comment on #{pr_number}: {body.splitlines()[0]}")
199
- return True
200
- try:
201
- run_gh(["pr", "comment", str(pr_number), "--body", body])
202
- return True
203
- except Exception as err:
204
- log(f"comment failed on #{pr_number}: {err}")
205
- return False
206
-
207
-
208
- def recommend(pr_number, verb, reason, operator):
209
- mention = f"@{operator} " if operator else ""
210
- body = (
211
- f"{mention}**CTO recommendation** - `{verb}`\n\n"
212
- f"{reason} Recommended action: `{verb}` for PR #{pr_number}.\n\n"
213
- "_Confirm in dashboard inbox or run the action manually. CTO will not act on its own._"
214
- )
215
- return post_comment(pr_number, body)
216
-
217
-
218
- def auto_run(pr_number, verb, reason):
219
- if os.environ.get("KODY_DRY_RUN") == "1":
220
- log(f"[dry-run] would dispatch kody.yml executable={verb} issue_number={pr_number}")
221
- else:
222
- try:
223
- run_gh(["workflow", "run", "kody.yml", "-f", f"executable={verb}", "-f", f"issue_number={pr_number}"])
224
- except Exception as err:
225
- log(f"workflow_dispatch failed #{pr_number} ({verb}): {err}")
226
- return False
227
- auto_reason = (
228
- "Policy: preview-health auto-runs `resolve` for merge conflicts."
229
- if verb in AUTO_VERBS
230
- else f"Graduated: operator approved `{verb}` repeatedly. A **Reject** on any `{verb}` returns me asking."
231
- )
232
- return post_comment(pr_number, f"**CTO action** - `{verb}` dispatched\n\n{reason}\n\n{auto_reason}")
233
-
234
-
235
- def print_row(pr, verb, fp, action, note):
236
- print(f"| #{pr} | {verb} | {fp} | {action} | {note} |")
237
-
238
-
239
- def main():
240
- slug = repo_slug()
241
- jobs_dir = os.environ["KODY_PREVIEW_HEALTH_JOBS_DIR"].rstrip("/")
242
- duty = os.environ["KODY_PREVIEW_HEALTH_DUTY"]
243
- state_path = f"{jobs_dir}/{duty}/state.json"
244
- loaded = load_state(slug, state_path)
245
- modes = read_ledger_modes()
246
- operator = duty_operator(jobs_dir, duty)
247
-
248
- prs = json.loads(
249
- run_gh([
250
- "pr",
251
- "list",
252
- "--state",
253
- "open",
254
- "--limit",
255
- "100",
256
- "--json",
257
- "number,title,headRefName,headRefOid,baseRefName,isDraft,mergeable,statusCheckRollup,updatedAt",
258
- ])
259
- or "[]"
260
- )
261
-
262
- prior = loaded["state"].get("data", {}).get("prs", {})
263
- open_numbers = {str(pr["number"]) for pr in prs}
264
- next_prs = {key: value for key, value in prior.items() if key in open_numbers and isinstance(value, dict)}
265
-
266
- print("| PR | verb | fingerprint | action | note |")
267
- print("|----|------|-------------|--------|------|")
268
-
269
- priority = {"resolve": 0, "fix-ci": 1, "sync": 2}
270
- queue = []
271
- for pr in prs:
272
- if pr.get("isDraft"):
273
- print_row(pr["number"], "-", "-", "skip", "draft")
274
- continue
275
- repair = detect_repair(slug, pr)
276
- if not repair:
277
- print_row(pr["number"], "-", "-", "skip", "healthy")
278
- continue
279
- verb, reason = repair
280
- if verb == "defer":
281
- print_row(pr["number"], "-", "-", "defer", "mergeable=UNKNOWN")
282
- continue
283
- queue.append((priority[verb], pr["number"], pr, verb, reason))
284
-
285
- actions_taken = 0
286
- for _, number, pr, verb, reason in sorted(queue):
287
- key = str(number)
288
- fp = f"{verb}|{pr.get('headRefOid', '')}"
289
- graduated = verb in AUTO_VERBS or modes.get(verb) == "auto"
290
- intended_stage = f"{verb}-auto" if graduated else f"{verb}-recommended"
291
- prev = next_prs.get(key, {})
292
- if prev.get("fp") == fp and prev.get("stage") == intended_stage:
293
- print_row(number, verb, fp[:24], "skip", "dedup")
294
- continue
295
- if actions_taken >= MAX_ACTIONS_PER_TICK:
296
- print_row(number, verb, fp[:24], "defer", "tick cap")
297
- continue
298
-
299
- ok = auto_run(number, verb, reason) if graduated else recommend(number, verb, reason, operator)
300
- action = "auto-ran" if graduated and ok else "auto-failed" if graduated else "recommended" if ok else "recommend-failed"
301
- if ok:
302
- actions_taken += 1
303
- next_prs[key] = {"fp": fp, "stage": intended_stage, "lastActAt": now_iso()}
304
- print_row(number, verb, fp[:24], action, "auto" if graduated else "advisory")
305
-
306
- next_state = {
307
- "version": 1,
308
- "rev": int(loaded["state"].get("rev", 0)) + 1,
309
- "cursor": "idle",
310
- "data": {"prs": next_prs, "lastFiredAt": now_iso()},
311
- "done": False,
312
- }
313
- save_state(slug, loaded, next_state)
314
- log(f"tick complete: {actions_taken} action(s), {len(next_prs)} tracked PR(s)")
315
- print("KODY_SKIP_AGENT=true")
316
-
317
-
318
- main()
319
- PY
@@ -1,35 +0,0 @@
1
- {
2
- "name": "probe-skill",
3
- "role": "utility",
4
- "describe": "Live-test executable. Loads the executable-local 'probe-skill-marker' skill (resolved from src/executables/probe-skill/skills/, NOT the central catalog) and asks the agent to emit the skill's token back as an issue comment. Validates the new local-skill resolution path in buildSyntheticPlugin end-to-end.",
5
- "inputs": [
6
- { "name": "issue", "flag": "--issue", "type": "int", "required": true, "describe": "GitHub issue number to verify against." }
7
- ],
8
- "claudeCode": {
9
- "model": "inherit",
10
- "permissionMode": "default",
11
- "maxTurns": 12,
12
- "systemPromptAppend": "You are running Kody's executable-local skill live verification. Emit the token exactly as the skill instructs.",
13
- "tools": ["Read", "Grep", "Glob", "Bash"],
14
- "hooks": [],
15
- "skills": ["probe-skill-marker"],
16
- "commands": [],
17
- "subagents": [],
18
- "plugins": [],
19
- "mcpServers": []
20
- },
21
- "cliTools": [],
22
- "scripts": {
23
- "preflight": [
24
- { "script": "loadIssueContext" },
25
- { "script": "loadTaskState" },
26
- { "script": "buildSyntheticPlugin" },
27
- { "script": "composePrompt" }
28
- ],
29
- "postflight": [
30
- { "script": "parseAgentResult" },
31
- { "script": "writeRunSummary" },
32
- { "script": "saveTaskState" }
33
- ]
34
- }
35
- }
@@ -1,31 +0,0 @@
1
- You are Kody's executable-local skill live verification agent. Your only job: prove that the skill named `probe-skill-marker` was loaded into your session from this executable's own directory (NOT the shared catalog), and report its token back to the issue.
2
-
3
- Issue #{{issue.number}}: {{issue.title}}
4
-
5
- ---
6
-
7
- # What to do
8
-
9
- 1. List your available skills. Confirm a skill named `probe-skill-marker` is loaded.
10
- 2. Activate it (its activation phrase is "probe-skill-marker"). The skill instructs you to emit a single token of the form `PROBE_SKILL_OK_<version>`.
11
- 3. Post a comment on issue #{{issue.number}} via `gh issue comment {{issue.number}} --body "..."`. The body must be a single line:
12
- ```
13
- probe-skill verification: <TOKEN>
14
- ```
15
- Replace `<TOKEN>` with whatever exact token the skill told you to emit. If the skill is NOT loaded, post `probe-skill verification: SKILL_NOT_LOADED` instead.
16
-
17
- # Output contract
18
-
19
- After posting the comment, your final message must be exactly:
20
-
21
- ```
22
- DONE
23
- COMMIT_MSG: probe-skill: live verification for #{{issue.number}}
24
- PR_SUMMARY: probe-skill ran; see issue comment for the token.
25
- ```
26
-
27
- # Rules
28
-
29
- - Read-only on the repo. Do NOT edit any file. Do NOT run git.
30
- - The only state-changing command you may run is `gh issue comment`.
31
- - Do not perform the issue's actual work.
@@ -1,18 +0,0 @@
1
- ---
2
- name: probe-skill-marker
3
- description: Internal Kody live-test skill, shipped from the probe-skill executable's own directory (not the shared src/plugins catalog). Activates when a prompt mentions "probe-skill-marker" and provides a versioned confirmation token.
4
- ---
5
-
6
- # probe-skill-marker
7
-
8
- This skill exists only to verify that Kody's executable-local plugin-part resolution actually loads skills from `src/executables/<name>/skills/` into the agent's session.
9
-
10
- ## When to activate
11
-
12
- When the user's prompt contains the phrase "probe-skill-marker" or explicitly asks whether the skill is loaded.
13
-
14
- ## What to do
15
-
16
- Emit the literal token `PROBE_SKILL_OK_v1` exactly as written. Do not modify, paraphrase, or interpret it. The token version (`v1`) lets us prove that edits to this file are picked up on the next run — bumping it to `v2` here should result in the agent reporting `PROBE_SKILL_OK_v2` after a fresh publish + trigger.
17
-
18
- Do not use this skill for anything else. It is a no-op confirmation signal for infrastructure validation.
@@ -1,65 +0,0 @@
1
- {
2
- "name": "qa-goal",
3
- "role": "primitive",
4
- "kind": "oneshot",
5
- "describe": "Operator-gated half of QA: promotes a QA report (already posted on an issue by qa-engineer) into a goal — manifest entry + one fix-ticket per finding + a committed .kody/goals/<id>/state.json. Deterministic, no agent. The qa / qa-sweep duties surface a `@kody qa-goal --issue <n>` inbox rec; this runs when the operator approves it, so QA never auto-creates goals on its own.",
6
- "inputs": [
7
- {
8
- "name": "issue",
9
- "flag": "--issue",
10
- "type": "int",
11
- "required": true,
12
- "describe": "Issue number carrying qa-engineer's QA report (the comment with the <!-- KODY_QA_REPORT_JSON --> block)."
13
- },
14
- {
15
- "name": "scope",
16
- "flag": "--scope",
17
- "type": "string",
18
- "required": false,
19
- "describe": "Optional scope label for the goal name (e.g. the changelog entry title). Defaults to 'smoke'."
20
- },
21
- {
22
- "name": "goal",
23
- "flag": "--goal",
24
- "type": "string",
25
- "required": false,
26
- "describe": "Optional existing goal id to attach findings to instead of creating a new one."
27
- }
28
- ],
29
- "claudeCode": {
30
- "model": "inherit",
31
- "permissionMode": "default",
32
- "maxTurns": null,
33
- "maxThinkingTokens": null,
34
- "systemPromptAppend": null,
35
- "tools": [],
36
- "hooks": [],
37
- "skills": [],
38
- "commands": [],
39
- "subagents": [],
40
- "plugins": [],
41
- "mcpServers": []
42
- },
43
- "cliTools": [
44
- {
45
- "name": "gh",
46
- "install": {
47
- "required": true,
48
- "checkCommand": "command -v gh"
49
- },
50
- "verify": "gh auth status",
51
- "usage": "Reads the QA report (`gh issue view --json comments`), appends to the goals manifest, and opens fix-ticket issues.",
52
- "allowedUses": ["issue", "api"]
53
- }
54
- ],
55
- "inputArtifacts": [],
56
- "outputArtifacts": [],
57
- "scripts": {
58
- "preflight": [
59
- {
60
- "script": "promoteQaGoal"
61
- }
62
- ],
63
- "postflight": []
64
- }
65
- }
@@ -1,157 +0,0 @@
1
- #!/usr/bin/env bash
2
- #
3
- # release/deploy.sh — function library for the deploy phase.
4
- #
5
- # Functions:
6
- # read_changelog_section <branch> <ver> -> prints the body of the matching ## header block
7
- # build_pr_body <ver> <changelog> <default_branch> <release_branch> <issue>
8
- # -> echoes the deploy PR body
9
- # open_deploy_pr <new_version> <issue> -> echoes deploy PR URL (or empty if no-op)
10
-
11
- # shellcheck disable=SC2148
12
-
13
- # Extract the section for $ver from CHANGELOG.md fetched from origin/$branch.
14
- # Handles three header shapes:
15
- # "## [0.25.0] - 2026-04-15" (bracketed, dash separator)
16
- # "## 0.22.0 (2026-03-25)" (bare, parenthesized date)
17
- # "## v0.25.5 — 2026-05-05" (v-prefixed, em-dash, kody style)
18
- read_changelog_section() {
19
- local branch="$1" ver="$2" raw=""
20
- if ! raw=$(git show "origin/${branch}:CHANGELOG.md" 2>/dev/null); then
21
- return 0
22
- fi
23
- printf '%s' "$raw" | awk -v ver="$ver" '
24
- BEGIN { capture = 0 }
25
- /^##[[:space:]]/ {
26
- if (capture) { exit }
27
- header = $0
28
- sub(/^##[[:space:]]+/, "", header)
29
- sub(/^\[/, "", header); sub(/\].*/, "", header)
30
- sub(/[[:space:]].*/, "", header)
31
- sub(/[(].*/, "", header)
32
- sub(/^v/, "", header)
33
- if (header == ver) { capture = 1; next }
34
- }
35
- capture { print }
36
- ' | awk '
37
- NF { if (!started) started = 1; out[++n] = $0; last = n; next }
38
- started { out[++n] = $0 }
39
- END { for (i = 1; i <= last; i++) print out[i] }
40
- '
41
- }
42
-
43
- build_pr_body() {
44
- local ver="$1" changelog="$2" default_branch="$3" release_branch="$4" issue="$5"
45
- printf 'Automated deploy PR opened by kody — promotes `%s` to `%s` for release **v%s**.\n\n' \
46
- "$default_branch" "$release_branch" "$ver"
47
- if [[ -n "$changelog" ]]; then
48
- printf '<!-- kody-changelog-start -->\n## What'\''s changing in v%s\n\n%s\n<!-- kody-changelog-end -->\n\n' \
49
- "$ver" "$changelog"
50
- fi
51
- printf 'Merge this PR to deploy v%s to `%s`.' "$ver" "$release_branch"
52
- if [[ "$issue" =~ ^[0-9]+$ && "$issue" != "0" ]]; then
53
- printf '\n\nTracking-Issue: #%s\n' "$issue"
54
- else
55
- printf '\n'
56
- fi
57
- }
58
-
59
- # Open or reuse the deploy PR (default_branch → release_branch).
60
- # Returns the PR URL via stdout. Empty stdout = single-branch repo, no-op.
61
- # Refreshes existing PR's body via gh pr edit (idempotent).
62
- open_deploy_pr() {
63
- local new_version="$1"
64
- local issue_arg="$2"
65
- local default_branch="${KODY_CFG_GIT_DEFAULTBRANCH:-main}"
66
- local release_branch="${KODY_CFG_RELEASE_RELEASEBRANCH:-}"
67
-
68
- # Single-branch repos: nothing to deploy.
69
- if [[ -z "$release_branch" || "$release_branch" == "$default_branch" ]]; then
70
- echo "[deploy] no releaseBranch configured (or equals defaultBranch) — skipping deploy PR" >&2
71
- echo ""
72
- return 0
73
- fi
74
-
75
- # Read the section from the integration branch (where release-prepare just merged).
76
- local changelog_section
77
- changelog_section=$(read_changelog_section "$default_branch" "$new_version" || true)
78
- if [[ -z "$changelog_section" ]]; then
79
- echo "[deploy] no CHANGELOG section for v${new_version} on origin/${default_branch} — minimal body" >&2
80
- fi
81
-
82
- local body
83
- body=$(build_pr_body "$new_version" "$changelog_section" "$default_branch" "$release_branch" "$issue_arg")
84
-
85
- # GitHub rejects a PR body over 65536 chars (GraphQL createPullRequest).
86
- # A large accumulated CHANGELOG section can blow past it, so clamp: drop the
87
- # changelog to a budget and rebuild, then hard-truncate as a final guard.
88
- local body_max=65000
89
- if (( ${#body} > body_max )); then
90
- echo "[deploy] PR body ${#body} chars > ${body_max} — truncating changelog" >&2
91
- local budget=$(( body_max - 2000 ))
92
- changelog_section="${changelog_section:0:budget}"$'\n\n_…changelog truncated; see CHANGELOG.md on the branch._'
93
- body=$(build_pr_body "$new_version" "$changelog_section" "$default_branch" "$release_branch" "$issue_arg")
94
- (( ${#body} > body_max )) && body="${body:0:body_max}"
95
- fi
96
-
97
- # Idempotency: reuse an open PR for this branch pair if one exists.
98
- local existing pr_url
99
- existing=$(gh pr list --head "$default_branch" --base "$release_branch" --state open --json url --limit 1 2>/dev/null \
100
- | python3 -c 'import json,sys; data=json.load(sys.stdin); print(data[0]["url"] if data else "")' 2>/dev/null \
101
- || echo "")
102
-
103
- if [[ -n "$existing" ]]; then
104
- echo " reusing existing deploy PR: ${existing}" >&2
105
- pr_url="$existing"
106
- # Refresh body via REST API instead of `gh pr edit` — gh's edit path
107
- # uses GraphQL which requires read:org scope on KODY_TOKEN. REST PATCH
108
- # works with plain `repo` scope.
109
- local pr_num="${pr_url##*/}"
110
- local owner="${KODY_CFG_GITHUB_OWNER:-}"
111
- local repo="${KODY_CFG_GITHUB_REPO:-}"
112
- if [[ -z "$owner" || -z "$repo" ]]; then
113
- # Fall back to extracting from the URL if config missing.
114
- local stripped="${pr_url#https://github.com/}"
115
- owner="${stripped%%/*}"
116
- repo=$(echo "$stripped" | cut -d/ -f2)
117
- fi
118
- local edit_err
119
- local refreshed_title="deploy: ${default_branch} → ${release_branch} (v${new_version})"
120
- if ! edit_err=$(gh api --method PATCH "repos/${owner}/${repo}/pulls/${pr_num}" \
121
- -f title="$refreshed_title" \
122
- -f body="$body" 2>&1 >/dev/null); then
123
- echo "[deploy] WARN: failed to refresh deploy PR title+body for ${pr_url}: ${edit_err}" >&2
124
- else
125
- echo " refreshed deploy PR title + body via REST" >&2
126
- fi
127
- else
128
- 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
129
- echo "[deploy] gh pr create failed" >&2
130
- return 1
131
- fi
132
- fi
133
-
134
- if [[ -z "$pr_url" ]]; then
135
- echo "[deploy] empty PR URL after gh pr create" >&2
136
- return 1
137
- fi
138
-
139
- # Persist the deploy-PR marker on the originating issue body, replacing
140
- # any prepare-PR marker so the dashboard pivots to the deploy PR.
141
- if [[ "$issue_arg" =~ ^[0-9]+$ && "$issue_arg" != "0" ]]; then
142
- local pr_number="${pr_url##*/}"
143
- if [[ "$pr_number" =~ ^[0-9]+$ ]]; then
144
- local cur_body cleaned_body
145
- cur_body=$(gh issue view "$issue_arg" --json body -q .body 2>/dev/null || echo "")
146
- cleaned_body=$(printf '%s' "$cur_body" | sed -E '/<!-- kody-release-pr:[^>]*-->/d')
147
- {
148
- printf '%s' "$cleaned_body"
149
- printf '\n\n<!-- kody-release-pr: #%s -->\n' "$pr_number"
150
- } | gh issue edit "$issue_arg" --body-file - >/dev/null 2>&1 || \
151
- echo "[deploy] WARN: failed to write kody-release-pr marker to issue #${issue_arg}" >&2
152
- fi
153
- fi
154
-
155
- echo "$pr_url"
156
- return 0
157
- }