@kody-ade/kody-engine 0.4.574 → 0.4.576
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
|
@@ -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.
|
|
18
|
+
version: "0.4.576",
|
|
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
|
type: "module",
|
|
@@ -2188,6 +2188,7 @@ function readCapabilityFolder(root, slug) {
|
|
|
2188
2188
|
rawProfile: contract ? {
|
|
2189
2189
|
...contract.execution ? { execution: contract.execution } : {},
|
|
2190
2190
|
...contract.deliveryPolicy ? { deliveryPolicy: contract.deliveryPolicy } : {},
|
|
2191
|
+
...contract.deliveryPathAllowlist ? { deliveryPathAllowlist: contract.deliveryPathAllowlist } : {},
|
|
2191
2192
|
input: contract.input,
|
|
2192
2193
|
output: contract.output
|
|
2193
2194
|
} : {},
|
|
@@ -2227,8 +2228,12 @@ function parseCapabilityContract(raw) {
|
|
|
2227
2228
|
if (requiredSubagents && parsed.execution !== "agent") {
|
|
2228
2229
|
throw new Error('contract.json requiredSubagents are supported only when execution is "agent"');
|
|
2229
2230
|
}
|
|
2231
|
+
const deliveryPathAllowlist = parseDeliveryPathAllowlist(parsed.deliveryPathAllowlist);
|
|
2232
|
+
if (deliveryPathAllowlist && parsed.execution !== "agent") {
|
|
2233
|
+
throw new Error('contract.json deliveryPathAllowlist is supported only when execution is "agent"');
|
|
2234
|
+
}
|
|
2230
2235
|
const unsupported = Object.keys(parsed).filter(
|
|
2231
|
-
(key) => key !== "execution" && key !== "deliveryPolicy" && key !== "requirements" && key !== "secrets" && key !== "timeoutMs" && key !== "requiredSubagents" && key !== "input" && key !== "output"
|
|
2236
|
+
(key) => key !== "execution" && key !== "deliveryPolicy" && key !== "deliveryPathAllowlist" && key !== "requirements" && key !== "secrets" && key !== "timeoutMs" && key !== "requiredSubagents" && key !== "input" && key !== "output"
|
|
2232
2237
|
);
|
|
2233
2238
|
if (unsupported.length > 0) {
|
|
2234
2239
|
throw new Error(`contract.json contains unsupported fields: ${unsupported.join(", ")}`);
|
|
@@ -2242,6 +2247,7 @@ function parseCapabilityContract(raw) {
|
|
|
2242
2247
|
return {
|
|
2243
2248
|
...parsed.execution ? { execution: parsed.execution } : {},
|
|
2244
2249
|
...parsed.deliveryPolicy === "checkpoint" ? { deliveryPolicy: "checkpoint" } : {},
|
|
2250
|
+
...deliveryPathAllowlist ? { deliveryPathAllowlist } : {},
|
|
2245
2251
|
...requirements ? { requirements } : {},
|
|
2246
2252
|
...secrets ? { secrets } : {},
|
|
2247
2253
|
...timeoutMs !== void 0 ? { timeoutMs } : {},
|
|
@@ -2250,6 +2256,25 @@ function parseCapabilityContract(raw) {
|
|
|
2250
2256
|
output: parsed.output
|
|
2251
2257
|
};
|
|
2252
2258
|
}
|
|
2259
|
+
function parseDeliveryPathAllowlist(raw) {
|
|
2260
|
+
if (raw === void 0) return void 0;
|
|
2261
|
+
if (!Array.isArray(raw) || raw.length === 0 || raw.length > 64) {
|
|
2262
|
+
throw new Error("contract.json deliveryPathAllowlist must contain 1 to 64 paths");
|
|
2263
|
+
}
|
|
2264
|
+
if (!raw.every((value) => typeof value === "string")) {
|
|
2265
|
+
throw new Error("contract.json deliveryPathAllowlist must contain paths");
|
|
2266
|
+
}
|
|
2267
|
+
const paths = [...new Set(raw)];
|
|
2268
|
+
for (const value of paths) {
|
|
2269
|
+
const subtree = value.endsWith("/**");
|
|
2270
|
+
const base = subtree ? value.slice(0, -3) : value;
|
|
2271
|
+
const segments = base.split("/");
|
|
2272
|
+
if (!base || base.startsWith("/") || base.startsWith(".") && !base.startsWith(".github/") || base.includes("\\") || base.includes("..") || base.includes("*") || segments.some((segment) => !segment) || subtree && segments.length < 2 || value === ".github/**") {
|
|
2273
|
+
throw new Error(`contract.json deliveryPathAllowlist contains an unsafe path: ${value}`);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
return paths;
|
|
2277
|
+
}
|
|
2253
2278
|
function parseCapabilityRequirements(raw) {
|
|
2254
2279
|
if (raw === void 0) return void 0;
|
|
2255
2280
|
if (!isPlainObject(raw)) throw new Error("contract.json requirements must be an object");
|
|
@@ -3043,6 +3068,12 @@ function createStateBackendFromEnv(env = process.env, client) {
|
|
|
3043
3068
|
transport = createConvexClientFromEnv(env);
|
|
3044
3069
|
}
|
|
3045
3070
|
return {
|
|
3071
|
+
async listLoops(tenantId2) {
|
|
3072
|
+
const result = await transport.query(anyApi.agencyRequestLoops.list, {
|
|
3073
|
+
tenantId: requireTenant(tenantId2)
|
|
3074
|
+
});
|
|
3075
|
+
return Array.isArray(result) ? result : [];
|
|
3076
|
+
},
|
|
3046
3077
|
async get(tenantId2, taskKey, kind) {
|
|
3047
3078
|
const result = await transport.query(anyApi.taskState.get, {
|
|
3048
3079
|
tenantId: requireTenant(tenantId2),
|
|
@@ -8655,12 +8686,18 @@ function abortUnfinishedGitOps(cwd) {
|
|
|
8655
8686
|
}
|
|
8656
8687
|
return aborted;
|
|
8657
8688
|
}
|
|
8658
|
-
function
|
|
8689
|
+
function isExplicitlyAllowed(p, allowlist) {
|
|
8690
|
+
return allowlist.some(
|
|
8691
|
+
(entry) => entry.endsWith("/**") ? p.startsWith(entry.slice(0, -2)) : p === entry
|
|
8692
|
+
);
|
|
8693
|
+
}
|
|
8694
|
+
function isForbiddenPath(p, deliveryPathAllowlist = []) {
|
|
8659
8695
|
if (FORBIDDEN_PATH_EXACT.has(p)) return true;
|
|
8660
|
-
if (isGitHubYamlPath(p)) return true;
|
|
8661
|
-
for (const pre of ALLOWED_PATH_PREFIXES) if (p.startsWith(pre)) return false;
|
|
8662
8696
|
for (const pre of FORBIDDEN_PATH_PREFIXES) if (p.startsWith(pre)) return true;
|
|
8663
8697
|
for (const suf of FORBIDDEN_PATH_SUFFIXES) if (p.endsWith(suf)) return true;
|
|
8698
|
+
if (isExplicitlyAllowed(p, deliveryPathAllowlist)) return false;
|
|
8699
|
+
if (isGitHubYamlPath(p)) return true;
|
|
8700
|
+
for (const pre of ALLOWED_PATH_PREFIXES) if (p.startsWith(pre)) return false;
|
|
8664
8701
|
return false;
|
|
8665
8702
|
}
|
|
8666
8703
|
function listChangedFiles(cwd) {
|
|
@@ -8696,14 +8733,14 @@ function normalizeCommitMessage(raw) {
|
|
|
8696
8733
|
}
|
|
8697
8734
|
return `chore: ${trimmed}`;
|
|
8698
8735
|
}
|
|
8699
|
-
function commitAndPush(branch, agentMessage, cwd) {
|
|
8736
|
+
function commitAndPush(branch, agentMessage, cwd, deliveryPathAllowlist = []) {
|
|
8700
8737
|
const allChanged = listChangedFiles(cwd);
|
|
8701
|
-
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
|
|
8738
|
+
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f, deliveryPathAllowlist));
|
|
8702
8739
|
const mergeHeadExists = fs29.existsSync(path28.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
|
|
8703
8740
|
if (allowedFiles.length === 0 && !mergeHeadExists) {
|
|
8704
8741
|
return { committed: false, pushed: false, sha: "", message: "" };
|
|
8705
8742
|
}
|
|
8706
|
-
const forbiddenFiles = allChanged.filter((f) => isForbiddenPath(f));
|
|
8743
|
+
const forbiddenFiles = allChanged.filter((f) => isForbiddenPath(f, deliveryPathAllowlist));
|
|
8707
8744
|
for (const f of forbiddenFiles) {
|
|
8708
8745
|
try {
|
|
8709
8746
|
git(["reset", "-q", "--", f], cwd);
|
|
@@ -12362,11 +12399,14 @@ var init_commitAndPush = __esm({
|
|
|
12362
12399
|
ctx.data.salvagedFromMissingMarker = true;
|
|
12363
12400
|
}
|
|
12364
12401
|
const message = ctx.data.commitMessage || DEFAULT_COMMIT_MESSAGE;
|
|
12402
|
+
const deliveryPathAllowlist = Array.isArray(ctx.data.deliveryPathAllowlist) ? ctx.data.deliveryPathAllowlist : [];
|
|
12365
12403
|
try {
|
|
12366
|
-
const result2 = commitAndPush(branch, message, ctx.cwd);
|
|
12404
|
+
const result2 = commitAndPush(branch, message, ctx.cwd, deliveryPathAllowlist);
|
|
12367
12405
|
ctx.data.commitResult = result2;
|
|
12368
12406
|
const postCommitFiles = result2.committed ? listFilesInCommit("HEAD", ctx.cwd) : listChangedFiles(ctx.cwd);
|
|
12369
|
-
ctx.data.changedFiles = postCommitFiles.filter(
|
|
12407
|
+
ctx.data.changedFiles = postCommitFiles.filter(
|
|
12408
|
+
(f) => !isForbiddenPath(f, deliveryPathAllowlist)
|
|
12409
|
+
);
|
|
12370
12410
|
if (result2.committed && !result2.pushed) {
|
|
12371
12411
|
const reason = result2.pushError ?? "push failed (no error detail)";
|
|
12372
12412
|
ctx.data.commitCrash = reason;
|
|
@@ -14181,7 +14221,7 @@ async function dispatchLoopsWith(input) {
|
|
|
14181
14221
|
let reason = "target did not complete";
|
|
14182
14222
|
let runFailed = false;
|
|
14183
14223
|
try {
|
|
14184
|
-
const result = await input.run(loopJob(loop), runId);
|
|
14224
|
+
const result = await input.run(loopJob(loop, runId), runId, loop.id);
|
|
14185
14225
|
exitCode = result.exitCode;
|
|
14186
14226
|
reason = result.reason ?? (exitCode === 0 ? "dispatched" : "target failed");
|
|
14187
14227
|
} catch (error) {
|
|
@@ -14251,6 +14291,14 @@ function selectRunnableLoops(loops, now, options) {
|
|
|
14251
14291
|
(loop) => loop.enabled && (!options.loopId || loop.id === options.loopId) && (options.force || loop.trigger.type === "schedule" && dueSlot(loop, now) !== null)
|
|
14252
14292
|
);
|
|
14253
14293
|
}
|
|
14294
|
+
function mergeLoopDefinitions(repositoryLoops, runtimeLoops) {
|
|
14295
|
+
const byId = new Map(repositoryLoops.map((loop) => [loop.id, loop]));
|
|
14296
|
+
for (const candidate of runtimeLoops) {
|
|
14297
|
+
const loop = normalizeLoopDefinition(candidate);
|
|
14298
|
+
if (loop) byId.set(loop.id, loop);
|
|
14299
|
+
}
|
|
14300
|
+
return [...byId.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
14301
|
+
}
|
|
14254
14302
|
function loopDispatchSlot(loop, now, force, nonce) {
|
|
14255
14303
|
return force ? `manual:${now.toISOString()}:${nonce}` : dueSlot(loop, now);
|
|
14256
14304
|
}
|
|
@@ -14284,9 +14332,9 @@ function dueSlot(loop, now) {
|
|
|
14284
14332
|
const day = parts.find((part) => part.type === "day")?.value;
|
|
14285
14333
|
return `${year}-${month}-${day}T${loop.trigger.at.time}[${loop.trigger.at.timezone}]`;
|
|
14286
14334
|
}
|
|
14287
|
-
function loopJob(loop) {
|
|
14335
|
+
function loopJob(loop, runId) {
|
|
14288
14336
|
const cliArgs = { ...loop.input };
|
|
14289
|
-
return loop.target.kind === "workflow" ? { workflow: loop.target.id, cliArgs, flavor: "scheduled" } : { capability: loop.target.id, cliArgs, flavor: "scheduled" };
|
|
14337
|
+
return loop.target.kind === "workflow" ? { workflow: loop.target.id, workflowRunId: runId, cliArgs, flavor: "scheduled" } : { capability: loop.target.id, cliArgs, flavor: "scheduled" };
|
|
14290
14338
|
}
|
|
14291
14339
|
function repositoryTenant(config) {
|
|
14292
14340
|
const [envOwner, envRepo] = (process.env.GITHUB_REPOSITORY ?? "").split("/");
|
|
@@ -14309,13 +14357,14 @@ var init_dispatchLoops = __esm({
|
|
|
14309
14357
|
const now = /* @__PURE__ */ new Date();
|
|
14310
14358
|
const force = ctx.data.jobForce === true;
|
|
14311
14359
|
const requestedLoopId = typeof ctx.args.loop === "string" ? ctx.args.loop.trim() : "";
|
|
14312
|
-
const
|
|
14360
|
+
const backend = createStateBackendFromEnv();
|
|
14361
|
+
const loops = mergeLoopDefinitions(listLoopDefinitions(ctx.cwd), await backend.listLoops(tenantId2));
|
|
14362
|
+
const due = selectRunnableLoops(loops, now, {
|
|
14313
14363
|
force,
|
|
14314
14364
|
...requestedLoopId ? { loopId: requestedLoopId } : {}
|
|
14315
14365
|
});
|
|
14316
14366
|
process.stdout.write(`\u2192 kody: Loop scheduler found ${due.length} runnable Loop(s)${force ? " (manual)" : ""}
|
|
14317
14367
|
`);
|
|
14318
|
-
const backend = createStateBackendFromEnv();
|
|
14319
14368
|
const results = await dispatchLoopsWith({
|
|
14320
14369
|
loops: due,
|
|
14321
14370
|
tenantId: tenantId2,
|
|
@@ -14323,13 +14372,13 @@ var init_dispatchLoops = __esm({
|
|
|
14323
14372
|
now,
|
|
14324
14373
|
force,
|
|
14325
14374
|
nonce: randomUUID,
|
|
14326
|
-
run: (job, parentRunId) => runJob(job, {
|
|
14375
|
+
run: (job, parentRunId, loopId) => runJob(job, {
|
|
14327
14376
|
cwd: ctx.cwd,
|
|
14328
14377
|
config: ctx.config,
|
|
14329
14378
|
verbose: ctx.verbose,
|
|
14330
14379
|
quiet: ctx.quiet,
|
|
14331
14380
|
chain: false,
|
|
14332
|
-
preloadedData: { parentRunId }
|
|
14381
|
+
preloadedData: { parentRunId, loopId }
|
|
14333
14382
|
})
|
|
14334
14383
|
});
|
|
14335
14384
|
for (const result of results) {
|
|
@@ -16326,6 +16375,9 @@ var init_loadSimpleCapability = __esm({
|
|
|
16326
16375
|
if (capability.contract?.deliveryPolicy) {
|
|
16327
16376
|
ctx.data.capabilityDeliveryPolicy = capability.contract.deliveryPolicy;
|
|
16328
16377
|
}
|
|
16378
|
+
if (capability.contract?.deliveryPathAllowlist) {
|
|
16379
|
+
ctx.data.deliveryPathAllowlist = capability.contract.deliveryPathAllowlist;
|
|
16380
|
+
}
|
|
16329
16381
|
if (capability.contract?.requirements) {
|
|
16330
16382
|
ctx.data.capabilityRequirements = capability.contract.requirements;
|
|
16331
16383
|
}
|
|
@@ -23327,6 +23379,7 @@ async function runJob(job, base) {
|
|
|
23327
23379
|
await notifyWorkflowCompleted({
|
|
23328
23380
|
workflowId: workflowIdentity,
|
|
23329
23381
|
runId: valid.workflowRunId,
|
|
23382
|
+
...typeof base.preloadedData?.loopId === "string" ? { loopId: base.preloadedData.loopId } : {},
|
|
23330
23383
|
status: result.workflowState?.status === "blocked" ? "blocked" : result.exitCode === 0 ? "success" : "failed",
|
|
23331
23384
|
...result.reason ? { summary: result.reason } : {},
|
|
23332
23385
|
...Object.keys(facts).length > 0 ? { output: facts } : {}
|
|
File without changes
|
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.576",
|
|
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
|
"type": "module",
|
|
@@ -12,30 +12,6 @@
|
|
|
12
12
|
"templates",
|
|
13
13
|
"kody.config.schema.json"
|
|
14
14
|
],
|
|
15
|
-
"scripts": {
|
|
16
|
-
"kody:run": "tsx bin/kody.ts",
|
|
17
|
-
"serve": "tsx bin/kody.ts serve",
|
|
18
|
-
"serve:vscode": "tsx bin/kody.ts serve vscode",
|
|
19
|
-
"serve:claude": "tsx bin/kody.ts serve claude",
|
|
20
|
-
"clean:dist": "node scripts/clean-dist.cjs",
|
|
21
|
-
"build": "pnpm clean:dist && tsup && node scripts/copy-assets.cjs",
|
|
22
|
-
"check:modularity": "tsx scripts/check-script-modularity.ts",
|
|
23
|
-
"pretest": "pnpm check:modularity",
|
|
24
|
-
"test": "vitest run tests/unit tests/int --coverage",
|
|
25
|
-
"posttest": "tsx scripts/check-coverage-floor.ts",
|
|
26
|
-
"test:smoke": "vitest run tests/smoke --no-coverage",
|
|
27
|
-
"test:e2e": "vitest run tests/e2e --no-coverage",
|
|
28
|
-
"verify:live-release": "tsx scripts/live-release-gate.ts",
|
|
29
|
-
"test:runtime-services": "node --test \"tests/runtime-services/*.test.mjs\"",
|
|
30
|
-
"test:all": "vitest run tests --no-coverage",
|
|
31
|
-
"typecheck": "tsc --noEmit",
|
|
32
|
-
"lint": "biome check",
|
|
33
|
-
"lint:fix": "biome check --write",
|
|
34
|
-
"format": "biome format --write",
|
|
35
|
-
"verify:package": "node scripts/verify-package-tarball.cjs",
|
|
36
|
-
"brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain --build-arg KODY_ENGINE_REF=$(git rev-parse HEAD) -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner",
|
|
37
|
-
"prepublishOnly": "pnpm typecheck && pnpm test:runtime-services && pnpm build && pnpm verify:package"
|
|
38
|
-
},
|
|
39
15
|
"dependencies": {
|
|
40
16
|
"@actions/cache": "^6.0.0",
|
|
41
17
|
"@anthropic-ai/claude-agent-sdk": "0.2.119",
|
|
@@ -62,5 +38,28 @@
|
|
|
62
38
|
"url": "git+https://github.com/aharonyaircohen/kody-engine.git"
|
|
63
39
|
},
|
|
64
40
|
"homepage": "https://github.com/aharonyaircohen/kody-engine",
|
|
65
|
-
"bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
|
|
66
|
-
|
|
41
|
+
"bugs": "https://github.com/aharonyaircohen/kody-engine/issues",
|
|
42
|
+
"scripts": {
|
|
43
|
+
"kody:run": "tsx bin/kody.ts",
|
|
44
|
+
"serve": "tsx bin/kody.ts serve",
|
|
45
|
+
"serve:vscode": "tsx bin/kody.ts serve vscode",
|
|
46
|
+
"serve:claude": "tsx bin/kody.ts serve claude",
|
|
47
|
+
"clean:dist": "node scripts/clean-dist.cjs",
|
|
48
|
+
"build": "pnpm clean:dist && tsup && node scripts/copy-assets.cjs",
|
|
49
|
+
"check:modularity": "tsx scripts/check-script-modularity.ts",
|
|
50
|
+
"pretest": "pnpm check:modularity",
|
|
51
|
+
"test": "vitest run tests/unit tests/int --coverage",
|
|
52
|
+
"posttest": "tsx scripts/check-coverage-floor.ts",
|
|
53
|
+
"test:smoke": "vitest run tests/smoke --no-coverage",
|
|
54
|
+
"test:e2e": "vitest run tests/e2e --no-coverage",
|
|
55
|
+
"verify:live-release": "tsx scripts/live-release-gate.ts",
|
|
56
|
+
"test:runtime-services": "node --test \"tests/runtime-services/*.test.mjs\"",
|
|
57
|
+
"test:all": "vitest run tests --no-coverage",
|
|
58
|
+
"typecheck": "tsc --noEmit",
|
|
59
|
+
"lint": "biome check",
|
|
60
|
+
"lint:fix": "biome check --write",
|
|
61
|
+
"format": "biome format --write",
|
|
62
|
+
"verify:package": "node scripts/verify-package-tarball.cjs",
|
|
63
|
+
"brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain --build-arg KODY_ENGINE_REF=$(git rev-parse HEAD) -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner"
|
|
64
|
+
}
|
|
65
|
+
}
|