@kody-ade/kody-engine 0.3.40 → 0.3.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/kody.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@kody-ade/kody-engine",
|
|
6
|
-
version: "0.3.
|
|
6
|
+
version: "0.3.41",
|
|
7
7
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
8
8
|
license: "MIT",
|
|
9
9
|
type: "module",
|
|
@@ -612,6 +612,68 @@ import * as path20 from "path";
|
|
|
612
612
|
// src/dispatch.ts
|
|
613
613
|
import * as fs6 from "fs";
|
|
614
614
|
|
|
615
|
+
// src/cron-match.ts
|
|
616
|
+
var FIELD_BOUNDS = [
|
|
617
|
+
[0, 59],
|
|
618
|
+
// minute
|
|
619
|
+
[0, 23],
|
|
620
|
+
// hour
|
|
621
|
+
[1, 31],
|
|
622
|
+
// day-of-month
|
|
623
|
+
[1, 12],
|
|
624
|
+
// month
|
|
625
|
+
[0, 6]
|
|
626
|
+
// day-of-week
|
|
627
|
+
];
|
|
628
|
+
function parseCron(spec) {
|
|
629
|
+
const fields = spec.trim().split(/\s+/);
|
|
630
|
+
if (fields.length !== 5) {
|
|
631
|
+
throw new Error(`Invalid cron expression: "${spec}" \u2014 expected 5 space-separated fields`);
|
|
632
|
+
}
|
|
633
|
+
const sets = fields.map((f, i) => parseField(f, FIELD_BOUNDS[i][0], FIELD_BOUNDS[i][1]));
|
|
634
|
+
return { minute: sets[0], hour: sets[1], dom: sets[2], month: sets[3], dow: sets[4] };
|
|
635
|
+
}
|
|
636
|
+
function parseField(field, min, max) {
|
|
637
|
+
const out = /* @__PURE__ */ new Set();
|
|
638
|
+
for (const part of field.split(",")) {
|
|
639
|
+
const [base, stepStr] = part.split("/");
|
|
640
|
+
const step = stepStr ? parseInt(stepStr, 10) : 1;
|
|
641
|
+
if (!Number.isFinite(step) || step < 1) {
|
|
642
|
+
throw new Error(`Invalid step in cron field "${field}"`);
|
|
643
|
+
}
|
|
644
|
+
let lo;
|
|
645
|
+
let hi;
|
|
646
|
+
if (base === "*") {
|
|
647
|
+
lo = min;
|
|
648
|
+
hi = max;
|
|
649
|
+
} else if (base.includes("-")) {
|
|
650
|
+
const [aStr, bStr] = base.split("-");
|
|
651
|
+
lo = parseInt(aStr, 10);
|
|
652
|
+
hi = parseInt(bStr, 10);
|
|
653
|
+
} else {
|
|
654
|
+
lo = parseInt(base, 10);
|
|
655
|
+
hi = lo;
|
|
656
|
+
}
|
|
657
|
+
if (!Number.isFinite(lo) || !Number.isFinite(hi) || lo < min || hi > max || lo > hi) {
|
|
658
|
+
throw new Error(`Invalid cron field "${field}" \u2014 out of range [${min},${max}] or reversed`);
|
|
659
|
+
}
|
|
660
|
+
for (let i = lo; i <= hi; i += step) out.add(i);
|
|
661
|
+
}
|
|
662
|
+
return out;
|
|
663
|
+
}
|
|
664
|
+
function cronMatchesAt(expr, date) {
|
|
665
|
+
return expr.minute.has(date.getUTCMinutes()) && expr.hour.has(date.getUTCHours()) && expr.dom.has(date.getUTCDate()) && expr.month.has(date.getUTCMonth() + 1) && expr.dow.has(date.getUTCDay());
|
|
666
|
+
}
|
|
667
|
+
function cronMatchesInWindow(spec, end, windowSec) {
|
|
668
|
+
const expr = parseCron(spec);
|
|
669
|
+
const endMs = Math.floor(end.getTime() / 6e4) * 6e4;
|
|
670
|
+
const minuteSteps = Math.max(1, Math.ceil(windowSec / 60));
|
|
671
|
+
for (let i = 0; i < minuteSteps; i++) {
|
|
672
|
+
if (cronMatchesAt(expr, new Date(endMs - i * 6e4))) return true;
|
|
673
|
+
}
|
|
674
|
+
return false;
|
|
675
|
+
}
|
|
676
|
+
|
|
615
677
|
// src/registry.ts
|
|
616
678
|
import * as fs5 from "fs";
|
|
617
679
|
import * as path5 from "path";
|
|
@@ -725,11 +787,9 @@ function autoDispatch(opts) {
|
|
|
725
787
|
if (!Number.isNaN(n) && n > 0) {
|
|
726
788
|
return { executable: "run", cliArgs: { issue: n }, target: n };
|
|
727
789
|
}
|
|
728
|
-
return
|
|
729
|
-
}
|
|
730
|
-
if (eventName === "schedule") {
|
|
731
|
-
return { executable: "mission-scheduler", cliArgs: {}, target: 0 };
|
|
790
|
+
return null;
|
|
732
791
|
}
|
|
792
|
+
if (eventName === "schedule") return null;
|
|
733
793
|
if (eventName === "pull_request") return null;
|
|
734
794
|
if (eventName !== "issue_comment") return null;
|
|
735
795
|
const rawBody = String(event.comment?.body ?? "");
|
|
@@ -771,6 +831,39 @@ function autoDispatch(opts) {
|
|
|
771
831
|
}
|
|
772
832
|
return { executable, cliArgs: args, target: targetNum };
|
|
773
833
|
}
|
|
834
|
+
function dispatchScheduledWatches(opts) {
|
|
835
|
+
const now = opts?.now ?? /* @__PURE__ */ new Date();
|
|
836
|
+
const envWindow = Number(process.env.KODY_SCHEDULE_WINDOW_SEC);
|
|
837
|
+
const windowSec = opts?.windowSec ?? (Number.isFinite(envWindow) && envWindow > 0 ? envWindow : 300);
|
|
838
|
+
const out = [];
|
|
839
|
+
for (const exe of listExecutables()) {
|
|
840
|
+
let raw;
|
|
841
|
+
try {
|
|
842
|
+
raw = fs6.readFileSync(exe.profilePath, "utf-8");
|
|
843
|
+
} catch {
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
let profile;
|
|
847
|
+
try {
|
|
848
|
+
profile = JSON.parse(raw);
|
|
849
|
+
} catch {
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
if (profile.role !== "watch") continue;
|
|
853
|
+
if (profile.kind !== "scheduled") continue;
|
|
854
|
+
const schedule = profile.schedule;
|
|
855
|
+
if (typeof schedule !== "string" || schedule.trim().length === 0) continue;
|
|
856
|
+
if (!opts?.force) {
|
|
857
|
+
try {
|
|
858
|
+
if (!cronMatchesInWindow(schedule, now, windowSec)) continue;
|
|
859
|
+
} catch {
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
out.push({ executable: exe.name, cliArgs: {}, target: 0 });
|
|
864
|
+
}
|
|
865
|
+
return out;
|
|
866
|
+
}
|
|
774
867
|
function extractAfterTag(body) {
|
|
775
868
|
const idx = body.indexOf("@kody");
|
|
776
869
|
if (idx === -1) return body;
|
|
@@ -1674,31 +1767,34 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
1674
1767
|
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1675
1768
|
const root = path9.join(os2.tmpdir(), `kody-synth-${runId}`);
|
|
1676
1769
|
fs10.mkdirSync(path9.join(root, ".claude-plugin"), { recursive: true });
|
|
1770
|
+
const resolvePart = (bucket, entry) => {
|
|
1771
|
+
const local = path9.join(profile.dir, bucket, entry);
|
|
1772
|
+
if (fs10.existsSync(local)) return local;
|
|
1773
|
+
const central = path9.join(catalog, bucket, entry);
|
|
1774
|
+
if (fs10.existsSync(central)) return central;
|
|
1775
|
+
throw new Error(
|
|
1776
|
+
`buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
|
|
1777
|
+
);
|
|
1778
|
+
};
|
|
1677
1779
|
if (cc.skills.length > 0) {
|
|
1678
1780
|
const dst = path9.join(root, "skills");
|
|
1679
1781
|
fs10.mkdirSync(dst, { recursive: true });
|
|
1680
1782
|
for (const name of cc.skills) {
|
|
1681
|
-
|
|
1682
|
-
if (!fs10.existsSync(src)) throw new Error(`buildSyntheticPlugin: skill not found in catalog: ${name}`);
|
|
1683
|
-
copyDir(src, path9.join(dst, name));
|
|
1783
|
+
copyDir(resolvePart("skills", name), path9.join(dst, name));
|
|
1684
1784
|
}
|
|
1685
1785
|
}
|
|
1686
1786
|
if (cc.commands.length > 0) {
|
|
1687
1787
|
const dst = path9.join(root, "commands");
|
|
1688
1788
|
fs10.mkdirSync(dst, { recursive: true });
|
|
1689
1789
|
for (const name of cc.commands) {
|
|
1690
|
-
|
|
1691
|
-
if (!fs10.existsSync(src)) throw new Error(`buildSyntheticPlugin: command not found in catalog: ${name}`);
|
|
1692
|
-
fs10.copyFileSync(src, path9.join(dst, `${name}.md`));
|
|
1790
|
+
fs10.copyFileSync(resolvePart("commands", `${name}.md`), path9.join(dst, `${name}.md`));
|
|
1693
1791
|
}
|
|
1694
1792
|
}
|
|
1695
1793
|
if (cc.subagents.length > 0) {
|
|
1696
1794
|
const dst = path9.join(root, "agents");
|
|
1697
1795
|
fs10.mkdirSync(dst, { recursive: true });
|
|
1698
1796
|
for (const name of cc.subagents) {
|
|
1699
|
-
|
|
1700
|
-
if (!fs10.existsSync(src)) throw new Error(`buildSyntheticPlugin: subagent not found in catalog: ${name}`);
|
|
1701
|
-
fs10.copyFileSync(src, path9.join(dst, `${name}.md`));
|
|
1797
|
+
fs10.copyFileSync(resolvePart("agents", `${name}.md`), path9.join(dst, `${name}.md`));
|
|
1702
1798
|
}
|
|
1703
1799
|
}
|
|
1704
1800
|
if (cc.hooks.length > 0) {
|
|
@@ -1706,8 +1802,7 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
1706
1802
|
fs10.mkdirSync(dst, { recursive: true });
|
|
1707
1803
|
const merged = { hooks: {} };
|
|
1708
1804
|
for (const name of cc.hooks) {
|
|
1709
|
-
const src =
|
|
1710
|
-
if (!fs10.existsSync(src)) throw new Error(`buildSyntheticPlugin: hook not found in catalog: ${name}`);
|
|
1805
|
+
const src = resolvePart("hooks", `${name}.json`);
|
|
1711
1806
|
const parsed = JSON.parse(fs10.readFileSync(src, "utf-8"));
|
|
1712
1807
|
for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
|
|
1713
1808
|
if (!Array.isArray(entries)) continue;
|
|
@@ -6333,6 +6428,22 @@ async function runCi(argv) {
|
|
|
6333
6428
|
} catch {
|
|
6334
6429
|
}
|
|
6335
6430
|
const autoFallback = !args.issueNumber ? autoDispatch({ config: earlyConfig }) : null;
|
|
6431
|
+
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
6432
|
+
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
6433
|
+
let manualWorkflowDispatch = false;
|
|
6434
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs23.existsSync(dispatchEventPath)) {
|
|
6435
|
+
try {
|
|
6436
|
+
const evt = JSON.parse(fs23.readFileSync(dispatchEventPath, "utf-8"));
|
|
6437
|
+
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
6438
|
+
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
6439
|
+
manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
|
|
6440
|
+
} catch {
|
|
6441
|
+
manualWorkflowDispatch = false;
|
|
6442
|
+
}
|
|
6443
|
+
}
|
|
6444
|
+
if (!args.issueNumber && !autoFallback && (eventName === "schedule" || manualWorkflowDispatch)) {
|
|
6445
|
+
return runScheduledFanOut(cwd, args, { force: manualWorkflowDispatch });
|
|
6446
|
+
}
|
|
6336
6447
|
if (!args.issueNumber && !autoFallback && process.env.GITHUB_EVENT_NAME) {
|
|
6337
6448
|
process.stdout.write(`\u2192 kody: no action for event ${process.env.GITHUB_EVENT_NAME} \u2014 exiting cleanly
|
|
6338
6449
|
`);
|
|
@@ -6418,6 +6529,79 @@ ${CI_HELP}`);
|
|
|
6418
6529
|
return 99;
|
|
6419
6530
|
}
|
|
6420
6531
|
}
|
|
6532
|
+
async function runScheduledFanOut(cwd, args, opts) {
|
|
6533
|
+
const matches = dispatchScheduledWatches({ force: opts.force });
|
|
6534
|
+
if (matches.length === 0) {
|
|
6535
|
+
process.stdout.write(
|
|
6536
|
+
`\u2192 kody: scheduled wake \u2014 no watches matched ${opts.force ? "(force mode, no watches discovered)" : "(window)"}, exiting cleanly
|
|
6537
|
+
`
|
|
6538
|
+
);
|
|
6539
|
+
return 0;
|
|
6540
|
+
}
|
|
6541
|
+
const names = matches.map((m) => m.executable).join(", ");
|
|
6542
|
+
process.stdout.write(`\u2192 kody: scheduled wake \u2014 firing ${matches.length} watch(es): ${names}
|
|
6543
|
+
`);
|
|
6544
|
+
try {
|
|
6545
|
+
const n = unpackAllSecrets();
|
|
6546
|
+
if (n > 0) process.stdout.write(`\u2192 kody: unpacked ${n} secret(s) from ALL_SECRETS
|
|
6547
|
+
`);
|
|
6548
|
+
resolveAuthToken();
|
|
6549
|
+
const pm = args.packageManager ?? detectPackageManager2(cwd);
|
|
6550
|
+
process.stdout.write(`\u2192 kody: package manager = ${pm}
|
|
6551
|
+
`);
|
|
6552
|
+
if (!args.skipInstall) {
|
|
6553
|
+
const code = installDeps(pm, cwd);
|
|
6554
|
+
if (code !== 0) {
|
|
6555
|
+
process.stderr.write(`[kody] dep install failed (${pm}, exit ${code})
|
|
6556
|
+
`);
|
|
6557
|
+
return 99;
|
|
6558
|
+
}
|
|
6559
|
+
}
|
|
6560
|
+
if (!args.skipLitellm) {
|
|
6561
|
+
const code = installLitellmIfNeeded(cwd);
|
|
6562
|
+
if (code !== 0) {
|
|
6563
|
+
process.stderr.write(`[kody] litellm install failed (exit ${code})
|
|
6564
|
+
`);
|
|
6565
|
+
return 99;
|
|
6566
|
+
}
|
|
6567
|
+
}
|
|
6568
|
+
configureGitIdentity(cwd);
|
|
6569
|
+
} catch (err) {
|
|
6570
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6571
|
+
process.stderr.write(`[kody] preflight crashed: ${msg}
|
|
6572
|
+
`);
|
|
6573
|
+
return 99;
|
|
6574
|
+
}
|
|
6575
|
+
const config = loadConfig(cwd);
|
|
6576
|
+
let worstExit = 0;
|
|
6577
|
+
for (const match of matches) {
|
|
6578
|
+
process.stdout.write(`
|
|
6579
|
+
\u2192 kody: running watch \`${match.executable}\`
|
|
6580
|
+
`);
|
|
6581
|
+
try {
|
|
6582
|
+
const result = await runExecutable(match.executable, {
|
|
6583
|
+
cliArgs: match.cliArgs,
|
|
6584
|
+
cwd,
|
|
6585
|
+
config,
|
|
6586
|
+
verbose: args.verbose,
|
|
6587
|
+
quiet: args.quiet
|
|
6588
|
+
});
|
|
6589
|
+
if (result.exitCode !== 0) {
|
|
6590
|
+
process.stderr.write(
|
|
6591
|
+
`[kody] watch \`${match.executable}\` exited ${result.exitCode}: ${result.reason ?? "(no reason)"}
|
|
6592
|
+
`
|
|
6593
|
+
);
|
|
6594
|
+
if (result.exitCode > worstExit) worstExit = result.exitCode;
|
|
6595
|
+
}
|
|
6596
|
+
} catch (err) {
|
|
6597
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6598
|
+
process.stderr.write(`[kody] watch \`${match.executable}\` crashed: ${msg}
|
|
6599
|
+
`);
|
|
6600
|
+
worstExit = Math.max(worstExit, 99);
|
|
6601
|
+
}
|
|
6602
|
+
}
|
|
6603
|
+
return worstExit;
|
|
6604
|
+
}
|
|
6421
6605
|
|
|
6422
6606
|
// src/chat-cli.ts
|
|
6423
6607
|
var DEFAULT_MODEL = "claude/claude-haiku-4-5-20251001";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "probe-skill",
|
|
3
|
+
"role": "utility",
|
|
4
|
+
"describe": "Live-test executable. Loads the executable-local 'probe-skill-marker' skill (resolved from src/executables/probe-skill/skills/, NOT the central catalog) and asks the agent to emit the skill's token back as an issue comment. Validates the new local-skill resolution path in buildSyntheticPlugin end-to-end.",
|
|
5
|
+
"inputs": [
|
|
6
|
+
{ "name": "issue", "flag": "--issue", "type": "int", "required": true, "describe": "GitHub issue number to verify against." }
|
|
7
|
+
],
|
|
8
|
+
"claudeCode": {
|
|
9
|
+
"model": "inherit",
|
|
10
|
+
"permissionMode": "default",
|
|
11
|
+
"maxTurns": 12,
|
|
12
|
+
"systemPromptAppend": "You are running Kody's executable-local skill live verification. Emit the token exactly as the skill instructs.",
|
|
13
|
+
"tools": ["Read", "Grep", "Glob", "Bash"],
|
|
14
|
+
"hooks": [],
|
|
15
|
+
"skills": ["probe-skill-marker"],
|
|
16
|
+
"commands": [],
|
|
17
|
+
"subagents": [],
|
|
18
|
+
"plugins": [],
|
|
19
|
+
"mcpServers": []
|
|
20
|
+
},
|
|
21
|
+
"cliTools": [],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"preflight": [
|
|
24
|
+
{ "script": "loadIssueContext" },
|
|
25
|
+
{ "script": "loadTaskState" },
|
|
26
|
+
{ "script": "buildSyntheticPlugin" },
|
|
27
|
+
{ "script": "composePrompt" }
|
|
28
|
+
],
|
|
29
|
+
"postflight": [
|
|
30
|
+
{ "script": "parseAgentResult" },
|
|
31
|
+
{ "script": "writeRunSummary" },
|
|
32
|
+
{ "script": "saveTaskState" }
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
You are Kody's executable-local skill live verification agent. Your only job: prove that the skill named `probe-skill-marker` was loaded into your session from this executable's own directory (NOT the shared catalog), and report its token back to the issue.
|
|
2
|
+
|
|
3
|
+
Issue #{{issue.number}}: {{issue.title}}
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# What to do
|
|
8
|
+
|
|
9
|
+
1. List your available skills. Confirm a skill named `probe-skill-marker` is loaded.
|
|
10
|
+
2. Activate it (its activation phrase is "probe-skill-marker"). The skill instructs you to emit a single token of the form `PROBE_SKILL_OK_<version>`.
|
|
11
|
+
3. Post a comment on issue #{{issue.number}} via `gh issue comment {{issue.number}} --body "..."`. The body must be a single line:
|
|
12
|
+
```
|
|
13
|
+
probe-skill verification: <TOKEN>
|
|
14
|
+
```
|
|
15
|
+
Replace `<TOKEN>` with whatever exact token the skill told you to emit. If the skill is NOT loaded, post `probe-skill verification: SKILL_NOT_LOADED` instead.
|
|
16
|
+
|
|
17
|
+
# Output contract
|
|
18
|
+
|
|
19
|
+
After posting the comment, your final message must be exactly:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
DONE
|
|
23
|
+
COMMIT_MSG: probe-skill: live verification for #{{issue.number}}
|
|
24
|
+
PR_SUMMARY: probe-skill ran; see issue comment for the token.
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
# Rules
|
|
28
|
+
|
|
29
|
+
- Read-only on the repo. Do NOT edit any file. Do NOT run git.
|
|
30
|
+
- The only state-changing command you may run is `gh issue comment`.
|
|
31
|
+
- Do not perform the issue's actual work.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: probe-skill-marker
|
|
3
|
+
description: Internal Kody live-test skill, shipped from the probe-skill executable's own directory (not the shared src/plugins catalog). Activates when a prompt mentions "probe-skill-marker" and provides a versioned confirmation token.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# probe-skill-marker
|
|
7
|
+
|
|
8
|
+
This skill exists only to verify that Kody's executable-local plugin-part resolution actually loads skills from `src/executables/<name>/skills/` into the agent's session.
|
|
9
|
+
|
|
10
|
+
## When to activate
|
|
11
|
+
|
|
12
|
+
When the user's prompt contains the phrase "probe-skill-marker" or explicitly asks whether the skill is loaded.
|
|
13
|
+
|
|
14
|
+
## What to do
|
|
15
|
+
|
|
16
|
+
Emit the literal token `PROBE_SKILL_OK_v1` exactly as written. Do not modify, paraphrase, or interpret it. The token version (`v1`) lets us prove that edits to this file are picked up on the next run — bumping it to `v2` here should result in the agent reporting `PROBE_SKILL_OK_v2` after a fresh publish + trigger.
|
|
17
|
+
|
|
18
|
+
Do not use this skill for anything else. It is a no-op confirmation signal for infrastructure validation.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.41",
|
|
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",
|