@kody-ade/kody-engine 0.4.285 → 0.4.287
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 +75 -30
- package/package.json +1 -1
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.287",
|
|
19
19
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
20
20
|
license: "MIT",
|
|
21
21
|
type: "module",
|
|
@@ -443,7 +443,7 @@ var STATE_BRANCH;
|
|
|
443
443
|
var init_stateBranch = __esm({
|
|
444
444
|
"src/stateBranch.ts"() {
|
|
445
445
|
"use strict";
|
|
446
|
-
STATE_BRANCH = "
|
|
446
|
+
STATE_BRANCH = "main";
|
|
447
447
|
}
|
|
448
448
|
});
|
|
449
449
|
|
|
@@ -497,18 +497,33 @@ function normalizeStatePath(raw, field = "statePath") {
|
|
|
497
497
|
}
|
|
498
498
|
return parts.join("/");
|
|
499
499
|
}
|
|
500
|
+
function normalizeStateBranch(raw, field = "state.branch") {
|
|
501
|
+
const value = raw?.trim() || STATE_BRANCH;
|
|
502
|
+
if (!value) throw new Error(`kody.config.json: ${field} must not be empty`);
|
|
503
|
+
if (value.startsWith("/") || value.endsWith("/") || value.includes("\\") || value.includes("..") || value.includes("@{") || /[\x00-\x20\x7f~^:?*\[]/.test(value)) {
|
|
504
|
+
throw new Error(`kody.config.json: ${field} contains an invalid branch`);
|
|
505
|
+
}
|
|
506
|
+
for (const part of value.split("/")) {
|
|
507
|
+
if (!part || part === "." || part === ".." || part.startsWith(".") || part.endsWith(".lock")) {
|
|
508
|
+
throw new Error(`kody.config.json: ${field} contains an invalid branch`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return value;
|
|
512
|
+
}
|
|
500
513
|
function resolveStateRepoConfig(config) {
|
|
501
514
|
if (config.state?.repo && config.state?.path) {
|
|
502
515
|
parseStateRepoSlug(config.state.repo);
|
|
503
516
|
return {
|
|
504
517
|
repo: config.state.repo,
|
|
505
|
-
path: normalizeStatePath(config.state.path)
|
|
518
|
+
path: normalizeStatePath(config.state.path),
|
|
519
|
+
branch: normalizeStateBranch(config.state.branch)
|
|
506
520
|
};
|
|
507
521
|
}
|
|
508
522
|
if (config.github?.owner && config.github?.repo) {
|
|
509
523
|
return {
|
|
510
524
|
repo: `${config.github.owner}/kody-state`,
|
|
511
|
-
path: normalizeStatePath(config.github.repo)
|
|
525
|
+
path: normalizeStatePath(config.github.repo),
|
|
526
|
+
branch: normalizeStateBranch(void 0)
|
|
512
527
|
};
|
|
513
528
|
}
|
|
514
529
|
throw new Error("stateRepo: config.state or config.github owner/repo is required");
|
|
@@ -516,7 +531,7 @@ function resolveStateRepoConfig(config) {
|
|
|
516
531
|
function parseStateRepo(config) {
|
|
517
532
|
const state = resolveStateRepoConfig(config);
|
|
518
533
|
const parsed = parseStateRepoSlug(state.repo);
|
|
519
|
-
return { ...parsed, basePath: state.path };
|
|
534
|
+
return { ...parsed, basePath: state.path, branch: state.branch };
|
|
520
535
|
}
|
|
521
536
|
function stateRepoPath(config, filePath) {
|
|
522
537
|
const state = resolveStateRepoConfig(config);
|
|
@@ -528,14 +543,15 @@ function apiPath(config, targetPath) {
|
|
|
528
543
|
return `/repos/${parsed.owner}/${parsed.repo}/contents/${targetPath}`;
|
|
529
544
|
}
|
|
530
545
|
function branchApiPath(config, targetPath) {
|
|
531
|
-
|
|
546
|
+
const state = resolveStateRepoConfig(config);
|
|
547
|
+
return `${apiPath(config, targetPath)}?ref=${encodeURIComponent(state.branch)}`;
|
|
532
548
|
}
|
|
533
549
|
function ensureStateBranch(config, cwd) {
|
|
534
550
|
const parsed = parseStateRepo(config);
|
|
535
|
-
const cacheKey3 = `${parsed.owner}/${parsed.repo}:${
|
|
551
|
+
const cacheKey3 = `${parsed.owner}/${parsed.repo}:${parsed.branch}`;
|
|
536
552
|
if (ensuredStateBranches.has(cacheKey3)) return;
|
|
537
553
|
try {
|
|
538
|
-
gh(["api", `/repos/${parsed.owner}/${parsed.repo}/git/ref/heads/${
|
|
554
|
+
gh(["api", `/repos/${parsed.owner}/${parsed.repo}/git/ref/heads/${parsed.branch}`], { cwd });
|
|
539
555
|
ensuredStateBranches.add(cacheKey3);
|
|
540
556
|
return;
|
|
541
557
|
} catch (err) {
|
|
@@ -550,7 +566,7 @@ function ensureStateBranch(config, cwd) {
|
|
|
550
566
|
try {
|
|
551
567
|
gh(["api", "--method", "POST", `/repos/${parsed.owner}/${parsed.repo}/git/refs`, "--input", "-"], {
|
|
552
568
|
cwd,
|
|
553
|
-
input: JSON.stringify({ ref: `refs/heads/${
|
|
569
|
+
input: JSON.stringify({ ref: `refs/heads/${parsed.branch}`, sha })
|
|
554
570
|
});
|
|
555
571
|
} catch (err) {
|
|
556
572
|
if (!isAlreadyExists(err)) throw err;
|
|
@@ -590,7 +606,7 @@ function writeStateText(config, cwd, filePath, content, message, sha) {
|
|
|
590
606
|
const payload = {
|
|
591
607
|
message,
|
|
592
608
|
content: Buffer.from(content, "utf-8").toString("base64"),
|
|
593
|
-
branch:
|
|
609
|
+
branch: resolveStateRepoConfig(config).branch
|
|
594
610
|
};
|
|
595
611
|
if (sha) payload.sha = sha;
|
|
596
612
|
gh(["api", "--method", "PUT", apiPath(config, targetPath), "--input", "-"], {
|
|
@@ -722,12 +738,14 @@ function parseStateConfig(raw, github) {
|
|
|
722
738
|
const nested = recordValue(raw.state) ?? {};
|
|
723
739
|
const repoRaw = typeof raw.stateRepo === "string" ? raw.stateRepo : nested.repo;
|
|
724
740
|
const pathRaw = typeof raw.statePath === "string" ? raw.statePath : nested.path;
|
|
741
|
+
const branchRaw = typeof raw.stateBranch === "string" ? raw.stateBranch : nested.branch;
|
|
725
742
|
const stateRepo = typeof repoRaw === "string" && repoRaw.trim().length > 0 ? repoRaw.trim() : `https://github.com/${String(github.owner)}/kody-state`;
|
|
726
743
|
parseStateRepoSlug(stateRepo);
|
|
727
744
|
const statePath = typeof pathRaw === "string" && pathRaw.trim().length > 0 ? pathRaw.trim() : String(github.repo);
|
|
728
745
|
return {
|
|
729
746
|
repo: stateRepo,
|
|
730
|
-
path: normalizeStatePath(statePath)
|
|
747
|
+
path: normalizeStatePath(statePath),
|
|
748
|
+
branch: typeof branchRaw === "string" && branchRaw.trim().length > 0 ? normalizeStateBranch(branchRaw) : normalizeStateBranch(void 0)
|
|
731
749
|
};
|
|
732
750
|
}
|
|
733
751
|
function parseAccessConfig(raw) {
|
|
@@ -6878,6 +6896,7 @@ function stateRepoContext(config, goalId, logPath) {
|
|
|
6878
6896
|
return {
|
|
6879
6897
|
repo: state.repo,
|
|
6880
6898
|
path: state.path,
|
|
6899
|
+
branch: state.branch,
|
|
6881
6900
|
goalStatePath: `${state.path}/${goalStateLogPath(goalId)}`,
|
|
6882
6901
|
logPath: `${state.path}/${logPath}`
|
|
6883
6902
|
};
|
|
@@ -6976,16 +6995,17 @@ function linkContext(stateRepo) {
|
|
|
6976
6995
|
const server = process.env.GITHUB_SERVER_URL?.trim() || "https://github.com";
|
|
6977
6996
|
if (runId && repository) links.workflowRun = `${server}/${repository}/actions/runs/${runId}`;
|
|
6978
6997
|
const repo = stringValue(stateRepo?.repo);
|
|
6998
|
+
const branch = stringValue(stateRepo?.branch);
|
|
6979
6999
|
const goalStatePath2 = stringValue(stateRepo?.goalStatePath);
|
|
6980
7000
|
const logPath = stringValue(stateRepo?.logPath);
|
|
6981
|
-
if (repo && goalStatePath2) links.goalState = githubBlobUrl(repo, goalStatePath2);
|
|
6982
|
-
if (repo && logPath) links.log = githubBlobUrl(repo, logPath);
|
|
7001
|
+
if (repo && branch && goalStatePath2) links.goalState = githubBlobUrl(repo, branch, goalStatePath2);
|
|
7002
|
+
if (repo && branch && logPath) links.log = githubBlobUrl(repo, branch, logPath);
|
|
6983
7003
|
return Object.keys(links).length > 0 ? links : void 0;
|
|
6984
7004
|
}
|
|
6985
|
-
function githubBlobUrl(repo, filePath) {
|
|
7005
|
+
function githubBlobUrl(repo, branch, filePath) {
|
|
6986
7006
|
try {
|
|
6987
7007
|
const parsed = parseStateRepoSlug(repo);
|
|
6988
|
-
return `https://github.com/${parsed.owner}/${parsed.repo}/blob/${
|
|
7008
|
+
return `https://github.com/${parsed.owner}/${parsed.repo}/blob/${encodeURIComponent(branch)}/${filePath}`;
|
|
6989
7009
|
} catch {
|
|
6990
7010
|
return void 0;
|
|
6991
7011
|
}
|
|
@@ -7068,7 +7088,6 @@ var LOGS_KEY, LOG_RUN_KEY, LOG_STARTED_KEY;
|
|
|
7068
7088
|
var init_runLog = __esm({
|
|
7069
7089
|
"src/goal/runLog.ts"() {
|
|
7070
7090
|
"use strict";
|
|
7071
|
-
init_stateBranch();
|
|
7072
7091
|
init_stateRepo();
|
|
7073
7092
|
init_state2();
|
|
7074
7093
|
LOGS_KEY = "__goalRunLogs";
|
|
@@ -7270,12 +7289,10 @@ var init_stateStore = __esm({
|
|
|
7270
7289
|
// src/goal/targetLoopResolution.ts
|
|
7271
7290
|
import * as fs26 from "fs";
|
|
7272
7291
|
import * as path24 from "path";
|
|
7273
|
-
function
|
|
7292
|
+
function resolveActiveGoalLoopTarget(config, cwd, loopGoalId, loopGoal) {
|
|
7274
7293
|
const targetId = loopGoal.loopTarget?.id.trim() ?? "";
|
|
7275
7294
|
assertSafeGoalId(targetId, "loop target");
|
|
7276
|
-
if (!hasExplicitStateRepo(config))
|
|
7277
|
-
return { targetId, templateId: targetId, reason: "literal target; state repo not configured" };
|
|
7278
|
-
}
|
|
7295
|
+
if (!hasExplicitStateRepo(config)) return null;
|
|
7279
7296
|
const activeInstance = findActiveTargetInstance(config, cwd, loopGoalId, targetId);
|
|
7280
7297
|
if (activeInstance) {
|
|
7281
7298
|
return {
|
|
@@ -7288,6 +7305,17 @@ function resolveGoalLoopTarget(config, cwd, loopGoalId, loopGoal, now) {
|
|
|
7288
7305
|
if (directTarget?.state === "active") {
|
|
7289
7306
|
return { targetId, templateId: targetId, reason: "active target goal" };
|
|
7290
7307
|
}
|
|
7308
|
+
return null;
|
|
7309
|
+
}
|
|
7310
|
+
function resolveGoalLoopTarget(config, cwd, loopGoalId, loopGoal, now) {
|
|
7311
|
+
const targetId = loopGoal.loopTarget?.id.trim() ?? "";
|
|
7312
|
+
assertSafeGoalId(targetId, "loop target");
|
|
7313
|
+
if (!hasExplicitStateRepo(config)) {
|
|
7314
|
+
return { targetId, templateId: targetId, reason: "literal target; state repo not configured" };
|
|
7315
|
+
}
|
|
7316
|
+
const activeTarget = resolveActiveGoalLoopTarget(config, cwd, loopGoalId, loopGoal);
|
|
7317
|
+
if (activeTarget) return activeTarget;
|
|
7318
|
+
const directTarget = fetchGoalState(config, targetId, cwd);
|
|
7291
7319
|
const template = loadGoalTemplate(cwd, targetId);
|
|
7292
7320
|
if (!template) {
|
|
7293
7321
|
if (directTarget) {
|
|
@@ -7961,7 +7989,9 @@ function planTargetLoopSchedule(opts) {
|
|
|
7961
7989
|
}
|
|
7962
7990
|
const preferred = opts.goal.preferredRunTime;
|
|
7963
7991
|
if (preferred) {
|
|
7964
|
-
const gate = preferredRunTimeGate(preferred, now, opts.previousScheduleState
|
|
7992
|
+
const gate = preferredRunTimeGate(preferred, now, opts.previousScheduleState, {
|
|
7993
|
+
allowRepeatAfterCompletedTarget: opts.allowRepeatAfterCompletedTarget === true
|
|
7994
|
+
});
|
|
7965
7995
|
if (!gate.ok) return targetLoopDecision("idle", gate.reason, at);
|
|
7966
7996
|
}
|
|
7967
7997
|
const dispatchTargetId = target.type === "goal" && opts.resolvedGoalTargetId?.trim() ? opts.resolvedGoalTargetId.trim() : targetId;
|
|
@@ -8123,7 +8153,7 @@ function targetLoopDecision(kind, reason, at) {
|
|
|
8123
8153
|
}
|
|
8124
8154
|
};
|
|
8125
8155
|
}
|
|
8126
|
-
function preferredRunTimeGate(preferred, now, previous) {
|
|
8156
|
+
function preferredRunTimeGate(preferred, now, previous, opts) {
|
|
8127
8157
|
const current = zonedTimeParts(now, preferred.timezone);
|
|
8128
8158
|
if (!current) return { ok: false, reason: `invalid preferred timezone: ${preferred.timezone}` };
|
|
8129
8159
|
const preferredMinute = preferredTimeToMinute(preferred.time);
|
|
@@ -8135,7 +8165,7 @@ function preferredRunTimeGate(preferred, now, previous) {
|
|
|
8135
8165
|
const lastDispatchAt = previous?.lastDecision.kind === "dispatch" ? previous.lastDecision.at : void 0;
|
|
8136
8166
|
if (lastDispatchAt) {
|
|
8137
8167
|
const last = zonedTimeParts(new Date(lastDispatchAt), preferred.timezone);
|
|
8138
|
-
if (last?.date === current.date) {
|
|
8168
|
+
if (last?.date === current.date && opts?.allowRepeatAfterCompletedTarget !== true) {
|
|
8139
8169
|
return { ok: false, reason: `already dispatched today at preferred time ${preferred.time} ${preferred.timezone}` };
|
|
8140
8170
|
}
|
|
8141
8171
|
}
|
|
@@ -8269,6 +8299,14 @@ function readSimpleGoalTaskSummary(goalId, cwd) {
|
|
|
8269
8299
|
const open = issues.filter((issue) => String(issue.state ?? "").toLowerCase() === "open").length;
|
|
8270
8300
|
return { total, open };
|
|
8271
8301
|
}
|
|
8302
|
+
function previousDispatchWasTargetInstance(managed, previousScheduleState) {
|
|
8303
|
+
const targetId = managed.loopTarget?.id.trim();
|
|
8304
|
+
const previous = previousScheduleState?.lastDecision;
|
|
8305
|
+
if (!targetId || previous?.kind !== "dispatch" || !("targetType" in previous) || previous.targetType !== "goal") {
|
|
8306
|
+
return false;
|
|
8307
|
+
}
|
|
8308
|
+
return previous.targetId === targetId || previous.targetId.startsWith(`${targetId}-`);
|
|
8309
|
+
}
|
|
8272
8310
|
function ensureIssueFactIfNeeded(goal, goalId, cwd) {
|
|
8273
8311
|
if (!routeNeedsIssueFact(goal)) return;
|
|
8274
8312
|
const existing = normalizeIssueNumber(goal.facts.issue);
|
|
@@ -8401,7 +8439,14 @@ var init_advanceManagedGoal = __esm({
|
|
|
8401
8439
|
const beforeSnapshot = goalRunLogSnapshot(goal.id, goal.state, managed);
|
|
8402
8440
|
const previousScheduleState = goal.raw.extra.scheduleState && typeof goal.raw.extra.scheduleState === "object" ? goal.raw.extra.scheduleState : void 0;
|
|
8403
8441
|
const now = goalLoopNow();
|
|
8404
|
-
|
|
8442
|
+
const activeTarget = isGoalTargetLoop(managed) ? resolveActiveGoalLoopTarget(ctx.config, ctx.cwd, goal.id, managed) : null;
|
|
8443
|
+
const allowRepeatAfterCompletedTarget = isGoalTargetLoop(managed) && !activeTarget && previousDispatchWasTargetInstance(managed, previousScheduleState);
|
|
8444
|
+
let decision2 = planTargetLoopSchedule({
|
|
8445
|
+
goal: managed,
|
|
8446
|
+
previousScheduleState,
|
|
8447
|
+
now,
|
|
8448
|
+
allowRepeatAfterCompletedTarget
|
|
8449
|
+
});
|
|
8405
8450
|
let targetResolution;
|
|
8406
8451
|
if (decision2.kind === "dispatch" && decision2.dispatch && isGoalTargetLoop(managed)) {
|
|
8407
8452
|
targetResolution = resolveGoalLoopTarget(ctx.config, ctx.cwd, goal.id, managed, now);
|
|
@@ -8409,7 +8454,8 @@ var init_advanceManagedGoal = __esm({
|
|
|
8409
8454
|
goal: managed,
|
|
8410
8455
|
previousScheduleState,
|
|
8411
8456
|
now,
|
|
8412
|
-
resolvedGoalTargetId: targetResolution.targetId
|
|
8457
|
+
resolvedGoalTargetId: targetResolution.targetId,
|
|
8458
|
+
allowRepeatAfterCompletedTarget
|
|
8413
8459
|
});
|
|
8414
8460
|
}
|
|
8415
8461
|
restoreGoalIdFact();
|
|
@@ -17509,7 +17555,7 @@ function overlayDirectoryChildren(cwd, sourceDir, localDir) {
|
|
|
17509
17555
|
function hydrateStateWorkspace(config, cwd) {
|
|
17510
17556
|
if (process.env.VITEST && process.env[TEST_FETCH_ENV] !== "1") return;
|
|
17511
17557
|
const parsed = parseStateRepo(config);
|
|
17512
|
-
const hydrateKey = `${path41.resolve(cwd)}|${parsed.owner}/${parsed.repo}|${parsed.basePath}|${
|
|
17558
|
+
const hydrateKey = `${path41.resolve(cwd)}|${parsed.owner}/${parsed.repo}|${parsed.basePath}|${parsed.branch}`;
|
|
17513
17559
|
if (hydratedWorkspaces.has(hydrateKey)) return;
|
|
17514
17560
|
const snapshotRoot = fetchStateSnapshot(parsed);
|
|
17515
17561
|
for (const mapping of DIR_MAPPINGS) {
|
|
@@ -17533,7 +17579,7 @@ function fetchStateSnapshot(parsed) {
|
|
|
17533
17579
|
runGit3(["clone", "--no-checkout", "--filter=blob:none", url, cacheDir]);
|
|
17534
17580
|
}
|
|
17535
17581
|
runGit3(["-C", cacheDir, "remote", "set-url", "origin", url]);
|
|
17536
|
-
runGit3(["-C", cacheDir, "fetch", "--depth=1", "origin",
|
|
17582
|
+
runGit3(["-C", cacheDir, "fetch", "--depth=1", "origin", parsed.branch]);
|
|
17537
17583
|
runGit3(["-C", cacheDir, "sparse-checkout", "init", "--cone"]);
|
|
17538
17584
|
runGit3(["-C", cacheDir, "sparse-checkout", "set", parsed.basePath]);
|
|
17539
17585
|
runGit3(["-C", cacheDir, "checkout", "--force", "--detach", "FETCH_HEAD"]);
|
|
@@ -17541,7 +17587,7 @@ function fetchStateSnapshot(parsed) {
|
|
|
17541
17587
|
} catch (err) {
|
|
17542
17588
|
const msg = err instanceof Error ? err.message : String(err);
|
|
17543
17589
|
throw new Error(
|
|
17544
|
-
`stateWorkspace: failed to fetch ${parsed.owner}/${parsed.repo}:${parsed.basePath}@${
|
|
17590
|
+
`stateWorkspace: failed to fetch ${parsed.owner}/${parsed.repo}:${parsed.basePath}@${parsed.branch}: ${msg}`
|
|
17545
17591
|
);
|
|
17546
17592
|
}
|
|
17547
17593
|
return path41.join(cacheDir, parsed.basePath);
|
|
@@ -17550,7 +17596,7 @@ function cacheRoot2() {
|
|
|
17550
17596
|
return process.env[CACHE_ENV2]?.trim() || path41.join(os7.homedir(), ".cache", "kody", "state-repo");
|
|
17551
17597
|
}
|
|
17552
17598
|
function cacheKey2(parsed) {
|
|
17553
|
-
return crypto3.createHash("sha256").update(`${parsed.owner}/${parsed.repo}#${
|
|
17599
|
+
return crypto3.createHash("sha256").update(`${parsed.owner}/${parsed.repo}#${parsed.branch}#${parsed.basePath}`).digest("hex").slice(0, 24);
|
|
17554
17600
|
}
|
|
17555
17601
|
function runGit3(args) {
|
|
17556
17602
|
try {
|
|
@@ -17585,7 +17631,6 @@ var DIR_MAPPINGS, FILE_MAPPINGS, CACHE_ENV2, TEST_FETCH_ENV, hydratedWorkspaces;
|
|
|
17585
17631
|
var init_stateWorkspace = __esm({
|
|
17586
17632
|
"src/stateWorkspace.ts"() {
|
|
17587
17633
|
"use strict";
|
|
17588
|
-
init_stateBranch();
|
|
17589
17634
|
init_stateRepo();
|
|
17590
17635
|
DIR_MAPPINGS = [
|
|
17591
17636
|
{ stateDir: "executables", localDir: path41.join(".kody", "executables") },
|
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.287",
|
|
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",
|