@kody-ade/kody-engine 0.4.206 → 0.4.207
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/README.md +41 -164
- package/dist/bin/kody.js +109 -40
- package/dist/jobs/watch-stale-prs.md +1 -0
- package/kody.config.schema.json +92 -364
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
**An autonomous development engine that runs in your GitHub Actions.**
|
|
9
9
|
|
|
10
10
|
Comment `@kody` on an issue and it implements the change, commits, and opens a
|
|
11
|
-
PR — all inside CI, no bot server to host. Comment on a PR
|
|
12
|
-
|
|
11
|
+
PR — all inside CI, no bot server to host. Comment on a PR with explicit
|
|
12
|
+
commands such as `@kody resolve` or `@kody sync` for PR maintenance. It's a
|
|
13
13
|
single-session Claude Code agent behind a generic executor and declarative JSON
|
|
14
14
|
profiles.
|
|
15
15
|
|
|
@@ -26,8 +26,8 @@ kody: reads the issue → writes the code → runs your tests → opens a PR
|
|
|
26
26
|
|
|
27
27
|
- **No infrastructure.** Runs on the GitHub Actions you already have. One ~20-line
|
|
28
28
|
workflow file, installed via `npx`. Nothing to deploy or keep online.
|
|
29
|
-
- **Whole PR lifecycle, not just authoring.** `run`, `
|
|
30
|
-
`
|
|
29
|
+
- **Whole PR lifecycle, not just authoring.** `run`, `resolve`, `sync`, `merge`,
|
|
30
|
+
`revert`, previews, releases, and scheduled duties — one executor, many verbs.
|
|
31
31
|
- **Declarative & extensible.** Every command is a folder of `profile.json` +
|
|
32
32
|
`prompt.md` + shell. Add a command by dropping a folder — no engine changes.
|
|
33
33
|
- **Bring your own model.** Anthropic native, or any provider via the built-in
|
|
@@ -68,7 +68,7 @@ See [SECURITY.md](SECURITY.md) to report a vulnerability.
|
|
|
68
68
|
|
|
69
69
|
```
|
|
70
70
|
┌─────────────────────────────────────────────┐
|
|
71
|
-
│ Consumer repo workflow (.github/kody.yml) │ @kody comments · schedule ·
|
|
71
|
+
│ Consumer repo workflow (.github/kody.yml) │ @kody comments · schedule · manual dispatch
|
|
72
72
|
└─────────────────────────────────────────────┘
|
|
73
73
|
↓
|
|
74
74
|
┌─────────────────────────────────────────────┐
|
|
@@ -96,181 +96,58 @@ npx -y -p @kody-ade/kody-engine@latest kody init
|
|
|
96
96
|
|
|
97
97
|
Required repo secrets: at least one model provider key (e.g. `MINIMAX_API_KEY`, `ANTHROPIC_API_KEY`). Recommended: `KODY_TOKEN` PAT so kody's commits trigger downstream CI and can modify `.github/workflows/*`.
|
|
98
98
|
|
|
99
|
-
The consumer workflow listens on
|
|
99
|
+
The consumer workflow listens on `issue_comment` for `@kody ...` dispatch and `workflow_dispatch` for manual runs, chat mode, and scheduled wakeups.
|
|
100
100
|
|
|
101
101
|
## Commands
|
|
102
102
|
|
|
103
103
|
```
|
|
104
|
-
#
|
|
105
|
-
kody
|
|
106
|
-
kody fix --pr <N> [--feedback ...] # apply PR review feedback
|
|
107
|
-
kody fix-ci --pr <N> [--run-id <ID>] # fix failing CI
|
|
108
|
-
kody resolve --pr <N> [--prefer ours|theirs] # merge default branch, resolve conflicts
|
|
109
|
-
|
|
110
|
-
# agent, read-only
|
|
111
|
-
kody plan --issue <N> # research + implementation plan
|
|
112
|
-
kody research --issue <N> # map repo context, surface gaps
|
|
113
|
-
kody review --pr <N> # structured diff review
|
|
114
|
-
kody ui-review --pr <N> [--preview-url <URL>] # UI review — browses preview via Playwright MCP
|
|
115
|
-
kody qa-engineer [--url <URL>] [--scope ...] # free-form QA — browses, opens findings as goal task issues
|
|
116
|
-
[--goal <id>] [--issue <N>]
|
|
117
|
-
[--auth-profile <storageState.json>]
|
|
118
|
-
kody classify --issue <N> # pick a flow type (feature/bug/spec/chore)
|
|
119
|
-
|
|
120
|
-
# flow orchestrators (no agent of their own — transition tables)
|
|
121
|
-
kody feature --issue <N> # research → plan → run → review (→ fix)
|
|
122
|
-
kody bug --issue <N> # plan → run → review (→ fix)
|
|
123
|
-
kody spec --issue <N> # research → plan (no code, terminates at plan)
|
|
124
|
-
kody chore --issue <N> # run → review (→ fix)
|
|
125
|
-
|
|
126
|
-
# jobs & watches (scheduled, coordinate work via issue state)
|
|
127
|
-
kody job-scheduler # fans out to per-issue job-tick
|
|
128
|
-
kody job-tick --issue <N> # one tick of a kody:job issue
|
|
129
|
-
kody watch-stale-prs # weekly stale-PR report
|
|
130
|
-
kody memorize # daily vault wiki update from recent PRs
|
|
131
|
-
|
|
132
|
-
# deterministic (no agent)
|
|
133
|
-
kody sync --pr <N> # merge default into PR branch
|
|
134
|
-
kody release --mode prepare|finalize [--bump patch|minor|major] [--dry-run]
|
|
135
|
-
kody init [--force] # scaffold consumer repo
|
|
136
|
-
|
|
137
|
-
# engine entrypoints
|
|
138
|
-
kody ci # auto-dispatches from the GHA event
|
|
139
|
-
kody chat [--session <id>] # dashboard-driven chat session
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### Flow orchestrators
|
|
143
|
-
|
|
144
|
-
Each flow (`feature`, `bug`, `spec`, `chore`) is a declarative transition table: postflight entries dispatch the next executable based on `data.taskState.core.lastOutcome.type` via `runWhen`. No engine changes to add a new flow — drop a new `src/executables/<flow-name>/` with a different table. `classify` picks the flow for an unlabeled issue.
|
|
145
|
-
|
|
146
|
-
### Jobs
|
|
147
|
-
|
|
148
|
-
A **job** is a stateful, bounded goal expressed as a labeled GitHub issue (`kody:job`). A **watch** is a stateless repeating loop. A **manager** is a job whose job happens to be overseeing other jobs. All three run on the same scheduled-executable substrate.
|
|
149
|
-
|
|
150
|
-
`job-scheduler` wakes on cron (default `*/5 * * * *`) or empty `workflow_dispatch`, finds every open `kody:job` issue, and calls `job-tick` once per issue. The tick agent reads the issue body (human-owned prose) and a dedicated state comment (bot-owned JSON), decides the next step, and emits a fenced `kody-job-next-state` block the postflight persists. Children are spawned via `gh workflow run kody.yml` (not `@kody` comments — the default `GITHUB_TOKEN` can dispatch workflows but can't post auto-triggering comments).
|
|
151
|
-
|
|
152
|
-
**Locked-toolbox jobs** (v0.4.175). A job file can add `tools: [...]` to its frontmatter to run the tick in a *locked toolbox*: the agent gets only those named tools (as `mcp__kody-duty__<name>`) plus `submit_state` — `Bash` and `Read` are revoked entirely. This removes the escape hatch where a job posted a raw `@kody <verb>` comment that the webhook silently drops for bot authors, so the job looked done while its verb never ran. The in-process kody-duty MCP server exposes high-level intents instead — `list_prs_to_repair`, `sync_pr` / `fix_ci_pr` / `resolve_pr` (each dispatches the matching `workflow_dispatch`, never a comment), `recommend_to_operator`, and `read_ledger`. Jobs without `tools:` keep the full Bash/gh toolbox unchanged.
|
|
153
|
-
|
|
154
|
-
### `ui-review`
|
|
155
|
-
|
|
156
|
-
PR-bound UI review. Drives the running preview deployment via the Playwright MCP server alongside the usual diff review, posts one structured review comment.
|
|
157
|
-
|
|
158
|
-
- Preview URL: `--preview-url` → `$PREVIEW_URL` → `http://localhost:3000`. Unreachable → falls back to diff-only.
|
|
159
|
-
- Credentials: dashboard-managed, per-repo — login username from the `LOGIN_USER` variable (`.kody/variables.json`), password from the `LOGIN_PASSWORD` vault secret (`.kody/secrets.enc`). Hand-written scenarios/notes come from the company profile (`.kody/profile/*.md`).
|
|
160
|
-
- Auto-discovery: routes, roles, login/admin paths, Payload CMS collections, API routes, env vars — fed to the agent as context.
|
|
161
|
-
|
|
162
|
-
For free-form QA passes (no diff, no PR), see [`qa-engineer`](#qa-engineer) below.
|
|
104
|
+
# The published bin is `kody-engine` (renamed from `kody` in v0.4.77 to avoid
|
|
105
|
+
# shadowing a consumer repo's own `kody` bin). Install globally or via `npx`.
|
|
163
106
|
|
|
164
|
-
|
|
107
|
+
# issue authoring
|
|
108
|
+
kody-engine run --issue <N> # implement an issue end-to-end
|
|
165
109
|
|
|
166
|
-
|
|
110
|
+
# PR operations
|
|
111
|
+
kody-engine resolve --pr <N> [--prefer ours|theirs] # merge default branch, resolve conflicts
|
|
112
|
+
kody-engine sync --pr <N> # merge default into a PR branch
|
|
113
|
+
kody-engine merge --pr <N> # merge a green PR
|
|
114
|
+
kody-engine revert --pr <N> --shas <sha...> # mechanically revert PR commits
|
|
115
|
+
kody-engine preview-build --pr <N> # build and publish a PR preview
|
|
167
116
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
kody
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
kody qa-engineer --scope "checkout flow"
|
|
174
|
-
|
|
175
|
-
# attach findings to an existing goal (resolves URL from goal-<id>'s Vercel deployment)
|
|
176
|
-
kody qa-engineer --goal admin-chat-memory-recall-ui
|
|
117
|
+
# release operations
|
|
118
|
+
kody-engine release --issue <N> [--bump patch|minor|major] [--dry-run]
|
|
119
|
+
kody-engine release-prepare --issue <N> [--bump patch|minor|major] [--dry-run]
|
|
120
|
+
kody-engine release-publish --issue <N> [--dry-run]
|
|
121
|
+
kody-engine release-deploy --issue <N> [--dry-run]
|
|
177
122
|
|
|
178
|
-
#
|
|
179
|
-
kody
|
|
123
|
+
# scheduled duties and goals
|
|
124
|
+
kody-engine job-scheduler # fan out due .kody/duties/*.md files
|
|
125
|
+
kody-engine job-tick --job <slug> [--force] # one agent tick for one duty
|
|
126
|
+
kody-engine job-tick-scripted --job <slug> [--force] # one deterministic tickScript duty tick
|
|
127
|
+
kody-engine goal-scheduler # fan out active .kody/goals/* state files
|
|
128
|
+
kody-engine goal-tick --goal <id> # advance one stacked-PR goal
|
|
180
129
|
|
|
181
|
-
#
|
|
182
|
-
kody
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
kody
|
|
130
|
+
# setup, servers, and utilities
|
|
131
|
+
kody-engine init [--force] # scaffold consumer repo
|
|
132
|
+
kody-engine ci # auto-dispatch from the GitHub Actions event
|
|
133
|
+
kody-engine chat [--session <id>] # dashboard-driven chat session
|
|
134
|
+
kody-engine serve # LiteLLM/editor helper
|
|
135
|
+
kody-engine brain-serve # Brain SSE server
|
|
136
|
+
kody-engine pool-serve # warm-pool owner
|
|
137
|
+
kody-engine runner-serve # warm-pool one-shot runner
|
|
138
|
+
kody-engine worker-ask --worker <slug> --message "..." # ad-hoc staff persona run
|
|
139
|
+
kody-engine stats # inspect run/event history
|
|
186
140
|
```
|
|
187
141
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
1. `--url <URL>` — explicit
|
|
191
|
-
2. `--goal <id>` → latest successful Vercel deployment for the `goal-<id>` branch (via `repos/.../deployments?ref=goal-<id>` + statuses)
|
|
192
|
-
3. `$PREVIEW_URL` env var
|
|
193
|
-
4. `QA_URL` variable in `.kody/variables.json` (per-repo, dashboard-managed)
|
|
194
|
-
5. error — no localhost defaults; CI has no localhost to fall back to.
|
|
195
|
-
|
|
196
|
-
Configure the per-repo default via the dashboard's variables store (`.kody/variables.json`), key `QA_URL` — e.g. `https://dev.example.com`.
|
|
142
|
+
### Duties
|
|
197
143
|
|
|
198
|
-
**
|
|
144
|
+
A **duty** is a markdown file at `.kody/duties/<slug>.md` with frontmatter such as `every:` and `staff:` plus human-owned prose. `job-scheduler` wakes on cron, finds due duties, and dispatches either `job-tick` for an agent tick or `job-tick-scripted` for a deterministic `tickScript:` duty. `kody init` copies built-in starter duties and scaffolds `.kody/staff/kody.md`.
|
|
199
145
|
|
|
200
|
-
|
|
201
|
-
|---|---|
|
|
202
|
-
| Findings + no `--goal` | Appends a new `qa-<scope>-<date>` entry to the `kody:goals-manifest` issue (description = full report markdown). Opens N task issues, each labelled `goal:<id>` + `severity:Px` + `kody:qa-finding`. Writes `.kody/goals/<id>/state.json` (state: `active`) and pushes — `goal-scheduler` picks it up next tick. |
|
|
203
|
-
| Findings + `--goal <id>` | Skips manifest body mutation (the existing goal owns its description). Posts the report markdown as a comment on the manifest issue. Opens N task issues with `goal:<id>` labels. |
|
|
204
|
-
| Zero findings + `--issue <N>` | Posts the report as a comment on issue N. No goal touched. |
|
|
205
|
-
| Zero findings + no `--issue` | Opens a single `kody:qa-finding`-labelled record issue with the full report body. |
|
|
206
|
-
|
|
207
|
-
**Agent-emitted JSON contract.** The prompt requires the agent's final message to end with a machine-readable block:
|
|
208
|
-
|
|
209
|
-
```
|
|
210
|
-
<!-- KODY_QA_REPORT_JSON
|
|
211
|
-
```json
|
|
212
|
-
{
|
|
213
|
-
"findings": [
|
|
214
|
-
{
|
|
215
|
-
"severity": "P0|P1|P2|P3",
|
|
216
|
-
"title": "Short imperative — becomes the issue title",
|
|
217
|
-
"route": "/admin/...",
|
|
218
|
-
"steps": "1. ...\n2. ...",
|
|
219
|
-
"expected": "...",
|
|
220
|
-
"actual": "...",
|
|
221
|
-
"evidence": ".kody/qa-reports/<scope>/<finding>.png"
|
|
222
|
-
}
|
|
223
|
-
]
|
|
224
|
-
}
|
|
225
|
-
```
|
|
226
|
-
-->
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
If the block is missing or malformed, the postflight falls back to single-issue mode and logs a warning. Severity rubric: P0 blocks core flow / data loss / security → verdict FAIL; P1 broken non-critical feature → typically FAIL; P2 degraded UX → typically CONCERNS; P3 polish → doesn't affect verdict.
|
|
230
|
-
|
|
231
|
-
**GHA usage.** Trigger from any issue comment (auto-dispatched by the existing kody.yml workflow):
|
|
232
|
-
|
|
233
|
-
```
|
|
234
|
-
@kody qa-engineer --goal add-per-user-chat-memory-recall-ui --scope "memory recall UI"
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
…or via `workflow_dispatch`:
|
|
238
|
-
|
|
239
|
-
```bash
|
|
240
|
-
gh workflow run kody.yml -F executable=qa-engineer \
|
|
241
|
-
-F args="--goal admin-chat-memory-recall-ui --scope 'admin chat'"
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
**Auto-discovery & credentials.** Same as `ui-review`: `discoverQaContext` scans the repo for routes/roles/admin path/Payload collections/env vars; `loadQaContext` reads the dashboard-managed login username (`LOGIN_USER` variable), password (`LOGIN_PASSWORD` vault secret), and hand-written scenarios/notes (`.kody/profile/*.md`). The agent only logs in if a route under test requires it.
|
|
245
|
-
|
|
246
|
-
**Artifacts.** Screenshots and DOM snapshots go to `.kody/qa-reports/<scope-slug>/`; the Playwright MCP also writes to `.playwright-mcp/`. Both should be in `.gitignore` and `.prettierignore` — `kody init` doesn't yet scaffold these, add them manually:
|
|
247
|
-
|
|
248
|
-
```gitignore
|
|
249
|
-
.kody/qa-reports/
|
|
250
|
-
.playwright-mcp/
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
```
|
|
254
|
-
.kody/**
|
|
255
|
-
.playwright-mcp/**
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
### `memorize` — vault wiki
|
|
259
|
-
|
|
260
|
-
A scheduled watch (cron `0 3 * * *`) that synthesizes recently merged PRs into a markdown knowledge base at `.kody/vault/` and opens a PR with the changes. Pages are entity-centric (`architecture/`, `conventions/`, `decisions/`, `components/`), not per-PR logs. Future kody runs see the relevant pages via the `loadVaultContext` preflight, which is wired into `run` / `fix` / `resolve` and exposes them as `{{vaultContext}}` in the prompt.
|
|
261
|
-
|
|
262
|
-
To enable in a consumer repo: ensure `.gitignore` un-ignores the vault if `.kody/*` is otherwise ignored:
|
|
263
|
-
|
|
264
|
-
```gitignore
|
|
265
|
-
.kody/*
|
|
266
|
-
!.kody/vault/
|
|
267
|
-
!.kody/vault/**
|
|
268
|
-
```
|
|
146
|
+
Locked-toolbox duties can declare `tools: [...]` to run with only the named high-level MCP intents plus `submit_state`; duties without `tools:` keep the legacy Bash/gh toolbox.
|
|
269
147
|
|
|
270
148
|
### `release`
|
|
271
149
|
|
|
272
|
-
|
|
273
|
-
- `--mode finalize` — tags, pushes, runs `prepublishOnly` + `npm publish`, creates a GH release. Runs **automatically** when a `release/vX.Y.Z` PR is merged (via `pull_request: [closed]` in the consumer workflow); manual trigger still works.
|
|
150
|
+
`release` is a single deterministic flow: bump version files, update `CHANGELOG.md`, open or reuse a release PR, wait for CI, merge, publish, deploy, and notify. `release-prepare`, `release-publish`, and `release-deploy` remain available as explicit stage commands.
|
|
274
151
|
|
|
275
152
|
## Profiles
|
|
276
153
|
|
package/dist/bin/kody.js
CHANGED
|
@@ -1486,7 +1486,7 @@ var init_loadCoverageRules = __esm({
|
|
|
1486
1486
|
// package.json
|
|
1487
1487
|
var package_default = {
|
|
1488
1488
|
name: "@kody-ade/kody-engine",
|
|
1489
|
-
version: "0.4.
|
|
1489
|
+
version: "0.4.207",
|
|
1490
1490
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
1491
1491
|
license: "MIT",
|
|
1492
1492
|
type: "module",
|
|
@@ -1717,10 +1717,10 @@ function loadConfig(projectDir = process.cwd()) {
|
|
|
1717
1717
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1718
1718
|
throw new Error(`kody.config.json is invalid JSON: ${msg}`);
|
|
1719
1719
|
}
|
|
1720
|
-
const quality = raw.quality ?? {};
|
|
1721
|
-
const git5 = raw.git ?? {};
|
|
1722
|
-
const github = raw.github ?? {};
|
|
1723
|
-
const agent = raw.agent ?? {};
|
|
1720
|
+
const quality = recordValue(raw.quality) ?? {};
|
|
1721
|
+
const git5 = recordValue(raw.git) ?? {};
|
|
1722
|
+
const github = recordValue(raw.github) ?? {};
|
|
1723
|
+
const agent = recordValue(raw.agent) ?? {};
|
|
1724
1724
|
if (!agent.model || typeof agent.model !== "string") {
|
|
1725
1725
|
throw new Error(`kody.config.json: agent.model is required (e.g. "minimax/MiniMax-M2.7-highspeed")`);
|
|
1726
1726
|
}
|
|
@@ -1748,7 +1748,7 @@ function loadConfig(projectDir = process.cwd()) {
|
|
|
1748
1748
|
issueContext: parseIssueContext(raw.issueContext),
|
|
1749
1749
|
testRequirements: parseTestRequirements(raw.testRequirements),
|
|
1750
1750
|
defaultExecutable: typeof raw.defaultExecutable === "string" && raw.defaultExecutable.length > 0 ? raw.defaultExecutable : "run",
|
|
1751
|
-
defaultPrExecutable: typeof raw.defaultPrExecutable === "string" && raw.defaultPrExecutable.length > 0 ? raw.defaultPrExecutable :
|
|
1751
|
+
defaultPrExecutable: typeof raw.defaultPrExecutable === "string" && raw.defaultPrExecutable.length > 0 ? raw.defaultPrExecutable : void 0,
|
|
1752
1752
|
onPullRequest: typeof raw.onPullRequest === "string" && raw.onPullRequest.length > 0 ? raw.onPullRequest : void 0,
|
|
1753
1753
|
aliases: mergeAliases(raw.aliases),
|
|
1754
1754
|
classify: parseClassifyConfig(raw.classify),
|
|
@@ -1811,10 +1811,11 @@ function parseJobsConfig(raw) {
|
|
|
1811
1811
|
}
|
|
1812
1812
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
1813
1813
|
}
|
|
1814
|
+
function recordValue(raw) {
|
|
1815
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : void 0;
|
|
1816
|
+
}
|
|
1814
1817
|
var BUILTIN_ALIASES = {
|
|
1815
|
-
build: "run"
|
|
1816
|
-
orchestrate: "bug",
|
|
1817
|
-
orchestrator: "bug"
|
|
1818
|
+
build: "run"
|
|
1818
1819
|
};
|
|
1819
1820
|
function mergeAliases(raw) {
|
|
1820
1821
|
const out = { ...BUILTIN_ALIASES };
|
|
@@ -3500,10 +3501,11 @@ function autoDispatch(opts) {
|
|
|
3500
3501
|
return null;
|
|
3501
3502
|
}
|
|
3502
3503
|
if (eventName === "workflow_dispatch") {
|
|
3503
|
-
const
|
|
3504
|
+
const inputs2 = objectValue(event.inputs);
|
|
3505
|
+
const n = parseInt(String(inputs2?.issue_number ?? ""), 10);
|
|
3504
3506
|
if (!Number.isNaN(n) && n > 0) {
|
|
3505
|
-
const exe = String(
|
|
3506
|
-
const base = String(
|
|
3507
|
+
const exe = String(inputs2?.executable ?? "").trim() || "run";
|
|
3508
|
+
const base = String(inputs2?.base ?? "").trim();
|
|
3507
3509
|
const targetKey = primaryNumericInputName(exe) ?? "issue";
|
|
3508
3510
|
const cliArgs = { [targetKey]: n };
|
|
3509
3511
|
if (base) cliArgs.base = base;
|
|
@@ -3516,7 +3518,8 @@ function autoDispatch(opts) {
|
|
|
3516
3518
|
const exe = opts?.config?.onPullRequest?.trim();
|
|
3517
3519
|
const action = String(event.action ?? "");
|
|
3518
3520
|
if (exe && (action === "opened" || action === "synchronize" || action === "reopened")) {
|
|
3519
|
-
const
|
|
3521
|
+
const pullRequest = objectValue(event.pull_request);
|
|
3522
|
+
const prNum = Number(pullRequest?.number ?? event.number ?? 0);
|
|
3520
3523
|
if (prNum > 0) {
|
|
3521
3524
|
const targetKey = primaryNumericInputName(exe) ?? "pr";
|
|
3522
3525
|
return { executable: exe, cliArgs: { [targetKey]: prNum }, target: prNum };
|
|
@@ -3525,15 +3528,18 @@ function autoDispatch(opts) {
|
|
|
3525
3528
|
return null;
|
|
3526
3529
|
}
|
|
3527
3530
|
if (eventName !== "issue_comment") return null;
|
|
3528
|
-
const
|
|
3529
|
-
const
|
|
3530
|
-
const
|
|
3531
|
+
const comment = objectValue(event.comment);
|
|
3532
|
+
const issue = objectValue(event.issue);
|
|
3533
|
+
const user = objectValue(comment?.user);
|
|
3534
|
+
const rawBody = String(comment?.body ?? "");
|
|
3535
|
+
const authorLogin = String(user?.login ?? "");
|
|
3536
|
+
const authorType = String(user?.type ?? "");
|
|
3531
3537
|
if (!hasKodyMention(rawBody)) return null;
|
|
3532
3538
|
const isBotAuthor = authorLogin === "kody-bot" || authorType === "Bot";
|
|
3533
3539
|
if (!associationAllowed(event, opts?.config)) return null;
|
|
3534
3540
|
const body = rawBody.toLowerCase();
|
|
3535
|
-
const targetNum = Number(
|
|
3536
|
-
const isPr = !!
|
|
3541
|
+
const targetNum = Number(issue?.number ?? 0);
|
|
3542
|
+
const isPr = !!issue?.pull_request;
|
|
3537
3543
|
if (!targetNum) return null;
|
|
3538
3544
|
const afterTag = extractAfterTag(body);
|
|
3539
3545
|
const firstTokenRaw = extractSubcommand(afterTag);
|
|
@@ -3554,7 +3560,7 @@ function autoDispatch(opts) {
|
|
|
3554
3560
|
}
|
|
3555
3561
|
}
|
|
3556
3562
|
if (!executable && !firstToken) {
|
|
3557
|
-
executable = isPr ? opts?.config?.defaultPrExecutable ??
|
|
3563
|
+
executable = isPr ? opts?.config?.defaultPrExecutable ?? null : opts?.config?.defaultExecutable ?? null;
|
|
3558
3564
|
}
|
|
3559
3565
|
if (isBotAuthor && !consumedFirstToken) {
|
|
3560
3566
|
process.stderr.write(
|
|
@@ -3564,9 +3570,10 @@ function autoDispatch(opts) {
|
|
|
3564
3570
|
return null;
|
|
3565
3571
|
}
|
|
3566
3572
|
if (!executable) {
|
|
3573
|
+
if (!firstToken) return null;
|
|
3567
3574
|
const profileMissing = aliased ? getProfileInputs(aliased) === null : true;
|
|
3568
3575
|
process.stderr.write(
|
|
3569
|
-
`[kody] dispatch: no executable resolved for issue_comment (firstToken=${firstToken ?? "<none>"}, aliased=${aliased ?? "<none>"}, profileFound=${!profileMissing}, defaultExecutable=${opts?.config?.defaultExecutable ?? "<unset>"})
|
|
3576
|
+
`[kody] dispatch: no executable resolved for issue_comment (firstToken=${firstToken ?? "<none>"}, aliased=${aliased ?? "<none>"}, profileFound=${!profileMissing}, defaultExecutable=${opts?.config?.defaultExecutable ?? "<unset>"}, defaultPrExecutable=${opts?.config?.defaultPrExecutable ?? "<unset>"})
|
|
3570
3577
|
`
|
|
3571
3578
|
);
|
|
3572
3579
|
return null;
|
|
@@ -3604,9 +3611,12 @@ function autoDispatchTyped(opts) {
|
|
|
3604
3611
|
} catch {
|
|
3605
3612
|
return { kind: "silent", reason: "GHA event payload unreadable" };
|
|
3606
3613
|
}
|
|
3607
|
-
const
|
|
3608
|
-
const
|
|
3609
|
-
const
|
|
3614
|
+
const comment = objectValue(event.comment);
|
|
3615
|
+
const issue = objectValue(event.issue);
|
|
3616
|
+
const user = objectValue(comment?.user);
|
|
3617
|
+
const rawBody = String(comment?.body ?? "");
|
|
3618
|
+
const authorLogin = String(user?.login ?? "");
|
|
3619
|
+
const authorType = String(user?.type ?? "");
|
|
3610
3620
|
if (!hasKodyMention(rawBody)) {
|
|
3611
3621
|
return { kind: "silent", reason: "comment does not mention @kody" };
|
|
3612
3622
|
}
|
|
@@ -3614,11 +3624,11 @@ function autoDispatchTyped(opts) {
|
|
|
3614
3624
|
return { kind: "silent", reason: `bot-authored comment (${authorLogin || authorType})` };
|
|
3615
3625
|
}
|
|
3616
3626
|
if (!associationAllowed(event, opts?.config)) {
|
|
3617
|
-
const assoc = String(
|
|
3627
|
+
const assoc = String(comment?.author_association ?? "").toUpperCase() || "<none>";
|
|
3618
3628
|
return { kind: "silent", reason: `commenter association '${assoc}' not in access.allowedAssociations` };
|
|
3619
3629
|
}
|
|
3620
|
-
const targetNum = Number(
|
|
3621
|
-
const isPr = !!
|
|
3630
|
+
const targetNum = Number(issue?.number ?? 0);
|
|
3631
|
+
const isPr = !!issue?.pull_request;
|
|
3622
3632
|
if (!targetNum) {
|
|
3623
3633
|
return { kind: "silent", reason: "comment has no associated issue/PR number" };
|
|
3624
3634
|
}
|
|
@@ -3674,9 +3684,13 @@ function dispatchScheduledWatches(opts) {
|
|
|
3674
3684
|
function associationAllowed(event, config) {
|
|
3675
3685
|
const allowed = config?.access?.allowedAssociations;
|
|
3676
3686
|
if (!allowed || allowed.length === 0) return true;
|
|
3677
|
-
const
|
|
3687
|
+
const comment = objectValue(event.comment);
|
|
3688
|
+
const assoc = String(comment?.author_association ?? "").toUpperCase();
|
|
3678
3689
|
return allowed.includes(assoc);
|
|
3679
3690
|
}
|
|
3691
|
+
function objectValue(value) {
|
|
3692
|
+
return value && typeof value === "object" ? value : void 0;
|
|
3693
|
+
}
|
|
3680
3694
|
var KODY_MENTION_RE = /(?:^|\s)@kody(?=\s|$|[^\w-])/i;
|
|
3681
3695
|
function hasKodyMention(body) {
|
|
3682
3696
|
return KODY_MENTION_RE.test(body);
|
|
@@ -5195,17 +5209,32 @@ function generateLitellmConfigYaml(model) {
|
|
|
5195
5209
|
""
|
|
5196
5210
|
].join("\n");
|
|
5197
5211
|
}
|
|
5212
|
+
function litellmImportable() {
|
|
5213
|
+
try {
|
|
5214
|
+
execFileSync6("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
|
|
5215
|
+
return true;
|
|
5216
|
+
} catch {
|
|
5217
|
+
return false;
|
|
5218
|
+
}
|
|
5219
|
+
}
|
|
5198
5220
|
function resolveLitellmCommand() {
|
|
5199
5221
|
try {
|
|
5200
5222
|
execFileSync6("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
|
|
5201
5223
|
return "litellm";
|
|
5202
5224
|
} catch {
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5225
|
+
if (litellmImportable()) return "python3";
|
|
5226
|
+
process.stderr.write("\u2192 kody: litellm not found \u2014 installing (pip install 'litellm[proxy]')\n");
|
|
5227
|
+
let installed = false;
|
|
5228
|
+
for (const pip of ["pip", "pip3"]) {
|
|
5229
|
+
try {
|
|
5230
|
+
execFileSync6(pip, ["install", "litellm[proxy]"], { timeout: 3e5, stdio: "inherit" });
|
|
5231
|
+
installed = true;
|
|
5232
|
+
break;
|
|
5233
|
+
} catch {
|
|
5234
|
+
}
|
|
5208
5235
|
}
|
|
5236
|
+
if (installed && litellmImportable()) return "python3";
|
|
5237
|
+
throw new Error("litellm not installed and auto-install failed \u2014 run: pip install 'litellm[proxy]'");
|
|
5209
5238
|
}
|
|
5210
5239
|
}
|
|
5211
5240
|
async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL) {
|
|
@@ -9539,7 +9568,6 @@ jobs:
|
|
|
9539
9568
|
if: >-
|
|
9540
9569
|
\${{ github.event_name == 'workflow_dispatch' ||
|
|
9541
9570
|
(github.event_name == 'issue_comment' &&
|
|
9542
|
-
!github.event.issue.pull_request &&
|
|
9543
9571
|
contains(github.event.comment.body, '@kody')) }}
|
|
9544
9572
|
runs-on: ubuntu-latest
|
|
9545
9573
|
timeout-minutes: 60
|
|
@@ -9564,7 +9592,14 @@ jobs:
|
|
|
9564
9592
|
|
|
9565
9593
|
- env:
|
|
9566
9594
|
ALL_SECRETS: \${{ toJSON(secrets) }}
|
|
9567
|
-
run: npx -y -p @kody-ade/kody-engine@latest kody-engine ci
|
|
9595
|
+
run: npx -y -p @kody-ade/kody-engine@latest kody-engine ci
|
|
9596
|
+
`;
|
|
9597
|
+
var DEFAULT_STAFF_PERSONA = `# Kody
|
|
9598
|
+
|
|
9599
|
+
You are Kody, the default maintenance staff member for scheduled duties.
|
|
9600
|
+
|
|
9601
|
+
Keep actions narrow, prefer read-only inspection, and only use the tools or commands named by the duty.
|
|
9602
|
+
When a duty writes a report or dispatches work, keep the output factual and concise.
|
|
9568
9603
|
`;
|
|
9569
9604
|
function defaultBranchFromGit(cwd) {
|
|
9570
9605
|
try {
|
|
@@ -9625,6 +9660,15 @@ function performInit(cwd, force) {
|
|
|
9625
9660
|
wrote.push(rel);
|
|
9626
9661
|
}
|
|
9627
9662
|
}
|
|
9663
|
+
const staffDir = path28.join(cwd, ".kody", "staff");
|
|
9664
|
+
const staffPath = path28.join(staffDir, "kody.md");
|
|
9665
|
+
if (fs31.existsSync(staffPath) && !force) {
|
|
9666
|
+
skipped.push(".kody/staff/kody.md");
|
|
9667
|
+
} else {
|
|
9668
|
+
fs31.mkdirSync(staffDir, { recursive: true });
|
|
9669
|
+
fs31.writeFileSync(staffPath, DEFAULT_STAFF_PERSONA);
|
|
9670
|
+
wrote.push(".kody/staff/kody.md");
|
|
9671
|
+
}
|
|
9628
9672
|
for (const exe of listExecutables()) {
|
|
9629
9673
|
let profile;
|
|
9630
9674
|
try {
|
|
@@ -9676,6 +9720,9 @@ jobs:
|
|
|
9676
9720
|
- uses: actions/setup-node@v4
|
|
9677
9721
|
with:
|
|
9678
9722
|
node-version: 22
|
|
9723
|
+
- uses: actions/setup-python@v5
|
|
9724
|
+
with:
|
|
9725
|
+
python-version: "3.12"
|
|
9679
9726
|
- env:
|
|
9680
9727
|
GH_TOKEN: \${{ secrets.KODY_TOKEN || github.token }}
|
|
9681
9728
|
run: npx -y -p @kody-ade/kody-engine@latest kody-engine ${name}
|
|
@@ -13306,6 +13353,23 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
13306
13353
|
import * as fs39 from "fs";
|
|
13307
13354
|
import * as path36 from "path";
|
|
13308
13355
|
var DEFAULT_STAFF_DIR = ".kody/staff";
|
|
13356
|
+
var BUILTIN_PERSONAS = {
|
|
13357
|
+
kody: [
|
|
13358
|
+
"You are **kody**, the repository's autonomous engineer.",
|
|
13359
|
+
"",
|
|
13360
|
+
"- You work the way this repo already works: follow its conventions, its",
|
|
13361
|
+
" existing patterns, and its tests. Read before you write.",
|
|
13362
|
+
"- You ship small, correct, reviewable changes. You don't refactor beyond the",
|
|
13363
|
+
" task, and you don't add scope nobody asked for.",
|
|
13364
|
+
"- You are honest about outcomes: if something failed, didn't run, or you're",
|
|
13365
|
+
" unsure, you say so plainly rather than papering over it.",
|
|
13366
|
+
"- The request that triggered you IS your authorization to do the work:",
|
|
13367
|
+
" opening a PR, pushing commits, and commenting are exactly what you're for \u2014",
|
|
13368
|
+
" do them without waiting for extra approval.",
|
|
13369
|
+
"- You still don't weaken security, delete work you didn't create, or take",
|
|
13370
|
+
" destructive actions beyond the task you were given."
|
|
13371
|
+
].join("\n")
|
|
13372
|
+
};
|
|
13309
13373
|
function stripFrontmatter(raw) {
|
|
13310
13374
|
const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
|
|
13311
13375
|
return (match ? match[1] : raw).trim();
|
|
@@ -13314,12 +13378,16 @@ function loadStaffPersona(cwd, slug, staffDir = DEFAULT_STAFF_DIR) {
|
|
|
13314
13378
|
const trimmed = slug.trim();
|
|
13315
13379
|
if (!trimmed) throw new Error("loadStaffPersona: empty staff slug");
|
|
13316
13380
|
const staffPath = path36.join(cwd, staffDir, `${trimmed}.md`);
|
|
13317
|
-
if (
|
|
13318
|
-
|
|
13381
|
+
if (fs39.existsSync(staffPath)) {
|
|
13382
|
+
const body = stripFrontmatter(fs39.readFileSync(staffPath, "utf-8"));
|
|
13383
|
+
if (body) return body;
|
|
13384
|
+
const builtinForEmpty = BUILTIN_PERSONAS[trimmed];
|
|
13385
|
+
if (builtinForEmpty) return builtinForEmpty;
|
|
13386
|
+
throw new Error(`loadStaffPersona: staff '${trimmed}' persona body is empty (${staffPath})`);
|
|
13319
13387
|
}
|
|
13320
|
-
const
|
|
13321
|
-
if (
|
|
13322
|
-
|
|
13388
|
+
const builtin = BUILTIN_PERSONAS[trimmed];
|
|
13389
|
+
if (builtin) return builtin;
|
|
13390
|
+
throw new Error(`loadStaffPersona: staff '${trimmed}' declared but ${staffPath} does not exist`);
|
|
13323
13391
|
}
|
|
13324
13392
|
function framePersona(slug, persona) {
|
|
13325
13393
|
return [
|
|
@@ -13507,7 +13575,8 @@ async function runExecutable(profileName, input) {
|
|
|
13507
13575
|
};
|
|
13508
13576
|
})() : null;
|
|
13509
13577
|
const ndjsonDir = path37.join(input.cwd, ".kody");
|
|
13510
|
-
const
|
|
13578
|
+
const personaSlug = typeof profile.staff === "string" && profile.staff.length > 0 ? profile.staff : typeof ctx.data.jobPersona === "string" && ctx.data.jobPersona.length > 0 ? ctx.data.jobPersona : null;
|
|
13579
|
+
const staffPersona = personaSlug ? framePersona(personaSlug, loadStaffPersona(input.cwd, personaSlug)) : null;
|
|
13511
13580
|
const invokeAgent = async (prompt) => {
|
|
13512
13581
|
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path37.isAbsolute(p) ? p : path37.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
13513
13582
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
package/kody.config.schema.json
CHANGED
|
@@ -1,448 +1,176 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"title": "Kody Engine
|
|
4
|
-
"description": "Configuration for
|
|
3
|
+
"title": "Kody Engine Configuration",
|
|
4
|
+
"description": "Configuration for @kody-ade/kody-engine consumer repositories.",
|
|
5
5
|
"type": "object",
|
|
6
|
+
"required": ["github", "agent"],
|
|
7
|
+
"additionalProperties": false,
|
|
6
8
|
"properties": {
|
|
9
|
+
"$schema": {
|
|
10
|
+
"type": "string"
|
|
11
|
+
},
|
|
7
12
|
"quality": {
|
|
8
13
|
"type": "object",
|
|
9
|
-
"description": "Quality gate commands
|
|
14
|
+
"description": "Quality gate commands. Empty strings disable individual gates.",
|
|
15
|
+
"additionalProperties": false,
|
|
10
16
|
"properties": {
|
|
11
|
-
"typecheck": {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"lint": {
|
|
17
|
-
"type": "string",
|
|
18
|
-
"description": "Lint command (e.g., 'pnpm lint'). Empty to skip.",
|
|
19
|
-
"default": ""
|
|
20
|
-
},
|
|
21
|
-
"lintFix": {
|
|
22
|
-
"type": "string",
|
|
23
|
-
"description": "Auto-fix lint command, run when verify fails (e.g., 'pnpm lint:fix')",
|
|
24
|
-
"default": ""
|
|
25
|
-
},
|
|
26
|
-
"format": {
|
|
27
|
-
"type": "string",
|
|
28
|
-
"description": "Format check command (e.g., 'pnpm format:check', 'prettier --check .'). Empty to skip.",
|
|
29
|
-
"default": ""
|
|
30
|
-
},
|
|
31
|
-
"formatFix": {
|
|
32
|
-
"type": "string",
|
|
33
|
-
"description": "Auto-fix format command, run when verify fails (e.g., 'pnpm format')",
|
|
34
|
-
"default": ""
|
|
35
|
-
},
|
|
36
|
-
"testUnit": {
|
|
37
|
-
"type": "string",
|
|
38
|
-
"description": "Unit test command. Use test:unit to exclude integration/e2e tests (e.g., 'pnpm test:unit')",
|
|
39
|
-
"default": "pnpm -s test"
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
"additionalProperties": false
|
|
17
|
+
"typecheck": { "type": "string", "default": "" },
|
|
18
|
+
"lint": { "type": "string", "default": "" },
|
|
19
|
+
"format": { "type": "string", "default": "" },
|
|
20
|
+
"testUnit": { "type": "string", "default": "" }
|
|
21
|
+
}
|
|
43
22
|
},
|
|
44
23
|
"git": {
|
|
45
24
|
"type": "object",
|
|
46
|
-
"
|
|
25
|
+
"additionalProperties": false,
|
|
47
26
|
"properties": {
|
|
48
27
|
"defaultBranch": {
|
|
49
28
|
"type": "string",
|
|
50
|
-
"description": "Default branch for PR base and branch syncing
|
|
51
|
-
"default": "
|
|
29
|
+
"description": "Default branch for PR base and branch syncing.",
|
|
30
|
+
"default": "main"
|
|
52
31
|
}
|
|
53
|
-
}
|
|
54
|
-
"additionalProperties": false
|
|
32
|
+
}
|
|
55
33
|
},
|
|
56
34
|
"github": {
|
|
57
35
|
"type": "object",
|
|
58
|
-
"
|
|
36
|
+
"required": ["owner", "repo"],
|
|
37
|
+
"additionalProperties": false,
|
|
59
38
|
"properties": {
|
|
60
|
-
"owner": {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
},
|
|
64
|
-
"repo": {
|
|
65
|
-
"type": "string",
|
|
66
|
-
"description": "GitHub repository name (e.g., 'my-repo')"
|
|
67
|
-
},
|
|
68
|
-
"postSummary": {
|
|
69
|
-
"type": "boolean",
|
|
70
|
-
"description": "Post a structured pipeline summary comment on the issue after completion. Default: true in CI, false locally.",
|
|
71
|
-
"default": true
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
"additionalProperties": false
|
|
39
|
+
"owner": { "type": "string" },
|
|
40
|
+
"repo": { "type": "string" }
|
|
41
|
+
}
|
|
75
42
|
},
|
|
76
|
-
"
|
|
43
|
+
"agent": {
|
|
77
44
|
"type": "object",
|
|
78
|
-
"
|
|
45
|
+
"required": ["model"],
|
|
46
|
+
"additionalProperties": false,
|
|
79
47
|
"properties": {
|
|
80
|
-
"
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
"
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
48
|
+
"model": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"pattern": "^[^/]+/.+$",
|
|
51
|
+
"description": "Base provider/model string, for example minimax/MiniMax-M2.7-highspeed or claude/claude-sonnet-4-6."
|
|
52
|
+
},
|
|
53
|
+
"perExecutable": {
|
|
54
|
+
"type": "object",
|
|
55
|
+
"description": "Optional provider/model override by executable name.",
|
|
56
|
+
"additionalProperties": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"pattern": "^[^/]+/.+$"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
89
62
|
},
|
|
90
63
|
"issueContext": {
|
|
91
64
|
"type": "object",
|
|
92
|
-
"
|
|
65
|
+
"additionalProperties": false,
|
|
93
66
|
"properties": {
|
|
94
67
|
"commentLimit": {
|
|
95
68
|
"type": "integer",
|
|
96
|
-
"minimum": 1
|
|
97
|
-
"description": "Max number of comments (most recent first) to include in the agent prompt. Default: 50.",
|
|
98
|
-
"default": 50
|
|
69
|
+
"minimum": 1
|
|
99
70
|
},
|
|
100
71
|
"commentMaxBytes": {
|
|
101
72
|
"type": "integer",
|
|
102
|
-
"minimum": 1
|
|
103
|
-
"description": "Max bytes per comment before truncation. Default: 10000.",
|
|
104
|
-
"default": 10000
|
|
73
|
+
"minimum": 1
|
|
105
74
|
}
|
|
106
|
-
}
|
|
107
|
-
"additionalProperties": false
|
|
75
|
+
}
|
|
108
76
|
},
|
|
109
77
|
"testRequirements": {
|
|
110
78
|
"type": "array",
|
|
111
|
-
"description": "kody enforces that newly added files matching `pattern` ship with a sibling test file matching `requireSibling`. Misses fail the run; the agent gets one retry with the gap as feedback. Empty array or absent = no enforcement.",
|
|
112
79
|
"items": {
|
|
113
80
|
"type": "object",
|
|
114
81
|
"required": ["pattern", "requireSibling"],
|
|
115
82
|
"additionalProperties": false,
|
|
116
83
|
"properties": {
|
|
117
|
-
"pattern": {
|
|
118
|
-
|
|
119
|
-
"description": "Glob-style pattern (* within segment, ** across segments). Example: 'src/app/api/**/route.ts'"
|
|
120
|
-
},
|
|
121
|
-
"requireSibling": {
|
|
122
|
-
"type": "string",
|
|
123
|
-
"description": "Sibling test path template. Tokens: {name} (filename without ext), {ext} (e.g. .ts). Example: '{name}.test{ext}'"
|
|
124
|
-
}
|
|
84
|
+
"pattern": { "type": "string" },
|
|
85
|
+
"requireSibling": { "type": "string" }
|
|
125
86
|
}
|
|
126
87
|
}
|
|
127
88
|
},
|
|
128
|
-
"
|
|
129
|
-
"type": "
|
|
130
|
-
"description": "
|
|
131
|
-
"
|
|
132
|
-
"model": {
|
|
133
|
-
"type": "string",
|
|
134
|
-
"pattern": "^[^/]+/.+$",
|
|
135
|
-
"description": "Single 'provider/model' string used by kody (single-session pipeline). Use 'claude/...' or 'anthropic/...' for direct Anthropic API; anything else routes through LiteLLM proxy.",
|
|
136
|
-
"examples": ["claude/claude-sonnet-4-6", "minimax/MiniMax-M2.7-highspeed"]
|
|
137
|
-
},
|
|
138
|
-
"perExecutable": {
|
|
139
|
-
"type": "object",
|
|
140
|
-
"description": "Per-executable model override. Wins over agent.model for the matching stage. Example: {\"classify\": \"claude/claude-haiku-4-5-20251001\", \"plan\": \"claude/claude-opus-4-7\"}.",
|
|
141
|
-
"additionalProperties": {
|
|
142
|
-
"type": "string",
|
|
143
|
-
"pattern": "^[^/]+/.+$"
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
"modelMap": {
|
|
147
|
-
"type": "object",
|
|
148
|
-
"description": "Maps model tiers to 'provider/model' strings. Use 'claude/...' or 'anthropic/...' for direct Anthropic API; anything else routes through LiteLLM proxy.",
|
|
149
|
-
"properties": {
|
|
150
|
-
"cheap": {
|
|
151
|
-
"type": "string",
|
|
152
|
-
"pattern": "^[^/]+/.+$",
|
|
153
|
-
"description": "Fast/cheap 'provider/model' for taskify (e.g., 'claude/claude-haiku-4-5-20251001', 'minimax/MiniMax-M2.7-highspeed')",
|
|
154
|
-
"examples": ["claude/claude-haiku-4-5-20251001", "minimax/MiniMax-M2.7-highspeed"]
|
|
155
|
-
},
|
|
156
|
-
"mid": {
|
|
157
|
-
"type": "string",
|
|
158
|
-
"pattern": "^[^/]+/.+$",
|
|
159
|
-
"description": "Mid-tier 'provider/model' for build, review-fix, autofix (e.g., 'claude/claude-sonnet-4-6')",
|
|
160
|
-
"examples": ["claude/claude-sonnet-4-6", "minimax/MiniMax-M2.7-highspeed"]
|
|
161
|
-
},
|
|
162
|
-
"strong": {
|
|
163
|
-
"type": "string",
|
|
164
|
-
"pattern": "^[^/]+/.+$",
|
|
165
|
-
"description": "Strongest 'provider/model' for plan, review — deep reasoning (e.g., 'claude/claude-opus-4-6')",
|
|
166
|
-
"examples": ["claude/claude-opus-4-6", "minimax/MiniMax-M2.7-highspeed"]
|
|
167
|
-
}
|
|
168
|
-
},
|
|
169
|
-
"additionalProperties": false
|
|
170
|
-
},
|
|
171
|
-
"defaultRunner": {
|
|
172
|
-
"type": "string",
|
|
173
|
-
"description": "Name of the default runner when multiple runners are configured (advanced)",
|
|
174
|
-
"default": "claude"
|
|
175
|
-
},
|
|
176
|
-
"runners": {
|
|
177
|
-
"type": "object",
|
|
178
|
-
"description": "Named runner definitions (advanced)",
|
|
179
|
-
"additionalProperties": {
|
|
180
|
-
"type": "object",
|
|
181
|
-
"properties": {
|
|
182
|
-
"type": {
|
|
183
|
-
"type": "string",
|
|
184
|
-
"enum": ["claude-code"]
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
"required": ["type"]
|
|
188
|
-
}
|
|
189
|
-
},
|
|
190
|
-
"stageRunners": {
|
|
191
|
-
"type": "object",
|
|
192
|
-
"description": "Per-stage runner assignment (advanced). Maps stage name to runner name.",
|
|
193
|
-
"properties": {
|
|
194
|
-
"taskify": { "type": "string" },
|
|
195
|
-
"plan": { "type": "string" },
|
|
196
|
-
"build": { "type": "string" },
|
|
197
|
-
"autofix": { "type": "string" },
|
|
198
|
-
"review": { "type": "string" },
|
|
199
|
-
"review-fix": { "type": "string" }
|
|
200
|
-
},
|
|
201
|
-
"additionalProperties": false
|
|
202
|
-
},
|
|
203
|
-
"default": {
|
|
204
|
-
"type": "string",
|
|
205
|
-
"pattern": "^[^/]+/.+$",
|
|
206
|
-
"description": "Default 'provider/model' string applied to every stage. Use 'claude/<model>' for direct Anthropic API; anything else routes through LiteLLM proxy. Overridden by entries in 'stages'.",
|
|
207
|
-
"examples": ["claude/claude-sonnet-4-6", "minimax/MiniMax-M2.7-highspeed", "openai/gpt-4o"]
|
|
208
|
-
},
|
|
209
|
-
"stages": {
|
|
210
|
-
"type": "object",
|
|
211
|
-
"description": "Per-stage 'provider/model' overrides. Takes precedence over 'default' and 'modelMap'.",
|
|
212
|
-
"properties": {
|
|
213
|
-
"taskify": { "type": "string", "pattern": "^[^/]+/.+$" },
|
|
214
|
-
"plan": { "type": "string", "pattern": "^[^/]+/.+$" },
|
|
215
|
-
"build": { "type": "string", "pattern": "^[^/]+/.+$" },
|
|
216
|
-
"verify": { "type": "string", "pattern": "^[^/]+/.+$" },
|
|
217
|
-
"review": { "type": "string", "pattern": "^[^/]+/.+$" },
|
|
218
|
-
"review-fix": { "type": "string", "pattern": "^[^/]+/.+$" },
|
|
219
|
-
"ship": { "type": "string", "pattern": "^[^/]+/.+$" }
|
|
220
|
-
},
|
|
221
|
-
"additionalProperties": false
|
|
222
|
-
},
|
|
223
|
-
"escalateOnTimeout": {
|
|
224
|
-
"type": "boolean",
|
|
225
|
-
"description": "Escalate to a stronger model tier when a stage times out and retries. Default: true.",
|
|
226
|
-
"default": true
|
|
227
|
-
}
|
|
228
|
-
},
|
|
229
|
-
"additionalProperties": false
|
|
89
|
+
"defaultExecutable": {
|
|
90
|
+
"type": "string",
|
|
91
|
+
"description": "Executable used for bare @kody comments on issues.",
|
|
92
|
+
"default": "run"
|
|
230
93
|
},
|
|
231
|
-
"
|
|
232
|
-
"type": "
|
|
233
|
-
"description": "
|
|
234
|
-
"properties": {
|
|
235
|
-
"enabled": {
|
|
236
|
-
"type": "boolean",
|
|
237
|
-
"description": "Enable Kody Watch periodic monitoring",
|
|
238
|
-
"default": false
|
|
239
|
-
},
|
|
240
|
-
"activityLog": {
|
|
241
|
-
"type": "number",
|
|
242
|
-
"description": "GitHub issue number for posting activity log reports. Auto-created by bootstrap."
|
|
243
|
-
},
|
|
244
|
-
"model": {
|
|
245
|
-
"type": "string",
|
|
246
|
-
"pattern": "^[^/]+/.+$",
|
|
247
|
-
"description": "'provider/model' string for watch agents. Falls back to agent.modelMap.cheap if not set.",
|
|
248
|
-
"examples": ["claude/claude-sonnet-4-6", "claude/claude-haiku-4-5-20251001", "minimax/MiniMax-M1"]
|
|
249
|
-
}
|
|
250
|
-
},
|
|
251
|
-
"additionalProperties": false
|
|
94
|
+
"defaultPrExecutable": {
|
|
95
|
+
"type": "string",
|
|
96
|
+
"description": "Optional executable used for bare @kody comments on PRs. Leave unset to require explicit PR commands."
|
|
252
97
|
},
|
|
253
|
-
"
|
|
98
|
+
"onPullRequest": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"description": "Optional executable to run on pull_request opened, synchronize, or reopened events."
|
|
101
|
+
},
|
|
102
|
+
"aliases": {
|
|
254
103
|
"type": "object",
|
|
255
|
-
"description": "
|
|
256
|
-
"
|
|
257
|
-
"
|
|
258
|
-
|
|
259
|
-
"description": "Enable the decompose command. Default: true.",
|
|
260
|
-
"default": true
|
|
261
|
-
},
|
|
262
|
-
"maxParallelSubTasks": {
|
|
263
|
-
"type": "number",
|
|
264
|
-
"description": "Maximum number of sub-tasks to build in parallel. Default: 3.",
|
|
265
|
-
"default": 3
|
|
266
|
-
},
|
|
267
|
-
"minComplexityScore": {
|
|
268
|
-
"type": "number",
|
|
269
|
-
"description": "Minimum complexity score (1-10) for a task to be decomposed. Set lower to decompose more tasks. Default: 4.",
|
|
270
|
-
"default": 4
|
|
271
|
-
}
|
|
272
|
-
},
|
|
273
|
-
"additionalProperties": false
|
|
104
|
+
"description": "Comment subcommand aliases, mapping typed word to executable name.",
|
|
105
|
+
"additionalProperties": {
|
|
106
|
+
"type": "string"
|
|
107
|
+
}
|
|
274
108
|
},
|
|
275
|
-
"
|
|
109
|
+
"classify": {
|
|
276
110
|
"type": "object",
|
|
277
|
-
"
|
|
111
|
+
"additionalProperties": false,
|
|
278
112
|
"properties": {
|
|
279
|
-
"
|
|
280
|
-
"type": "boolean",
|
|
281
|
-
"description": "Enable MCP server integration",
|
|
282
|
-
"default": false
|
|
283
|
-
},
|
|
284
|
-
"servers": {
|
|
113
|
+
"labelMap": {
|
|
285
114
|
"type": "object",
|
|
286
|
-
"description": "Named MCP server definitions. Each key is a server name.",
|
|
287
115
|
"additionalProperties": {
|
|
288
|
-
"type": "
|
|
289
|
-
"properties": {
|
|
290
|
-
"command": {
|
|
291
|
-
"type": "string",
|
|
292
|
-
"description": "Command to start the MCP server (e.g., 'npx')"
|
|
293
|
-
},
|
|
294
|
-
"args": {
|
|
295
|
-
"type": "array",
|
|
296
|
-
"items": { "type": "string" },
|
|
297
|
-
"description": "Command arguments"
|
|
298
|
-
},
|
|
299
|
-
"env": {
|
|
300
|
-
"type": "object",
|
|
301
|
-
"additionalProperties": { "type": "string" },
|
|
302
|
-
"description": "Environment variables for the server process"
|
|
303
|
-
}
|
|
304
|
-
},
|
|
305
|
-
"required": ["command"]
|
|
116
|
+
"type": "string"
|
|
306
117
|
}
|
|
307
|
-
},
|
|
308
|
-
"stages": {
|
|
309
|
-
"type": "array",
|
|
310
|
-
"items": { "type": "string" },
|
|
311
|
-
"description": "Stages that can use MCP tools. Defaults to [\"build\", \"verify\", \"review\", \"review-fix\"]",
|
|
312
|
-
"default": ["build", "verify", "review", "review-fix"]
|
|
313
|
-
},
|
|
314
|
-
"devServer": {
|
|
315
|
-
"type": "object",
|
|
316
|
-
"description": "DEPRECATED: Use top-level devServer instead. Kept for backward compatibility.",
|
|
317
|
-
"properties": {
|
|
318
|
-
"command": {
|
|
319
|
-
"type": "string",
|
|
320
|
-
"description": "Command to start the dev server (e.g., 'pnpm dev')"
|
|
321
|
-
},
|
|
322
|
-
"url": {
|
|
323
|
-
"type": "string",
|
|
324
|
-
"description": "URL where the dev server will be accessible (e.g., 'http://localhost:3000')"
|
|
325
|
-
},
|
|
326
|
-
"readyPattern": {
|
|
327
|
-
"type": "string",
|
|
328
|
-
"description": "Regex pattern to match in stdout when server is ready. Default: 'Ready in|compiled|started server|Local:'"
|
|
329
|
-
},
|
|
330
|
-
"readyTimeout": {
|
|
331
|
-
"type": "number",
|
|
332
|
-
"description": "Seconds to wait for the server to be ready. Default: 180"
|
|
333
|
-
}
|
|
334
|
-
},
|
|
335
|
-
"required": ["command", "url"]
|
|
336
118
|
}
|
|
337
|
-
}
|
|
338
|
-
"required": ["enabled", "servers"],
|
|
339
|
-
"additionalProperties": false
|
|
119
|
+
}
|
|
340
120
|
},
|
|
341
121
|
"release": {
|
|
342
122
|
"type": "object",
|
|
343
|
-
"
|
|
123
|
+
"additionalProperties": false,
|
|
344
124
|
"properties": {
|
|
345
125
|
"versionFiles": {
|
|
346
126
|
"type": "array",
|
|
347
|
-
"items": { "type": "string" }
|
|
348
|
-
"description": "Files containing version strings to update on release. Default: [\"package.json\"]",
|
|
349
|
-
"default": ["package.json"]
|
|
350
|
-
},
|
|
351
|
-
"publishCommand": {
|
|
352
|
-
"type": "string",
|
|
353
|
-
"description": "Shell command to run after tagging for publishing. Empty = skip. NOT hardcoded to any registry.",
|
|
354
|
-
"default": "",
|
|
355
|
-
"examples": ["npm publish --access public", "cargo publish", "make publish"]
|
|
356
|
-
},
|
|
357
|
-
"notifyCommand": {
|
|
358
|
-
"type": "string",
|
|
359
|
-
"description": "Shell command for post-release notifications. $VERSION is interpolated at runtime. Empty = skip.",
|
|
360
|
-
"default": "",
|
|
361
|
-
"examples": ["curl -X POST $SLACK_WEBHOOK -d '{\"text\": \"Released v$VERSION\"}'"]
|
|
362
|
-
},
|
|
363
|
-
"e2eCommand": {
|
|
364
|
-
"type": "string",
|
|
365
|
-
"description": "Shell command to run E2E tests as a release gate. Runs before publishCommand. $VERSION is interpolated at runtime. Empty = skip (no E2E gate).",
|
|
366
|
-
"default": "",
|
|
367
|
-
"examples": ["pnpm test:e2e", "npm run e2e", "playwright test"]
|
|
127
|
+
"items": { "type": "string" }
|
|
368
128
|
},
|
|
129
|
+
"publishCommand": { "type": "string" },
|
|
130
|
+
"notifyCommand": { "type": "string" },
|
|
131
|
+
"e2eCommand": { "type": "string" },
|
|
132
|
+
"draftRelease": { "type": "boolean" },
|
|
133
|
+
"releaseBranch": { "type": "string" },
|
|
369
134
|
"timeoutMs": {
|
|
370
135
|
"type": "integer",
|
|
371
|
-
"
|
|
372
|
-
"default": 600000,
|
|
373
|
-
"examples": [600000, 1200000, 1800000]
|
|
374
|
-
},
|
|
375
|
-
"releaseBranch": {
|
|
376
|
-
"type": "string",
|
|
377
|
-
"description": "Production branch — E2E gates PRs into this branch. Release PR targets git.defaultBranch. Defaults to 'main'.",
|
|
378
|
-
"default": "main"
|
|
379
|
-
},
|
|
380
|
-
"labels": {
|
|
381
|
-
"type": "array",
|
|
382
|
-
"items": { "type": "string" },
|
|
383
|
-
"description": "Labels to add to the release PR. Default: [\"release\"]",
|
|
384
|
-
"default": ["kody:release"]
|
|
385
|
-
},
|
|
386
|
-
"draftRelease": {
|
|
387
|
-
"type": "boolean",
|
|
388
|
-
"description": "Create GitHub Releases as drafts. Default: false",
|
|
389
|
-
"default": false
|
|
136
|
+
"minimum": 1
|
|
390
137
|
}
|
|
391
|
-
}
|
|
392
|
-
"additionalProperties": false
|
|
138
|
+
}
|
|
393
139
|
},
|
|
394
140
|
"jobs": {
|
|
395
141
|
"type": "object",
|
|
396
|
-
"
|
|
142
|
+
"additionalProperties": false,
|
|
397
143
|
"properties": {
|
|
398
144
|
"stateBackend": {
|
|
399
145
|
"type": "string",
|
|
400
146
|
"enum": ["contents-api", "local-file"],
|
|
401
|
-
"description": "Storage backend for job state. \"contents-api\" (default) commits state to a tracked file via the GitHub Contents API — durable across runs but creates a commit per change. \"local-file\" stores state on disk and snapshots it to the GitHub Actions cache between workflow runs — no commit churn, but bound to cache eviction (7-day idle).",
|
|
402
147
|
"default": "contents-api"
|
|
403
148
|
}
|
|
404
|
-
}
|
|
405
|
-
"additionalProperties": false
|
|
149
|
+
}
|
|
406
150
|
},
|
|
407
151
|
"access": {
|
|
408
152
|
"type": "object",
|
|
409
|
-
"
|
|
153
|
+
"additionalProperties": false,
|
|
410
154
|
"properties": {
|
|
411
155
|
"allowedAssociations": {
|
|
412
156
|
"type": "array",
|
|
157
|
+
"description": "GitHub author_association values allowed to trigger @kody. Empty array disables the gate.",
|
|
413
158
|
"items": {
|
|
414
159
|
"type": "string",
|
|
415
|
-
"enum": [
|
|
160
|
+
"enum": [
|
|
161
|
+
"OWNER",
|
|
162
|
+
"MEMBER",
|
|
163
|
+
"COLLABORATOR",
|
|
164
|
+
"CONTRIBUTOR",
|
|
165
|
+
"FIRST_TIME_CONTRIBUTOR",
|
|
166
|
+
"FIRST_TIMER",
|
|
167
|
+
"MANNEQUIN",
|
|
168
|
+
"NONE"
|
|
169
|
+
]
|
|
416
170
|
},
|
|
417
|
-
"description": "Allowlist of author_association values permitted to trigger kody. OMIT this key for the secure default — only the team (OWNER, MEMBER, COLLABORATOR) may trigger; public drive-by comments are silently ignored. A non-empty list overrides which associations are allowed. An explicit empty list ([]) DISABLES the gate (anyone may trigger). MEMBER applies only to org-owned repos; on a user-owned repo the owner is OWNER and invited people are COLLABORATOR.",
|
|
418
171
|
"default": ["OWNER", "MEMBER", "COLLABORATOR"]
|
|
419
172
|
}
|
|
420
|
-
}
|
|
421
|
-
"additionalProperties": false
|
|
422
|
-
},
|
|
423
|
-
"devServer": {
|
|
424
|
-
"type": "object",
|
|
425
|
-
"description": "Dev server configuration for browser tool verification. Works with any provider (MCP or CLI-based).",
|
|
426
|
-
"properties": {
|
|
427
|
-
"command": {
|
|
428
|
-
"type": "string",
|
|
429
|
-
"description": "Command to start the dev server (e.g., 'pnpm dev')"
|
|
430
|
-
},
|
|
431
|
-
"url": {
|
|
432
|
-
"type": "string",
|
|
433
|
-
"description": "URL where the dev server will be accessible (e.g., 'http://localhost:3000')"
|
|
434
|
-
},
|
|
435
|
-
"readyPattern": {
|
|
436
|
-
"type": "string",
|
|
437
|
-
"description": "Regex pattern to match in stdout when server is ready. Default: 'Ready in|compiled|started server|Local:'"
|
|
438
|
-
},
|
|
439
|
-
"readyTimeout": {
|
|
440
|
-
"type": "number",
|
|
441
|
-
"description": "Seconds to wait for the server to be ready. Default: 180"
|
|
442
|
-
}
|
|
443
|
-
},
|
|
444
|
-
"required": ["command", "url"]
|
|
173
|
+
}
|
|
445
174
|
}
|
|
446
|
-
}
|
|
447
|
-
"additionalProperties": false
|
|
175
|
+
}
|
|
448
176
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.207",
|
|
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",
|