@kody-ade/kody-engine 0.3.42 → 0.3.44
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 +489 -64
- package/dist/executables/fix/profile.json +1 -0
- package/dist/executables/fix/prompt.md +2 -0
- package/dist/executables/memorize/profile.json +48 -0
- package/dist/executables/memorize/prompt.md +59 -0
- package/dist/executables/resolve/profile.json +3 -0
- package/dist/executables/resolve/prompt.md +1 -1
- package/dist/executables/run/profile.json +1 -0
- package/dist/executables/run/prompt.md +2 -0
- package/package.json +1 -1
- package/templates/kody.yml +4 -0
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.44",
|
|
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",
|
|
@@ -50,9 +50,9 @@ var package_default = {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
// src/chat-cli.ts
|
|
53
|
-
import { execFileSync as
|
|
54
|
-
import * as
|
|
55
|
-
import * as
|
|
53
|
+
import { execFileSync as execFileSync24 } from "child_process";
|
|
54
|
+
import * as fs26 from "fs";
|
|
55
|
+
import * as path23 from "path";
|
|
56
56
|
|
|
57
57
|
// src/chat/events.ts
|
|
58
58
|
import * as fs from "fs";
|
|
@@ -154,7 +154,7 @@ function loadConfig(projectDir = process.cwd()) {
|
|
|
154
154
|
throw new Error(`kody.config.json is invalid JSON: ${msg}`);
|
|
155
155
|
}
|
|
156
156
|
const quality = raw.quality ?? {};
|
|
157
|
-
const
|
|
157
|
+
const git4 = raw.git ?? {};
|
|
158
158
|
const github = raw.github ?? {};
|
|
159
159
|
const agent = raw.agent ?? {};
|
|
160
160
|
if (!agent.model || typeof agent.model !== "string") {
|
|
@@ -171,7 +171,7 @@ function loadConfig(projectDir = process.cwd()) {
|
|
|
171
171
|
testUnit: typeof quality.testUnit === "string" ? quality.testUnit : ""
|
|
172
172
|
},
|
|
173
173
|
git: {
|
|
174
|
-
defaultBranch: typeof
|
|
174
|
+
defaultBranch: typeof git4.defaultBranch === "string" ? git4.defaultBranch : "main"
|
|
175
175
|
},
|
|
176
176
|
github: {
|
|
177
177
|
owner: String(github.owner),
|
|
@@ -605,9 +605,9 @@ async function emit(sink, type, sessionId, suffix, payload) {
|
|
|
605
605
|
}
|
|
606
606
|
|
|
607
607
|
// src/kody-cli.ts
|
|
608
|
-
import { execFileSync as
|
|
609
|
-
import * as
|
|
610
|
-
import * as
|
|
608
|
+
import { execFileSync as execFileSync23 } from "child_process";
|
|
609
|
+
import * as fs25 from "fs";
|
|
610
|
+
import * as path22 from "path";
|
|
611
611
|
|
|
612
612
|
// src/dispatch.ts
|
|
613
613
|
import * as fs6 from "fs";
|
|
@@ -948,8 +948,8 @@ function coerceBare(spec, value) {
|
|
|
948
948
|
|
|
949
949
|
// src/executor.ts
|
|
950
950
|
import { spawnSync } from "child_process";
|
|
951
|
-
import * as
|
|
952
|
-
import * as
|
|
951
|
+
import * as fs24 from "fs";
|
|
952
|
+
import * as path21 from "path";
|
|
953
953
|
|
|
954
954
|
// src/litellm.ts
|
|
955
955
|
import { execFileSync, spawn } from "child_process";
|
|
@@ -1332,6 +1332,7 @@ var FORBIDDEN_PATH_PREFIXES = [
|
|
|
1332
1332
|
"dist/",
|
|
1333
1333
|
"build/"
|
|
1334
1334
|
];
|
|
1335
|
+
var ALLOWED_PATH_PREFIXES = [".kody/vault/"];
|
|
1335
1336
|
var FORBIDDEN_PATH_EXACT = /* @__PURE__ */ new Set([".env", ".kody-pip-requirements.txt"]);
|
|
1336
1337
|
var FORBIDDEN_PATH_SUFFIXES = [".log"];
|
|
1337
1338
|
var CONVENTIONAL_PREFIXES = [
|
|
@@ -1402,6 +1403,7 @@ function abortUnfinishedGitOps(cwd) {
|
|
|
1402
1403
|
}
|
|
1403
1404
|
function isForbiddenPath(p) {
|
|
1404
1405
|
if (FORBIDDEN_PATH_EXACT.has(p)) return true;
|
|
1406
|
+
for (const pre of ALLOWED_PATH_PREFIXES) if (p.startsWith(pre)) return false;
|
|
1405
1407
|
for (const pre of FORBIDDEN_PATH_PREFIXES) if (p.startsWith(pre)) return true;
|
|
1406
1408
|
for (const suf of FORBIDDEN_PATH_SUFFIXES) if (p.endsWith(suf)) return true;
|
|
1407
1409
|
return false;
|
|
@@ -3161,6 +3163,101 @@ function ensurePr(opts) {
|
|
|
3161
3163
|
return { url, number, draft: opts.draft, action: "created" };
|
|
3162
3164
|
}
|
|
3163
3165
|
|
|
3166
|
+
// src/scripts/ensureMemorizePr.ts
|
|
3167
|
+
var TITLE_MAX2 = 72;
|
|
3168
|
+
var ensureMemorizePr = async (ctx) => {
|
|
3169
|
+
if (ctx.skipAgent && ctx.output.exitCode !== void 0 && ctx.output.exitCode !== 0) {
|
|
3170
|
+
return;
|
|
3171
|
+
}
|
|
3172
|
+
const commitResult = ctx.data.commitResult;
|
|
3173
|
+
const hasCommits = Boolean(ctx.data.hasCommitsAhead);
|
|
3174
|
+
if (!commitResult?.committed && !hasCommits) {
|
|
3175
|
+
process.stdout.write("[kody memorize] no vault changes \u2014 skipping PR\n");
|
|
3176
|
+
ctx.output.exitCode = 0;
|
|
3177
|
+
ctx.output.reason = "no vault changes";
|
|
3178
|
+
return;
|
|
3179
|
+
}
|
|
3180
|
+
const branch = ctx.data.branch;
|
|
3181
|
+
if (!branch) {
|
|
3182
|
+
ctx.output.exitCode = 4;
|
|
3183
|
+
ctx.output.reason = "memorize: no branch on ctx.data.branch";
|
|
3184
|
+
return;
|
|
3185
|
+
}
|
|
3186
|
+
const datestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3187
|
+
const titleBase = `kody memorize: vault update ${datestamp}`;
|
|
3188
|
+
const title = titleBase.length <= TITLE_MAX2 ? titleBase : `${titleBase.slice(0, TITLE_MAX2 - 1)}\u2026`;
|
|
3189
|
+
const body = buildBody(ctx, branch, datestamp);
|
|
3190
|
+
const existing = findExistingPr(branch, ctx.cwd);
|
|
3191
|
+
if (existing) {
|
|
3192
|
+
try {
|
|
3193
|
+
gh2(["pr", "edit", String(existing.number), "--body-file", "-"], { input: body, cwd: ctx.cwd });
|
|
3194
|
+
ctx.output.prUrl = existing.url;
|
|
3195
|
+
ctx.data.prResult = { url: existing.url, number: existing.number, action: "updated" };
|
|
3196
|
+
process.stdout.write(`[kody memorize] updated PR ${existing.url}
|
|
3197
|
+
`);
|
|
3198
|
+
} catch (err) {
|
|
3199
|
+
ctx.output.exitCode = 4;
|
|
3200
|
+
ctx.output.reason = `gh pr edit #${existing.number} failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
3201
|
+
}
|
|
3202
|
+
return;
|
|
3203
|
+
}
|
|
3204
|
+
try {
|
|
3205
|
+
const output = gh2(
|
|
3206
|
+
[
|
|
3207
|
+
"pr",
|
|
3208
|
+
"create",
|
|
3209
|
+
"--head",
|
|
3210
|
+
branch,
|
|
3211
|
+
"--base",
|
|
3212
|
+
ctx.config.git.defaultBranch,
|
|
3213
|
+
"--title",
|
|
3214
|
+
title,
|
|
3215
|
+
"--body-file",
|
|
3216
|
+
"-"
|
|
3217
|
+
],
|
|
3218
|
+
{ input: body, cwd: ctx.cwd }
|
|
3219
|
+
);
|
|
3220
|
+
const url = output.trim();
|
|
3221
|
+
const match = url.match(/\/pull\/(\d+)$/);
|
|
3222
|
+
const number = match ? parseInt(match[1], 10) : 0;
|
|
3223
|
+
ctx.output.prUrl = url;
|
|
3224
|
+
ctx.data.prResult = { url, number, action: "created" };
|
|
3225
|
+
process.stdout.write(`[kody memorize] opened PR ${url}
|
|
3226
|
+
`);
|
|
3227
|
+
} catch (err) {
|
|
3228
|
+
ctx.output.exitCode = 4;
|
|
3229
|
+
ctx.output.reason = `PR creation failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
3230
|
+
}
|
|
3231
|
+
};
|
|
3232
|
+
function buildBody(ctx, branch, datestamp) {
|
|
3233
|
+
const lines = [];
|
|
3234
|
+
lines.push("## Summary");
|
|
3235
|
+
lines.push("");
|
|
3236
|
+
const summary = ctx.data.prSummary?.trim();
|
|
3237
|
+
if (summary) {
|
|
3238
|
+
lines.push(summary);
|
|
3239
|
+
} else {
|
|
3240
|
+
lines.push(`Vault knowledge base update for ${datestamp}.`);
|
|
3241
|
+
}
|
|
3242
|
+
lines.push("");
|
|
3243
|
+
const changedFiles = ctx.data.changedFiles ?? [];
|
|
3244
|
+
if (changedFiles.length > 0) {
|
|
3245
|
+
lines.push("## Changes");
|
|
3246
|
+
lines.push("");
|
|
3247
|
+
for (const f of changedFiles.slice(0, 50)) lines.push(`- \`${f}\``);
|
|
3248
|
+
if (changedFiles.length > 50) lines.push(`- \u2026 and ${changedFiles.length - 50} more`);
|
|
3249
|
+
lines.push("");
|
|
3250
|
+
}
|
|
3251
|
+
const recentCount = ctx.data.recentPrCount;
|
|
3252
|
+
if (typeof recentCount === "number") {
|
|
3253
|
+
lines.push(`Synthesized from ${recentCount} merged PR(s) since ${ctx.data.vaultSinceIso ?? "(unknown)"}.`);
|
|
3254
|
+
lines.push("");
|
|
3255
|
+
}
|
|
3256
|
+
lines.push("---");
|
|
3257
|
+
lines.push(`_Opened by kody memorize on branch \`${branch}\`._`);
|
|
3258
|
+
return lines.join("\n");
|
|
3259
|
+
}
|
|
3260
|
+
|
|
3164
3261
|
// src/scripts/ensurePr.ts
|
|
3165
3262
|
var ensurePr2 = async (ctx) => {
|
|
3166
3263
|
if (ctx.skipAgent && ctx.output.exitCode !== void 0) {
|
|
@@ -4367,8 +4464,333 @@ var loadTaskState = async (ctx) => {
|
|
|
4367
4464
|
ctx.data.taskState = readTaskState(target, number, ctx.cwd);
|
|
4368
4465
|
};
|
|
4369
4466
|
|
|
4370
|
-
// src/scripts/
|
|
4467
|
+
// src/scripts/loadVaultContext.ts
|
|
4468
|
+
import * as fs21 from "fs";
|
|
4469
|
+
import * as path19 from "path";
|
|
4470
|
+
var VAULT_DIR_RELATIVE = ".kody/vault";
|
|
4471
|
+
var MAX_PAGES = 8;
|
|
4472
|
+
var PER_PAGE_MAX_BYTES = 4e3;
|
|
4473
|
+
var TOTAL_MAX_BYTES2 = 24e3;
|
|
4474
|
+
var TRUNCATED_SUFFIX2 = "\n\n\u2026 (truncated)";
|
|
4475
|
+
var loadVaultContext = async (ctx) => {
|
|
4476
|
+
const vaultAbs = path19.join(ctx.cwd, VAULT_DIR_RELATIVE);
|
|
4477
|
+
if (!fs21.existsSync(vaultAbs)) {
|
|
4478
|
+
ctx.data.vaultContext = "";
|
|
4479
|
+
return;
|
|
4480
|
+
}
|
|
4481
|
+
let pages = [];
|
|
4482
|
+
try {
|
|
4483
|
+
pages = collectPages(vaultAbs);
|
|
4484
|
+
} catch {
|
|
4485
|
+
ctx.data.vaultContext = "";
|
|
4486
|
+
return;
|
|
4487
|
+
}
|
|
4488
|
+
if (pages.length === 0) {
|
|
4489
|
+
ctx.data.vaultContext = "";
|
|
4490
|
+
return;
|
|
4491
|
+
}
|
|
4492
|
+
const queryTerms = extractQueryTerms(ctx);
|
|
4493
|
+
const ranked = queryTerms.length > 0 ? scorePages(pages, queryTerms) : sortByRecency(pages);
|
|
4494
|
+
const top = ranked.slice(0, MAX_PAGES);
|
|
4495
|
+
ctx.data.vaultContext = formatBlock(top);
|
|
4496
|
+
};
|
|
4497
|
+
function collectPages(vaultAbs) {
|
|
4498
|
+
const out = [];
|
|
4499
|
+
walkMd(vaultAbs, (file) => {
|
|
4500
|
+
let stat;
|
|
4501
|
+
try {
|
|
4502
|
+
stat = fs21.statSync(file);
|
|
4503
|
+
} catch {
|
|
4504
|
+
return;
|
|
4505
|
+
}
|
|
4506
|
+
let raw;
|
|
4507
|
+
try {
|
|
4508
|
+
raw = fs21.readFileSync(file, "utf-8");
|
|
4509
|
+
} catch {
|
|
4510
|
+
return;
|
|
4511
|
+
}
|
|
4512
|
+
const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
4513
|
+
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path19.basename(file, ".md");
|
|
4514
|
+
const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
|
|
4515
|
+
out.push({
|
|
4516
|
+
relPath: path19.relative(vaultAbs, file),
|
|
4517
|
+
title,
|
|
4518
|
+
updated,
|
|
4519
|
+
content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX2 : raw,
|
|
4520
|
+
mtime: stat.mtimeMs
|
|
4521
|
+
});
|
|
4522
|
+
});
|
|
4523
|
+
return out;
|
|
4524
|
+
}
|
|
4525
|
+
function extractQueryTerms(ctx) {
|
|
4526
|
+
const terms = [];
|
|
4527
|
+
const issue = ctx.data.issue;
|
|
4528
|
+
const pr = ctx.data.pr;
|
|
4529
|
+
if (issue?.title) terms.push(...tokenize(issue.title));
|
|
4530
|
+
if (pr?.title) terms.push(...tokenize(pr.title));
|
|
4531
|
+
return Array.from(new Set(terms)).slice(0, 20);
|
|
4532
|
+
}
|
|
4533
|
+
function tokenize(s) {
|
|
4534
|
+
return s.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((t) => t.length >= 3);
|
|
4535
|
+
}
|
|
4536
|
+
function scorePages(pages, terms) {
|
|
4537
|
+
return pages.map((p) => {
|
|
4538
|
+
const haystack = `${p.title} ${p.content}`.toLowerCase();
|
|
4539
|
+
let score = 0;
|
|
4540
|
+
for (const t of terms) {
|
|
4541
|
+
if (haystack.includes(t)) score++;
|
|
4542
|
+
}
|
|
4543
|
+
return { p, score };
|
|
4544
|
+
}).sort((a, b) => {
|
|
4545
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
4546
|
+
return b.p.mtime - a.p.mtime;
|
|
4547
|
+
}).map((x) => x.p);
|
|
4548
|
+
}
|
|
4549
|
+
function sortByRecency(pages) {
|
|
4550
|
+
return [...pages].sort((a, b) => {
|
|
4551
|
+
if (a.updated && b.updated && a.updated !== b.updated) {
|
|
4552
|
+
return b.updated.localeCompare(a.updated);
|
|
4553
|
+
}
|
|
4554
|
+
return b.mtime - a.mtime;
|
|
4555
|
+
});
|
|
4556
|
+
}
|
|
4557
|
+
function formatBlock(pages) {
|
|
4558
|
+
if (pages.length === 0) return "";
|
|
4559
|
+
const lines = ["# Project memory (`.kody/vault/`)", "", "Pages from prior memorize ticks. Treat as advisory context \u2014 confirm against the codebase before acting.", ""];
|
|
4560
|
+
let total = 0;
|
|
4561
|
+
for (const p of pages) {
|
|
4562
|
+
const block = [`## ${p.title} \u2014 \`${p.relPath}\``, "", p.content].join("\n");
|
|
4563
|
+
if (total + block.length > TOTAL_MAX_BYTES2) {
|
|
4564
|
+
lines.push("_\u2026 (further pages truncated to fit budget)_");
|
|
4565
|
+
break;
|
|
4566
|
+
}
|
|
4567
|
+
lines.push(block);
|
|
4568
|
+
lines.push("");
|
|
4569
|
+
total += block.length;
|
|
4570
|
+
}
|
|
4571
|
+
return lines.join("\n");
|
|
4572
|
+
}
|
|
4573
|
+
function walkMd(root, visit) {
|
|
4574
|
+
let stack = [root];
|
|
4575
|
+
while (stack.length > 0) {
|
|
4576
|
+
const dir = stack.pop();
|
|
4577
|
+
let names;
|
|
4578
|
+
try {
|
|
4579
|
+
names = fs21.readdirSync(dir);
|
|
4580
|
+
} catch {
|
|
4581
|
+
continue;
|
|
4582
|
+
}
|
|
4583
|
+
for (const name of names) {
|
|
4584
|
+
if (name.startsWith(".")) continue;
|
|
4585
|
+
const full = path19.join(dir, name);
|
|
4586
|
+
let stat;
|
|
4587
|
+
try {
|
|
4588
|
+
stat = fs21.statSync(full);
|
|
4589
|
+
} catch {
|
|
4590
|
+
continue;
|
|
4591
|
+
}
|
|
4592
|
+
if (stat.isDirectory()) {
|
|
4593
|
+
stack.push(full);
|
|
4594
|
+
continue;
|
|
4595
|
+
}
|
|
4596
|
+
if (stat.isFile() && full.endsWith(".md")) visit(full);
|
|
4597
|
+
}
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
4600
|
+
|
|
4601
|
+
// src/scripts/memorizeFlow.ts
|
|
4371
4602
|
import { execFileSync as execFileSync14 } from "child_process";
|
|
4603
|
+
import * as fs22 from "fs";
|
|
4604
|
+
import * as path20 from "path";
|
|
4605
|
+
var VAULT_DIR_RELATIVE2 = ".kody/vault";
|
|
4606
|
+
var DEFAULT_LOOKBACK_HOURS = 36;
|
|
4607
|
+
var MAX_RECENT_PRS = 25;
|
|
4608
|
+
var MAX_VAULT_INDEX_ENTRIES = 200;
|
|
4609
|
+
var PR_BODY_TRUNC = 2e3;
|
|
4610
|
+
var memorizeFlow = async (ctx) => {
|
|
4611
|
+
const vaultAbs = path20.join(ctx.cwd, VAULT_DIR_RELATIVE2);
|
|
4612
|
+
ensureBranch(ctx, vaultAbs);
|
|
4613
|
+
if (ctx.skipAgent) return;
|
|
4614
|
+
const sinceIso = computeSinceIso(vaultAbs);
|
|
4615
|
+
ctx.data.vaultSinceIso = sinceIso;
|
|
4616
|
+
ctx.data.vaultUpdatedIso = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
4617
|
+
ctx.data.vaultDir = VAULT_DIR_RELATIVE2;
|
|
4618
|
+
const recent = fetchRecentPrs(ctx.cwd, sinceIso);
|
|
4619
|
+
ctx.data.recentPrs = formatRecentPrs(recent);
|
|
4620
|
+
ctx.data.recentPrCount = recent.length;
|
|
4621
|
+
if (!fs22.existsSync(vaultAbs)) {
|
|
4622
|
+
fs22.mkdirSync(vaultAbs, { recursive: true });
|
|
4623
|
+
}
|
|
4624
|
+
ctx.data.vaultIndex = formatVaultIndex(vaultAbs);
|
|
4625
|
+
if (recent.length === 0) {
|
|
4626
|
+
process.stdout.write(`[kody memorize] no merged PRs since ${sinceIso}; agent may emit no changes
|
|
4627
|
+
`);
|
|
4628
|
+
} else {
|
|
4629
|
+
process.stdout.write(`[kody memorize] ${recent.length} merged PR(s) since ${sinceIso} \u2192 branch ${ctx.data.branch}
|
|
4630
|
+
`);
|
|
4631
|
+
}
|
|
4632
|
+
};
|
|
4633
|
+
function ensureBranch(ctx, vaultAbs) {
|
|
4634
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replace(/-/g, "");
|
|
4635
|
+
const branch = `kody/memorize-${stamp}`;
|
|
4636
|
+
const defaultBranch = ctx.config.git.defaultBranch;
|
|
4637
|
+
try {
|
|
4638
|
+
git3(["fetch", "origin", defaultBranch], ctx.cwd);
|
|
4639
|
+
} catch {
|
|
4640
|
+
}
|
|
4641
|
+
try {
|
|
4642
|
+
git3(["rev-parse", "--verify", `origin/${branch}`], ctx.cwd);
|
|
4643
|
+
git3(["checkout", "-B", branch, `origin/${branch}`], ctx.cwd);
|
|
4644
|
+
} catch {
|
|
4645
|
+
try {
|
|
4646
|
+
git3(["checkout", "-B", branch, `origin/${defaultBranch}`], ctx.cwd);
|
|
4647
|
+
} catch {
|
|
4648
|
+
git3(["checkout", "-B", branch], ctx.cwd);
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4651
|
+
ctx.data.branch = branch;
|
|
4652
|
+
if (!fs22.existsSync(vaultAbs)) {
|
|
4653
|
+
fs22.mkdirSync(vaultAbs, { recursive: true });
|
|
4654
|
+
}
|
|
4655
|
+
}
|
|
4656
|
+
function computeSinceIso(vaultAbs) {
|
|
4657
|
+
const fallback = new Date(Date.now() - DEFAULT_LOOKBACK_HOURS * 60 * 60 * 1e3).toISOString();
|
|
4658
|
+
if (!fs22.existsSync(vaultAbs)) return fallback;
|
|
4659
|
+
let latest = "";
|
|
4660
|
+
walkMd2(vaultAbs, (file) => {
|
|
4661
|
+
let raw;
|
|
4662
|
+
try {
|
|
4663
|
+
raw = fs22.readFileSync(file, "utf-8");
|
|
4664
|
+
} catch {
|
|
4665
|
+
return;
|
|
4666
|
+
}
|
|
4667
|
+
const m = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
4668
|
+
if (!m) return;
|
|
4669
|
+
const updated = m[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m);
|
|
4670
|
+
if (!updated) return;
|
|
4671
|
+
const ts = new Date(updated[1].trim()).toISOString();
|
|
4672
|
+
if (ts > latest) latest = ts;
|
|
4673
|
+
});
|
|
4674
|
+
return latest || fallback;
|
|
4675
|
+
}
|
|
4676
|
+
function fetchRecentPrs(cwd, sinceIso) {
|
|
4677
|
+
let raw;
|
|
4678
|
+
try {
|
|
4679
|
+
raw = gh2(
|
|
4680
|
+
[
|
|
4681
|
+
"pr",
|
|
4682
|
+
"list",
|
|
4683
|
+
"--state",
|
|
4684
|
+
"merged",
|
|
4685
|
+
"--limit",
|
|
4686
|
+
String(MAX_RECENT_PRS * 2),
|
|
4687
|
+
"--json",
|
|
4688
|
+
"number,title,url,mergedAt,body"
|
|
4689
|
+
],
|
|
4690
|
+
{ cwd }
|
|
4691
|
+
);
|
|
4692
|
+
} catch {
|
|
4693
|
+
return [];
|
|
4694
|
+
}
|
|
4695
|
+
let arr;
|
|
4696
|
+
try {
|
|
4697
|
+
arr = JSON.parse(raw);
|
|
4698
|
+
} catch {
|
|
4699
|
+
return [];
|
|
4700
|
+
}
|
|
4701
|
+
if (!Array.isArray(arr)) return [];
|
|
4702
|
+
const since = Date.parse(sinceIso);
|
|
4703
|
+
const filtered = [];
|
|
4704
|
+
for (const p of arr) {
|
|
4705
|
+
if (typeof p.number !== "number") continue;
|
|
4706
|
+
const merged = p.mergedAt ? Date.parse(p.mergedAt) : NaN;
|
|
4707
|
+
if (!Number.isFinite(merged) || merged <= since) continue;
|
|
4708
|
+
filtered.push({
|
|
4709
|
+
number: p.number,
|
|
4710
|
+
title: p.title ?? "(no title)",
|
|
4711
|
+
url: p.url ?? "",
|
|
4712
|
+
mergedAt: p.mergedAt ?? "",
|
|
4713
|
+
body: (p.body ?? "").slice(0, PR_BODY_TRUNC)
|
|
4714
|
+
});
|
|
4715
|
+
if (filtered.length >= MAX_RECENT_PRS) break;
|
|
4716
|
+
}
|
|
4717
|
+
return filtered;
|
|
4718
|
+
}
|
|
4719
|
+
function formatRecentPrs(prs) {
|
|
4720
|
+
if (prs.length === 0) return "_(no merged PRs since the last memorize tick)_";
|
|
4721
|
+
const lines = [];
|
|
4722
|
+
for (const p of prs) {
|
|
4723
|
+
lines.push(`### PR #${p.number} \u2014 ${p.title}`);
|
|
4724
|
+
if (p.url) lines.push(p.url);
|
|
4725
|
+
lines.push(`Merged: ${p.mergedAt}`);
|
|
4726
|
+
lines.push("");
|
|
4727
|
+
if (p.body.trim()) {
|
|
4728
|
+
lines.push(p.body.trim());
|
|
4729
|
+
} else {
|
|
4730
|
+
lines.push("_(no PR body)_");
|
|
4731
|
+
}
|
|
4732
|
+
lines.push("");
|
|
4733
|
+
}
|
|
4734
|
+
return lines.join("\n");
|
|
4735
|
+
}
|
|
4736
|
+
function formatVaultIndex(vaultAbs) {
|
|
4737
|
+
const entries = [];
|
|
4738
|
+
walkMd2(vaultAbs, (file) => {
|
|
4739
|
+
if (entries.length >= MAX_VAULT_INDEX_ENTRIES) return;
|
|
4740
|
+
const rel = path20.relative(vaultAbs, file);
|
|
4741
|
+
let title = rel;
|
|
4742
|
+
try {
|
|
4743
|
+
const raw = fs22.readFileSync(file, "utf-8");
|
|
4744
|
+
const m = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
4745
|
+
const titleMatch = m?.[1]?.match(/^title:\s*(.+)$/m);
|
|
4746
|
+
if (titleMatch) title = `${titleMatch[1].trim()} (${rel})`;
|
|
4747
|
+
} catch {
|
|
4748
|
+
}
|
|
4749
|
+
entries.push(`- ${title}`);
|
|
4750
|
+
});
|
|
4751
|
+
if (entries.length === 0) return "_(vault is empty)_";
|
|
4752
|
+
return entries.join("\n");
|
|
4753
|
+
}
|
|
4754
|
+
function walkMd2(root, visit) {
|
|
4755
|
+
if (!fs22.existsSync(root)) return;
|
|
4756
|
+
let stack = [root];
|
|
4757
|
+
while (stack.length > 0) {
|
|
4758
|
+
const dir = stack.pop();
|
|
4759
|
+
let names;
|
|
4760
|
+
try {
|
|
4761
|
+
names = fs22.readdirSync(dir);
|
|
4762
|
+
} catch {
|
|
4763
|
+
continue;
|
|
4764
|
+
}
|
|
4765
|
+
for (const name of names) {
|
|
4766
|
+
if (name.startsWith(".")) continue;
|
|
4767
|
+
const full = path20.join(dir, name);
|
|
4768
|
+
let stat;
|
|
4769
|
+
try {
|
|
4770
|
+
stat = fs22.statSync(full);
|
|
4771
|
+
} catch {
|
|
4772
|
+
continue;
|
|
4773
|
+
}
|
|
4774
|
+
if (stat.isDirectory()) {
|
|
4775
|
+
stack.push(full);
|
|
4776
|
+
continue;
|
|
4777
|
+
}
|
|
4778
|
+
if (stat.isFile() && full.endsWith(".md")) visit(full);
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4781
|
+
}
|
|
4782
|
+
function git3(args, cwd) {
|
|
4783
|
+
return execFileSync14("git", args, {
|
|
4784
|
+
encoding: "utf-8",
|
|
4785
|
+
timeout: 3e4,
|
|
4786
|
+
cwd,
|
|
4787
|
+
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
4788
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4789
|
+
}).trim();
|
|
4790
|
+
}
|
|
4791
|
+
|
|
4792
|
+
// src/scripts/mergeReleasePr.ts
|
|
4793
|
+
import { execFileSync as execFileSync15 } from "child_process";
|
|
4372
4794
|
var API_TIMEOUT_MS6 = 6e4;
|
|
4373
4795
|
var mergeReleasePr = async (ctx) => {
|
|
4374
4796
|
const state = ctx.data.taskState;
|
|
@@ -4385,7 +4807,7 @@ var mergeReleasePr = async (ctx) => {
|
|
|
4385
4807
|
return;
|
|
4386
4808
|
}
|
|
4387
4809
|
try {
|
|
4388
|
-
|
|
4810
|
+
execFileSync15("gh", ["pr", "merge", String(prNumber), "--merge"], {
|
|
4389
4811
|
timeout: API_TIMEOUT_MS6,
|
|
4390
4812
|
cwd: ctx.cwd,
|
|
4391
4813
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -4650,7 +5072,7 @@ var persistFlowState = async (ctx) => {
|
|
|
4650
5072
|
};
|
|
4651
5073
|
|
|
4652
5074
|
// src/scripts/postClassification.ts
|
|
4653
|
-
import { execFileSync as
|
|
5075
|
+
import { execFileSync as execFileSync16 } from "child_process";
|
|
4654
5076
|
var API_TIMEOUT_MS7 = 3e4;
|
|
4655
5077
|
var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
4656
5078
|
var postClassification = async (ctx) => {
|
|
@@ -4680,7 +5102,7 @@ var postClassification = async (ctx) => {
|
|
|
4680
5102
|
}
|
|
4681
5103
|
tryAuditComment(issueNumber, `\u{1F50E} kody classified as \`${classification}\`${reason ? ` \u2014 ${reason}` : ""}`, ctx.cwd);
|
|
4682
5104
|
try {
|
|
4683
|
-
|
|
5105
|
+
execFileSync16("gh", ["issue", "comment", String(issueNumber), "--body", `@kody ${classification}`], {
|
|
4684
5106
|
cwd: ctx.cwd,
|
|
4685
5107
|
timeout: API_TIMEOUT_MS7,
|
|
4686
5108
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -4714,7 +5136,7 @@ function parseClassification(prSummary) {
|
|
|
4714
5136
|
}
|
|
4715
5137
|
function tryAuditComment(issueNumber, body, cwd) {
|
|
4716
5138
|
try {
|
|
4717
|
-
|
|
5139
|
+
execFileSync16("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
4718
5140
|
cwd,
|
|
4719
5141
|
timeout: API_TIMEOUT_MS7,
|
|
4720
5142
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -5010,7 +5432,7 @@ var resolveArtifacts = async (ctx, profile) => {
|
|
|
5010
5432
|
};
|
|
5011
5433
|
|
|
5012
5434
|
// src/scripts/resolveFlow.ts
|
|
5013
|
-
import { execFileSync as
|
|
5435
|
+
import { execFileSync as execFileSync17 } from "child_process";
|
|
5014
5436
|
var CONFLICT_DIFF_MAX_BYTES = 4e4;
|
|
5015
5437
|
var resolveFlow = async (ctx) => {
|
|
5016
5438
|
const prNumber = ctx.args.pr;
|
|
@@ -5080,7 +5502,7 @@ function buildPreferBlock(prefer, baseBranch) {
|
|
|
5080
5502
|
}
|
|
5081
5503
|
function getConflictedFiles(cwd) {
|
|
5082
5504
|
try {
|
|
5083
|
-
const out =
|
|
5505
|
+
const out = execFileSync17("git", ["diff", "--name-only", "--diff-filter=U"], {
|
|
5084
5506
|
encoding: "utf-8",
|
|
5085
5507
|
cwd,
|
|
5086
5508
|
env: { ...process.env, HUSKY: "0" }
|
|
@@ -5095,7 +5517,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
|
|
|
5095
5517
|
let total = 0;
|
|
5096
5518
|
for (const f of files) {
|
|
5097
5519
|
try {
|
|
5098
|
-
const content =
|
|
5520
|
+
const content = execFileSync17("cat", [f], { encoding: "utf-8", cwd }).toString();
|
|
5099
5521
|
const snippet = `### ${f}
|
|
5100
5522
|
|
|
5101
5523
|
\`\`\`
|
|
@@ -5269,11 +5691,11 @@ var skipAgent = async (ctx) => {
|
|
|
5269
5691
|
};
|
|
5270
5692
|
|
|
5271
5693
|
// src/scripts/stageMergeConflicts.ts
|
|
5272
|
-
import { execFileSync as
|
|
5694
|
+
import { execFileSync as execFileSync18 } from "child_process";
|
|
5273
5695
|
var stageMergeConflicts = async (ctx) => {
|
|
5274
5696
|
if (ctx.data.agentDone === false) return;
|
|
5275
5697
|
try {
|
|
5276
|
-
|
|
5698
|
+
execFileSync18("git", ["add", "-A"], {
|
|
5277
5699
|
cwd: ctx.cwd,
|
|
5278
5700
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
5279
5701
|
stdio: "pipe"
|
|
@@ -5283,7 +5705,7 @@ var stageMergeConflicts = async (ctx) => {
|
|
|
5283
5705
|
};
|
|
5284
5706
|
|
|
5285
5707
|
// src/scripts/startFlow.ts
|
|
5286
|
-
import { execFileSync as
|
|
5708
|
+
import { execFileSync as execFileSync19 } from "child_process";
|
|
5287
5709
|
var API_TIMEOUT_MS8 = 3e4;
|
|
5288
5710
|
var startFlow = async (ctx, profile, _agentResult, args) => {
|
|
5289
5711
|
const entry = args?.entry;
|
|
@@ -5317,7 +5739,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
|
|
|
5317
5739
|
const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
|
|
5318
5740
|
const body = `@kody ${next}`;
|
|
5319
5741
|
try {
|
|
5320
|
-
|
|
5742
|
+
execFileSync19("gh", [sub, "comment", String(targetNumber), "--body", body], {
|
|
5321
5743
|
timeout: API_TIMEOUT_MS8,
|
|
5322
5744
|
cwd,
|
|
5323
5745
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -5331,7 +5753,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
|
|
|
5331
5753
|
}
|
|
5332
5754
|
|
|
5333
5755
|
// src/scripts/syncFlow.ts
|
|
5334
|
-
import { execFileSync as
|
|
5756
|
+
import { execFileSync as execFileSync20 } from "child_process";
|
|
5335
5757
|
var syncFlow = async (ctx, _profile, args) => {
|
|
5336
5758
|
const announceOnSuccess = Boolean(args?.announceOnSuccess);
|
|
5337
5759
|
const prNumber = ctx.args.pr;
|
|
@@ -5403,7 +5825,7 @@ function bail2(ctx, prNumber, reason) {
|
|
|
5403
5825
|
}
|
|
5404
5826
|
function revParseHead(cwd) {
|
|
5405
5827
|
try {
|
|
5406
|
-
return
|
|
5828
|
+
return execFileSync20("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
|
|
5407
5829
|
} catch {
|
|
5408
5830
|
return "";
|
|
5409
5831
|
}
|
|
@@ -5411,9 +5833,9 @@ function revParseHead(cwd) {
|
|
|
5411
5833
|
function pushBranch(branch, cwd) {
|
|
5412
5834
|
const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
|
|
5413
5835
|
try {
|
|
5414
|
-
|
|
5836
|
+
execFileSync20("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
|
|
5415
5837
|
} catch {
|
|
5416
|
-
|
|
5838
|
+
execFileSync20("git", ["push", "--force-with-lease", "-u", "origin", branch], {
|
|
5417
5839
|
cwd,
|
|
5418
5840
|
env,
|
|
5419
5841
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -5524,7 +5946,7 @@ var verify = async (ctx) => {
|
|
|
5524
5946
|
};
|
|
5525
5947
|
|
|
5526
5948
|
// src/scripts/waitForCi.ts
|
|
5527
|
-
import { execFileSync as
|
|
5949
|
+
import { execFileSync as execFileSync21 } from "child_process";
|
|
5528
5950
|
var API_TIMEOUT_MS9 = 3e4;
|
|
5529
5951
|
var waitForCi = async (ctx, _profile, _agentResult, args) => {
|
|
5530
5952
|
const timeoutMinutes = numArg(args, "timeoutMinutes", 30);
|
|
@@ -5602,7 +6024,7 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
|
|
|
5602
6024
|
};
|
|
5603
6025
|
function fetchChecks(prNumber, cwd) {
|
|
5604
6026
|
try {
|
|
5605
|
-
const raw =
|
|
6027
|
+
const raw = execFileSync21("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
|
|
5606
6028
|
encoding: "utf-8",
|
|
5607
6029
|
timeout: API_TIMEOUT_MS9,
|
|
5608
6030
|
cwd,
|
|
@@ -5777,7 +6199,7 @@ var writeMissionStateFile = async (ctx, _profile, _agentResult) => {
|
|
|
5777
6199
|
};
|
|
5778
6200
|
|
|
5779
6201
|
// src/scripts/writeRunSummary.ts
|
|
5780
|
-
import * as
|
|
6202
|
+
import * as fs23 from "fs";
|
|
5781
6203
|
var writeRunSummary = async (ctx, profile) => {
|
|
5782
6204
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
5783
6205
|
if (!summaryPath) return;
|
|
@@ -5799,7 +6221,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
5799
6221
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
5800
6222
|
lines.push("");
|
|
5801
6223
|
try {
|
|
5802
|
-
|
|
6224
|
+
fs23.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
5803
6225
|
`);
|
|
5804
6226
|
} catch {
|
|
5805
6227
|
}
|
|
@@ -5815,7 +6237,9 @@ var preflightScripts = {
|
|
|
5815
6237
|
syncFlow,
|
|
5816
6238
|
initFlow,
|
|
5817
6239
|
watchStalePrsFlow,
|
|
6240
|
+
memorizeFlow,
|
|
5818
6241
|
loadTaskState,
|
|
6242
|
+
loadVaultContext,
|
|
5819
6243
|
loadIssueContext,
|
|
5820
6244
|
loadIssueStateComment,
|
|
5821
6245
|
loadMissionFromFile,
|
|
@@ -5850,6 +6274,7 @@ var postflightScripts = {
|
|
|
5850
6274
|
stageMergeConflicts,
|
|
5851
6275
|
commitAndPush: commitAndPush2,
|
|
5852
6276
|
ensurePr: ensurePr2,
|
|
6277
|
+
ensureMemorizePr,
|
|
5853
6278
|
postIssueComment: postIssueComment2,
|
|
5854
6279
|
postPlanComment,
|
|
5855
6280
|
postResearchComment,
|
|
@@ -5875,7 +6300,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
|
|
|
5875
6300
|
]);
|
|
5876
6301
|
|
|
5877
6302
|
// src/tools.ts
|
|
5878
|
-
import { execFileSync as
|
|
6303
|
+
import { execFileSync as execFileSync22 } from "child_process";
|
|
5879
6304
|
function verifyCliTools(tools, cwd) {
|
|
5880
6305
|
const out = [];
|
|
5881
6306
|
for (const t of tools) out.push(verifyOne(t, cwd));
|
|
@@ -5908,7 +6333,7 @@ function verifyOne(tool, cwd) {
|
|
|
5908
6333
|
}
|
|
5909
6334
|
function runShell(cmd, cwd, timeoutMs = 3e4) {
|
|
5910
6335
|
try {
|
|
5911
|
-
|
|
6336
|
+
execFileSync22("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
|
|
5912
6337
|
return true;
|
|
5913
6338
|
} catch {
|
|
5914
6339
|
return false;
|
|
@@ -5976,9 +6401,9 @@ async function runExecutable(profileName, input) {
|
|
|
5976
6401
|
data: {},
|
|
5977
6402
|
output: { exitCode: 0 }
|
|
5978
6403
|
};
|
|
5979
|
-
const ndjsonDir =
|
|
6404
|
+
const ndjsonDir = path21.join(input.cwd, ".kody");
|
|
5980
6405
|
const invokeAgent = async (prompt) => {
|
|
5981
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
6406
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path21.isAbsolute(p) ? p : path21.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
5982
6407
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
5983
6408
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
5984
6409
|
return runAgent({
|
|
@@ -6056,17 +6481,17 @@ async function runExecutable(profileName, input) {
|
|
|
6056
6481
|
function resolveProfilePath(profileName) {
|
|
6057
6482
|
const found = resolveExecutable(profileName);
|
|
6058
6483
|
if (found) return found;
|
|
6059
|
-
const here =
|
|
6484
|
+
const here = path21.dirname(new URL(import.meta.url).pathname);
|
|
6060
6485
|
const candidates = [
|
|
6061
|
-
|
|
6486
|
+
path21.join(here, "executables", profileName, "profile.json"),
|
|
6062
6487
|
// same-dir sibling (dev)
|
|
6063
|
-
|
|
6488
|
+
path21.join(here, "..", "executables", profileName, "profile.json"),
|
|
6064
6489
|
// up one (prod: dist/bin → dist/executables)
|
|
6065
|
-
|
|
6490
|
+
path21.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
6066
6491
|
// fallback
|
|
6067
6492
|
];
|
|
6068
6493
|
for (const c of candidates) {
|
|
6069
|
-
if (
|
|
6494
|
+
if (fs24.existsSync(c)) return c;
|
|
6070
6495
|
}
|
|
6071
6496
|
return candidates[0];
|
|
6072
6497
|
}
|
|
@@ -6159,8 +6584,8 @@ function finish(out) {
|
|
|
6159
6584
|
var SHELL_TIMEOUT_MS = 3e5;
|
|
6160
6585
|
function runShellEntry(entry, ctx, profile) {
|
|
6161
6586
|
const shellName = entry.shell;
|
|
6162
|
-
const shellPath =
|
|
6163
|
-
if (!
|
|
6587
|
+
const shellPath = path21.join(profile.dir, shellName);
|
|
6588
|
+
if (!fs24.existsSync(shellPath)) {
|
|
6164
6589
|
ctx.skipAgent = true;
|
|
6165
6590
|
ctx.output.exitCode = 99;
|
|
6166
6591
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -6310,14 +6735,14 @@ function resolveAuthToken(env = process.env) {
|
|
|
6310
6735
|
return token;
|
|
6311
6736
|
}
|
|
6312
6737
|
function detectPackageManager2(cwd) {
|
|
6313
|
-
if (
|
|
6314
|
-
if (
|
|
6315
|
-
if (
|
|
6738
|
+
if (fs25.existsSync(path22.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
6739
|
+
if (fs25.existsSync(path22.join(cwd, "yarn.lock"))) return "yarn";
|
|
6740
|
+
if (fs25.existsSync(path22.join(cwd, "bun.lockb"))) return "bun";
|
|
6316
6741
|
return "npm";
|
|
6317
6742
|
}
|
|
6318
6743
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
6319
6744
|
try {
|
|
6320
|
-
|
|
6745
|
+
execFileSync23(cmd, args, {
|
|
6321
6746
|
cwd,
|
|
6322
6747
|
stdio: stream ? "inherit" : "pipe",
|
|
6323
6748
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
|
|
@@ -6330,7 +6755,7 @@ function shellOut(cmd, args, cwd, stream = true) {
|
|
|
6330
6755
|
}
|
|
6331
6756
|
function isOnPath(bin) {
|
|
6332
6757
|
try {
|
|
6333
|
-
|
|
6758
|
+
execFileSync23("which", [bin], { stdio: "pipe" });
|
|
6334
6759
|
return true;
|
|
6335
6760
|
} catch {
|
|
6336
6761
|
return false;
|
|
@@ -6364,7 +6789,7 @@ function installLitellmIfNeeded(cwd) {
|
|
|
6364
6789
|
} catch {
|
|
6365
6790
|
}
|
|
6366
6791
|
try {
|
|
6367
|
-
|
|
6792
|
+
execFileSync23("python3", ["-c", "import litellm"], { stdio: "pipe" });
|
|
6368
6793
|
process.stdout.write("\u2192 kody: litellm already installed\n");
|
|
6369
6794
|
return 0;
|
|
6370
6795
|
} catch {
|
|
@@ -6374,16 +6799,16 @@ function installLitellmIfNeeded(cwd) {
|
|
|
6374
6799
|
}
|
|
6375
6800
|
function configureGitIdentity(cwd) {
|
|
6376
6801
|
try {
|
|
6377
|
-
const name =
|
|
6802
|
+
const name = execFileSync23("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
|
|
6378
6803
|
if (name) return;
|
|
6379
6804
|
} catch {
|
|
6380
6805
|
}
|
|
6381
6806
|
try {
|
|
6382
|
-
|
|
6807
|
+
execFileSync23("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
|
|
6383
6808
|
} catch {
|
|
6384
6809
|
}
|
|
6385
6810
|
try {
|
|
6386
|
-
|
|
6811
|
+
execFileSync23("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
|
|
6387
6812
|
cwd,
|
|
6388
6813
|
stdio: "pipe"
|
|
6389
6814
|
});
|
|
@@ -6392,11 +6817,11 @@ function configureGitIdentity(cwd) {
|
|
|
6392
6817
|
}
|
|
6393
6818
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
6394
6819
|
if (!issueNumber) return;
|
|
6395
|
-
const logPath =
|
|
6820
|
+
const logPath = path22.join(cwd, ".kody", "last-run.jsonl");
|
|
6396
6821
|
let tail = "";
|
|
6397
6822
|
try {
|
|
6398
|
-
if (
|
|
6399
|
-
const content =
|
|
6823
|
+
if (fs25.existsSync(logPath)) {
|
|
6824
|
+
const content = fs25.readFileSync(logPath, "utf-8");
|
|
6400
6825
|
tail = content.slice(-3e3);
|
|
6401
6826
|
}
|
|
6402
6827
|
} catch {
|
|
@@ -6421,7 +6846,7 @@ async function runCi(argv) {
|
|
|
6421
6846
|
return 0;
|
|
6422
6847
|
}
|
|
6423
6848
|
const args = parseCiArgs(argv);
|
|
6424
|
-
const cwd = args.cwd ?
|
|
6849
|
+
const cwd = args.cwd ? path22.resolve(args.cwd) : process.cwd();
|
|
6425
6850
|
let earlyConfig;
|
|
6426
6851
|
try {
|
|
6427
6852
|
earlyConfig = loadConfig(cwd);
|
|
@@ -6431,9 +6856,9 @@ async function runCi(argv) {
|
|
|
6431
6856
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
6432
6857
|
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
6433
6858
|
let manualWorkflowDispatch = false;
|
|
6434
|
-
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath &&
|
|
6859
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs25.existsSync(dispatchEventPath)) {
|
|
6435
6860
|
try {
|
|
6436
|
-
const evt = JSON.parse(
|
|
6861
|
+
const evt = JSON.parse(fs25.readFileSync(dispatchEventPath, "utf-8"));
|
|
6437
6862
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
6438
6863
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
6439
6864
|
manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
|
|
@@ -6648,15 +7073,15 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
6648
7073
|
return result;
|
|
6649
7074
|
}
|
|
6650
7075
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
6651
|
-
const sessionFile =
|
|
6652
|
-
const eventsFile =
|
|
6653
|
-
const paths = [sessionFile, eventsFile].filter((p) =>
|
|
7076
|
+
const sessionFile = path23.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
7077
|
+
const eventsFile = path23.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
7078
|
+
const paths = [sessionFile, eventsFile].filter((p) => fs26.existsSync(path23.join(cwd, p)));
|
|
6654
7079
|
if (paths.length === 0) return;
|
|
6655
7080
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
6656
7081
|
try {
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
7082
|
+
execFileSync24("git", ["add", ...paths], opts);
|
|
7083
|
+
execFileSync24("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
|
|
7084
|
+
execFileSync24("git", ["push", "--quiet", "origin", "HEAD"], opts);
|
|
6660
7085
|
} catch (err) {
|
|
6661
7086
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6662
7087
|
process.stderr.write(`[kody:chat] commit/push skipped: ${msg}
|
|
@@ -6688,7 +7113,7 @@ async function runChat(argv) {
|
|
|
6688
7113
|
${CHAT_HELP}`);
|
|
6689
7114
|
return 64;
|
|
6690
7115
|
}
|
|
6691
|
-
const cwd = args.cwd ?
|
|
7116
|
+
const cwd = args.cwd ? path23.resolve(args.cwd) : process.cwd();
|
|
6692
7117
|
const sessionId = args.sessionId;
|
|
6693
7118
|
const unpackedSecrets = unpackAllSecrets();
|
|
6694
7119
|
if (unpackedSecrets > 0) {
|
|
@@ -21,6 +21,8 @@ You are Kody, an autonomous engineer. Apply the feedback below to the existing P
|
|
|
21
21
|
|
|
22
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
23
|
|
|
24
|
+
{{vaultContext}}
|
|
25
|
+
|
|
24
26
|
# Required steps
|
|
25
27
|
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.
|
|
26
28
|
2. **Number each item** internally (Item 1, Item 2, …). You will account for every one of them in your final message below.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "memorize",
|
|
3
|
+
"role": "watch",
|
|
4
|
+
"describe": "Scheduled: synthesize recently merged PRs into the project's .kody/vault/ markdown wiki and open a PR with the changes.",
|
|
5
|
+
"kind": "scheduled",
|
|
6
|
+
"schedule": "0 3 * * *",
|
|
7
|
+
"inputs": [],
|
|
8
|
+
"claudeCode": {
|
|
9
|
+
"model": "inherit",
|
|
10
|
+
"permissionMode": "acceptEdits",
|
|
11
|
+
"maxTurns": null,
|
|
12
|
+
"maxThinkingTokens": null,
|
|
13
|
+
"systemPromptAppend": null,
|
|
14
|
+
"tools": ["Read", "Write", "Edit", "Bash", "Grep", "Glob"],
|
|
15
|
+
"hooks": ["block-git"],
|
|
16
|
+
"skills": [],
|
|
17
|
+
"commands": [],
|
|
18
|
+
"subagents": [],
|
|
19
|
+
"plugins": [],
|
|
20
|
+
"mcpServers": []
|
|
21
|
+
},
|
|
22
|
+
"cliTools": [
|
|
23
|
+
{
|
|
24
|
+
"name": "gh",
|
|
25
|
+
"install": {
|
|
26
|
+
"required": true,
|
|
27
|
+
"checkCommand": "command -v gh"
|
|
28
|
+
},
|
|
29
|
+
"verify": "gh auth status",
|
|
30
|
+
"usage": "Use `gh` only for read-only inspection (`gh pr view`, `gh pr list`, `gh api`) when you need extra context on a referenced PR. Never use it to commit, push, or open PRs — the wrapper does that.",
|
|
31
|
+
"allowedUses": ["pr", "api", "issue"]
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"inputArtifacts": [],
|
|
35
|
+
"outputArtifacts": [],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"preflight": [
|
|
38
|
+
{ "script": "memorizeFlow" },
|
|
39
|
+
{ "script": "composePrompt" }
|
|
40
|
+
],
|
|
41
|
+
"postflight": [
|
|
42
|
+
{ "script": "parseAgentResult" },
|
|
43
|
+
{ "script": "abortUnfinishedGitOps" },
|
|
44
|
+
{ "script": "commitAndPush" },
|
|
45
|
+
{ "script": "ensureMemorizePr" }
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
You are **kody memorize**, the project's long-term memory keeper. You synthesize recently merged work into a markdown knowledge base at `.kody/vault/` so future kody runs can recall decisions, conventions, and component knowledge.
|
|
2
|
+
|
|
3
|
+
## What you have
|
|
4
|
+
|
|
5
|
+
### Recent merged PRs (since {{vaultSinceIso}})
|
|
6
|
+
|
|
7
|
+
{{recentPrs}}
|
|
8
|
+
|
|
9
|
+
### Existing vault index
|
|
10
|
+
|
|
11
|
+
Vault root: `.kody/vault/`
|
|
12
|
+
|
|
13
|
+
{{vaultIndex}}
|
|
14
|
+
|
|
15
|
+
## What to do
|
|
16
|
+
|
|
17
|
+
1. **Read each recent PR's title, body, and (if useful) diff via `gh pr view <n>` / `gh pr diff <n>`.**
|
|
18
|
+
2. **Map each PR to the concept pages it affects** — files like `architecture/<area>.md`, `conventions/<topic>.md`, `decisions/<slug>.md`, `components/<name>.md`, or whatever organization the existing vault uses. If the vault is empty, start with a small set of pages reflecting what you actually learned.
|
|
19
|
+
3. **Update or create those pages.** Each page is a concept (e.g. "executor", "release flow"), NOT a per-PR log. A PR contributes one or more updates — small additions, edits to keep current, links back to the PR URL.
|
|
20
|
+
4. **Cross-link** related pages with relative markdown links so the vault forms a connected graph.
|
|
21
|
+
5. **Be terse.** Each page is reference material, not a story. One short paragraph per fact, bullet lists where useful, links instead of recapping.
|
|
22
|
+
6. **Don't duplicate the codebase.** Capture *what was decided* and *why*, not *how* the code looks — the code is authoritative for that.
|
|
23
|
+
7. **Don't invent.** If a PR's intent isn't clear, skip it rather than guessing.
|
|
24
|
+
|
|
25
|
+
## Page conventions
|
|
26
|
+
|
|
27
|
+
- Filename: `kebab-case.md`.
|
|
28
|
+
- Frontmatter (YAML) on every page:
|
|
29
|
+
```yaml
|
|
30
|
+
---
|
|
31
|
+
title: <Human Title>
|
|
32
|
+
type: architecture | convention | decision | component | runbook
|
|
33
|
+
updated: {{vaultUpdatedIso}}
|
|
34
|
+
sources:
|
|
35
|
+
- <PR URL or file path>
|
|
36
|
+
---
|
|
37
|
+
```
|
|
38
|
+
- Body: one short intro paragraph, then sections.
|
|
39
|
+
- Cross-references via relative links: `[executor](../architecture/executor.md)`.
|
|
40
|
+
|
|
41
|
+
## Rules
|
|
42
|
+
|
|
43
|
+
- Edit files only under `.kody/vault/`. Do not touch any other path.
|
|
44
|
+
- Do not commit or push. The wrapper does it.
|
|
45
|
+
- Do not run `git` or `gh` for anything except read-only inspection of referenced PRs.
|
|
46
|
+
- If there is nothing meaningful to add (PRs are trivial chores, all already captured, etc.), say so and emit `DONE` with `COMMIT_MSG: chore(vault): no updates`. The wrapper will detect no changes and skip the PR.
|
|
47
|
+
|
|
48
|
+
## Output contract (MANDATORY)
|
|
49
|
+
|
|
50
|
+
End your response with these lines, exactly:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
DONE
|
|
54
|
+
COMMIT_MSG: chore(vault): <one-line summary>
|
|
55
|
+
PR_SUMMARY:
|
|
56
|
+
<2–6 line summary of what changed in the vault and why>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
If you decide to abort with no changes, emit `DONE` with the no-updates `COMMIT_MSG` and a brief `PR_SUMMARY` saying so.
|
|
@@ -9,7 +9,7 @@ You are Kody, an autonomous engineer. A `git merge origin/{{baseBranch}}` into P
|
|
|
9
9
|
|
|
10
10
|
{{conflictedFiles}}
|
|
11
11
|
|
|
12
|
-
{{preferBlock}}{{conventionsBlock}}{{toolsUsage}}# Working-tree conflict markers (truncated)
|
|
12
|
+
{{preferBlock}}{{conventionsBlock}}{{vaultContext}}{{toolsUsage}}# Working-tree conflict markers (truncated)
|
|
13
13
|
|
|
14
14
|
{{conflictMarkersPreview}}
|
|
15
15
|
|
|
@@ -17,6 +17,8 @@ If the plan above is non-empty, TREAT IT AS AUTHORITATIVE — follow its file li
|
|
|
17
17
|
|
|
18
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
19
|
|
|
20
|
+
{{vaultContext}}
|
|
21
|
+
|
|
20
22
|
# Required steps (all in this one session — no handoff)
|
|
21
23
|
1. **Research** — read the issue carefully, then meet the research floor below before any Edit/Write. Use Grep/Glob/Read to investigate.
|
|
22
24
|
|
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.44",
|
|
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",
|
package/templates/kody.yml
CHANGED
|
@@ -47,6 +47,10 @@ on:
|
|
|
47
47
|
types: [created]
|
|
48
48
|
pull_request:
|
|
49
49
|
types: [closed]
|
|
50
|
+
schedule:
|
|
51
|
+
# Wakes every 5 minutes; kody fans out to whichever scheduled executables
|
|
52
|
+
# (mission-scheduler, memorize, watch-stale-prs, …) match this minute.
|
|
53
|
+
- cron: "*/5 * * * *"
|
|
50
54
|
|
|
51
55
|
jobs:
|
|
52
56
|
run:
|