@kody-ade/kody-engine 0.3.40 → 0.3.42
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 +200 -16
- package/dist/executables/classify/prompt.md +33 -0
- package/dist/executables/fix/profile.json +2 -1
- package/dist/executables/fix/prompt.md +41 -0
- package/dist/executables/fix-ci/profile.json +1 -1
- package/dist/executables/fix-ci/prompt.md +42 -6
- package/dist/executables/plan/profile.json +1 -1
- package/dist/executables/probe-skill/profile.json +35 -0
- package/dist/executables/probe-skill/prompt.md +31 -0
- package/dist/executables/probe-skill/skills/probe-skill-marker/SKILL.md +18 -0
- package/dist/executables/research/profile.json +4 -1
- package/dist/executables/research/prompt.md +5 -0
- package/dist/executables/resolve/profile.json +1 -1
- package/dist/executables/resolve/prompt.md +19 -2
- package/dist/executables/review/profile.json +1 -1
- package/dist/executables/review/prompt.md +35 -1
- package/dist/executables/run/profile.json +2 -1
- package/dist/executables/run/prompt.md +13 -1
- package/dist/executables/ui-review/profile.json +1 -1
- package/dist/executables/ui-review/prompt.md +10 -0
- package/dist/plugins/hooks/block-git.json +16 -0
- package/dist/plugins/hooks/block-write.json +16 -0
- package/package.json +1 -1
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.42",
|
|
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";
|
|
@@ -30,6 +30,39 @@ Pick **exactly one** of:
|
|
|
30
30
|
**If the issue is "tweak config / bump dep / fix typo" with no real design choice → `chore`.**
|
|
31
31
|
**Otherwise → `feature`.**
|
|
32
32
|
|
|
33
|
+
# Worked disambiguation examples
|
|
34
|
+
|
|
35
|
+
These are the cases that catch classifiers out. Read them before deciding.
|
|
36
|
+
|
|
37
|
+
**Example A — label says "bug", body opens design space → `feature`**
|
|
38
|
+
> Title: "Login is slow"
|
|
39
|
+
> Labels: `bug`
|
|
40
|
+
> Body: "Login takes 4 seconds. We should figure out why and fix it. Probably involves the auth service, the session cache, and possibly the new SSO integration."
|
|
41
|
+
|
|
42
|
+
Pick: `feature`. The body opens an investigation across multiple subsystems — that's a design space, not a localized fix. Label loses to content.
|
|
43
|
+
|
|
44
|
+
**Example B — body says "bug" but the ask is exploratory → `spec`**
|
|
45
|
+
> Title: "Investigate why our queue throughput dropped"
|
|
46
|
+
> Body: "Throughput dropped 30% last week. Write up what you find — root cause, options for fixing, recommendation. We'll decide next steps from your write-up."
|
|
47
|
+
|
|
48
|
+
Pick: `spec`. The deliverable is an analysis document. No code change is being requested in this issue.
|
|
49
|
+
|
|
50
|
+
**Example C — labeled `feature` but trivial → `chore`**
|
|
51
|
+
> Title: "Bump prettier to 3.4"
|
|
52
|
+
> Labels: `feature`, `dependencies`
|
|
53
|
+
> Body: "Bump devDep prettier to 3.4. Format will not change."
|
|
54
|
+
|
|
55
|
+
Pick: `chore`. No design choice; mechanical dep bump. Label is wrong.
|
|
56
|
+
|
|
57
|
+
**Example D — labeled `chore` but real → `bug`**
|
|
58
|
+
> Title: "README typo"
|
|
59
|
+
> Labels: `chore`
|
|
60
|
+
> Body: "The README claims our API returns `data` but actually returns `result`. Fix the docs OR the API to make them match."
|
|
61
|
+
|
|
62
|
+
Pick: `bug`. The "OR" forces a real decision and the fix may touch code, not just docs. Not chore-grade.
|
|
63
|
+
|
|
64
|
+
**Precedence rule:** when label and body conflict, body wins. Labels are author hints, often stale or wrong; the body is the actual ask.
|
|
65
|
+
|
|
33
66
|
# Required output
|
|
34
67
|
|
|
35
68
|
Your FINAL message must be exactly this shape (no extra text before or after):
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"Glob",
|
|
34
34
|
"mcp__playwright"
|
|
35
35
|
],
|
|
36
|
-
"hooks": [],
|
|
36
|
+
"hooks": ["block-git"],
|
|
37
37
|
"skills": [],
|
|
38
38
|
"commands": [],
|
|
39
39
|
"subagents": [],
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
{ "script": "fixFlow" },
|
|
73
73
|
{ "script": "loadTaskState" },
|
|
74
74
|
{ "script": "loadConventions" },
|
|
75
|
+
{ "script": "loadPriorArt" },
|
|
75
76
|
{ "script": "loadCoverageRules" },
|
|
76
77
|
{ "script": "composePrompt" }
|
|
77
78
|
],
|
|
@@ -16,10 +16,20 @@ You are Kody, an autonomous engineer. Apply the feedback below to the existing P
|
|
|
16
16
|
{{prDiff}}
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
# Prior art (closed/merged PRs that previously attempted this work, if any)
|
|
20
|
+
{{priorArt}}
|
|
21
|
+
|
|
22
|
+
If a prior-art block is present above, scan it before editing — those are earlier attempts (possibly by you, possibly by a human) at the same fix. Note what was rejected and why; do not repeat a discarded approach.
|
|
23
|
+
|
|
19
24
|
# Required steps
|
|
20
25
|
1. **Extract** every actionable item from the feedback. A structured review uses headings like `### Concerns`, `### Suggestions`, and `### Bugs`; each bullet under those headings is a distinct item. `### Strengths`, `### Summary`, and `### Bottom line` are NOT items — skip them. If the feedback has no headings (plain inline feedback), treat the whole feedback as one item.
|
|
21
26
|
2. **Number each item** internally (Item 1, Item 2, …). You will account for every one of them in your final message below.
|
|
22
27
|
3. **Research** — read only what's needed to act on the items. Make the minimum edits required to implement each one. If the feedback or PR body links to external URLs (reproduction sites, bug recordings, spec pages), use the **Playwright MCP** tools (`mcp__playwright__browser_navigate`, `mcp__playwright__browser_snapshot`) to load them — do not rely on your interpretation of the URL alone.
|
|
28
|
+
|
|
29
|
+
**Research floor (MUST be met before any Edit/Write):**
|
|
30
|
+
- Read the **full** contents of every file you intend to change.
|
|
31
|
+
- Read the test file for each of those files, if one exists.
|
|
32
|
+
- Skipping the floor on the assumption "feedback says exactly what to change" is a hard failure when the change touches code with non-obvious invariants.
|
|
23
33
|
4. **Verify** — run each quality command with Bash. Fix the root cause of any failure you introduced by this round of edits.
|
|
24
34
|
5. Your FINAL message MUST use this exact format (or a single `FAILED: <reason>` line on failure). The `FEEDBACK_ACTIONS:` block is REQUIRED — omitting it or leaving it empty makes your DONE invalid.
|
|
25
35
|
|
|
@@ -34,12 +44,43 @@ You are Kody, an autonomous engineer. Apply the feedback below to the existing P
|
|
|
34
44
|
<2-4 bullets describing what changed in THIS fix round — not the whole PR>
|
|
35
45
|
```
|
|
36
46
|
|
|
47
|
+
**Worked example.** Suppose the feedback was:
|
|
48
|
+
|
|
49
|
+
> ### Concerns
|
|
50
|
+
> - The retry loop in `src/queue.ts:42` has no upper bound — could spin forever if the API is down.
|
|
51
|
+
> - `validateInput` accepts negative numbers but the schema says positive.
|
|
52
|
+
>
|
|
53
|
+
> ### Suggestions
|
|
54
|
+
> - Consider extracting the date-parsing logic into a helper.
|
|
55
|
+
|
|
56
|
+
A valid `FEEDBACK_ACTIONS` block:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
FEEDBACK_ACTIONS:
|
|
60
|
+
- Item 1: "retry loop has no upper bound" — fixed: src/queue.ts:42 added maxRetries=5 with exponential backoff and a final throw.
|
|
61
|
+
- Item 2: "validateInput accepts negative numbers but schema says positive" — fixed: src/validate.ts:18 changed z.number() to z.number().positive(); added test cases for -1 and 0.
|
|
62
|
+
- Item 3: "extract date-parsing helper" — declined: the parsing only appears in one call site (src/handlers/webhook.ts:71); extracting now would create a one-caller helper. Will revisit if a second call site appears.
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Notes on the example:
|
|
66
|
+
- Every extracted item appears as exactly one line. None are dropped, none merged.
|
|
67
|
+
- "Strengths" / "Summary" / "Bottom line" sections from the feedback do NOT become items.
|
|
68
|
+
- `declined:` is paired with concrete evidence (one call site + path), not a vague preference.
|
|
69
|
+
|
|
37
70
|
# Rules
|
|
38
71
|
- **The feedback is the scope.** You are here to address the extracted items — nothing else. Do NOT make unrelated refactors, rename variables the reviewer did not flag, or "tighten" types that were not called out. Every edit in your diff must trace back to a specific Item in `FEEDBACK_ACTIONS`.
|
|
39
72
|
- **Default to `fixed`.** `declined` is only acceptable when (a) the item is factually wrong about the code, or (b) it is explicitly out of scope per the issue body. In both cases the `declined: <reason>` line must point to concrete evidence (a file:line that contradicts the item, or a specific issue-body clause).
|
|
40
73
|
- **Treat each item as a concrete change request, not a code review to argue with.** "Add an X branch" means add an X branch — not document that Y already covers the case. "Already handles it in a different way" is NOT an acceptable reason to decline.
|
|
41
74
|
- **Your DONE is only valid if your diff materially implements each `fixed` item.** A diff that only adds tests asserting the current behavior, or only tweaks comments/docs, does NOT count as addressing a change request. If an item asks for a new code path, the diff MUST contain that new code path.
|
|
42
75
|
- **"Already satisfied" (i.e. skipping the edit because the code already does what's asked) is only allowed when you can cite the exact file:line that already implements it.** If in doubt, make the edit — under `fixed`.
|
|
76
|
+
- **Stale feedback.** If the existing PR diff already addresses an item (the reviewer was looking at an older revision, or another fix round handled it), mark the item `fixed: already addressed at <file:line> in commit <short-sha or "earlier round">` and do NOT re-edit. Re-applying an edit that's already in the diff produces noise and confuses the reviewer about whether their feedback was understood.
|
|
77
|
+
- **Not all feedback is an item.** These are NOT items, even if they appear in the feedback body:
|
|
78
|
+
- Questions ("why did you choose X?") — answer in the PR comment thread, not via an edit.
|
|
79
|
+
- Hedges and asides ("interesting", "let me know", "thoughts?") — no action required.
|
|
80
|
+
- Documentation links and references that aren't tied to a concrete change ask.
|
|
81
|
+
- Praise / strengths bullets, even if they suggest improvements implicitly.
|
|
82
|
+
|
|
83
|
+
When in doubt: an item is something with an imperative or a concrete change that would alter the diff. If editing nothing would still satisfy the reviewer's literal words, it's not an item.
|
|
43
84
|
- Do NOT run git/gh commands. The wrapper handles it.
|
|
44
85
|
- Stay on `{{branch}}`.
|
|
45
86
|
- Do not modify files under `.kody/`, `.kody-engine/`, `.kody/`, `node_modules/`, `dist/`, `build/`, `.env`, `*.log`.
|
|
@@ -22,10 +22,23 @@ You are Kody, an autonomous engineer. A CI workflow on PR #{{pr.number}} (`{{bra
|
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
# Required steps
|
|
25
|
-
1. Read the log
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
1. **Classify the failure.** Read the log and identify which type of failure this is. Different failure types call for different strategies; misidentifying the type usually leads to masking the symptom rather than fixing the root cause.
|
|
26
|
+
|
|
27
|
+
| Failure type | Signals in the log | Strategy |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| **Compile / type error** | `error TS…`, `cannot find module`, `undefined symbol`, `mismatched types` | Edit the code to satisfy the compiler. Don't add `any`, `// @ts-ignore`, `# type: ignore`, or weaken the type to dodge the check. |
|
|
30
|
+
| **Failing test** | `expect(...).toBe(...)`, assertion diff, "1 failed, N passed" | Read the test AND the code under test. Fix whichever has the bug — usually the code, sometimes the test if the test encodes wrong expectations. Never fix it by widening the assertion (`toBeTruthy` instead of a real check, `expect.any(Object)` instead of a real shape). |
|
|
31
|
+
| **Lint / format** | `eslint`, `prettier`, `ruff`, `gofmt`, `--check` | Run the formatter / fix the lint rule. Don't disable the rule unless it's a documented project decision. |
|
|
32
|
+
| **Missing dependency** | `Module not found`, `cannot find package`, `command not found` | Check whether the dep should be installed (add to package.json/requirements/go.mod) or whether the import path is wrong. Don't `npm install` a transitive dep that should already be inherited. |
|
|
33
|
+
| **Build / packaging** | tsup/webpack/vite/turbo errors, "out of memory", "duplicate exports" | Read the actual error. Often a real bug (circular import, wrong export shape), occasionally a config gap. |
|
|
34
|
+
| **Flaky / non-deterministic** | passes locally and on retry; race conditions; timing-sensitive assertions | See "Flaky-test escape hatch" below. Do NOT add retries, `setTimeout`, or `--retries=N` to make a real flake green. |
|
|
35
|
+
| **Environmental** | missing secret, broken runner, network failure, unreachable registry | Emit `FAILED: <explanation>`. Code can't fix infrastructure. |
|
|
36
|
+
|
|
37
|
+
2. **Make the minimum edits to fix the root cause.** Do not bundle unrelated cleanups into a CI fix.
|
|
38
|
+
|
|
39
|
+
3. **Re-run the relevant quality command locally with Bash and confirm exit 0.**
|
|
40
|
+
|
|
41
|
+
4. **Final message format** (or `FAILED: <reason>` on failure):
|
|
29
42
|
|
|
30
43
|
```
|
|
31
44
|
DONE
|
|
@@ -34,9 +47,32 @@ You are Kody, an autonomous engineer. A CI workflow on PR #{{pr.number}} (`{{bra
|
|
|
34
47
|
<2-4 bullets: what was failing, what you changed, why it fixes it>
|
|
35
48
|
```
|
|
36
49
|
|
|
50
|
+
# Flaky-test escape hatch
|
|
51
|
+
|
|
52
|
+
If a test passes locally and on a CI retry but fails non-deterministically (timing, race, port collision, network-dependent), do NOT paper over it. Output:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
FAILED: flaky test — <test name / file:line> appears non-deterministic. Local: pass. CI retry: <pass|fail>. Suspected cause: <one line>. Recommend a separate issue to stabilize, not a fix-CI patch.
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
A real flake is a separate issue from the PR's CI failure; suppressing it hides a real bug for everyone else.
|
|
59
|
+
|
|
60
|
+
# What you must NEVER do to make CI green
|
|
61
|
+
|
|
62
|
+
These all turn a real failure into a silent one. They are hard failures, even if the resulting CI run is green:
|
|
63
|
+
|
|
64
|
+
- Add `// @ts-ignore`, `// @ts-expect-error`, `# type: ignore`, `# noqa`, or equivalents to silence a real type/lint error.
|
|
65
|
+
- Mark a test `.skip`, `.todo`, `xit`, `xdescribe`, or comment it out.
|
|
66
|
+
- Update a snapshot blindly (`-u`, `--update-snapshots`) without first reading the diff and confirming the new snapshot is intentionally correct.
|
|
67
|
+
- Replace a specific assertion with a permissive one (`expect.any(...)`, `toBeTruthy()`, `toBeDefined()`, removing fields from a matcher).
|
|
68
|
+
- Loosen a regex / matcher to match the unexpected output instead of fixing the output.
|
|
69
|
+
- Add `--retries=N`, `retry` decorators, or `setTimeout` to mask a race.
|
|
70
|
+
- Disable a CI step, change `if: always()`, or comment out a workflow job.
|
|
71
|
+
- Pin a dependency to an older version specifically to avoid a new failing test, when the new dep is otherwise correct.
|
|
72
|
+
|
|
73
|
+
If the only way you can think of to make CI pass falls under one of these, the right answer is `FAILED:` with the actual blocker, not a green run.
|
|
74
|
+
|
|
37
75
|
# Rules
|
|
38
76
|
- Do NOT run git/gh. Wrapper handles it.
|
|
39
|
-
- Do NOT disable/skip tests or lint rules just to pass CI.
|
|
40
|
-
- If the failure is environmental (missing secret, broken runner) and not code, emit `FAILED: <explanation>`.
|
|
41
77
|
- Stay on `{{branch}}`.
|
|
42
78
|
{{systemPromptAppend}}
|
|
@@ -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.
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"Bash",
|
|
24
24
|
"mcp__playwright"
|
|
25
25
|
],
|
|
26
|
-
"hooks": [],
|
|
26
|
+
"hooks": ["block-write"],
|
|
27
27
|
"skills": [],
|
|
28
28
|
"commands": [],
|
|
29
29
|
"subagents": [],
|
|
@@ -70,6 +70,9 @@
|
|
|
70
70
|
{
|
|
71
71
|
"script": "loadConventions"
|
|
72
72
|
},
|
|
73
|
+
{
|
|
74
|
+
"script": "loadPriorArt"
|
|
75
|
+
},
|
|
73
76
|
{
|
|
74
77
|
"script": "composePrompt"
|
|
75
78
|
}
|
|
@@ -26,6 +26,11 @@ Recent comments (most recent first, truncated):
|
|
|
26
26
|
|
|
27
27
|
{{conventionsBlock}}
|
|
28
28
|
|
|
29
|
+
# Prior art (closed/merged PRs flagged in earlier research, if any)
|
|
30
|
+
{{priorArt}}
|
|
31
|
+
|
|
32
|
+
If a prior-art block is present above, scan the diffs and review comments — those are previously-attempted solutions to this same issue. Surface the *outcome* (what landed, what was rejected, what's still open) under "Repo context"; this is part of what an implementer needs to know. Do NOT re-recommend an approach the diffs show was already tried and abandoned.
|
|
33
|
+
|
|
29
34
|
---
|
|
30
35
|
|
|
31
36
|
# Required output
|
|
@@ -16,8 +16,25 @@ You are Kody, an autonomous engineer. A `git merge origin/{{baseBranch}}` into P
|
|
|
16
16
|
# Required steps
|
|
17
17
|
1. For each conflicted file: read it, understand both sides of the `<<<<<<<` / `=======` / `>>>>>>>` markers, and produce the correct merged content. Remove all conflict markers.
|
|
18
18
|
2. If a conflict resolution directive is given above, follow it exactly — take the specified side for every conflict, no judgement. Otherwise, preserve the PR's intent (the HEAD side) unless `origin/{{baseBranch}}` made a change that should be preserved (e.g. security fix, renamed API), and use judgement.
|
|
19
|
-
3.
|
|
20
|
-
|
|
19
|
+
3. **Asymmetric conflicts.** Symmetric conflicts (both sides modified the same lines) are easy: merge the content. Asymmetric ones are harder — apply this decision tree:
|
|
20
|
+
|
|
21
|
+
- **One side deletes, the other modifies.** Read commit messages and surrounding code on both sides.
|
|
22
|
+
- If base deletes (file/function removed) and HEAD modifies → likely the PR was written against an older revision; **prefer deletion**, then check whether HEAD's modification still has a home elsewhere (it may have moved). If the modification was a refactor, deletion wins.
|
|
23
|
+
- If base modifies and HEAD deletes (PR removed something that base improved) → **prefer deletion** unless the base modification was a security/correctness fix the PR depends on.
|
|
24
|
+
- If you cannot determine intent from the code, emit `FAILED: cannot resolve asymmetric conflict in <file> — <one-line description>` and stop. Do NOT guess.
|
|
25
|
+
|
|
26
|
+
- **Both sides add (parallel additions of the same name/symbol).** Keep both if they are genuinely different (e.g. two new functions with similar names that do different things — rename one). Keep one if they are duplicates of the same intent.
|
|
27
|
+
|
|
28
|
+
4. **Generated files.** Do NOT manually merge generated artifacts:
|
|
29
|
+
- Lockfiles (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, `bun.lockb`, `Cargo.lock`, `go.sum`, `poetry.lock`, `Pipfile.lock`).
|
|
30
|
+
- Test snapshots (`__snapshots__/*.snap`, `*.snap`, Playwright snapshots).
|
|
31
|
+
- Build outputs (anything under `dist/`, `build/`, `.next/`, `out/`).
|
|
32
|
+
- Schema dumps (`prisma/schema.prisma` migrations directory, generated GraphQL schemas).
|
|
33
|
+
|
|
34
|
+
For these, take the conflicted file from base (`origin/{{baseBranch}}`), then re-run the generator (`pnpm install`, `pnpm test -u` *only with confirmation that the snapshot diff is intentional*, `pnpm prisma generate`, etc.). If you cannot determine the right generator command from the repo, emit `FAILED: generated-file conflict in <file> — needs manual regeneration` and stop.
|
|
35
|
+
|
|
36
|
+
5. After resolving, run the quality commands with Bash and fix any issues YOUR resolution introduced.
|
|
37
|
+
6. Final message format (or `FAILED: <reason>` on failure):
|
|
21
38
|
|
|
22
39
|
```
|
|
23
40
|
DONE
|
|
@@ -10,6 +10,16 @@ Base: {{pr.baseRefName}} ← Head: {{pr.headRefName}}
|
|
|
10
10
|
|
|
11
11
|
{{conventionsBlock}}
|
|
12
12
|
|
|
13
|
+
# Research floor (MUST be met before forming a verdict)
|
|
14
|
+
|
|
15
|
+
A diff hunk in isolation is not enough context for a real review. Before you write the Concerns / Suggestions sections:
|
|
16
|
+
|
|
17
|
+
- For every file in the diff, **Read the full file** (not just the hunk). A bug introduced 30 lines above the hunk will not appear in the diff.
|
|
18
|
+
- For every modified function, scan the rest of the module (and any sibling test file) for callers and existing tests of that function. A signature change is only safe if its callers also changed.
|
|
19
|
+
- If the PR adds a new module, read at least one sibling implementing the same pattern in the repo. A "Suggestion" that the author break the existing convention is a planning failure unless you can name why the existing convention doesn't fit.
|
|
20
|
+
|
|
21
|
+
Do **not** invent file:line citations from memory or from grep snippets — every citation in your review must come from a file you actually Read in this session.
|
|
22
|
+
|
|
13
23
|
# Diff
|
|
14
24
|
|
|
15
25
|
```diff
|
|
@@ -40,10 +50,34 @@ Your FINAL message must be a markdown-formatted review comment, **structured exa
|
|
|
40
50
|
<one sentence>
|
|
41
51
|
```
|
|
42
52
|
|
|
53
|
+
# Verdict calibration (worked examples)
|
|
54
|
+
|
|
55
|
+
Verdicts gate downstream automation: a `CONCERNS` sends the PR back into a `fix` round; a `FAIL` aborts. Miscalibration costs concrete agent time, so calibrate carefully.
|
|
56
|
+
|
|
57
|
+
**PASS** — meets spec, no blocking issues. Examples:
|
|
58
|
+
- Diff implements the issue exactly; tests cover happy + failure paths; no regressions surfaced from reading the changed files.
|
|
59
|
+
- Refactor with no behavior change; existing tests still cover the surface; no obvious dead code introduced.
|
|
60
|
+
|
|
61
|
+
**CONCERNS** — should land but with a note. Examples:
|
|
62
|
+
- Test coverage gap: a new public function has only a happy-path test; the failure path is exercised but not asserted.
|
|
63
|
+
- Naming/structure: a new module duplicates a pattern that already exists in a sibling — flag the sibling, suggest reuse, but don't block.
|
|
64
|
+
- Doc gap: a public API was added without an updated README/CHANGELOG and the repo conventions clearly require it.
|
|
65
|
+
|
|
66
|
+
**FAIL** — must not merge as-is. Examples:
|
|
67
|
+
- Correctness: a regex change drops a previously-handled case; reading the test file confirms the case was tested and the test was deleted.
|
|
68
|
+
- Security: a request handler reads `req.body.userId` and queries by it without checking the session — privilege-escalation risk.
|
|
69
|
+
- Regression: a public function's signature changed but callers in other files weren't updated; build will pass but runtime will throw.
|
|
70
|
+
|
|
71
|
+
**Do NOT verdict CONCERNS for:**
|
|
72
|
+
- Style / formatting / naming choices that the project's linter or formatter would catch (or *should* catch — it's not the reviewer's job to be the linter).
|
|
73
|
+
- Subjective preferences ("I'd have written this differently") with no concrete failure mode.
|
|
74
|
+
- Bundled-PR scope objections — flag in Suggestions, not as a CONCERNS verdict, unless the unrelated changes hide real risk.
|
|
75
|
+
- Things the diff didn't change. Pre-existing issues are not your scope.
|
|
76
|
+
|
|
43
77
|
# Rules
|
|
44
78
|
|
|
45
79
|
- No file edits. No `git`/`gh` invocations. Read-only investigation.
|
|
46
80
|
- Be specific: cite file paths and line numbers. No generic advice.
|
|
47
81
|
- Verdict **FAIL** only for clear correctness / security / regression risks.
|
|
48
|
-
- Verdict **CONCERNS** for
|
|
82
|
+
- Verdict **CONCERNS** for test-coverage / doc / structural gaps that shouldn't block but warrant a follow-up edit.
|
|
49
83
|
- Verdict **PASS** when the PR meets spec with no blocking issues.
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"Grep",
|
|
25
25
|
"Glob"
|
|
26
26
|
],
|
|
27
|
-
"hooks": [],
|
|
27
|
+
"hooks": ["block-git"],
|
|
28
28
|
"skills": [],
|
|
29
29
|
"commands": [],
|
|
30
30
|
"subagents": [],
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
{ "script": "runFlow" },
|
|
46
46
|
{ "script": "loadTaskState" },
|
|
47
47
|
{ "script": "resolveArtifacts" },
|
|
48
|
+
{ "script": "loadPriorArt" },
|
|
48
49
|
{ "script": "loadConventions" },
|
|
49
50
|
{ "script": "loadCoverageRules" },
|
|
50
51
|
{ "script": "composePrompt" }
|
|
@@ -12,8 +12,19 @@ You are Kody, an autonomous engineer. Take a GitHub issue from spec to a tested
|
|
|
12
12
|
|
|
13
13
|
If the plan above is non-empty, TREAT IT AS AUTHORITATIVE — follow its file list and approach rather than inventing your own. Deviate only if the plan is wrong; if you do, you MUST declare each deviation in the `PLAN_DEVIATIONS:` block of your final message (format below). Silent deviations are a hard failure, even if the code works. If the plan is empty, proceed from first principles and emit `PLAN_DEVIATIONS: none` in the final message.
|
|
14
14
|
|
|
15
|
+
# Prior art (closed/merged PRs that previously attempted this issue, if any)
|
|
16
|
+
{{priorArt}}
|
|
17
|
+
|
|
18
|
+
If a prior-art block is present above, READ THE DIFFS — those are failed or superseded attempts at this same issue. Identify what went wrong (review comments, the fact they were closed without merging, or behavioural gaps in the diff itself) and pick a different approach. Repeating a prior failed attempt is a hard failure even if your tests pass locally.
|
|
19
|
+
|
|
15
20
|
# Required steps (all in this one session — no handoff)
|
|
16
|
-
1. **Research** — read the issue carefully
|
|
21
|
+
1. **Research** — read the issue carefully, then meet the research floor below before any Edit/Write. Use Grep/Glob/Read to investigate.
|
|
22
|
+
|
|
23
|
+
**Research floor (MUST be met before step 3):**
|
|
24
|
+
- Read the **full** contents of every file you intend to change (not just a grep hit).
|
|
25
|
+
- Read the tests for each of those files, if tests exist for the module.
|
|
26
|
+
- Read at least one sibling module that already implements the same pattern you're about to follow — your edits should mirror an existing convention unless you can name why a new one is needed.
|
|
27
|
+
- If a file you need to read does not exist, say so explicitly in your plan (step 2). Do not guess at its contents.
|
|
17
28
|
2. **Plan** — before any Edit/Write, output a short plan (5–10 lines): what files you'll change, the approach, what could go wrong. No fluff.
|
|
18
29
|
3. **Build** — Edit/Write to implement the change. Stay within the plan; if you discover the plan was wrong, briefly say so and adjust.
|
|
19
30
|
4. **Test** — for every new module you added and every behavior you changed, write or update tests. If the plan above contains a "Test plan" section, treat it as authoritative: every item there must produce a corresponding test. Match the repo's existing test layout (look at `tests/` or sibling `*.test.ts` files in the codebase to see the convention). Cover at least one happy path and one failure path per change. Skipping tests is a hard failure. A change may only be declared untestable if you can name the specific blocker (e.g., "no fake exists for the X SDK and stubbing it would mock the entire call surface"); vague "this is just config" claims are rejected. Untestable changes go in `PLAN_DEVIATIONS:` with the named blocker.
|
|
@@ -31,6 +42,7 @@ If the plan above is non-empty, TREAT IT AS AUTHORITATIVE — follow its file li
|
|
|
31
42
|
```
|
|
32
43
|
|
|
33
44
|
# Rules
|
|
45
|
+
- **No speculative refactors.** Stay inside the issue's scope. Do not rename variables, retype function signatures, restructure modules, reorder imports, reformat unchanged lines, or "clean up" code adjacent to the change unless that cleanup is *required* by the change. Scope drift in your diff is a hard failure even if the change works — reviewers can't tell what was intentional. If you find a real adjacent bug while working, mention it in `PR_SUMMARY` (without fixing it) so a follow-up issue can be opened.
|
|
34
46
|
- Do NOT run **any** `git` or `gh` commands. The wrapper handles all git/gh operations. If a quality gate fails, that's the failure — do not investigate it via git.
|
|
35
47
|
- Stay on the current branch (`{{branch}}`). It is already checked out for you.
|
|
36
48
|
- Do NOT modify files under: `.kody/`, `.kody-engine/`, `.kody-lean/`, `.kody/`, `node_modules/`, `dist/`, `build/`, `.env`, or any `*.log`.
|
|
@@ -56,6 +56,16 @@ If the response is not 2xx or 3xx, the preview is unreachable. In that case, SKI
|
|
|
56
56
|
|
|
57
57
|
Include a `playwright.config.ts` at `.kody/ui-review/playwright.config.ts` only if you need custom config; otherwise rely on defaults (headless chromium).
|
|
58
58
|
|
|
59
|
+
**UI-state checklist.** Browsing the happy path is not enough. For each UI surface the PR changes, verify the following states *if they're plausibly reachable*; explicitly note in "Gaps" any state you couldn't reach:
|
|
60
|
+
|
|
61
|
+
- **Loading.** What does the page look like before data resolves? Are there skeletons / spinners / placeholders? Does the layout shift on data arrival?
|
|
62
|
+
- **Empty.** What does it look like with zero items (no rows, no results, no notifications)? Is there an empty-state message, or is the screen confusingly blank?
|
|
63
|
+
- **Error.** What does it look like when a request fails? Force a failure if you can (network throttle, invalid input, broken nav). Is the error visible and actionable?
|
|
64
|
+
- **Mobile / narrow viewport.** Take a screenshot at ~375px wide. Is anything cut off, overlapping, or stacked illegibly?
|
|
65
|
+
- **Keyboard navigation.** Tab through the changed surface. Is focus visible at every step? Can the user reach every interactive element without a mouse? Does Enter/Space activate the right control?
|
|
66
|
+
|
|
67
|
+
These map directly to UI findings — flag any that fail or look broken. Do NOT pad your review by enumerating every state for trivial diffs (e.g. a copy change in static text); apply the checklist where the diff plausibly affects the state.
|
|
68
|
+
|
|
59
69
|
4. **Run it.** Invoke:
|
|
60
70
|
|
|
61
71
|
```bash
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"PreToolUse": [
|
|
5
|
+
{
|
|
6
|
+
"matcher": "Bash",
|
|
7
|
+
"hooks": [
|
|
8
|
+
{
|
|
9
|
+
"type": "command",
|
|
10
|
+
"command": "node -e 'let s=\"\";process.stdin.on(\"data\",c=>s+=c).on(\"end\",()=>{try{const d=JSON.parse(s);const cmd=(d.tool_input&&d.tool_input.command)||\"\";if(cmd.split(/[;&|\\n]+/).some(p=>/^(git|gh)(\\s|$)/.test(p.trim()))){process.stderr.write(\"kody blocks git/gh — the wrapper handles VCS; do not run git or gh commands\\n\");process.exit(2)}}catch{}})'"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"PreToolUse": [
|
|
5
|
+
{
|
|
6
|
+
"matcher": "Write|Edit|NotebookEdit",
|
|
7
|
+
"hooks": [
|
|
8
|
+
{
|
|
9
|
+
"type": "command",
|
|
10
|
+
"command": "node -e 'process.stderr.write(\"kody read-only mode: this executable does not modify files; do not call Write/Edit/NotebookEdit\\n\");process.exit(2)'"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
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.42",
|
|
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",
|