@kody-ade/kody-engine 0.3.83 → 0.3.84
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.
|
|
6
|
+
version: "0.3.84",
|
|
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",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "goal-scheduler",
|
|
3
|
+
"role": "watch",
|
|
4
|
+
"describe": "Scheduled: for every active goal under .kody/goals/<id>/state.json, invoke goal-tick once. No agent on the scheduler itself.",
|
|
5
|
+
"kind": "scheduled",
|
|
6
|
+
"schedule": "*/5 * * * *",
|
|
7
|
+
"inputs": [],
|
|
8
|
+
"claudeCode": {
|
|
9
|
+
"model": "inherit",
|
|
10
|
+
"permissionMode": "default",
|
|
11
|
+
"maxTurns": 0,
|
|
12
|
+
"maxThinkingTokens": null,
|
|
13
|
+
"systemPromptAppend": null,
|
|
14
|
+
"tools": [],
|
|
15
|
+
"hooks": [],
|
|
16
|
+
"skills": [],
|
|
17
|
+
"commands": [],
|
|
18
|
+
"subagents": [],
|
|
19
|
+
"plugins": [],
|
|
20
|
+
"mcpServers": []
|
|
21
|
+
},
|
|
22
|
+
"cliTools": [
|
|
23
|
+
{
|
|
24
|
+
"name": "gh",
|
|
25
|
+
"install": {
|
|
26
|
+
"required": true,
|
|
27
|
+
"checkCommand": "command -v gh"
|
|
28
|
+
},
|
|
29
|
+
"verify": "gh auth status",
|
|
30
|
+
"usage": "",
|
|
31
|
+
"allowedUses": ["api", "issue", "pr"]
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"inputArtifacts": [],
|
|
35
|
+
"outputArtifacts": [],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"preflight": [
|
|
38
|
+
{ "shell": "scheduler.sh" },
|
|
39
|
+
{ "script": "skipAgent" }
|
|
40
|
+
],
|
|
41
|
+
"postflight": []
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# goal-scheduler: enumerate every goal state file under .kody/goals/ and
|
|
4
|
+
# dispatch goal-tick once for each whose state == "active". Runs as a
|
|
5
|
+
# scheduled executable (cron `*/5 * * * *`). No agent.
|
|
6
|
+
#
|
|
7
|
+
# A failed individual tick logs and continues — one stuck goal must not
|
|
8
|
+
# starve the rest.
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
goals_dir=".kody/goals"
|
|
13
|
+
|
|
14
|
+
if [ ! -d "$goals_dir" ]; then
|
|
15
|
+
echo "[goal-scheduler] no $goals_dir — nothing to schedule"
|
|
16
|
+
echo "KODY_SKIP_AGENT=true"
|
|
17
|
+
exit 0
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
shopt -s nullglob
|
|
21
|
+
state_files=("$goals_dir"/*/state.json)
|
|
22
|
+
shopt -u nullglob
|
|
23
|
+
|
|
24
|
+
if [ "${#state_files[@]}" = "0" ]; then
|
|
25
|
+
echo "[goal-scheduler] no goal state files yet"
|
|
26
|
+
echo "KODY_SKIP_AGENT=true"
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
active=0
|
|
31
|
+
for state_file in "${state_files[@]}"; do
|
|
32
|
+
[ -f "$state_file" ] || continue
|
|
33
|
+
goal_id=$(basename "$(dirname "$state_file")")
|
|
34
|
+
|
|
35
|
+
state=$(python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get('state',''))" "$state_file" 2>/dev/null || echo "")
|
|
36
|
+
|
|
37
|
+
if [ "$state" != "active" ]; then
|
|
38
|
+
continue
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
active=$((active + 1))
|
|
42
|
+
echo "[goal-scheduler] → tick $goal_id"
|
|
43
|
+
|
|
44
|
+
# Run the tick in a subshell so a non-zero exit doesn't kill the loop.
|
|
45
|
+
if ! kody dispatch goal-tick --goal "$goal_id"; then
|
|
46
|
+
echo "[goal-scheduler] tick $goal_id failed (continuing)"
|
|
47
|
+
fi
|
|
48
|
+
done
|
|
49
|
+
|
|
50
|
+
echo "[goal-scheduler] ticked $active active goal(s) of ${#state_files[@]} total"
|
|
51
|
+
echo "KODY_SKIP_AGENT=true"
|
|
52
|
+
exit 0
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "goal-tick",
|
|
3
|
+
"role": "primitive",
|
|
4
|
+
"describe": "One deterministic tick for one goal: read .kody/goals/<id>/state.json, dispatch @kody on the next ready task (or mark goal done when all tasks closed). No agent.",
|
|
5
|
+
"kind": "oneshot",
|
|
6
|
+
"inputs": [
|
|
7
|
+
{
|
|
8
|
+
"name": "goal",
|
|
9
|
+
"flag": "--goal",
|
|
10
|
+
"type": "string",
|
|
11
|
+
"required": true,
|
|
12
|
+
"describe": "Goal id — directory name under .kody/goals/."
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"claudeCode": {
|
|
16
|
+
"model": "inherit",
|
|
17
|
+
"permissionMode": "default",
|
|
18
|
+
"maxTurns": 0,
|
|
19
|
+
"maxThinkingTokens": null,
|
|
20
|
+
"systemPromptAppend": null,
|
|
21
|
+
"tools": [],
|
|
22
|
+
"hooks": [],
|
|
23
|
+
"skills": [],
|
|
24
|
+
"commands": [],
|
|
25
|
+
"subagents": [],
|
|
26
|
+
"plugins": [],
|
|
27
|
+
"mcpServers": []
|
|
28
|
+
},
|
|
29
|
+
"cliTools": [
|
|
30
|
+
{
|
|
31
|
+
"name": "gh",
|
|
32
|
+
"install": {
|
|
33
|
+
"required": true,
|
|
34
|
+
"checkCommand": "command -v gh"
|
|
35
|
+
},
|
|
36
|
+
"verify": "gh auth status",
|
|
37
|
+
"usage": "Use `gh issue list/comment/edit` to read goal-labelled tasks and dispatch @kody.",
|
|
38
|
+
"allowedUses": ["issue", "pr", "api"]
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"inputArtifacts": [],
|
|
42
|
+
"outputArtifacts": [],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"preflight": [
|
|
45
|
+
{ "shell": "tick.sh" },
|
|
46
|
+
{ "script": "skipAgent" }
|
|
47
|
+
],
|
|
48
|
+
"postflight": []
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# goal-tick: one deterministic tick for one goal. No agent. Pure scripts +
|
|
4
|
+
# `gh` against the connected repo.
|
|
5
|
+
#
|
|
6
|
+
# Inputs (env, set by the executor):
|
|
7
|
+
# KODY_ARG_GOAL the goal id (directory name under .kody/goals/)
|
|
8
|
+
#
|
|
9
|
+
# What it does, per tick:
|
|
10
|
+
# 1. Read .kody/goals/<id>/state.json. If missing or state != "active", exit.
|
|
11
|
+
# 2. List issues with label `goal:<id>`.
|
|
12
|
+
# 3. If every such issue is closed → set state=done, completedAt=now,
|
|
13
|
+
# commit + push state.json, exit.
|
|
14
|
+
# 4. Otherwise pick the lowest-numbered open issue without label
|
|
15
|
+
# `goal-runner:dispatched`. Dispatch `@kody` on it (one new task per tick),
|
|
16
|
+
# add the label so we don't re-dispatch on the next tick.
|
|
17
|
+
# 5. Bump updatedAt and commit state.json (cheap, but lets the engine
|
|
18
|
+
# track per-tick activity in the repo history).
|
|
19
|
+
#
|
|
20
|
+
# Stdout signals:
|
|
21
|
+
# KODY_SKIP_AGENT=true — always; this is a no-agent flow.
|
|
22
|
+
# KODY_REASON=<text> — failure context (rare; gh errors etc).
|
|
23
|
+
|
|
24
|
+
set -euo pipefail
|
|
25
|
+
|
|
26
|
+
goal_id="${KODY_ARG_GOAL:-}"
|
|
27
|
+
if [ -z "$goal_id" ]; then
|
|
28
|
+
echo "KODY_REASON=missing --goal"
|
|
29
|
+
echo "KODY_SKIP_AGENT=true"
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# Defensive: refuse path traversal in goal id.
|
|
34
|
+
if [[ "$goal_id" == *"/"* || "$goal_id" == *".."* ]]; then
|
|
35
|
+
echo "KODY_REASON=invalid goal id (no slashes or '..' allowed)"
|
|
36
|
+
echo "KODY_SKIP_AGENT=true"
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
state_dir=".kody/goals/${goal_id}"
|
|
41
|
+
state_file="${state_dir}/state.json"
|
|
42
|
+
|
|
43
|
+
if [ ! -f "$state_file" ]; then
|
|
44
|
+
echo "[goal-tick] no state file at $state_file — nothing to tick"
|
|
45
|
+
echo "KODY_SKIP_AGENT=true"
|
|
46
|
+
exit 0
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Read current state. python3 is already required by other shell executables
|
|
50
|
+
# (e.g. release-prepare.sh), so we lean on it for JSON read/write.
|
|
51
|
+
state=$(python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get('state',''))" "$state_file")
|
|
52
|
+
|
|
53
|
+
if [ "$state" != "active" ]; then
|
|
54
|
+
echo "[goal-tick] $goal_id is '$state' — skipping"
|
|
55
|
+
echo "KODY_SKIP_AGENT=true"
|
|
56
|
+
exit 0
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
label="goal:${goal_id}"
|
|
60
|
+
dispatched_label="goal-runner:dispatched"
|
|
61
|
+
|
|
62
|
+
# Fetch up to 100 goal-labelled issues. 100 is a soft ceiling — if a goal
|
|
63
|
+
# grows past that, we'll need pagination.
|
|
64
|
+
#
|
|
65
|
+
# Use `gh api` (not `gh issue list --label`) because the latter chokes on
|
|
66
|
+
# colons in label names, which is exactly the convention used here
|
|
67
|
+
# (`goal:<id>` etc). `gh api` also lets us filter PRs out cleanly via the
|
|
68
|
+
# `pull_request` field GitHub attaches to issue payloads.
|
|
69
|
+
issues_json=$(gh api \
|
|
70
|
+
"repos/{owner}/{repo}/issues?labels=${label}&state=all&per_page=100" \
|
|
71
|
+
--jq '[.[] | select(.pull_request == null) | {number, state: (.state | ascii_upcase), labels: .labels}]')
|
|
72
|
+
|
|
73
|
+
total=$(echo "$issues_json" | python3 -c "import json,sys; print(len(json.load(sys.stdin)))")
|
|
74
|
+
if [ "$total" = "0" ]; then
|
|
75
|
+
echo "[goal-tick] no issues with label '$label' — leaving state untouched"
|
|
76
|
+
echo "KODY_SKIP_AGENT=true"
|
|
77
|
+
exit 0
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
open_count=$(echo "$issues_json" | python3 -c "import json,sys; print(sum(1 for i in json.load(sys.stdin) if i['state']=='OPEN'))")
|
|
81
|
+
|
|
82
|
+
# All done? mark goal done and commit.
|
|
83
|
+
if [ "$open_count" = "0" ]; then
|
|
84
|
+
echo "[goal-tick] all $total task(s) closed — marking goal done"
|
|
85
|
+
python3 - "$state_file" <<'PY'
|
|
86
|
+
import json, sys
|
|
87
|
+
from datetime import datetime, timezone
|
|
88
|
+
path = sys.argv[1]
|
|
89
|
+
with open(path) as f:
|
|
90
|
+
s = json.load(f)
|
|
91
|
+
now = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
92
|
+
s["state"] = "done"
|
|
93
|
+
s["completedAt"] = now
|
|
94
|
+
s["updatedAt"] = now
|
|
95
|
+
with open(path, "w") as f:
|
|
96
|
+
json.dump(s, f, indent=2)
|
|
97
|
+
f.write("\n")
|
|
98
|
+
PY
|
|
99
|
+
git add "$state_file"
|
|
100
|
+
if ! git diff --cached --quiet; then
|
|
101
|
+
git commit -m "chore(goals): mark $goal_id done" --quiet
|
|
102
|
+
git push --quiet || echo "[goal-tick] push failed (will retry next tick)"
|
|
103
|
+
fi
|
|
104
|
+
echo "KODY_SKIP_AGENT=true"
|
|
105
|
+
exit 0
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# Pick the lowest-numbered open issue without the dispatched marker.
|
|
109
|
+
next_issue=$(echo "$issues_json" | python3 -c "
|
|
110
|
+
import json, sys
|
|
111
|
+
data = json.load(sys.stdin)
|
|
112
|
+
opens = [
|
|
113
|
+
i for i in data
|
|
114
|
+
if i['state'] == 'OPEN'
|
|
115
|
+
and 'goal-runner:dispatched' not in [l['name'] for l in i.get('labels', [])]
|
|
116
|
+
]
|
|
117
|
+
opens.sort(key=lambda x: x['number'])
|
|
118
|
+
print(opens[0]['number'] if opens else '')
|
|
119
|
+
")
|
|
120
|
+
|
|
121
|
+
if [ -z "$next_issue" ]; then
|
|
122
|
+
echo "[goal-tick] all open tasks already dispatched — waiting for them to complete"
|
|
123
|
+
# Bump updatedAt so the dashboard can show "last ticked at" without parsing logs.
|
|
124
|
+
python3 - "$state_file" <<'PY'
|
|
125
|
+
import json, sys
|
|
126
|
+
from datetime import datetime, timezone
|
|
127
|
+
path = sys.argv[1]
|
|
128
|
+
with open(path) as f:
|
|
129
|
+
s = json.load(f)
|
|
130
|
+
s["updatedAt"] = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
131
|
+
with open(path, "w") as f:
|
|
132
|
+
json.dump(s, f, indent=2)
|
|
133
|
+
f.write("\n")
|
|
134
|
+
PY
|
|
135
|
+
git add "$state_file"
|
|
136
|
+
if ! git diff --cached --quiet; then
|
|
137
|
+
git commit -m "chore(goals): tick $goal_id (idle)" --quiet
|
|
138
|
+
git push --quiet || echo "[goal-tick] push failed (will retry next tick)"
|
|
139
|
+
fi
|
|
140
|
+
echo "KODY_SKIP_AGENT=true"
|
|
141
|
+
exit 0
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
echo "[goal-tick] dispatching @kody on task #$next_issue"
|
|
145
|
+
gh issue comment "$next_issue" --body "@kody"
|
|
146
|
+
gh issue edit "$next_issue" --add-label "$dispatched_label" || true
|
|
147
|
+
|
|
148
|
+
# Bump updatedAt so the file changes (cheap audit trail in git log).
|
|
149
|
+
python3 - "$state_file" "$next_issue" <<'PY'
|
|
150
|
+
import json, sys
|
|
151
|
+
from datetime import datetime, timezone
|
|
152
|
+
path = sys.argv[1]
|
|
153
|
+
issue = int(sys.argv[2])
|
|
154
|
+
with open(path) as f:
|
|
155
|
+
s = json.load(f)
|
|
156
|
+
s["updatedAt"] = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
157
|
+
s["lastDispatchedIssue"] = issue
|
|
158
|
+
with open(path, "w") as f:
|
|
159
|
+
json.dump(s, f, indent=2)
|
|
160
|
+
f.write("\n")
|
|
161
|
+
PY
|
|
162
|
+
git add "$state_file"
|
|
163
|
+
if ! git diff --cached --quiet; then
|
|
164
|
+
git commit -m "chore(goals): dispatched #${next_issue} for ${goal_id}" --quiet
|
|
165
|
+
git push --quiet || echo "[goal-tick] push failed (will retry next tick)"
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
echo "KODY_SKIP_AGENT=true"
|
|
169
|
+
exit 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.84",
|
|
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",
|