@kody-ade/kody-engine 0.4.100 → 0.4.102
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 +184 -158
- package/dist/executables/review-parallel/agents/review-correctness.md +26 -0
- package/dist/executables/review-parallel/agents/review-security.md +25 -0
- package/dist/executables/review-parallel/agents/review-style.md +25 -0
- package/dist/executables/review-parallel/profile.json +56 -0
- package/dist/executables/review-parallel/prompt.md +63 -0
- package/package.json +1 -1
package/dist/bin/kody.js
CHANGED
|
@@ -470,11 +470,11 @@ var init_issue = __esm({
|
|
|
470
470
|
|
|
471
471
|
// src/prompt.ts
|
|
472
472
|
import * as fs18 from "fs";
|
|
473
|
-
import * as
|
|
473
|
+
import * as path17 from "path";
|
|
474
474
|
function loadProjectConventions(projectDir) {
|
|
475
475
|
const out = [];
|
|
476
476
|
for (const rel of CONVENTION_FILES) {
|
|
477
|
-
const abs =
|
|
477
|
+
const abs = path17.join(projectDir, rel);
|
|
478
478
|
if (!fs18.existsSync(abs)) continue;
|
|
479
479
|
let content;
|
|
480
480
|
try {
|
|
@@ -627,7 +627,7 @@ __export(loadMemoryContext_exports, {
|
|
|
627
627
|
loadMemoryContext: () => loadMemoryContext
|
|
628
628
|
});
|
|
629
629
|
import * as fs33 from "fs";
|
|
630
|
-
import * as
|
|
630
|
+
import * as path32 from "path";
|
|
631
631
|
function collectPages(memoryAbs) {
|
|
632
632
|
const out = [];
|
|
633
633
|
walkMd(memoryAbs, (file) => {
|
|
@@ -644,10 +644,10 @@ function collectPages(memoryAbs) {
|
|
|
644
644
|
return;
|
|
645
645
|
}
|
|
646
646
|
const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
647
|
-
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ??
|
|
647
|
+
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path32.basename(file, ".md");
|
|
648
648
|
const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
|
|
649
649
|
out.push({
|
|
650
|
-
relPath:
|
|
650
|
+
relPath: path32.relative(memoryAbs, file),
|
|
651
651
|
title,
|
|
652
652
|
updated,
|
|
653
653
|
content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX : raw,
|
|
@@ -721,7 +721,7 @@ function walkMd(root, visit) {
|
|
|
721
721
|
}
|
|
722
722
|
for (const name of names) {
|
|
723
723
|
if (name.startsWith(".")) continue;
|
|
724
|
-
const full =
|
|
724
|
+
const full = path32.join(dir, name);
|
|
725
725
|
let stat;
|
|
726
726
|
try {
|
|
727
727
|
stat = fs33.statSync(full);
|
|
@@ -747,7 +747,7 @@ var init_loadMemoryContext = __esm({
|
|
|
747
747
|
TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
|
|
748
748
|
loadMemoryContext = async (ctx) => {
|
|
749
749
|
if (typeof ctx.data.memoryContext === "string") return;
|
|
750
|
-
const memoryAbs =
|
|
750
|
+
const memoryAbs = path32.join(ctx.cwd, MEMORY_DIR_RELATIVE);
|
|
751
751
|
if (!fs33.existsSync(memoryAbs)) {
|
|
752
752
|
ctx.data.memoryContext = "";
|
|
753
753
|
return;
|
|
@@ -765,7 +765,10 @@ var init_loadMemoryContext = __esm({
|
|
|
765
765
|
}
|
|
766
766
|
const queryTerms = extractQueryTerms(ctx);
|
|
767
767
|
const ranked = queryTerms.length > 0 ? scorePages(pages, queryTerms) : sortByRecency(pages);
|
|
768
|
-
const
|
|
768
|
+
const indexPage = pages.find((p) => p.relPath === "INDEX.md");
|
|
769
|
+
const withoutIndex = ranked.filter((p) => p.relPath !== "INDEX.md");
|
|
770
|
+
const ordered = indexPage ? [indexPage, ...withoutIndex] : withoutIndex;
|
|
771
|
+
const top = ordered.slice(0, MAX_PAGES);
|
|
769
772
|
ctx.data.memoryContext = formatBlock(top);
|
|
770
773
|
};
|
|
771
774
|
}
|
|
@@ -877,7 +880,7 @@ var init_loadPriorArt = __esm({
|
|
|
877
880
|
// package.json
|
|
878
881
|
var package_default = {
|
|
879
882
|
name: "@kody-ade/kody-engine",
|
|
880
|
-
version: "0.4.
|
|
883
|
+
version: "0.4.102",
|
|
881
884
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
882
885
|
license: "MIT",
|
|
883
886
|
type: "module",
|
|
@@ -933,7 +936,7 @@ var package_default = {
|
|
|
933
936
|
// src/chat-cli.ts
|
|
934
937
|
import { execFileSync as execFileSync32 } from "child_process";
|
|
935
938
|
import * as fs39 from "fs";
|
|
936
|
-
import * as
|
|
939
|
+
import * as path37 from "path";
|
|
937
940
|
|
|
938
941
|
// src/chat/events.ts
|
|
939
942
|
import * as fs from "fs";
|
|
@@ -1000,6 +1003,7 @@ function makeRunId(sessionId, suffix) {
|
|
|
1000
1003
|
|
|
1001
1004
|
// src/chat/loop.ts
|
|
1002
1005
|
import * as fs9 from "fs";
|
|
1006
|
+
import * as path9 from "path";
|
|
1003
1007
|
|
|
1004
1008
|
// src/task-artifacts.ts
|
|
1005
1009
|
import fs2 from "fs";
|
|
@@ -1996,7 +2000,8 @@ async function runChatTurn(opts) {
|
|
|
1996
2000
|
taskType: "chat",
|
|
1997
2001
|
relDir: taskArtifactsPaths.relDir
|
|
1998
2002
|
});
|
|
1999
|
-
const
|
|
2003
|
+
const memoryBlock = readMemoryIndexBlock(opts.cwd);
|
|
2004
|
+
const systemPrompt = [basePrompt, memoryBlock, catalog, artifactAddendum].filter((s) => typeof s === "string" && s.length > 0).join("\n\n");
|
|
2000
2005
|
const prompt = buildPrompt(turns);
|
|
2001
2006
|
let progressSeq = 0;
|
|
2002
2007
|
const invoke = opts.invokeAgent ?? ((p) => runAgent({
|
|
@@ -2088,12 +2093,33 @@ async function emit(sink, type, sessionId, suffix, payload) {
|
|
|
2088
2093
|
emittedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2089
2094
|
});
|
|
2090
2095
|
}
|
|
2096
|
+
var MEMORY_INDEX_REL = ".kody/memory/INDEX.md";
|
|
2097
|
+
var MAX_INDEX_BYTES = 8e3;
|
|
2098
|
+
function readMemoryIndexBlock(cwd) {
|
|
2099
|
+
const indexPath = path9.join(cwd, MEMORY_INDEX_REL);
|
|
2100
|
+
let raw;
|
|
2101
|
+
try {
|
|
2102
|
+
raw = fs9.readFileSync(indexPath, "utf-8");
|
|
2103
|
+
} catch {
|
|
2104
|
+
return "";
|
|
2105
|
+
}
|
|
2106
|
+
const trimmed = raw.trim();
|
|
2107
|
+
if (!trimmed) return "";
|
|
2108
|
+
const body = trimmed.length > MAX_INDEX_BYTES ? trimmed.slice(0, MAX_INDEX_BYTES) + "\n\n_\u2026 (memory index truncated; open individual files under `.kody/memory/` to read more)_" : trimmed;
|
|
2109
|
+
return [
|
|
2110
|
+
"# Project memory index (`.kody/memory/INDEX.md`)",
|
|
2111
|
+
"",
|
|
2112
|
+
"These are the lessons, decisions, and preferences already captured for this repo. Skim before acting; read individual files only if a line looks relevant to the current task.",
|
|
2113
|
+
"",
|
|
2114
|
+
body
|
|
2115
|
+
].join("\n");
|
|
2116
|
+
}
|
|
2091
2117
|
|
|
2092
2118
|
// src/chat/modes/interactive.ts
|
|
2093
2119
|
init_issue();
|
|
2094
2120
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
2095
2121
|
import * as fs10 from "fs";
|
|
2096
|
-
import * as
|
|
2122
|
+
import * as path10 from "path";
|
|
2097
2123
|
|
|
2098
2124
|
// src/chat/inbox.ts
|
|
2099
2125
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
@@ -2252,9 +2278,9 @@ function findNextUserTurn(turns, fromIdx) {
|
|
|
2252
2278
|
return -1;
|
|
2253
2279
|
}
|
|
2254
2280
|
function commitTurn(cwd, sessionId, _verbose) {
|
|
2255
|
-
const sessionRel =
|
|
2256
|
-
const eventsRel =
|
|
2257
|
-
const rels = [sessionRel, eventsRel].filter((p) => fs10.existsSync(
|
|
2281
|
+
const sessionRel = path10.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
2282
|
+
const eventsRel = path10.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
2283
|
+
const rels = [sessionRel, eventsRel].filter((p) => fs10.existsSync(path10.join(cwd, p)));
|
|
2258
2284
|
if (rels.length === 0) return;
|
|
2259
2285
|
const repository = process.env.GITHUB_REPOSITORY;
|
|
2260
2286
|
if (!repository) {
|
|
@@ -2266,8 +2292,8 @@ function commitTurn(cwd, sessionId, _verbose) {
|
|
|
2266
2292
|
}
|
|
2267
2293
|
const branch = defaultBranch(cwd) ?? "main";
|
|
2268
2294
|
for (const rel of rels) {
|
|
2269
|
-
const repoPath = rel.split(
|
|
2270
|
-
const localText = fs10.readFileSync(
|
|
2295
|
+
const repoPath = rel.split(path10.sep).join("/");
|
|
2296
|
+
const localText = fs10.readFileSync(path10.join(cwd, rel), "utf-8");
|
|
2271
2297
|
putJsonlViaContents(repository, branch, repoPath, localText, sessionId, cwd);
|
|
2272
2298
|
}
|
|
2273
2299
|
}
|
|
@@ -2358,7 +2384,7 @@ async function emit2(sink, type, sessionId, suffix, payload) {
|
|
|
2358
2384
|
// src/kody-cli.ts
|
|
2359
2385
|
import { execFileSync as execFileSync31 } from "child_process";
|
|
2360
2386
|
import * as fs38 from "fs";
|
|
2361
|
-
import * as
|
|
2387
|
+
import * as path36 from "path";
|
|
2362
2388
|
|
|
2363
2389
|
// src/dispatch.ts
|
|
2364
2390
|
import * as fs11 from "fs";
|
|
@@ -2681,7 +2707,7 @@ init_issue();
|
|
|
2681
2707
|
// src/executor.ts
|
|
2682
2708
|
import { execFileSync as execFileSync30, spawn as spawn6 } from "child_process";
|
|
2683
2709
|
import * as fs37 from "fs";
|
|
2684
|
-
import * as
|
|
2710
|
+
import * as path35 from "path";
|
|
2685
2711
|
init_events();
|
|
2686
2712
|
|
|
2687
2713
|
// src/lifecycleLabels.ts
|
|
@@ -2689,7 +2715,7 @@ init_issue();
|
|
|
2689
2715
|
|
|
2690
2716
|
// src/profile.ts
|
|
2691
2717
|
import * as fs12 from "fs";
|
|
2692
|
-
import * as
|
|
2718
|
+
import * as path11 from "path";
|
|
2693
2719
|
|
|
2694
2720
|
// src/profile-error.ts
|
|
2695
2721
|
var ProfileError = class extends Error {
|
|
@@ -2873,7 +2899,7 @@ function loadProfile(profilePath) {
|
|
|
2873
2899
|
const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
|
|
2874
2900
|
if (unknownKeys.length > 0) {
|
|
2875
2901
|
process.stderr.write(
|
|
2876
|
-
`[kody profile] ${
|
|
2902
|
+
`[kody profile] ${path11.basename(path11.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
|
|
2877
2903
|
`
|
|
2878
2904
|
);
|
|
2879
2905
|
}
|
|
@@ -2934,7 +2960,7 @@ function loadProfile(profilePath) {
|
|
|
2934
2960
|
// Phase 5 in-process handoff opt-in. Default false; containers
|
|
2935
2961
|
// flip to true after end-to-end verification.
|
|
2936
2962
|
preloadContext: r.preloadContext === true,
|
|
2937
|
-
dir:
|
|
2963
|
+
dir: path11.dirname(profilePath)
|
|
2938
2964
|
};
|
|
2939
2965
|
if (lifecycle) {
|
|
2940
2966
|
applyLifecycle(profile, profilePath);
|
|
@@ -3306,7 +3332,7 @@ function errMsg(err) {
|
|
|
3306
3332
|
import { execFileSync as execFileSync4, spawn as spawn2 } from "child_process";
|
|
3307
3333
|
import * as fs13 from "fs";
|
|
3308
3334
|
import * as os2 from "os";
|
|
3309
|
-
import * as
|
|
3335
|
+
import * as path12 from "path";
|
|
3310
3336
|
async function checkLitellmHealth(url) {
|
|
3311
3337
|
try {
|
|
3312
3338
|
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
@@ -3353,13 +3379,13 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
3353
3379
|
throw new Error("litellm not installed \u2014 run: pip install 'litellm[proxy]'");
|
|
3354
3380
|
}
|
|
3355
3381
|
}
|
|
3356
|
-
const configPath =
|
|
3382
|
+
const configPath = path12.join(os2.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
|
|
3357
3383
|
fs13.writeFileSync(configPath, generateLitellmConfigYaml(model));
|
|
3358
3384
|
const portMatch = url.match(/:(\d+)/);
|
|
3359
3385
|
const port = portMatch ? portMatch[1] : "4000";
|
|
3360
3386
|
const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
|
|
3361
3387
|
const dotenvVars = readDotenvApiKeys(projectDir);
|
|
3362
|
-
const logPath =
|
|
3388
|
+
const logPath = path12.join(os2.tmpdir(), `kody-litellm-${Date.now()}.log`);
|
|
3363
3389
|
const outFd = fs13.openSync(logPath, "w");
|
|
3364
3390
|
const child = spawn2(cmd, args, {
|
|
3365
3391
|
stdio: ["ignore", outFd, outFd],
|
|
@@ -3397,7 +3423,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
3397
3423
|
${logTail}`);
|
|
3398
3424
|
}
|
|
3399
3425
|
function readDotenvApiKeys(projectDir) {
|
|
3400
|
-
const dotenvPath =
|
|
3426
|
+
const dotenvPath = path12.join(projectDir, ".env");
|
|
3401
3427
|
if (!fs13.existsSync(dotenvPath)) return {};
|
|
3402
3428
|
const result = {};
|
|
3403
3429
|
for (const rawLine of fs13.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
@@ -3503,7 +3529,7 @@ function pushWithRetry(opts = {}) {
|
|
|
3503
3529
|
|
|
3504
3530
|
// src/commit.ts
|
|
3505
3531
|
import * as fs14 from "fs";
|
|
3506
|
-
import * as
|
|
3532
|
+
import * as path13 from "path";
|
|
3507
3533
|
var FORBIDDEN_PATH_PREFIXES = [
|
|
3508
3534
|
".kody/",
|
|
3509
3535
|
".kody-engine/",
|
|
@@ -3514,7 +3540,7 @@ var FORBIDDEN_PATH_PREFIXES = [
|
|
|
3514
3540
|
"dist/",
|
|
3515
3541
|
"build/"
|
|
3516
3542
|
];
|
|
3517
|
-
var ALLOWED_PATH_PREFIXES = [".kody/memory/"];
|
|
3543
|
+
var ALLOWED_PATH_PREFIXES = [".kody/memory/", ".kody/tasks/"];
|
|
3518
3544
|
var FORBIDDEN_PATH_EXACT = /* @__PURE__ */ new Set([".env", ".kody-pip-requirements.txt"]);
|
|
3519
3545
|
var FORBIDDEN_PATH_SUFFIXES = [".log"];
|
|
3520
3546
|
var CONVENTIONAL_PREFIXES = [
|
|
@@ -3559,18 +3585,18 @@ function tryGit(args, cwd) {
|
|
|
3559
3585
|
}
|
|
3560
3586
|
function abortUnfinishedGitOps(cwd) {
|
|
3561
3587
|
const aborted = [];
|
|
3562
|
-
const gitDir =
|
|
3588
|
+
const gitDir = path13.join(cwd ?? process.cwd(), ".git");
|
|
3563
3589
|
if (!fs14.existsSync(gitDir)) return aborted;
|
|
3564
|
-
if (fs14.existsSync(
|
|
3590
|
+
if (fs14.existsSync(path13.join(gitDir, "MERGE_HEAD"))) {
|
|
3565
3591
|
if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
|
|
3566
3592
|
}
|
|
3567
|
-
if (fs14.existsSync(
|
|
3593
|
+
if (fs14.existsSync(path13.join(gitDir, "CHERRY_PICK_HEAD"))) {
|
|
3568
3594
|
if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
|
|
3569
3595
|
}
|
|
3570
|
-
if (fs14.existsSync(
|
|
3596
|
+
if (fs14.existsSync(path13.join(gitDir, "REVERT_HEAD"))) {
|
|
3571
3597
|
if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
|
|
3572
3598
|
}
|
|
3573
|
-
if (fs14.existsSync(
|
|
3599
|
+
if (fs14.existsSync(path13.join(gitDir, "rebase-merge")) || fs14.existsSync(path13.join(gitDir, "rebase-apply"))) {
|
|
3574
3600
|
if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
|
|
3575
3601
|
}
|
|
3576
3602
|
try {
|
|
@@ -3626,7 +3652,7 @@ function normalizeCommitMessage(raw) {
|
|
|
3626
3652
|
function commitAndPush(branch, agentMessage, cwd) {
|
|
3627
3653
|
const allChanged = listChangedFiles(cwd);
|
|
3628
3654
|
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
|
|
3629
|
-
const mergeHeadExists = fs14.existsSync(
|
|
3655
|
+
const mergeHeadExists = fs14.existsSync(path13.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
|
|
3630
3656
|
if (allowedFiles.length === 0 && !mergeHeadExists) {
|
|
3631
3657
|
return { committed: false, pushed: false, sha: "", message: "" };
|
|
3632
3658
|
}
|
|
@@ -3926,14 +3952,14 @@ var advanceFlow = async (ctx, profile) => {
|
|
|
3926
3952
|
// src/scripts/brainServe.ts
|
|
3927
3953
|
import { createServer } from "http";
|
|
3928
3954
|
import * as fs16 from "fs";
|
|
3929
|
-
import * as
|
|
3955
|
+
import * as path15 from "path";
|
|
3930
3956
|
|
|
3931
3957
|
// src/scripts/brainTurnLog.ts
|
|
3932
3958
|
import * as fs15 from "fs";
|
|
3933
|
-
import * as
|
|
3959
|
+
import * as path14 from "path";
|
|
3934
3960
|
var live = /* @__PURE__ */ new Map();
|
|
3935
3961
|
function eventsPath(dir, chatId) {
|
|
3936
|
-
return
|
|
3962
|
+
return path14.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
|
|
3937
3963
|
}
|
|
3938
3964
|
function lastPersistedSeq(dir, chatId) {
|
|
3939
3965
|
const p = eventsPath(dir, chatId);
|
|
@@ -3976,7 +4002,7 @@ function beginTurn(dir, chatId) {
|
|
|
3976
4002
|
};
|
|
3977
4003
|
live.set(chatId, state);
|
|
3978
4004
|
const p = eventsPath(dir, chatId);
|
|
3979
|
-
fs15.mkdirSync(
|
|
4005
|
+
fs15.mkdirSync(path14.dirname(p), { recursive: true });
|
|
3980
4006
|
return (event) => {
|
|
3981
4007
|
state.seq += 1;
|
|
3982
4008
|
const rec = { seq: state.seq, turn, ts: Date.now(), event };
|
|
@@ -4236,7 +4262,7 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
4236
4262
|
return;
|
|
4237
4263
|
}
|
|
4238
4264
|
const sessionFile = sessionFilePath(opts.cwd, chatId);
|
|
4239
|
-
fs16.mkdirSync(
|
|
4265
|
+
fs16.mkdirSync(path15.dirname(sessionFile), { recursive: true });
|
|
4240
4266
|
appendTurn(sessionFile, {
|
|
4241
4267
|
role: "user",
|
|
4242
4268
|
content: message,
|
|
@@ -4378,15 +4404,15 @@ var brainServe = async (ctx) => {
|
|
|
4378
4404
|
// src/scripts/buildSyntheticPlugin.ts
|
|
4379
4405
|
import * as fs17 from "fs";
|
|
4380
4406
|
import * as os3 from "os";
|
|
4381
|
-
import * as
|
|
4407
|
+
import * as path16 from "path";
|
|
4382
4408
|
function getPluginsCatalogRoot() {
|
|
4383
|
-
const here =
|
|
4409
|
+
const here = path16.dirname(new URL(import.meta.url).pathname);
|
|
4384
4410
|
const candidates = [
|
|
4385
|
-
|
|
4411
|
+
path16.join(here, "..", "plugins"),
|
|
4386
4412
|
// dev: src/scripts → src/plugins
|
|
4387
|
-
|
|
4413
|
+
path16.join(here, "..", "..", "plugins"),
|
|
4388
4414
|
// built: dist/scripts → dist/plugins
|
|
4389
|
-
|
|
4415
|
+
path16.join(here, "..", "..", "src", "plugins")
|
|
4390
4416
|
// fallback
|
|
4391
4417
|
];
|
|
4392
4418
|
for (const c of candidates) {
|
|
@@ -4400,40 +4426,40 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
4400
4426
|
if (!needsSynthetic) return;
|
|
4401
4427
|
const catalog = getPluginsCatalogRoot();
|
|
4402
4428
|
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
4403
|
-
const root =
|
|
4404
|
-
fs17.mkdirSync(
|
|
4429
|
+
const root = path16.join(os3.tmpdir(), `kody-synth-${runId}`);
|
|
4430
|
+
fs17.mkdirSync(path16.join(root, ".claude-plugin"), { recursive: true });
|
|
4405
4431
|
const resolvePart = (bucket, entry) => {
|
|
4406
|
-
const local =
|
|
4432
|
+
const local = path16.join(profile.dir, bucket, entry);
|
|
4407
4433
|
if (fs17.existsSync(local)) return local;
|
|
4408
|
-
const central =
|
|
4434
|
+
const central = path16.join(catalog, bucket, entry);
|
|
4409
4435
|
if (fs17.existsSync(central)) return central;
|
|
4410
4436
|
throw new Error(
|
|
4411
4437
|
`buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
|
|
4412
4438
|
);
|
|
4413
4439
|
};
|
|
4414
4440
|
if (cc.skills.length > 0) {
|
|
4415
|
-
const dst =
|
|
4441
|
+
const dst = path16.join(root, "skills");
|
|
4416
4442
|
fs17.mkdirSync(dst, { recursive: true });
|
|
4417
4443
|
for (const name of cc.skills) {
|
|
4418
|
-
copyDir(resolvePart("skills", name),
|
|
4444
|
+
copyDir(resolvePart("skills", name), path16.join(dst, name));
|
|
4419
4445
|
}
|
|
4420
4446
|
}
|
|
4421
4447
|
if (cc.commands.length > 0) {
|
|
4422
|
-
const dst =
|
|
4448
|
+
const dst = path16.join(root, "commands");
|
|
4423
4449
|
fs17.mkdirSync(dst, { recursive: true });
|
|
4424
4450
|
for (const name of cc.commands) {
|
|
4425
|
-
fs17.copyFileSync(resolvePart("commands", `${name}.md`),
|
|
4451
|
+
fs17.copyFileSync(resolvePart("commands", `${name}.md`), path16.join(dst, `${name}.md`));
|
|
4426
4452
|
}
|
|
4427
4453
|
}
|
|
4428
4454
|
if (cc.subagents.length > 0) {
|
|
4429
|
-
const dst =
|
|
4455
|
+
const dst = path16.join(root, "agents");
|
|
4430
4456
|
fs17.mkdirSync(dst, { recursive: true });
|
|
4431
4457
|
for (const name of cc.subagents) {
|
|
4432
|
-
fs17.copyFileSync(resolvePart("agents", `${name}.md`),
|
|
4458
|
+
fs17.copyFileSync(resolvePart("agents", `${name}.md`), path16.join(dst, `${name}.md`));
|
|
4433
4459
|
}
|
|
4434
4460
|
}
|
|
4435
4461
|
if (cc.hooks.length > 0) {
|
|
4436
|
-
const dst =
|
|
4462
|
+
const dst = path16.join(root, "hooks");
|
|
4437
4463
|
fs17.mkdirSync(dst, { recursive: true });
|
|
4438
4464
|
const merged = { hooks: {} };
|
|
4439
4465
|
for (const name of cc.hooks) {
|
|
@@ -4445,7 +4471,7 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
4445
4471
|
merged.hooks[event].push(...entries);
|
|
4446
4472
|
}
|
|
4447
4473
|
}
|
|
4448
|
-
fs17.writeFileSync(
|
|
4474
|
+
fs17.writeFileSync(path16.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
|
|
4449
4475
|
`);
|
|
4450
4476
|
}
|
|
4451
4477
|
const manifest = {
|
|
@@ -4456,15 +4482,15 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
4456
4482
|
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
4457
4483
|
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
4458
4484
|
if (cc.subagents.length > 0) manifest.agents = cc.subagents.map((n) => `./agents/${n}.md`);
|
|
4459
|
-
fs17.writeFileSync(
|
|
4485
|
+
fs17.writeFileSync(path16.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
4460
4486
|
`);
|
|
4461
4487
|
ctx.data.syntheticPluginPath = root;
|
|
4462
4488
|
};
|
|
4463
4489
|
function copyDir(src, dst) {
|
|
4464
4490
|
fs17.mkdirSync(dst, { recursive: true });
|
|
4465
4491
|
for (const ent of fs17.readdirSync(src, { withFileTypes: true })) {
|
|
4466
|
-
const s =
|
|
4467
|
-
const d =
|
|
4492
|
+
const s = path16.join(src, ent.name);
|
|
4493
|
+
const d = path16.join(dst, ent.name);
|
|
4468
4494
|
if (ent.isDirectory()) copyDir(s, d);
|
|
4469
4495
|
else if (ent.isFile()) fs17.copyFileSync(s, d);
|
|
4470
4496
|
}
|
|
@@ -4608,12 +4634,12 @@ function defaultLabelMap() {
|
|
|
4608
4634
|
|
|
4609
4635
|
// src/scripts/commitAndPush.ts
|
|
4610
4636
|
import * as fs19 from "fs";
|
|
4611
|
-
import * as
|
|
4637
|
+
import * as path18 from "path";
|
|
4612
4638
|
init_events();
|
|
4613
4639
|
var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
|
|
4614
4640
|
function sentinelPathForStage(cwd, profileName) {
|
|
4615
4641
|
const runId = resolveRunId();
|
|
4616
|
-
return
|
|
4642
|
+
return path18.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
|
|
4617
4643
|
}
|
|
4618
4644
|
var commitAndPush2 = async (ctx, profile) => {
|
|
4619
4645
|
const branch = ctx.data.branch;
|
|
@@ -4678,7 +4704,7 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
4678
4704
|
const result = ctx.data.commitResult;
|
|
4679
4705
|
if (sentinel && result?.committed) {
|
|
4680
4706
|
try {
|
|
4681
|
-
fs19.mkdirSync(
|
|
4707
|
+
fs19.mkdirSync(path18.dirname(sentinel), { recursive: true });
|
|
4682
4708
|
fs19.writeFileSync(
|
|
4683
4709
|
sentinel,
|
|
4684
4710
|
JSON.stringify(
|
|
@@ -4700,11 +4726,11 @@ var commitAndPush2 = async (ctx, profile) => {
|
|
|
4700
4726
|
|
|
4701
4727
|
// src/scripts/commitGoalState.ts
|
|
4702
4728
|
import { execFileSync as execFileSync10 } from "child_process";
|
|
4703
|
-
import * as
|
|
4729
|
+
import * as path19 from "path";
|
|
4704
4730
|
var commitGoalState = async (ctx) => {
|
|
4705
4731
|
const goal = ctx.data.goal;
|
|
4706
4732
|
if (!goal) return;
|
|
4707
|
-
const stateRel =
|
|
4733
|
+
const stateRel = path19.posix.join(".kody", "goals", goal.id, "state.json");
|
|
4708
4734
|
try {
|
|
4709
4735
|
execFileSync10("git", ["add", stateRel], { cwd: ctx.cwd, stdio: "pipe" });
|
|
4710
4736
|
} catch (err) {
|
|
@@ -4750,15 +4776,15 @@ function describeCommitMessage(goal) {
|
|
|
4750
4776
|
|
|
4751
4777
|
// src/scripts/composePrompt.ts
|
|
4752
4778
|
import * as fs20 from "fs";
|
|
4753
|
-
import * as
|
|
4779
|
+
import * as path20 from "path";
|
|
4754
4780
|
var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
4755
4781
|
var composePrompt = async (ctx, profile) => {
|
|
4756
4782
|
const explicit = ctx.data.promptTemplate;
|
|
4757
4783
|
const mode = ctx.args.mode;
|
|
4758
4784
|
const candidates = [
|
|
4759
|
-
explicit ?
|
|
4760
|
-
mode ?
|
|
4761
|
-
|
|
4785
|
+
explicit ? path20.join(profile.dir, explicit) : null,
|
|
4786
|
+
mode ? path20.join(profile.dir, "prompts", `${mode}.md`) : null,
|
|
4787
|
+
path20.join(profile.dir, "prompt.md")
|
|
4762
4788
|
].filter(Boolean);
|
|
4763
4789
|
let templatePath = "";
|
|
4764
4790
|
for (const c of candidates) {
|
|
@@ -4850,7 +4876,7 @@ function formatToolsUsage(profile) {
|
|
|
4850
4876
|
init_issue();
|
|
4851
4877
|
import { execFileSync as execFileSync11 } from "child_process";
|
|
4852
4878
|
import * as fs21 from "fs";
|
|
4853
|
-
import * as
|
|
4879
|
+
import * as path21 from "path";
|
|
4854
4880
|
|
|
4855
4881
|
// src/scripts/postReviewResult.ts
|
|
4856
4882
|
init_issue();
|
|
@@ -5103,7 +5129,7 @@ function createOrUpdateManifestIssue(number, manifest, cwd) {
|
|
|
5103
5129
|
return { number: Number(m[1]), created: true };
|
|
5104
5130
|
}
|
|
5105
5131
|
function writeStateFile(cwd, goalId, lastDispatchedIssue) {
|
|
5106
|
-
const dir =
|
|
5132
|
+
const dir = path21.join(cwd, ".kody", "goals", goalId);
|
|
5107
5133
|
fs21.mkdirSync(dir, { recursive: true });
|
|
5108
5134
|
const state = {
|
|
5109
5135
|
version: 1,
|
|
@@ -5112,7 +5138,7 @@ function writeStateFile(cwd, goalId, lastDispatchedIssue) {
|
|
|
5112
5138
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5113
5139
|
...typeof lastDispatchedIssue === "number" ? { lastDispatchedIssue } : {}
|
|
5114
5140
|
};
|
|
5115
|
-
const filePath =
|
|
5141
|
+
const filePath = path21.join(dir, "state.json");
|
|
5116
5142
|
fs21.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
|
|
5117
5143
|
`);
|
|
5118
5144
|
return filePath;
|
|
@@ -5624,10 +5650,10 @@ function filterGoalTaskPrs(prs, taskIssueNumbers) {
|
|
|
5624
5650
|
import { execFileSync as execFileSync12 } from "child_process";
|
|
5625
5651
|
import * as fs22 from "fs";
|
|
5626
5652
|
import * as os4 from "os";
|
|
5627
|
-
import * as
|
|
5653
|
+
import * as path22 from "path";
|
|
5628
5654
|
var diagMcp = async (_ctx) => {
|
|
5629
5655
|
const home = os4.homedir();
|
|
5630
|
-
const cacheDir =
|
|
5656
|
+
const cacheDir = path22.join(home, ".cache", "ms-playwright");
|
|
5631
5657
|
let entries = [];
|
|
5632
5658
|
try {
|
|
5633
5659
|
entries = fs22.readdirSync(cacheDir);
|
|
@@ -5657,16 +5683,16 @@ var diagMcp = async (_ctx) => {
|
|
|
5657
5683
|
|
|
5658
5684
|
// src/scripts/discoverQaContext.ts
|
|
5659
5685
|
import * as fs24 from "fs";
|
|
5660
|
-
import * as
|
|
5686
|
+
import * as path24 from "path";
|
|
5661
5687
|
|
|
5662
5688
|
// src/scripts/frameworkDetectors.ts
|
|
5663
5689
|
import * as fs23 from "fs";
|
|
5664
|
-
import * as
|
|
5690
|
+
import * as path23 from "path";
|
|
5665
5691
|
function detectFrameworks(cwd) {
|
|
5666
5692
|
const out = [];
|
|
5667
5693
|
let deps = {};
|
|
5668
5694
|
try {
|
|
5669
|
-
const pkg = JSON.parse(fs23.readFileSync(
|
|
5695
|
+
const pkg = JSON.parse(fs23.readFileSync(path23.join(cwd, "package.json"), "utf-8"));
|
|
5670
5696
|
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
5671
5697
|
} catch {
|
|
5672
5698
|
return out;
|
|
@@ -5703,7 +5729,7 @@ function detectFrameworks(cwd) {
|
|
|
5703
5729
|
}
|
|
5704
5730
|
function findFile(cwd, candidates) {
|
|
5705
5731
|
for (const c of candidates) {
|
|
5706
|
-
if (fs23.existsSync(
|
|
5732
|
+
if (fs23.existsSync(path23.join(cwd, c))) return c;
|
|
5707
5733
|
}
|
|
5708
5734
|
return null;
|
|
5709
5735
|
}
|
|
@@ -5716,7 +5742,7 @@ var COLLECTION_DIRS = [
|
|
|
5716
5742
|
function discoverPayloadCollections(cwd) {
|
|
5717
5743
|
const out = [];
|
|
5718
5744
|
for (const dir of COLLECTION_DIRS) {
|
|
5719
|
-
const full =
|
|
5745
|
+
const full = path23.join(cwd, dir);
|
|
5720
5746
|
if (!fs23.existsSync(full)) continue;
|
|
5721
5747
|
let files;
|
|
5722
5748
|
try {
|
|
@@ -5726,7 +5752,7 @@ function discoverPayloadCollections(cwd) {
|
|
|
5726
5752
|
}
|
|
5727
5753
|
for (const file of files) {
|
|
5728
5754
|
try {
|
|
5729
|
-
const filePath =
|
|
5755
|
+
const filePath = path23.join(full, file);
|
|
5730
5756
|
const content = fs23.readFileSync(filePath, "utf-8").slice(0, 1e4);
|
|
5731
5757
|
const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
|
|
5732
5758
|
if (!slugMatch) continue;
|
|
@@ -5741,7 +5767,7 @@ function discoverPayloadCollections(cwd) {
|
|
|
5741
5767
|
out.push({
|
|
5742
5768
|
name,
|
|
5743
5769
|
slug,
|
|
5744
|
-
filePath:
|
|
5770
|
+
filePath: path23.relative(cwd, filePath),
|
|
5745
5771
|
fields: fields.slice(0, 20),
|
|
5746
5772
|
hasAdmin
|
|
5747
5773
|
});
|
|
@@ -5755,7 +5781,7 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
|
|
|
5755
5781
|
function discoverAdminComponents(cwd, collections) {
|
|
5756
5782
|
const out = [];
|
|
5757
5783
|
for (const dir of ADMIN_COMPONENT_DIRS) {
|
|
5758
|
-
const full =
|
|
5784
|
+
const full = path23.join(cwd, dir);
|
|
5759
5785
|
if (!fs23.existsSync(full)) continue;
|
|
5760
5786
|
let entries;
|
|
5761
5787
|
try {
|
|
@@ -5764,19 +5790,19 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
5764
5790
|
continue;
|
|
5765
5791
|
}
|
|
5766
5792
|
for (const entry of entries) {
|
|
5767
|
-
const entryPath =
|
|
5793
|
+
const entryPath = path23.join(full, entry.name);
|
|
5768
5794
|
let name;
|
|
5769
5795
|
let filePath;
|
|
5770
5796
|
if (entry.isDirectory()) {
|
|
5771
5797
|
const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
|
|
5772
|
-
(f) => fs23.existsSync(
|
|
5798
|
+
(f) => fs23.existsSync(path23.join(entryPath, f))
|
|
5773
5799
|
);
|
|
5774
5800
|
if (!indexFile) continue;
|
|
5775
5801
|
name = entry.name;
|
|
5776
|
-
filePath =
|
|
5802
|
+
filePath = path23.relative(cwd, path23.join(entryPath, indexFile));
|
|
5777
5803
|
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
5778
5804
|
name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
|
|
5779
|
-
filePath =
|
|
5805
|
+
filePath = path23.relative(cwd, entryPath);
|
|
5780
5806
|
} else {
|
|
5781
5807
|
continue;
|
|
5782
5808
|
}
|
|
@@ -5784,7 +5810,7 @@ function discoverAdminComponents(cwd, collections) {
|
|
|
5784
5810
|
if (collections) {
|
|
5785
5811
|
for (const col of collections) {
|
|
5786
5812
|
try {
|
|
5787
|
-
const colContent = fs23.readFileSync(
|
|
5813
|
+
const colContent = fs23.readFileSync(path23.join(cwd, col.filePath), "utf-8");
|
|
5788
5814
|
if (colContent.includes(name)) {
|
|
5789
5815
|
usedInCollection = col.slug;
|
|
5790
5816
|
break;
|
|
@@ -5803,7 +5829,7 @@ function scanApiRoutes(cwd) {
|
|
|
5803
5829
|
const out = [];
|
|
5804
5830
|
const appDirs = ["src/app", "app"];
|
|
5805
5831
|
for (const appDir of appDirs) {
|
|
5806
|
-
const apiDir =
|
|
5832
|
+
const apiDir = path23.join(cwd, appDir, "api");
|
|
5807
5833
|
if (!fs23.existsSync(apiDir)) continue;
|
|
5808
5834
|
walkApiRoutes(apiDir, "/api", cwd, out);
|
|
5809
5835
|
break;
|
|
@@ -5820,7 +5846,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
5820
5846
|
const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
|
|
5821
5847
|
if (routeFile) {
|
|
5822
5848
|
try {
|
|
5823
|
-
const content = fs23.readFileSync(
|
|
5849
|
+
const content = fs23.readFileSync(path23.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
|
|
5824
5850
|
const methods = HTTP_METHODS.filter(
|
|
5825
5851
|
(m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
|
|
5826
5852
|
);
|
|
@@ -5828,7 +5854,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
5828
5854
|
out.push({
|
|
5829
5855
|
path: prefix,
|
|
5830
5856
|
methods,
|
|
5831
|
-
filePath:
|
|
5857
|
+
filePath: path23.relative(cwd, path23.join(dir, routeFile.name))
|
|
5832
5858
|
});
|
|
5833
5859
|
}
|
|
5834
5860
|
} catch {
|
|
@@ -5839,7 +5865,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
5839
5865
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
5840
5866
|
let segment = entry.name;
|
|
5841
5867
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
5842
|
-
walkApiRoutes(
|
|
5868
|
+
walkApiRoutes(path23.join(dir, entry.name), prefix, cwd, out);
|
|
5843
5869
|
continue;
|
|
5844
5870
|
}
|
|
5845
5871
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -5847,7 +5873,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
|
|
|
5847
5873
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
5848
5874
|
segment = `:${segment.slice(1, -1)}`;
|
|
5849
5875
|
}
|
|
5850
|
-
walkApiRoutes(
|
|
5876
|
+
walkApiRoutes(path23.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
|
|
5851
5877
|
}
|
|
5852
5878
|
}
|
|
5853
5879
|
var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
@@ -5867,7 +5893,7 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
|
5867
5893
|
function scanEnvVars(cwd) {
|
|
5868
5894
|
const candidates = [".env.example", ".env.local.example", ".env.template"];
|
|
5869
5895
|
for (const envFile of candidates) {
|
|
5870
|
-
const envPath =
|
|
5896
|
+
const envPath = path23.join(cwd, envFile);
|
|
5871
5897
|
if (!fs23.existsSync(envPath)) continue;
|
|
5872
5898
|
try {
|
|
5873
5899
|
const content = fs23.readFileSync(envPath, "utf-8");
|
|
@@ -5918,9 +5944,9 @@ function runQaDiscovery(cwd) {
|
|
|
5918
5944
|
}
|
|
5919
5945
|
function detectDevServer(cwd, out) {
|
|
5920
5946
|
try {
|
|
5921
|
-
const pkg = JSON.parse(fs24.readFileSync(
|
|
5947
|
+
const pkg = JSON.parse(fs24.readFileSync(path24.join(cwd, "package.json"), "utf-8"));
|
|
5922
5948
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
5923
|
-
const pm = fs24.existsSync(
|
|
5949
|
+
const pm = fs24.existsSync(path24.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs24.existsSync(path24.join(cwd, "yarn.lock")) ? "yarn" : fs24.existsSync(path24.join(cwd, "bun.lockb")) ? "bun" : "npm";
|
|
5924
5950
|
if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
|
|
5925
5951
|
if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
|
|
5926
5952
|
else if (allDeps.vite) out.devPort = 5173;
|
|
@@ -5930,7 +5956,7 @@ function detectDevServer(cwd, out) {
|
|
|
5930
5956
|
function scanFrontendRoutes(cwd, out) {
|
|
5931
5957
|
const appDirs = ["src/app", "app"];
|
|
5932
5958
|
for (const appDir of appDirs) {
|
|
5933
|
-
const full =
|
|
5959
|
+
const full = path24.join(cwd, appDir);
|
|
5934
5960
|
if (!fs24.existsSync(full)) continue;
|
|
5935
5961
|
walkFrontendRoutes(full, "", out);
|
|
5936
5962
|
break;
|
|
@@ -5956,7 +5982,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
5956
5982
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
5957
5983
|
let segment = entry.name;
|
|
5958
5984
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
5959
|
-
walkFrontendRoutes(
|
|
5985
|
+
walkFrontendRoutes(path24.join(dir, entry.name), prefix, out);
|
|
5960
5986
|
continue;
|
|
5961
5987
|
}
|
|
5962
5988
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
@@ -5964,7 +5990,7 @@ function walkFrontendRoutes(dir, prefix, out) {
|
|
|
5964
5990
|
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
5965
5991
|
segment = `:${segment.slice(1, -1)}`;
|
|
5966
5992
|
}
|
|
5967
|
-
walkFrontendRoutes(
|
|
5993
|
+
walkFrontendRoutes(path24.join(dir, entry.name), `${prefix}/${segment}`, out);
|
|
5968
5994
|
}
|
|
5969
5995
|
}
|
|
5970
5996
|
function detectAuthFiles(cwd, out) {
|
|
@@ -5981,13 +6007,13 @@ function detectAuthFiles(cwd, out) {
|
|
|
5981
6007
|
"src/app/api/oauth"
|
|
5982
6008
|
];
|
|
5983
6009
|
for (const c of candidates) {
|
|
5984
|
-
if (fs24.existsSync(
|
|
6010
|
+
if (fs24.existsSync(path24.join(cwd, c))) out.authFiles.push(c);
|
|
5985
6011
|
}
|
|
5986
6012
|
}
|
|
5987
6013
|
function detectRoles(cwd, out) {
|
|
5988
6014
|
const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
|
|
5989
6015
|
for (const rp of rolePaths) {
|
|
5990
|
-
const dir =
|
|
6016
|
+
const dir = path24.join(cwd, rp);
|
|
5991
6017
|
if (!fs24.existsSync(dir)) continue;
|
|
5992
6018
|
let files;
|
|
5993
6019
|
try {
|
|
@@ -5997,7 +6023,7 @@ function detectRoles(cwd, out) {
|
|
|
5997
6023
|
}
|
|
5998
6024
|
for (const f of files) {
|
|
5999
6025
|
try {
|
|
6000
|
-
const content = fs24.readFileSync(
|
|
6026
|
+
const content = fs24.readFileSync(path24.join(dir, f), "utf-8").slice(0, 5e3);
|
|
6001
6027
|
const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
|
|
6002
6028
|
if (roleMatches) {
|
|
6003
6029
|
for (const m of roleMatches) {
|
|
@@ -6244,7 +6270,7 @@ function failedAction3(reason) {
|
|
|
6244
6270
|
|
|
6245
6271
|
// src/scripts/dispatchJobFileTicks.ts
|
|
6246
6272
|
import * as fs26 from "fs";
|
|
6247
|
-
import * as
|
|
6273
|
+
import * as path26 from "path";
|
|
6248
6274
|
|
|
6249
6275
|
// src/scripts/jobFrontmatter.ts
|
|
6250
6276
|
var SCHEDULE_EVERY_VALUES = [
|
|
@@ -6506,7 +6532,7 @@ var ContentsApiBackend = class {
|
|
|
6506
6532
|
|
|
6507
6533
|
// src/scripts/jobState/localFileBackend.ts
|
|
6508
6534
|
import * as fs25 from "fs";
|
|
6509
|
-
import * as
|
|
6535
|
+
import * as path25 from "path";
|
|
6510
6536
|
var LocalFileBackend = class {
|
|
6511
6537
|
name = "local-file";
|
|
6512
6538
|
cwd;
|
|
@@ -6521,7 +6547,7 @@ var LocalFileBackend = class {
|
|
|
6521
6547
|
if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
|
|
6522
6548
|
this.cwd = opts.cwd;
|
|
6523
6549
|
this.jobsDir = opts.jobsDir;
|
|
6524
|
-
this.absDir =
|
|
6550
|
+
this.absDir = path25.join(opts.cwd, opts.jobsDir);
|
|
6525
6551
|
this.owner = opts.owner;
|
|
6526
6552
|
this.repo = opts.repo;
|
|
6527
6553
|
this.cache = opts.cache ?? defaultCacheAdapter();
|
|
@@ -6581,7 +6607,7 @@ var LocalFileBackend = class {
|
|
|
6581
6607
|
}
|
|
6582
6608
|
load(slug) {
|
|
6583
6609
|
const relPath = stateFilePath(this.jobsDir, slug);
|
|
6584
|
-
const absPath =
|
|
6610
|
+
const absPath = path25.join(this.cwd, relPath);
|
|
6585
6611
|
if (!fs25.existsSync(absPath)) {
|
|
6586
6612
|
return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
|
|
6587
6613
|
}
|
|
@@ -6602,8 +6628,8 @@ var LocalFileBackend = class {
|
|
|
6602
6628
|
if (!loaded.created && isStateUnchanged(loaded.state, next)) {
|
|
6603
6629
|
return false;
|
|
6604
6630
|
}
|
|
6605
|
-
const absPath =
|
|
6606
|
-
fs25.mkdirSync(
|
|
6631
|
+
const absPath = path25.join(this.cwd, loaded.path);
|
|
6632
|
+
fs25.mkdirSync(path25.dirname(absPath), { recursive: true });
|
|
6607
6633
|
const body = JSON.stringify(next, null, 2) + "\n";
|
|
6608
6634
|
fs25.writeFileSync(absPath, body, "utf-8");
|
|
6609
6635
|
return true;
|
|
@@ -6683,7 +6709,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
|
|
|
6683
6709
|
await backend.hydrate();
|
|
6684
6710
|
}
|
|
6685
6711
|
try {
|
|
6686
|
-
const slugs = listJobSlugs(
|
|
6712
|
+
const slugs = listJobSlugs(path26.join(ctx.cwd, jobsDir));
|
|
6687
6713
|
ctx.data.jobSlugCount = slugs.length;
|
|
6688
6714
|
if (slugs.length === 0) {
|
|
6689
6715
|
process.stdout.write(`[jobs] no job files in ${jobsDir}
|
|
@@ -6796,7 +6822,7 @@ function formatAgo(ms) {
|
|
|
6796
6822
|
}
|
|
6797
6823
|
function readJobFrontmatter(cwd, jobsDir, slug) {
|
|
6798
6824
|
try {
|
|
6799
|
-
const raw = fs26.readFileSync(
|
|
6825
|
+
const raw = fs26.readFileSync(path26.join(cwd, jobsDir, `${slug}.md`), "utf-8");
|
|
6800
6826
|
return splitFrontmatter(raw).frontmatter;
|
|
6801
6827
|
} catch {
|
|
6802
6828
|
return {};
|
|
@@ -7867,14 +7893,14 @@ var handleAbandonedGoal = async (ctx) => {
|
|
|
7867
7893
|
// src/scripts/initFlow.ts
|
|
7868
7894
|
import { execFileSync as execFileSync19 } from "child_process";
|
|
7869
7895
|
import * as fs29 from "fs";
|
|
7870
|
-
import * as
|
|
7896
|
+
import * as path28 from "path";
|
|
7871
7897
|
|
|
7872
7898
|
// src/scripts/loadQaGuide.ts
|
|
7873
7899
|
import * as fs28 from "fs";
|
|
7874
|
-
import * as
|
|
7900
|
+
import * as path27 from "path";
|
|
7875
7901
|
var QA_GUIDE_REL_PATH = ".kody/qa-guide.md";
|
|
7876
7902
|
var loadQaGuide = async (ctx) => {
|
|
7877
|
-
const full =
|
|
7903
|
+
const full = path27.join(ctx.cwd, QA_GUIDE_REL_PATH);
|
|
7878
7904
|
if (!fs28.existsSync(full)) {
|
|
7879
7905
|
ctx.data.qaGuide = "";
|
|
7880
7906
|
ctx.data.qaGuidePath = "";
|
|
@@ -7891,9 +7917,9 @@ var loadQaGuide = async (ctx) => {
|
|
|
7891
7917
|
|
|
7892
7918
|
// src/scripts/initFlow.ts
|
|
7893
7919
|
function detectPackageManager(cwd) {
|
|
7894
|
-
if (fs29.existsSync(
|
|
7895
|
-
if (fs29.existsSync(
|
|
7896
|
-
if (fs29.existsSync(
|
|
7920
|
+
if (fs29.existsSync(path28.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
7921
|
+
if (fs29.existsSync(path28.join(cwd, "yarn.lock"))) return "yarn";
|
|
7922
|
+
if (fs29.existsSync(path28.join(cwd, "bun.lockb"))) return "bun";
|
|
7897
7923
|
return "npm";
|
|
7898
7924
|
}
|
|
7899
7925
|
function qualityCommandsFor(pm) {
|
|
@@ -8015,7 +8041,7 @@ function performInit(cwd, force) {
|
|
|
8015
8041
|
const pm = detectPackageManager(cwd);
|
|
8016
8042
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
8017
8043
|
const defaultBranch2 = defaultBranchFromGit(cwd);
|
|
8018
|
-
const configPath =
|
|
8044
|
+
const configPath = path28.join(cwd, "kody.config.json");
|
|
8019
8045
|
if (fs29.existsSync(configPath) && !force) {
|
|
8020
8046
|
skipped.push("kody.config.json");
|
|
8021
8047
|
} else {
|
|
@@ -8024,8 +8050,8 @@ function performInit(cwd, force) {
|
|
|
8024
8050
|
`);
|
|
8025
8051
|
wrote.push("kody.config.json");
|
|
8026
8052
|
}
|
|
8027
|
-
const workflowDir =
|
|
8028
|
-
const workflowPath =
|
|
8053
|
+
const workflowDir = path28.join(cwd, ".github", "workflows");
|
|
8054
|
+
const workflowPath = path28.join(workflowDir, "kody.yml");
|
|
8029
8055
|
if (fs29.existsSync(workflowPath) && !force) {
|
|
8030
8056
|
skipped.push(".github/workflows/kody.yml");
|
|
8031
8057
|
} else {
|
|
@@ -8033,13 +8059,13 @@ function performInit(cwd, force) {
|
|
|
8033
8059
|
fs29.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
8034
8060
|
wrote.push(".github/workflows/kody.yml");
|
|
8035
8061
|
}
|
|
8036
|
-
const hasUi = fs29.existsSync(
|
|
8062
|
+
const hasUi = fs29.existsSync(path28.join(cwd, "src/app")) || fs29.existsSync(path28.join(cwd, "app")) || fs29.existsSync(path28.join(cwd, "pages"));
|
|
8037
8063
|
if (hasUi) {
|
|
8038
|
-
const qaGuidePath =
|
|
8064
|
+
const qaGuidePath = path28.join(cwd, QA_GUIDE_REL_PATH);
|
|
8039
8065
|
if (fs29.existsSync(qaGuidePath) && !force) {
|
|
8040
8066
|
skipped.push(QA_GUIDE_REL_PATH);
|
|
8041
8067
|
} else {
|
|
8042
|
-
fs29.mkdirSync(
|
|
8068
|
+
fs29.mkdirSync(path28.dirname(qaGuidePath), { recursive: true });
|
|
8043
8069
|
const discovery = runQaDiscovery(cwd);
|
|
8044
8070
|
fs29.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
|
|
8045
8071
|
wrote.push(QA_GUIDE_REL_PATH);
|
|
@@ -8047,11 +8073,11 @@ function performInit(cwd, force) {
|
|
|
8047
8073
|
}
|
|
8048
8074
|
const builtinJobs = listBuiltinJobs();
|
|
8049
8075
|
if (builtinJobs.length > 0) {
|
|
8050
|
-
const jobsDir =
|
|
8076
|
+
const jobsDir = path28.join(cwd, ".kody", "jobs");
|
|
8051
8077
|
fs29.mkdirSync(jobsDir, { recursive: true });
|
|
8052
8078
|
for (const job of builtinJobs) {
|
|
8053
|
-
const rel =
|
|
8054
|
-
const target =
|
|
8079
|
+
const rel = path28.join(".kody", "jobs", `${job.slug}.md`);
|
|
8080
|
+
const target = path28.join(cwd, rel);
|
|
8055
8081
|
if (fs29.existsSync(target) && !force) {
|
|
8056
8082
|
skipped.push(rel);
|
|
8057
8083
|
continue;
|
|
@@ -8068,7 +8094,7 @@ function performInit(cwd, force) {
|
|
|
8068
8094
|
continue;
|
|
8069
8095
|
}
|
|
8070
8096
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
8071
|
-
const target =
|
|
8097
|
+
const target = path28.join(workflowDir, `kody-${exe.name}.yml`);
|
|
8072
8098
|
if (fs29.existsSync(target) && !force) {
|
|
8073
8099
|
skipped.push(`.github/workflows/kody-${exe.name}.yml`);
|
|
8074
8100
|
continue;
|
|
@@ -8152,13 +8178,13 @@ init_loadCoverageRules();
|
|
|
8152
8178
|
|
|
8153
8179
|
// src/goal/state.ts
|
|
8154
8180
|
import * as fs30 from "fs";
|
|
8155
|
-
import * as
|
|
8181
|
+
import * as path29 from "path";
|
|
8156
8182
|
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
|
|
8157
8183
|
var GoalStateError = class extends Error {
|
|
8158
|
-
constructor(
|
|
8159
|
-
super(`Invalid goal state at ${
|
|
8184
|
+
constructor(path38, message) {
|
|
8185
|
+
super(`Invalid goal state at ${path38}:
|
|
8160
8186
|
${message}`);
|
|
8161
|
-
this.path =
|
|
8187
|
+
this.path = path38;
|
|
8162
8188
|
this.name = "GoalStateError";
|
|
8163
8189
|
}
|
|
8164
8190
|
path;
|
|
@@ -8206,7 +8232,7 @@ function serializeGoalState(s) {
|
|
|
8206
8232
|
`;
|
|
8207
8233
|
}
|
|
8208
8234
|
function goalStatePath(cwd, goalId) {
|
|
8209
|
-
return
|
|
8235
|
+
return path29.join(cwd, ".kody", "goals", goalId, "state.json");
|
|
8210
8236
|
}
|
|
8211
8237
|
function readGoalState(cwd, goalId) {
|
|
8212
8238
|
const file = goalStatePath(cwd, goalId);
|
|
@@ -8223,7 +8249,7 @@ function readGoalState(cwd, goalId) {
|
|
|
8223
8249
|
}
|
|
8224
8250
|
function writeGoalState(cwd, goalId, state) {
|
|
8225
8251
|
const file = goalStatePath(cwd, goalId);
|
|
8226
|
-
fs30.mkdirSync(
|
|
8252
|
+
fs30.mkdirSync(path29.dirname(file), { recursive: true });
|
|
8227
8253
|
fs30.writeFileSync(file, serializeGoalState(state), "utf-8");
|
|
8228
8254
|
}
|
|
8229
8255
|
function nowIso() {
|
|
@@ -8322,7 +8348,7 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
|
|
|
8322
8348
|
|
|
8323
8349
|
// src/scripts/loadJobFromFile.ts
|
|
8324
8350
|
import * as fs31 from "fs";
|
|
8325
|
-
import * as
|
|
8351
|
+
import * as path30 from "path";
|
|
8326
8352
|
var loadJobFromFile = async (ctx, _profile, args) => {
|
|
8327
8353
|
const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
|
|
8328
8354
|
const workersDir = String(args?.workersDir ?? ".kody/workers");
|
|
@@ -8331,7 +8357,7 @@ var loadJobFromFile = async (ctx, _profile, args) => {
|
|
|
8331
8357
|
if (!slug) {
|
|
8332
8358
|
throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
|
|
8333
8359
|
}
|
|
8334
|
-
const absPath =
|
|
8360
|
+
const absPath = path30.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
8335
8361
|
if (!fs31.existsSync(absPath)) {
|
|
8336
8362
|
throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
|
|
8337
8363
|
}
|
|
@@ -8341,7 +8367,7 @@ var loadJobFromFile = async (ctx, _profile, args) => {
|
|
|
8341
8367
|
let workerTitle = "";
|
|
8342
8368
|
let workerPersona = "";
|
|
8343
8369
|
if (workerSlug) {
|
|
8344
|
-
const workerPath =
|
|
8370
|
+
const workerPath = path30.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
8345
8371
|
if (!fs31.existsSync(workerPath)) {
|
|
8346
8372
|
throw new Error(
|
|
8347
8373
|
`loadJobFromFile: job '${slug}' declares worker '${workerSlug}' but ${workerPath} does not exist`
|
|
@@ -8386,14 +8412,14 @@ function humanizeSlug(slug) {
|
|
|
8386
8412
|
|
|
8387
8413
|
// src/scripts/loadWorkerAdhoc.ts
|
|
8388
8414
|
import * as fs32 from "fs";
|
|
8389
|
-
import * as
|
|
8415
|
+
import * as path31 from "path";
|
|
8390
8416
|
var loadWorkerAdhoc = async (ctx, _profile, args) => {
|
|
8391
8417
|
const workersDir = String(args?.workersDir ?? ".kody/workers");
|
|
8392
8418
|
const workerSlug = String(ctx.args.worker ?? "").trim();
|
|
8393
8419
|
if (!workerSlug) {
|
|
8394
8420
|
throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
|
|
8395
8421
|
}
|
|
8396
|
-
const workerPath =
|
|
8422
|
+
const workerPath = path31.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
8397
8423
|
if (!fs32.existsSync(workerPath)) {
|
|
8398
8424
|
throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
|
|
8399
8425
|
}
|
|
@@ -8466,7 +8492,7 @@ init_events();
|
|
|
8466
8492
|
|
|
8467
8493
|
// src/taskContext.ts
|
|
8468
8494
|
import * as fs34 from "fs";
|
|
8469
|
-
import * as
|
|
8495
|
+
import * as path33 from "path";
|
|
8470
8496
|
var TASK_CONTEXT_SCHEMA_VERSION = 1;
|
|
8471
8497
|
function buildTaskContext(args) {
|
|
8472
8498
|
return {
|
|
@@ -8482,9 +8508,9 @@ function buildTaskContext(args) {
|
|
|
8482
8508
|
}
|
|
8483
8509
|
function persistTaskContext(cwd, ctx) {
|
|
8484
8510
|
try {
|
|
8485
|
-
const dir =
|
|
8511
|
+
const dir = path33.join(cwd, ".kody", "runs", ctx.runId);
|
|
8486
8512
|
fs34.mkdirSync(dir, { recursive: true });
|
|
8487
|
-
const file =
|
|
8513
|
+
const file = path33.join(dir, "task-context.json");
|
|
8488
8514
|
fs34.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
|
|
8489
8515
|
`);
|
|
8490
8516
|
return file;
|
|
@@ -9849,7 +9875,7 @@ function resolveBaseOverride(value) {
|
|
|
9849
9875
|
// src/scripts/runTickScript.ts
|
|
9850
9876
|
import { spawnSync } from "child_process";
|
|
9851
9877
|
import * as fs35 from "fs";
|
|
9852
|
-
import * as
|
|
9878
|
+
import * as path34 from "path";
|
|
9853
9879
|
var runTickScript = async (ctx, _profile, args) => {
|
|
9854
9880
|
ctx.skipAgent = true;
|
|
9855
9881
|
const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
|
|
@@ -9861,7 +9887,7 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
9861
9887
|
ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
|
|
9862
9888
|
return;
|
|
9863
9889
|
}
|
|
9864
|
-
const jobPath =
|
|
9890
|
+
const jobPath = path34.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
9865
9891
|
if (!fs35.existsSync(jobPath)) {
|
|
9866
9892
|
ctx.output.exitCode = 99;
|
|
9867
9893
|
ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
|
|
@@ -9875,7 +9901,7 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
9875
9901
|
ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
|
|
9876
9902
|
return;
|
|
9877
9903
|
}
|
|
9878
|
-
const scriptPath =
|
|
9904
|
+
const scriptPath = path34.isAbsolute(tickScript) ? tickScript : path34.join(ctx.cwd, tickScript);
|
|
9879
9905
|
if (!fs35.existsSync(scriptPath)) {
|
|
9880
9906
|
ctx.output.exitCode = 99;
|
|
9881
9907
|
ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
|
|
@@ -11154,9 +11180,9 @@ async function runExecutable(profileName, input) {
|
|
|
11154
11180
|
})
|
|
11155
11181
|
};
|
|
11156
11182
|
})() : null;
|
|
11157
|
-
const ndjsonDir =
|
|
11183
|
+
const ndjsonDir = path35.join(input.cwd, ".kody");
|
|
11158
11184
|
const invokeAgent = async (prompt) => {
|
|
11159
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
11185
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path35.isAbsolute(p) ? p : path35.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
11160
11186
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
11161
11187
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
11162
11188
|
return runAgent({
|
|
@@ -11372,13 +11398,13 @@ function getProfileInputsForChild(profileName, _cwd) {
|
|
|
11372
11398
|
function resolveProfilePath(profileName) {
|
|
11373
11399
|
const found = resolveExecutable(profileName);
|
|
11374
11400
|
if (found) return found;
|
|
11375
|
-
const here =
|
|
11401
|
+
const here = path35.dirname(new URL(import.meta.url).pathname);
|
|
11376
11402
|
const candidates = [
|
|
11377
|
-
|
|
11403
|
+
path35.join(here, "executables", profileName, "profile.json"),
|
|
11378
11404
|
// same-dir sibling (dev)
|
|
11379
|
-
|
|
11405
|
+
path35.join(here, "..", "executables", profileName, "profile.json"),
|
|
11380
11406
|
// up one (prod: dist/bin → dist/executables)
|
|
11381
|
-
|
|
11407
|
+
path35.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
11382
11408
|
// fallback
|
|
11383
11409
|
];
|
|
11384
11410
|
for (const c of candidates) {
|
|
@@ -11482,7 +11508,7 @@ function resolveShellTimeoutMs(entry) {
|
|
|
11482
11508
|
var SIGKILL_GRACE_MS = 5e3;
|
|
11483
11509
|
async function runShellEntry(entry, ctx, profile) {
|
|
11484
11510
|
const shellName = entry.shell;
|
|
11485
|
-
const shellPath =
|
|
11511
|
+
const shellPath = path35.join(profile.dir, shellName);
|
|
11486
11512
|
if (!fs37.existsSync(shellPath)) {
|
|
11487
11513
|
ctx.skipAgent = true;
|
|
11488
11514
|
ctx.output.exitCode = 99;
|
|
@@ -11962,9 +11988,9 @@ function resolveAuthToken(env = process.env) {
|
|
|
11962
11988
|
return token;
|
|
11963
11989
|
}
|
|
11964
11990
|
function detectPackageManager2(cwd) {
|
|
11965
|
-
if (fs38.existsSync(
|
|
11966
|
-
if (fs38.existsSync(
|
|
11967
|
-
if (fs38.existsSync(
|
|
11991
|
+
if (fs38.existsSync(path36.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
11992
|
+
if (fs38.existsSync(path36.join(cwd, "yarn.lock"))) return "yarn";
|
|
11993
|
+
if (fs38.existsSync(path36.join(cwd, "bun.lockb"))) return "bun";
|
|
11968
11994
|
return "npm";
|
|
11969
11995
|
}
|
|
11970
11996
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -12051,7 +12077,7 @@ function configureGitIdentity(cwd) {
|
|
|
12051
12077
|
}
|
|
12052
12078
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
12053
12079
|
if (!issueNumber) return;
|
|
12054
|
-
const logPath =
|
|
12080
|
+
const logPath = path36.join(cwd, ".kody", "last-run.jsonl");
|
|
12055
12081
|
let tail = "";
|
|
12056
12082
|
try {
|
|
12057
12083
|
if (fs38.existsSync(logPath)) {
|
|
@@ -12080,7 +12106,7 @@ async function runCi(argv) {
|
|
|
12080
12106
|
return 0;
|
|
12081
12107
|
}
|
|
12082
12108
|
const args = parseCiArgs(argv);
|
|
12083
|
-
const cwd = args.cwd ?
|
|
12109
|
+
const cwd = args.cwd ? path36.resolve(args.cwd) : process.cwd();
|
|
12084
12110
|
let earlyConfig;
|
|
12085
12111
|
try {
|
|
12086
12112
|
earlyConfig = loadConfig(cwd);
|
|
@@ -12351,12 +12377,12 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
12351
12377
|
return result;
|
|
12352
12378
|
}
|
|
12353
12379
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
12354
|
-
const sessionFile =
|
|
12355
|
-
const eventsFile =
|
|
12380
|
+
const sessionFile = path37.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
12381
|
+
const eventsFile = path37.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
12356
12382
|
const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
12357
|
-
const tasksDir =
|
|
12383
|
+
const tasksDir = path37.join(".kody", "tasks", safeSession);
|
|
12358
12384
|
const candidatePaths = [sessionFile, eventsFile, tasksDir];
|
|
12359
|
-
const paths = candidatePaths.filter((p) => fs39.existsSync(
|
|
12385
|
+
const paths = candidatePaths.filter((p) => fs39.existsSync(path37.join(cwd, p)));
|
|
12360
12386
|
if (paths.length === 0) return;
|
|
12361
12387
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
12362
12388
|
try {
|
|
@@ -12400,7 +12426,7 @@ async function runChat(argv) {
|
|
|
12400
12426
|
${CHAT_HELP}`);
|
|
12401
12427
|
return 64;
|
|
12402
12428
|
}
|
|
12403
|
-
const cwd = args.cwd ?
|
|
12429
|
+
const cwd = args.cwd ? path37.resolve(args.cwd) : process.cwd();
|
|
12404
12430
|
const sessionId = args.sessionId;
|
|
12405
12431
|
const unpackedSecrets = unpackAllSecrets();
|
|
12406
12432
|
if (unpackedSecrets > 0) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: review-correctness
|
|
3
|
+
description: Correctness-focused PR reviewer. Inspects a diff and surrounding code for logic bugs, regressions, broken callers, missing edge cases, and test gaps. Returns findings only; never edits files.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are a correctness reviewer examining one pull request. You are read-only: never edit files, never run `git`/`gh` write commands. Use Read / Grep / Glob and read-only `git diff` / `git show` to inspect.
|
|
8
|
+
|
|
9
|
+
Scope yourself to correctness and regression risk. Ignore security (another reviewer owns it) and pure style.
|
|
10
|
+
|
|
11
|
+
Method:
|
|
12
|
+
- Read the FULL changed files. A bug introduced 30 lines above a hunk won't show in the diff.
|
|
13
|
+
- For every modified function, grep the repo for its callers and existing tests. A signature or behavior change is only safe if callers and tests changed too.
|
|
14
|
+
- Check edge cases the diff may have dropped: empty input, null/undefined, boundary values, error paths. If a test was deleted, find what case it covered.
|
|
15
|
+
- Cite real `file:line` from files you actually read. Never invent citations.
|
|
16
|
+
|
|
17
|
+
Return ONLY this block — no preamble:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
CORRECTNESS
|
|
21
|
+
- severity: BLOCK | WARN | NONE
|
|
22
|
+
- findings:
|
|
23
|
+
- <file:line — concrete bug/regression and how it manifests at runtime, or "None">
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Use `BLOCK` only for a clear correctness or regression risk (wrong output, broken caller, dropped tested case). Test-coverage gaps that aren't outright bugs are `WARN`.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: review-security
|
|
3
|
+
description: Security-focused PR reviewer. Inspects a diff and surrounding code for vulnerabilities — injection, authz/authn gaps, secret leakage, SSRF, unsafe deserialization, missing input validation. Returns findings only; never edits files.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are a security reviewer examining one pull request. You are read-only: never edit files, never run `git`/`gh` write commands. Use Read / Grep / Glob and read-only `git diff` / `git show` to inspect.
|
|
8
|
+
|
|
9
|
+
Scope yourself strictly to security. Ignore style, naming, and general correctness unless it creates a security risk.
|
|
10
|
+
|
|
11
|
+
Method:
|
|
12
|
+
- Read the FULL changed files, not just the hunks — a vulnerability often lives outside the diff window.
|
|
13
|
+
- For every request handler, query, or external call in the diff, check: is user input validated? Is it parameterized? Is authorization checked before the sensitive action? Are secrets read from env, not hardcoded?
|
|
14
|
+
- Cite real `file:line` from files you actually read. Never invent citations.
|
|
15
|
+
|
|
16
|
+
Return ONLY this block — no preamble:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
SECURITY
|
|
20
|
+
- severity: BLOCK | WARN | NONE
|
|
21
|
+
- findings:
|
|
22
|
+
- <file:line — concrete issue and the exploit it enables, or "None">
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Use `BLOCK` only for a real, exploitable vulnerability introduced by this diff. Pre-existing issues the diff didn't touch are out of scope.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: review-style
|
|
3
|
+
description: Structure and convention reviewer. Inspects a diff for adherence to repo conventions, module organization, duplication, and documentation gaps. Returns findings only; never edits files.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are a structure/convention reviewer examining one pull request. You are read-only: never edit files, never run `git`/`gh` write commands. Use Read / Grep / Glob and read-only `git diff` / `git show` to inspect.
|
|
8
|
+
|
|
9
|
+
Scope yourself to structure, conventions, duplication, and docs. Do NOT flag things a linter/formatter would catch — that is not a reviewer's job. Ignore security and runtime correctness (other reviewers own those).
|
|
10
|
+
|
|
11
|
+
Method:
|
|
12
|
+
- When the PR adds a new module, find a sibling implementing the same pattern and check the new code follows it. If it diverges, name the sibling and why the divergence is or isn't justified.
|
|
13
|
+
- Flag genuine duplication (logic that already exists elsewhere) and missing docs the repo conventions clearly require (README/CHANGELOG for a public API).
|
|
14
|
+
- Cite real `file:line` from files you actually read. Never invent citations.
|
|
15
|
+
|
|
16
|
+
Return ONLY this block — no preamble:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
STRUCTURE
|
|
20
|
+
- severity: WARN | NONE
|
|
21
|
+
- findings:
|
|
22
|
+
- <file:line — concrete structural/convention/doc gap and the existing pattern it should follow, or "None">
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Structure findings never `BLOCK` — they are advisory. Use `WARN` for real gaps, `NONE` otherwise.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "review-parallel",
|
|
3
|
+
"role": "primitive",
|
|
4
|
+
"phase": "reviewing",
|
|
5
|
+
"describe": "A/B variant of `review`: fans out to parallel read-only reviewer subagents (security, correctness, style) via the Task tool, then synthesizes ONE structured comment. Side-effect-light — posts a comment, never drives the pipeline. Used to benchmark swarm review against single-agent `review`.",
|
|
6
|
+
"inputs": [
|
|
7
|
+
{
|
|
8
|
+
"name": "pr",
|
|
9
|
+
"flag": "--pr",
|
|
10
|
+
"type": "int",
|
|
11
|
+
"required": true,
|
|
12
|
+
"describe": "GitHub PR number to review."
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"claudeCode": {
|
|
16
|
+
"model": "inherit",
|
|
17
|
+
"permissionMode": "default",
|
|
18
|
+
"maxTurns": null,
|
|
19
|
+
"systemPromptAppend": null,
|
|
20
|
+
"tools": [
|
|
21
|
+
"Read",
|
|
22
|
+
"Grep",
|
|
23
|
+
"Glob",
|
|
24
|
+
"Bash",
|
|
25
|
+
"Task"
|
|
26
|
+
],
|
|
27
|
+
"hooks": ["block-write"],
|
|
28
|
+
"skills": [],
|
|
29
|
+
"commands": [],
|
|
30
|
+
"subagents": ["review-security", "review-correctness", "review-style"],
|
|
31
|
+
"plugins": [],
|
|
32
|
+
"mcpServers": []
|
|
33
|
+
},
|
|
34
|
+
"cliTools": [],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"preflight": [
|
|
37
|
+
{
|
|
38
|
+
"script": "reviewFlow"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"script": "loadTaskState"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"script": "loadConventions"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"script": "composePrompt"
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"postflight": [
|
|
51
|
+
{
|
|
52
|
+
"script": "postReviewResult"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
You are Kody, a senior code reviewer leading a review of PR #{{pr.number}}. You coordinate three specialist reviewers, then write ONE structured review comment. Do NOT edit any files. Do NOT run `git`/`gh` write commands. Read-only inspection only.
|
|
2
|
+
|
|
3
|
+
# PR #{{pr.number}}: {{pr.title}}
|
|
4
|
+
|
|
5
|
+
Base: {{pr.baseRefName}} ← Head: {{pr.headRefName}}
|
|
6
|
+
|
|
7
|
+
{{pr.body}}
|
|
8
|
+
|
|
9
|
+
{{conventionsBlock}}
|
|
10
|
+
|
|
11
|
+
# Diff
|
|
12
|
+
|
|
13
|
+
```diff
|
|
14
|
+
{{prDiff}}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
# How to run this review
|
|
18
|
+
|
|
19
|
+
1. **Fan out in parallel.** In a SINGLE message, issue three `Task` calls — one to each subagent — so they run concurrently:
|
|
20
|
+
- `review-security` — security vulnerabilities.
|
|
21
|
+
- `review-correctness` — logic bugs, regressions, test gaps.
|
|
22
|
+
- `review-style` — structure, conventions, duplication, docs.
|
|
23
|
+
|
|
24
|
+
Give each subagent the same context: PR #{{pr.number}}, the base/head refs above, and the diff. Instruct each to read the full changed files (not just hunks) and return only its structured block.
|
|
25
|
+
|
|
26
|
+
2. **Synthesize.** Once all three return, merge their findings into the single comment below. Resolve the verdict from the worst severity reported:
|
|
27
|
+
- any `BLOCK` (security or correctness) → **FAIL**
|
|
28
|
+
- no BLOCK but any `WARN` → **CONCERNS**
|
|
29
|
+
- all `NONE` → **PASS**
|
|
30
|
+
|
|
31
|
+
3. Drop duplicate findings, keep every distinct `file:line` citation. Do not invent citations — only pass through what the subagents reported.
|
|
32
|
+
|
|
33
|
+
# Required output
|
|
34
|
+
|
|
35
|
+
Your FINAL message must be exactly this markdown — no preamble, no DONE/COMMIT_MSG markers. The entire final message IS the review comment, posted verbatim:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
## Verdict: PASS | CONCERNS | FAIL
|
|
39
|
+
|
|
40
|
+
> Reviewed in parallel by 3 subagents (security · correctness · structure).
|
|
41
|
+
|
|
42
|
+
### Summary
|
|
43
|
+
<2-3 sentences: what this PR does, is the approach sound>
|
|
44
|
+
|
|
45
|
+
### Strengths
|
|
46
|
+
- <bullet>
|
|
47
|
+
|
|
48
|
+
### Concerns
|
|
49
|
+
- <bullet with file:line, or "None">
|
|
50
|
+
|
|
51
|
+
### Suggestions
|
|
52
|
+
- <bullet with file:line where possible, or "None">
|
|
53
|
+
|
|
54
|
+
### Bottom line
|
|
55
|
+
<one sentence>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
# Rules
|
|
59
|
+
|
|
60
|
+
- No file edits. No `git`/`gh` writes. Read-only.
|
|
61
|
+
- Every citation must come from a file a subagent actually read — no citations from memory.
|
|
62
|
+
- **FAIL** only for clear correctness/security/regression risk. **CONCERNS** for test-coverage/doc/structural gaps that shouldn't block. **PASS** when the PR meets spec with no blocking issues.
|
|
63
|
+
- Pre-existing issues the diff didn't touch are out of scope.
|
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.102",
|
|
4
4
|
"description": "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|