@kody-ade/kody-engine 0.4.621 → 0.4.623

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 CHANGED
@@ -57,7 +57,9 @@ key. It's built to keep that blast radius small:
57
57
  downstream CI.
58
58
  - **Write allowlist.** The agent commits through `commitAndPush`, which blocks
59
59
  writes to engine runtime or configuration paths — it can't touch your
60
- runtime state.
60
+ runtime state. See [Delivery boundary](docs/delivery-boundary.md) for what is
61
+ silently discarded, what fails visibly, and how an approved workflow file is
62
+ delivered.
61
63
  - **Locked-toolbox mode.** A job can declare `tools: [...]` to drop `Bash` and
62
64
  shell entirely, running only a fixed set of high-level intents.
63
65
  - **Review like any contributor.** kody opens PRs; you merge them.
@@ -107,6 +109,12 @@ fans out scheduled capabilities at runtime, so capability schedules never
107
109
  generate additional GitHub workflow files. Idempotent — pass `--force` to
108
110
  overwrite.
109
111
 
112
+ Kody also discovers conventional package scripts (`typecheck`, `lint`,
113
+ `test:unit` or `test`, and non-mutating format-check scripts). This means a
114
+ repository that adds its application after Kody onboarding still receives a
115
+ real verification gate. Explicit `quality` commands in `kody.config.json`
116
+ always override discovery. See [Repository quality gates](docs/quality-gates.md).
117
+
110
118
  Store model provider keys (for example `MINIMAX_API_KEY`) in the connected
111
119
  repository's Kody vault. GitHub Actions uses OIDC to request only the selected
112
120
  model key at runtime; user secrets are not copied into Actions. `KODY_TOKEN`
package/dist/bin/kody.js CHANGED
@@ -15,7 +15,7 @@ var init_package = __esm({
15
15
  "package.json"() {
16
16
  package_default = {
17
17
  name: "@kody-ade/kody-engine",
18
- version: "0.4.621",
18
+ version: "0.4.623",
19
19
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
20
20
  license: "MIT",
21
21
  repository: {
@@ -29,6 +29,7 @@ var init_package = __esm({
29
29
  files: [
30
30
  "dist",
31
31
  "templates",
32
+ "docs/delivery-boundary.md",
32
33
  "kody.config.schema.json"
33
34
  ],
34
35
  scripts: {
@@ -182,6 +183,32 @@ var init_completionGuard = __esm({
182
183
  // src/config.ts
183
184
  import * as fs2 from "fs";
184
185
  import * as path3 from "path";
186
+ function packageManagerFor(projectDir) {
187
+ if (fs2.existsSync(path3.join(projectDir, "pnpm-lock.yaml"))) return "pnpm";
188
+ if (fs2.existsSync(path3.join(projectDir, "yarn.lock"))) return "yarn";
189
+ if (fs2.existsSync(path3.join(projectDir, "bun.lockb")) || fs2.existsSync(path3.join(projectDir, "bun.lock"))) return "bun";
190
+ return "npm";
191
+ }
192
+ function inferQualityCommands(projectDir) {
193
+ const empty = { typecheck: "", lint: "", format: "", testUnit: "" };
194
+ try {
195
+ const pkg = JSON.parse(fs2.readFileSync(path3.join(projectDir, "package.json"), "utf-8"));
196
+ const scripts = pkg.scripts ?? {};
197
+ const pm = packageManagerFor(projectDir);
198
+ const command = (names) => {
199
+ const name = names.find((candidate) => typeof scripts[candidate] === "string");
200
+ return name ? `${pm} run ${name}` : "";
201
+ };
202
+ return {
203
+ typecheck: command(["typecheck", "type-check", "check:types"]),
204
+ lint: command(["lint"]),
205
+ format: command(["format:check", "format-check", "prettier:check"]),
206
+ testUnit: command(["test:unit", "test"])
207
+ };
208
+ } catch {
209
+ return empty;
210
+ }
211
+ }
185
212
  function parseReasoningEffort(raw) {
186
213
  if (!raw) return null;
187
214
  const v = raw.trim().toLowerCase();
@@ -284,6 +311,7 @@ function loadConfig(projectDir = process.cwd()) {
284
311
  throw new Error(`kody.config.json is invalid JSON: ${msg}`);
285
312
  }
286
313
  const quality = recordValue(raw.quality) ?? {};
314
+ const inferredQuality = inferQualityCommands(projectDir);
287
315
  const git5 = recordValue(raw.git) ?? {};
288
316
  const github = recordValue(raw.github) ?? {};
289
317
  const agent = recordValue(raw.agent) ?? {};
@@ -299,10 +327,10 @@ function loadConfig(projectDir = process.cwd()) {
299
327
  }
300
328
  return {
301
329
  quality: {
302
- typecheck: typeof quality.typecheck === "string" ? quality.typecheck : "",
303
- lint: typeof quality.lint === "string" ? quality.lint : "",
304
- format: typeof quality.format === "string" ? quality.format : "",
305
- testUnit: typeof quality.testUnit === "string" ? quality.testUnit : "",
330
+ typecheck: typeof quality.typecheck === "string" ? quality.typecheck : inferredQuality.typecheck,
331
+ lint: typeof quality.lint === "string" ? quality.lint : inferredQuality.lint,
332
+ format: typeof quality.format === "string" ? quality.format : inferredQuality.format,
333
+ testUnit: typeof quality.testUnit === "string" ? quality.testUnit : inferredQuality.testUnit,
306
334
  coverage: typeof quality.coverage === "string" ? quality.coverage : ""
307
335
  },
308
336
  git: {
@@ -15809,19 +15837,6 @@ var init_workflow_template = __esm({
15809
15837
  import { execFileSync as execFileSync14 } from "child_process";
15810
15838
  import * as fs40 from "fs";
15811
15839
  import * as path39 from "path";
15812
- function detectPackageManager(cwd) {
15813
- if (fs40.existsSync(path39.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
15814
- if (fs40.existsSync(path39.join(cwd, "yarn.lock"))) return "yarn";
15815
- if (fs40.existsSync(path39.join(cwd, "bun.lockb"))) return "bun";
15816
- return "npm";
15817
- }
15818
- function qualityCommandsFor(pm) {
15819
- return {
15820
- typecheck: `${pm} tsc --noEmit`,
15821
- lint: "",
15822
- testUnit: `${pm} test`
15823
- };
15824
- }
15825
15840
  function schemaUrlFromPkg() {
15826
15841
  const fallback = "https://raw.githubusercontent.com/aharonyaircohen/kody-engine/main/kody.config.schema.json";
15827
15842
  const repoUrl = package_default.repository?.url;
@@ -15844,10 +15859,10 @@ function detectOwnerRepo(cwd) {
15844
15859
  if (!m) return null;
15845
15860
  return { owner: m[1], repo: m[2] };
15846
15861
  }
15847
- function makeConfig(pm, ownerRepo, defaultBranch) {
15862
+ function makeConfig(cwd, ownerRepo, defaultBranch) {
15848
15863
  return {
15849
15864
  $schema: schemaUrlFromPkg(),
15850
- quality: qualityCommandsFor(pm),
15865
+ quality: inferQualityCommands(cwd),
15851
15866
  git: { defaultBranch },
15852
15867
  github: {
15853
15868
  owner: ownerRepo?.owner ?? "OWNER",
@@ -15881,14 +15896,13 @@ function defaultBranchFromGit(cwd) {
15881
15896
  function performInit(cwd, force) {
15882
15897
  const wrote = [];
15883
15898
  const skipped = [];
15884
- const pm = detectPackageManager(cwd);
15885
15899
  const ownerRepo = detectOwnerRepo(cwd);
15886
15900
  const defaultBranch = defaultBranchFromGit(cwd);
15887
15901
  const configPath = path39.join(cwd, "kody.config.json");
15888
15902
  if (fs40.existsSync(configPath) && !force) {
15889
15903
  skipped.push("kody.config.json");
15890
15904
  } else {
15891
- const cfg = makeConfig(pm, ownerRepo, defaultBranch);
15905
+ const cfg = makeConfig(cwd, ownerRepo, defaultBranch);
15892
15906
  fs40.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
15893
15907
  `);
15894
15908
  wrote.push("kody.config.json");
@@ -15915,6 +15929,7 @@ var init_initFlow = __esm({
15915
15929
  "src/scripts/initFlow.ts"() {
15916
15930
  "use strict";
15917
15931
  init_package();
15932
+ init_config();
15918
15933
  init_lifecycleLabels();
15919
15934
  init_workflow_template();
15920
15935
  initFlow = async (ctx) => {
@@ -27171,7 +27186,7 @@ async function resolveAuthToken(env = process.env) {
27171
27186
  );
27172
27187
  return void 0;
27173
27188
  }
27174
- function detectPackageManager2(cwd) {
27189
+ function detectPackageManager(cwd) {
27175
27190
  if (fs56.existsSync(path53.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
27176
27191
  if (fs56.existsSync(path53.join(cwd, "yarn.lock"))) return "yarn";
27177
27192
  if (fs56.existsSync(path53.join(cwd, "bun.lockb"))) return "bun";
@@ -27471,7 +27486,7 @@ async function runCi(argv) {
27471
27486
  if (n > 0) process.stdout.write(`\u2192 kody: unpacked ${n} secret(s)
27472
27487
  `);
27473
27488
  await resolveAuthToken();
27474
- const pm = args.packageManager ?? detectPackageManager2(cwd);
27489
+ const pm = args.packageManager ?? detectPackageManager(cwd);
27475
27490
  if (!args.skipInstall) {
27476
27491
  const code = installDeps(pm, cwd);
27477
27492
  if (code !== 0) {
@@ -27632,7 +27647,7 @@ ${CI_HELP}`);
27632
27647
  `);
27633
27648
  await resolveAuthToken();
27634
27649
  reactToTriggerComment(cwd);
27635
- const pm = args.packageManager ?? detectPackageManager2(cwd);
27650
+ const pm = args.packageManager ?? detectPackageManager(cwd);
27636
27651
  process.stdout.write(`\u2192 kody: package manager = ${pm}
27637
27652
  `);
27638
27653
  const buildOnly = dispatch2.implementation === "preview-build";
@@ -27713,7 +27728,7 @@ async function runScheduledFanOut(cwd, args, opts) {
27713
27728
  if (n > 0) process.stdout.write(`\u2192 kody: unpacked ${n} secret(s) from ALL_SECRETS
27714
27729
  `);
27715
27730
  await resolveAuthToken();
27716
- const pm = args.packageManager ?? detectPackageManager2(cwd);
27731
+ const pm = args.packageManager ?? detectPackageManager(cwd);
27717
27732
  process.stdout.write(`\u2192 kody: package manager = ${pm}
27718
27733
  `);
27719
27734
  if (!args.skipInstall) {
@@ -0,0 +1,29 @@
1
+ # Delivery boundary
2
+
3
+ Kody may inspect the whole checkout, but it does not commit every changed file.
4
+ The postflight delivery step separates product work from runtime state and
5
+ operator-owned configuration before creating the PR.
6
+
7
+ ## What happens to each kind of change
8
+
9
+ | Change | Result |
10
+ |---|---|
11
+ | Normal source, test, or documentation file | Committed and pushed to the task branch |
12
+ | Engine-generated cache or runtime state (`.kody-engine/`, `.kody-lean/`, `.codegraph/`, `node_modules/`, `dist/`, `build/`, logs) | Discarded from delivery without failing otherwise valid product work |
13
+ | Protected operator or security configuration (`.github/*.yml`, `.env`, `kody.config.json`, legacy `.kody/` state) | Excluded from the commit and reported as a blocked delivery; the run fails visibly instead of claiming full completion |
14
+
15
+ This distinction matters because definition hydration updates
16
+ `.kody-engine/definitions/manifest.json` on normal runs. That file belongs to
17
+ the Engine runtime; it is not work the agent attempted to deliver.
18
+
19
+ ## Delivering an approved protected file
20
+
21
+ Do not weaken the global boundary. A capability that legitimately owns a
22
+ GitHub workflow or a repository loop definition must declare the exact path in
23
+ its delivery contract. The Engine validates that allowlist and only then stages
24
+ the ignored file. Ordinary `run` work cannot opt itself into that permission.
25
+
26
+ When a requested protected change is blocked, the issue status names the exact
27
+ omitted path and the workflow exits nonzero. The PR can still contain the safe
28
+ files that were successfully delivered; treat it as partial work until an
29
+ approved owner supplies the protected file.
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "quality": {
13
13
  "type": "object",
14
- "description": "Quality gate commands. Empty strings disable individual gates.",
14
+ "description": "Quality gate commands. Missing commands are inferred from conventional package.json scripts; explicit empty strings disable individual gates.",
15
15
  "additionalProperties": false,
16
16
  "properties": {
17
17
  "typecheck": { "type": "string", "default": "" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.621",
3
+ "version": "0.4.623",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -14,6 +14,7 @@
14
14
  "files": [
15
15
  "dist",
16
16
  "templates",
17
+ "docs/delivery-boundary.md",
17
18
  "kody.config.schema.json"
18
19
  ],
19
20
  "scripts": {