@kody-ade/kody-engine 0.3.42 → 0.3.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/kody.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@kody-ade/kody-engine",
6
- version: "0.3.42",
6
+ version: "0.3.43",
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 execFileSync23 } from "child_process";
54
- import * as fs24 from "fs";
55
- import * as path21 from "path";
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 git3 = raw.git ?? {};
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 git3.defaultBranch === "string" ? git3.defaultBranch : "main"
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 execFileSync22 } from "child_process";
609
- import * as fs23 from "fs";
610
- import * as path20 from "path";
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 fs22 from "fs";
952
- import * as path19 from "path";
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";
@@ -3161,6 +3161,101 @@ function ensurePr(opts) {
3161
3161
  return { url, number, draft: opts.draft, action: "created" };
3162
3162
  }
3163
3163
 
3164
+ // src/scripts/ensureMemorizePr.ts
3165
+ var TITLE_MAX2 = 72;
3166
+ var ensureMemorizePr = async (ctx) => {
3167
+ if (ctx.skipAgent && ctx.output.exitCode !== void 0 && ctx.output.exitCode !== 0) {
3168
+ return;
3169
+ }
3170
+ const commitResult = ctx.data.commitResult;
3171
+ const hasCommits = Boolean(ctx.data.hasCommitsAhead);
3172
+ if (!commitResult?.committed && !hasCommits) {
3173
+ process.stdout.write("[kody memorize] no vault changes \u2014 skipping PR\n");
3174
+ ctx.output.exitCode = 0;
3175
+ ctx.output.reason = "no vault changes";
3176
+ return;
3177
+ }
3178
+ const branch = ctx.data.branch;
3179
+ if (!branch) {
3180
+ ctx.output.exitCode = 4;
3181
+ ctx.output.reason = "memorize: no branch on ctx.data.branch";
3182
+ return;
3183
+ }
3184
+ const datestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3185
+ const titleBase = `kody memorize: vault update ${datestamp}`;
3186
+ const title = titleBase.length <= TITLE_MAX2 ? titleBase : `${titleBase.slice(0, TITLE_MAX2 - 1)}\u2026`;
3187
+ const body = buildBody(ctx, branch, datestamp);
3188
+ const existing = findExistingPr(branch, ctx.cwd);
3189
+ if (existing) {
3190
+ try {
3191
+ gh2(["pr", "edit", String(existing.number), "--body-file", "-"], { input: body, cwd: ctx.cwd });
3192
+ ctx.output.prUrl = existing.url;
3193
+ ctx.data.prResult = { url: existing.url, number: existing.number, action: "updated" };
3194
+ process.stdout.write(`[kody memorize] updated PR ${existing.url}
3195
+ `);
3196
+ } catch (err) {
3197
+ ctx.output.exitCode = 4;
3198
+ ctx.output.reason = `gh pr edit #${existing.number} failed: ${err instanceof Error ? err.message : String(err)}`;
3199
+ }
3200
+ return;
3201
+ }
3202
+ try {
3203
+ const output = gh2(
3204
+ [
3205
+ "pr",
3206
+ "create",
3207
+ "--head",
3208
+ branch,
3209
+ "--base",
3210
+ ctx.config.git.defaultBranch,
3211
+ "--title",
3212
+ title,
3213
+ "--body-file",
3214
+ "-"
3215
+ ],
3216
+ { input: body, cwd: ctx.cwd }
3217
+ );
3218
+ const url = output.trim();
3219
+ const match = url.match(/\/pull\/(\d+)$/);
3220
+ const number = match ? parseInt(match[1], 10) : 0;
3221
+ ctx.output.prUrl = url;
3222
+ ctx.data.prResult = { url, number, action: "created" };
3223
+ process.stdout.write(`[kody memorize] opened PR ${url}
3224
+ `);
3225
+ } catch (err) {
3226
+ ctx.output.exitCode = 4;
3227
+ ctx.output.reason = `PR creation failed: ${err instanceof Error ? err.message : String(err)}`;
3228
+ }
3229
+ };
3230
+ function buildBody(ctx, branch, datestamp) {
3231
+ const lines = [];
3232
+ lines.push("## Summary");
3233
+ lines.push("");
3234
+ const summary = ctx.data.prSummary?.trim();
3235
+ if (summary) {
3236
+ lines.push(summary);
3237
+ } else {
3238
+ lines.push(`Vault knowledge base update for ${datestamp}.`);
3239
+ }
3240
+ lines.push("");
3241
+ const changedFiles = ctx.data.changedFiles ?? [];
3242
+ if (changedFiles.length > 0) {
3243
+ lines.push("## Changes");
3244
+ lines.push("");
3245
+ for (const f of changedFiles.slice(0, 50)) lines.push(`- \`${f}\``);
3246
+ if (changedFiles.length > 50) lines.push(`- \u2026 and ${changedFiles.length - 50} more`);
3247
+ lines.push("");
3248
+ }
3249
+ const recentCount = ctx.data.recentPrCount;
3250
+ if (typeof recentCount === "number") {
3251
+ lines.push(`Synthesized from ${recentCount} merged PR(s) since ${ctx.data.vaultSinceIso ?? "(unknown)"}.`);
3252
+ lines.push("");
3253
+ }
3254
+ lines.push("---");
3255
+ lines.push(`_Opened by kody memorize on branch \`${branch}\`._`);
3256
+ return lines.join("\n");
3257
+ }
3258
+
3164
3259
  // src/scripts/ensurePr.ts
3165
3260
  var ensurePr2 = async (ctx) => {
3166
3261
  if (ctx.skipAgent && ctx.output.exitCode !== void 0) {
@@ -4367,8 +4462,333 @@ var loadTaskState = async (ctx) => {
4367
4462
  ctx.data.taskState = readTaskState(target, number, ctx.cwd);
4368
4463
  };
4369
4464
 
4370
- // src/scripts/mergeReleasePr.ts
4465
+ // src/scripts/loadVaultContext.ts
4466
+ import * as fs21 from "fs";
4467
+ import * as path19 from "path";
4468
+ var VAULT_DIR_RELATIVE = ".kody/vault";
4469
+ var MAX_PAGES = 8;
4470
+ var PER_PAGE_MAX_BYTES = 4e3;
4471
+ var TOTAL_MAX_BYTES2 = 24e3;
4472
+ var TRUNCATED_SUFFIX2 = "\n\n\u2026 (truncated)";
4473
+ var loadVaultContext = async (ctx) => {
4474
+ const vaultAbs = path19.join(ctx.cwd, VAULT_DIR_RELATIVE);
4475
+ if (!fs21.existsSync(vaultAbs)) {
4476
+ ctx.data.vaultContext = "";
4477
+ return;
4478
+ }
4479
+ let pages = [];
4480
+ try {
4481
+ pages = collectPages(vaultAbs);
4482
+ } catch {
4483
+ ctx.data.vaultContext = "";
4484
+ return;
4485
+ }
4486
+ if (pages.length === 0) {
4487
+ ctx.data.vaultContext = "";
4488
+ return;
4489
+ }
4490
+ const queryTerms = extractQueryTerms(ctx);
4491
+ const ranked = queryTerms.length > 0 ? scorePages(pages, queryTerms) : sortByRecency(pages);
4492
+ const top = ranked.slice(0, MAX_PAGES);
4493
+ ctx.data.vaultContext = formatBlock(top);
4494
+ };
4495
+ function collectPages(vaultAbs) {
4496
+ const out = [];
4497
+ walkMd(vaultAbs, (file) => {
4498
+ let stat;
4499
+ try {
4500
+ stat = fs21.statSync(file);
4501
+ } catch {
4502
+ return;
4503
+ }
4504
+ let raw;
4505
+ try {
4506
+ raw = fs21.readFileSync(file, "utf-8");
4507
+ } catch {
4508
+ return;
4509
+ }
4510
+ const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
4511
+ const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path19.basename(file, ".md");
4512
+ const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
4513
+ out.push({
4514
+ relPath: path19.relative(vaultAbs, file),
4515
+ title,
4516
+ updated,
4517
+ content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX2 : raw,
4518
+ mtime: stat.mtimeMs
4519
+ });
4520
+ });
4521
+ return out;
4522
+ }
4523
+ function extractQueryTerms(ctx) {
4524
+ const terms = [];
4525
+ const issue = ctx.data.issue;
4526
+ const pr = ctx.data.pr;
4527
+ if (issue?.title) terms.push(...tokenize(issue.title));
4528
+ if (pr?.title) terms.push(...tokenize(pr.title));
4529
+ return Array.from(new Set(terms)).slice(0, 20);
4530
+ }
4531
+ function tokenize(s) {
4532
+ return s.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((t) => t.length >= 3);
4533
+ }
4534
+ function scorePages(pages, terms) {
4535
+ return pages.map((p) => {
4536
+ const haystack = `${p.title} ${p.content}`.toLowerCase();
4537
+ let score = 0;
4538
+ for (const t of terms) {
4539
+ if (haystack.includes(t)) score++;
4540
+ }
4541
+ return { p, score };
4542
+ }).sort((a, b) => {
4543
+ if (b.score !== a.score) return b.score - a.score;
4544
+ return b.p.mtime - a.p.mtime;
4545
+ }).map((x) => x.p);
4546
+ }
4547
+ function sortByRecency(pages) {
4548
+ return [...pages].sort((a, b) => {
4549
+ if (a.updated && b.updated && a.updated !== b.updated) {
4550
+ return b.updated.localeCompare(a.updated);
4551
+ }
4552
+ return b.mtime - a.mtime;
4553
+ });
4554
+ }
4555
+ function formatBlock(pages) {
4556
+ if (pages.length === 0) return "";
4557
+ const lines = ["# Project memory (`.kody/vault/`)", "", "Pages from prior memorize ticks. Treat as advisory context \u2014 confirm against the codebase before acting.", ""];
4558
+ let total = 0;
4559
+ for (const p of pages) {
4560
+ const block = [`## ${p.title} \u2014 \`${p.relPath}\``, "", p.content].join("\n");
4561
+ if (total + block.length > TOTAL_MAX_BYTES2) {
4562
+ lines.push("_\u2026 (further pages truncated to fit budget)_");
4563
+ break;
4564
+ }
4565
+ lines.push(block);
4566
+ lines.push("");
4567
+ total += block.length;
4568
+ }
4569
+ return lines.join("\n");
4570
+ }
4571
+ function walkMd(root, visit) {
4572
+ let stack = [root];
4573
+ while (stack.length > 0) {
4574
+ const dir = stack.pop();
4575
+ let names;
4576
+ try {
4577
+ names = fs21.readdirSync(dir);
4578
+ } catch {
4579
+ continue;
4580
+ }
4581
+ for (const name of names) {
4582
+ if (name.startsWith(".")) continue;
4583
+ const full = path19.join(dir, name);
4584
+ let stat;
4585
+ try {
4586
+ stat = fs21.statSync(full);
4587
+ } catch {
4588
+ continue;
4589
+ }
4590
+ if (stat.isDirectory()) {
4591
+ stack.push(full);
4592
+ continue;
4593
+ }
4594
+ if (stat.isFile() && full.endsWith(".md")) visit(full);
4595
+ }
4596
+ }
4597
+ }
4598
+
4599
+ // src/scripts/memorizeFlow.ts
4371
4600
  import { execFileSync as execFileSync14 } from "child_process";
4601
+ import * as fs22 from "fs";
4602
+ import * as path20 from "path";
4603
+ var VAULT_DIR_RELATIVE2 = ".kody/vault";
4604
+ var DEFAULT_LOOKBACK_HOURS = 36;
4605
+ var MAX_RECENT_PRS = 25;
4606
+ var MAX_VAULT_INDEX_ENTRIES = 200;
4607
+ var PR_BODY_TRUNC = 2e3;
4608
+ var memorizeFlow = async (ctx) => {
4609
+ const vaultAbs = path20.join(ctx.cwd, VAULT_DIR_RELATIVE2);
4610
+ ensureBranch(ctx, vaultAbs);
4611
+ if (ctx.skipAgent) return;
4612
+ const sinceIso = computeSinceIso(vaultAbs);
4613
+ ctx.data.vaultSinceIso = sinceIso;
4614
+ ctx.data.vaultUpdatedIso = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4615
+ ctx.data.vaultDir = VAULT_DIR_RELATIVE2;
4616
+ const recent = fetchRecentPrs(ctx.cwd, sinceIso);
4617
+ ctx.data.recentPrs = formatRecentPrs(recent);
4618
+ ctx.data.recentPrCount = recent.length;
4619
+ if (!fs22.existsSync(vaultAbs)) {
4620
+ fs22.mkdirSync(vaultAbs, { recursive: true });
4621
+ }
4622
+ ctx.data.vaultIndex = formatVaultIndex(vaultAbs);
4623
+ if (recent.length === 0) {
4624
+ process.stdout.write(`[kody memorize] no merged PRs since ${sinceIso}; agent may emit no changes
4625
+ `);
4626
+ } else {
4627
+ process.stdout.write(`[kody memorize] ${recent.length} merged PR(s) since ${sinceIso} \u2192 branch ${ctx.data.branch}
4628
+ `);
4629
+ }
4630
+ };
4631
+ function ensureBranch(ctx, vaultAbs) {
4632
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replace(/-/g, "");
4633
+ const branch = `kody/memorize-${stamp}`;
4634
+ const defaultBranch = ctx.config.git.defaultBranch;
4635
+ try {
4636
+ git3(["fetch", "origin", defaultBranch], ctx.cwd);
4637
+ } catch {
4638
+ }
4639
+ try {
4640
+ git3(["rev-parse", "--verify", `origin/${branch}`], ctx.cwd);
4641
+ git3(["checkout", "-B", branch, `origin/${branch}`], ctx.cwd);
4642
+ } catch {
4643
+ try {
4644
+ git3(["checkout", "-B", branch, `origin/${defaultBranch}`], ctx.cwd);
4645
+ } catch {
4646
+ git3(["checkout", "-B", branch], ctx.cwd);
4647
+ }
4648
+ }
4649
+ ctx.data.branch = branch;
4650
+ if (!fs22.existsSync(vaultAbs)) {
4651
+ fs22.mkdirSync(vaultAbs, { recursive: true });
4652
+ }
4653
+ }
4654
+ function computeSinceIso(vaultAbs) {
4655
+ const fallback = new Date(Date.now() - DEFAULT_LOOKBACK_HOURS * 60 * 60 * 1e3).toISOString();
4656
+ if (!fs22.existsSync(vaultAbs)) return fallback;
4657
+ let latest = "";
4658
+ walkMd2(vaultAbs, (file) => {
4659
+ let raw;
4660
+ try {
4661
+ raw = fs22.readFileSync(file, "utf-8");
4662
+ } catch {
4663
+ return;
4664
+ }
4665
+ const m = raw.match(/^---\s*\n([\s\S]*?)\n---/);
4666
+ if (!m) return;
4667
+ const updated = m[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m);
4668
+ if (!updated) return;
4669
+ const ts = new Date(updated[1].trim()).toISOString();
4670
+ if (ts > latest) latest = ts;
4671
+ });
4672
+ return latest || fallback;
4673
+ }
4674
+ function fetchRecentPrs(cwd, sinceIso) {
4675
+ let raw;
4676
+ try {
4677
+ raw = gh2(
4678
+ [
4679
+ "pr",
4680
+ "list",
4681
+ "--state",
4682
+ "merged",
4683
+ "--limit",
4684
+ String(MAX_RECENT_PRS * 2),
4685
+ "--json",
4686
+ "number,title,url,mergedAt,body"
4687
+ ],
4688
+ { cwd }
4689
+ );
4690
+ } catch {
4691
+ return [];
4692
+ }
4693
+ let arr;
4694
+ try {
4695
+ arr = JSON.parse(raw);
4696
+ } catch {
4697
+ return [];
4698
+ }
4699
+ if (!Array.isArray(arr)) return [];
4700
+ const since = Date.parse(sinceIso);
4701
+ const filtered = [];
4702
+ for (const p of arr) {
4703
+ if (typeof p.number !== "number") continue;
4704
+ const merged = p.mergedAt ? Date.parse(p.mergedAt) : NaN;
4705
+ if (!Number.isFinite(merged) || merged <= since) continue;
4706
+ filtered.push({
4707
+ number: p.number,
4708
+ title: p.title ?? "(no title)",
4709
+ url: p.url ?? "",
4710
+ mergedAt: p.mergedAt ?? "",
4711
+ body: (p.body ?? "").slice(0, PR_BODY_TRUNC)
4712
+ });
4713
+ if (filtered.length >= MAX_RECENT_PRS) break;
4714
+ }
4715
+ return filtered;
4716
+ }
4717
+ function formatRecentPrs(prs) {
4718
+ if (prs.length === 0) return "_(no merged PRs since the last memorize tick)_";
4719
+ const lines = [];
4720
+ for (const p of prs) {
4721
+ lines.push(`### PR #${p.number} \u2014 ${p.title}`);
4722
+ if (p.url) lines.push(p.url);
4723
+ lines.push(`Merged: ${p.mergedAt}`);
4724
+ lines.push("");
4725
+ if (p.body.trim()) {
4726
+ lines.push(p.body.trim());
4727
+ } else {
4728
+ lines.push("_(no PR body)_");
4729
+ }
4730
+ lines.push("");
4731
+ }
4732
+ return lines.join("\n");
4733
+ }
4734
+ function formatVaultIndex(vaultAbs) {
4735
+ const entries = [];
4736
+ walkMd2(vaultAbs, (file) => {
4737
+ if (entries.length >= MAX_VAULT_INDEX_ENTRIES) return;
4738
+ const rel = path20.relative(vaultAbs, file);
4739
+ let title = rel;
4740
+ try {
4741
+ const raw = fs22.readFileSync(file, "utf-8");
4742
+ const m = raw.match(/^---\s*\n([\s\S]*?)\n---/);
4743
+ const titleMatch = m?.[1]?.match(/^title:\s*(.+)$/m);
4744
+ if (titleMatch) title = `${titleMatch[1].trim()} (${rel})`;
4745
+ } catch {
4746
+ }
4747
+ entries.push(`- ${title}`);
4748
+ });
4749
+ if (entries.length === 0) return "_(vault is empty)_";
4750
+ return entries.join("\n");
4751
+ }
4752
+ function walkMd2(root, visit) {
4753
+ if (!fs22.existsSync(root)) return;
4754
+ let stack = [root];
4755
+ while (stack.length > 0) {
4756
+ const dir = stack.pop();
4757
+ let names;
4758
+ try {
4759
+ names = fs22.readdirSync(dir);
4760
+ } catch {
4761
+ continue;
4762
+ }
4763
+ for (const name of names) {
4764
+ if (name.startsWith(".")) continue;
4765
+ const full = path20.join(dir, name);
4766
+ let stat;
4767
+ try {
4768
+ stat = fs22.statSync(full);
4769
+ } catch {
4770
+ continue;
4771
+ }
4772
+ if (stat.isDirectory()) {
4773
+ stack.push(full);
4774
+ continue;
4775
+ }
4776
+ if (stat.isFile() && full.endsWith(".md")) visit(full);
4777
+ }
4778
+ }
4779
+ }
4780
+ function git3(args, cwd) {
4781
+ return execFileSync14("git", args, {
4782
+ encoding: "utf-8",
4783
+ timeout: 3e4,
4784
+ cwd,
4785
+ env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
4786
+ stdio: ["pipe", "pipe", "pipe"]
4787
+ }).trim();
4788
+ }
4789
+
4790
+ // src/scripts/mergeReleasePr.ts
4791
+ import { execFileSync as execFileSync15 } from "child_process";
4372
4792
  var API_TIMEOUT_MS6 = 6e4;
4373
4793
  var mergeReleasePr = async (ctx) => {
4374
4794
  const state = ctx.data.taskState;
@@ -4385,7 +4805,7 @@ var mergeReleasePr = async (ctx) => {
4385
4805
  return;
4386
4806
  }
4387
4807
  try {
4388
- execFileSync14("gh", ["pr", "merge", String(prNumber), "--merge"], {
4808
+ execFileSync15("gh", ["pr", "merge", String(prNumber), "--merge"], {
4389
4809
  timeout: API_TIMEOUT_MS6,
4390
4810
  cwd: ctx.cwd,
4391
4811
  stdio: ["ignore", "pipe", "pipe"]
@@ -4650,7 +5070,7 @@ var persistFlowState = async (ctx) => {
4650
5070
  };
4651
5071
 
4652
5072
  // src/scripts/postClassification.ts
4653
- import { execFileSync as execFileSync15 } from "child_process";
5073
+ import { execFileSync as execFileSync16 } from "child_process";
4654
5074
  var API_TIMEOUT_MS7 = 3e4;
4655
5075
  var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
4656
5076
  var postClassification = async (ctx) => {
@@ -4680,7 +5100,7 @@ var postClassification = async (ctx) => {
4680
5100
  }
4681
5101
  tryAuditComment(issueNumber, `\u{1F50E} kody classified as \`${classification}\`${reason ? ` \u2014 ${reason}` : ""}`, ctx.cwd);
4682
5102
  try {
4683
- execFileSync15("gh", ["issue", "comment", String(issueNumber), "--body", `@kody ${classification}`], {
5103
+ execFileSync16("gh", ["issue", "comment", String(issueNumber), "--body", `@kody ${classification}`], {
4684
5104
  cwd: ctx.cwd,
4685
5105
  timeout: API_TIMEOUT_MS7,
4686
5106
  stdio: ["ignore", "pipe", "pipe"]
@@ -4714,7 +5134,7 @@ function parseClassification(prSummary) {
4714
5134
  }
4715
5135
  function tryAuditComment(issueNumber, body, cwd) {
4716
5136
  try {
4717
- execFileSync15("gh", ["issue", "comment", String(issueNumber), "--body", body], {
5137
+ execFileSync16("gh", ["issue", "comment", String(issueNumber), "--body", body], {
4718
5138
  cwd,
4719
5139
  timeout: API_TIMEOUT_MS7,
4720
5140
  stdio: ["ignore", "pipe", "pipe"]
@@ -5010,7 +5430,7 @@ var resolveArtifacts = async (ctx, profile) => {
5010
5430
  };
5011
5431
 
5012
5432
  // src/scripts/resolveFlow.ts
5013
- import { execFileSync as execFileSync16 } from "child_process";
5433
+ import { execFileSync as execFileSync17 } from "child_process";
5014
5434
  var CONFLICT_DIFF_MAX_BYTES = 4e4;
5015
5435
  var resolveFlow = async (ctx) => {
5016
5436
  const prNumber = ctx.args.pr;
@@ -5080,7 +5500,7 @@ function buildPreferBlock(prefer, baseBranch) {
5080
5500
  }
5081
5501
  function getConflictedFiles(cwd) {
5082
5502
  try {
5083
- const out = execFileSync16("git", ["diff", "--name-only", "--diff-filter=U"], {
5503
+ const out = execFileSync17("git", ["diff", "--name-only", "--diff-filter=U"], {
5084
5504
  encoding: "utf-8",
5085
5505
  cwd,
5086
5506
  env: { ...process.env, HUSKY: "0" }
@@ -5095,7 +5515,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
5095
5515
  let total = 0;
5096
5516
  for (const f of files) {
5097
5517
  try {
5098
- const content = execFileSync16("cat", [f], { encoding: "utf-8", cwd }).toString();
5518
+ const content = execFileSync17("cat", [f], { encoding: "utf-8", cwd }).toString();
5099
5519
  const snippet = `### ${f}
5100
5520
 
5101
5521
  \`\`\`
@@ -5269,11 +5689,11 @@ var skipAgent = async (ctx) => {
5269
5689
  };
5270
5690
 
5271
5691
  // src/scripts/stageMergeConflicts.ts
5272
- import { execFileSync as execFileSync17 } from "child_process";
5692
+ import { execFileSync as execFileSync18 } from "child_process";
5273
5693
  var stageMergeConflicts = async (ctx) => {
5274
5694
  if (ctx.data.agentDone === false) return;
5275
5695
  try {
5276
- execFileSync17("git", ["add", "-A"], {
5696
+ execFileSync18("git", ["add", "-A"], {
5277
5697
  cwd: ctx.cwd,
5278
5698
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
5279
5699
  stdio: "pipe"
@@ -5283,7 +5703,7 @@ var stageMergeConflicts = async (ctx) => {
5283
5703
  };
5284
5704
 
5285
5705
  // src/scripts/startFlow.ts
5286
- import { execFileSync as execFileSync18 } from "child_process";
5706
+ import { execFileSync as execFileSync19 } from "child_process";
5287
5707
  var API_TIMEOUT_MS8 = 3e4;
5288
5708
  var startFlow = async (ctx, profile, _agentResult, args) => {
5289
5709
  const entry = args?.entry;
@@ -5317,7 +5737,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
5317
5737
  const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
5318
5738
  const body = `@kody ${next}`;
5319
5739
  try {
5320
- execFileSync18("gh", [sub, "comment", String(targetNumber), "--body", body], {
5740
+ execFileSync19("gh", [sub, "comment", String(targetNumber), "--body", body], {
5321
5741
  timeout: API_TIMEOUT_MS8,
5322
5742
  cwd,
5323
5743
  stdio: ["ignore", "pipe", "pipe"]
@@ -5331,7 +5751,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
5331
5751
  }
5332
5752
 
5333
5753
  // src/scripts/syncFlow.ts
5334
- import { execFileSync as execFileSync19 } from "child_process";
5754
+ import { execFileSync as execFileSync20 } from "child_process";
5335
5755
  var syncFlow = async (ctx, _profile, args) => {
5336
5756
  const announceOnSuccess = Boolean(args?.announceOnSuccess);
5337
5757
  const prNumber = ctx.args.pr;
@@ -5403,7 +5823,7 @@ function bail2(ctx, prNumber, reason) {
5403
5823
  }
5404
5824
  function revParseHead(cwd) {
5405
5825
  try {
5406
- return execFileSync19("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
5826
+ return execFileSync20("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
5407
5827
  } catch {
5408
5828
  return "";
5409
5829
  }
@@ -5411,9 +5831,9 @@ function revParseHead(cwd) {
5411
5831
  function pushBranch(branch, cwd) {
5412
5832
  const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
5413
5833
  try {
5414
- execFileSync19("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
5834
+ execFileSync20("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
5415
5835
  } catch {
5416
- execFileSync19("git", ["push", "--force-with-lease", "-u", "origin", branch], {
5836
+ execFileSync20("git", ["push", "--force-with-lease", "-u", "origin", branch], {
5417
5837
  cwd,
5418
5838
  env,
5419
5839
  stdio: ["ignore", "pipe", "pipe"]
@@ -5524,7 +5944,7 @@ var verify = async (ctx) => {
5524
5944
  };
5525
5945
 
5526
5946
  // src/scripts/waitForCi.ts
5527
- import { execFileSync as execFileSync20 } from "child_process";
5947
+ import { execFileSync as execFileSync21 } from "child_process";
5528
5948
  var API_TIMEOUT_MS9 = 3e4;
5529
5949
  var waitForCi = async (ctx, _profile, _agentResult, args) => {
5530
5950
  const timeoutMinutes = numArg(args, "timeoutMinutes", 30);
@@ -5602,7 +6022,7 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
5602
6022
  };
5603
6023
  function fetchChecks(prNumber, cwd) {
5604
6024
  try {
5605
- const raw = execFileSync20("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
6025
+ const raw = execFileSync21("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
5606
6026
  encoding: "utf-8",
5607
6027
  timeout: API_TIMEOUT_MS9,
5608
6028
  cwd,
@@ -5777,7 +6197,7 @@ var writeMissionStateFile = async (ctx, _profile, _agentResult) => {
5777
6197
  };
5778
6198
 
5779
6199
  // src/scripts/writeRunSummary.ts
5780
- import * as fs21 from "fs";
6200
+ import * as fs23 from "fs";
5781
6201
  var writeRunSummary = async (ctx, profile) => {
5782
6202
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
5783
6203
  if (!summaryPath) return;
@@ -5799,7 +6219,7 @@ var writeRunSummary = async (ctx, profile) => {
5799
6219
  if (reason) lines.push(`- **Reason:** ${reason}`);
5800
6220
  lines.push("");
5801
6221
  try {
5802
- fs21.appendFileSync(summaryPath, `${lines.join("\n")}
6222
+ fs23.appendFileSync(summaryPath, `${lines.join("\n")}
5803
6223
  `);
5804
6224
  } catch {
5805
6225
  }
@@ -5815,7 +6235,9 @@ var preflightScripts = {
5815
6235
  syncFlow,
5816
6236
  initFlow,
5817
6237
  watchStalePrsFlow,
6238
+ memorizeFlow,
5818
6239
  loadTaskState,
6240
+ loadVaultContext,
5819
6241
  loadIssueContext,
5820
6242
  loadIssueStateComment,
5821
6243
  loadMissionFromFile,
@@ -5850,6 +6272,7 @@ var postflightScripts = {
5850
6272
  stageMergeConflicts,
5851
6273
  commitAndPush: commitAndPush2,
5852
6274
  ensurePr: ensurePr2,
6275
+ ensureMemorizePr,
5853
6276
  postIssueComment: postIssueComment2,
5854
6277
  postPlanComment,
5855
6278
  postResearchComment,
@@ -5875,7 +6298,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
5875
6298
  ]);
5876
6299
 
5877
6300
  // src/tools.ts
5878
- import { execFileSync as execFileSync21 } from "child_process";
6301
+ import { execFileSync as execFileSync22 } from "child_process";
5879
6302
  function verifyCliTools(tools, cwd) {
5880
6303
  const out = [];
5881
6304
  for (const t of tools) out.push(verifyOne(t, cwd));
@@ -5908,7 +6331,7 @@ function verifyOne(tool, cwd) {
5908
6331
  }
5909
6332
  function runShell(cmd, cwd, timeoutMs = 3e4) {
5910
6333
  try {
5911
- execFileSync21("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
6334
+ execFileSync22("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
5912
6335
  return true;
5913
6336
  } catch {
5914
6337
  return false;
@@ -5976,9 +6399,9 @@ async function runExecutable(profileName, input) {
5976
6399
  data: {},
5977
6400
  output: { exitCode: 0 }
5978
6401
  };
5979
- const ndjsonDir = path19.join(input.cwd, ".kody");
6402
+ const ndjsonDir = path21.join(input.cwd, ".kody");
5980
6403
  const invokeAgent = async (prompt) => {
5981
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path19.isAbsolute(p) ? p : path19.resolve(profile.dir, p)).filter((p) => p.length > 0);
6404
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path21.isAbsolute(p) ? p : path21.resolve(profile.dir, p)).filter((p) => p.length > 0);
5982
6405
  const syntheticPath = ctx.data.syntheticPluginPath;
5983
6406
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
5984
6407
  return runAgent({
@@ -6056,17 +6479,17 @@ async function runExecutable(profileName, input) {
6056
6479
  function resolveProfilePath(profileName) {
6057
6480
  const found = resolveExecutable(profileName);
6058
6481
  if (found) return found;
6059
- const here = path19.dirname(new URL(import.meta.url).pathname);
6482
+ const here = path21.dirname(new URL(import.meta.url).pathname);
6060
6483
  const candidates = [
6061
- path19.join(here, "executables", profileName, "profile.json"),
6484
+ path21.join(here, "executables", profileName, "profile.json"),
6062
6485
  // same-dir sibling (dev)
6063
- path19.join(here, "..", "executables", profileName, "profile.json"),
6486
+ path21.join(here, "..", "executables", profileName, "profile.json"),
6064
6487
  // up one (prod: dist/bin → dist/executables)
6065
- path19.join(here, "..", "src", "executables", profileName, "profile.json")
6488
+ path21.join(here, "..", "src", "executables", profileName, "profile.json")
6066
6489
  // fallback
6067
6490
  ];
6068
6491
  for (const c of candidates) {
6069
- if (fs22.existsSync(c)) return c;
6492
+ if (fs24.existsSync(c)) return c;
6070
6493
  }
6071
6494
  return candidates[0];
6072
6495
  }
@@ -6159,8 +6582,8 @@ function finish(out) {
6159
6582
  var SHELL_TIMEOUT_MS = 3e5;
6160
6583
  function runShellEntry(entry, ctx, profile) {
6161
6584
  const shellName = entry.shell;
6162
- const shellPath = path19.join(profile.dir, shellName);
6163
- if (!fs22.existsSync(shellPath)) {
6585
+ const shellPath = path21.join(profile.dir, shellName);
6586
+ if (!fs24.existsSync(shellPath)) {
6164
6587
  ctx.skipAgent = true;
6165
6588
  ctx.output.exitCode = 99;
6166
6589
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -6310,14 +6733,14 @@ function resolveAuthToken(env = process.env) {
6310
6733
  return token;
6311
6734
  }
6312
6735
  function detectPackageManager2(cwd) {
6313
- if (fs23.existsSync(path20.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
6314
- if (fs23.existsSync(path20.join(cwd, "yarn.lock"))) return "yarn";
6315
- if (fs23.existsSync(path20.join(cwd, "bun.lockb"))) return "bun";
6736
+ if (fs25.existsSync(path22.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
6737
+ if (fs25.existsSync(path22.join(cwd, "yarn.lock"))) return "yarn";
6738
+ if (fs25.existsSync(path22.join(cwd, "bun.lockb"))) return "bun";
6316
6739
  return "npm";
6317
6740
  }
6318
6741
  function shellOut(cmd, args, cwd, stream = true) {
6319
6742
  try {
6320
- execFileSync22(cmd, args, {
6743
+ execFileSync23(cmd, args, {
6321
6744
  cwd,
6322
6745
  stdio: stream ? "inherit" : "pipe",
6323
6746
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
@@ -6330,7 +6753,7 @@ function shellOut(cmd, args, cwd, stream = true) {
6330
6753
  }
6331
6754
  function isOnPath(bin) {
6332
6755
  try {
6333
- execFileSync22("which", [bin], { stdio: "pipe" });
6756
+ execFileSync23("which", [bin], { stdio: "pipe" });
6334
6757
  return true;
6335
6758
  } catch {
6336
6759
  return false;
@@ -6364,7 +6787,7 @@ function installLitellmIfNeeded(cwd) {
6364
6787
  } catch {
6365
6788
  }
6366
6789
  try {
6367
- execFileSync22("python3", ["-c", "import litellm"], { stdio: "pipe" });
6790
+ execFileSync23("python3", ["-c", "import litellm"], { stdio: "pipe" });
6368
6791
  process.stdout.write("\u2192 kody: litellm already installed\n");
6369
6792
  return 0;
6370
6793
  } catch {
@@ -6374,16 +6797,16 @@ function installLitellmIfNeeded(cwd) {
6374
6797
  }
6375
6798
  function configureGitIdentity(cwd) {
6376
6799
  try {
6377
- const name = execFileSync22("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
6800
+ const name = execFileSync23("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
6378
6801
  if (name) return;
6379
6802
  } catch {
6380
6803
  }
6381
6804
  try {
6382
- execFileSync22("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
6805
+ execFileSync23("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
6383
6806
  } catch {
6384
6807
  }
6385
6808
  try {
6386
- execFileSync22("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
6809
+ execFileSync23("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
6387
6810
  cwd,
6388
6811
  stdio: "pipe"
6389
6812
  });
@@ -6392,11 +6815,11 @@ function configureGitIdentity(cwd) {
6392
6815
  }
6393
6816
  function postFailureTail(issueNumber, cwd, reason) {
6394
6817
  if (!issueNumber) return;
6395
- const logPath = path20.join(cwd, ".kody", "last-run.jsonl");
6818
+ const logPath = path22.join(cwd, ".kody", "last-run.jsonl");
6396
6819
  let tail = "";
6397
6820
  try {
6398
- if (fs23.existsSync(logPath)) {
6399
- const content = fs23.readFileSync(logPath, "utf-8");
6821
+ if (fs25.existsSync(logPath)) {
6822
+ const content = fs25.readFileSync(logPath, "utf-8");
6400
6823
  tail = content.slice(-3e3);
6401
6824
  }
6402
6825
  } catch {
@@ -6421,7 +6844,7 @@ async function runCi(argv) {
6421
6844
  return 0;
6422
6845
  }
6423
6846
  const args = parseCiArgs(argv);
6424
- const cwd = args.cwd ? path20.resolve(args.cwd) : process.cwd();
6847
+ const cwd = args.cwd ? path22.resolve(args.cwd) : process.cwd();
6425
6848
  let earlyConfig;
6426
6849
  try {
6427
6850
  earlyConfig = loadConfig(cwd);
@@ -6431,9 +6854,9 @@ async function runCi(argv) {
6431
6854
  const eventName = process.env.GITHUB_EVENT_NAME;
6432
6855
  const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
6433
6856
  let manualWorkflowDispatch = false;
6434
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs23.existsSync(dispatchEventPath)) {
6857
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs25.existsSync(dispatchEventPath)) {
6435
6858
  try {
6436
- const evt = JSON.parse(fs23.readFileSync(dispatchEventPath, "utf-8"));
6859
+ const evt = JSON.parse(fs25.readFileSync(dispatchEventPath, "utf-8"));
6437
6860
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
6438
6861
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
6439
6862
  manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
@@ -6648,15 +7071,15 @@ function parseChatArgs(argv, env = process.env) {
6648
7071
  return result;
6649
7072
  }
6650
7073
  function commitChatFiles(cwd, sessionId, verbose) {
6651
- const sessionFile = path21.relative(cwd, sessionFilePath(cwd, sessionId));
6652
- const eventsFile = path21.relative(cwd, eventsFilePath(cwd, sessionId));
6653
- const paths = [sessionFile, eventsFile].filter((p) => fs24.existsSync(path21.join(cwd, p)));
7074
+ const sessionFile = path23.relative(cwd, sessionFilePath(cwd, sessionId));
7075
+ const eventsFile = path23.relative(cwd, eventsFilePath(cwd, sessionId));
7076
+ const paths = [sessionFile, eventsFile].filter((p) => fs26.existsSync(path23.join(cwd, p)));
6654
7077
  if (paths.length === 0) return;
6655
7078
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
6656
7079
  try {
6657
- execFileSync23("git", ["add", ...paths], opts);
6658
- execFileSync23("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
6659
- execFileSync23("git", ["push", "--quiet", "origin", "HEAD"], opts);
7080
+ execFileSync24("git", ["add", ...paths], opts);
7081
+ execFileSync24("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
7082
+ execFileSync24("git", ["push", "--quiet", "origin", "HEAD"], opts);
6660
7083
  } catch (err) {
6661
7084
  const msg = err instanceof Error ? err.message : String(err);
6662
7085
  process.stderr.write(`[kody:chat] commit/push skipped: ${msg}
@@ -6688,7 +7111,7 @@ async function runChat(argv) {
6688
7111
  ${CHAT_HELP}`);
6689
7112
  return 64;
6690
7113
  }
6691
- const cwd = args.cwd ? path21.resolve(args.cwd) : process.cwd();
7114
+ const cwd = args.cwd ? path23.resolve(args.cwd) : process.cwd();
6692
7115
  const sessionId = args.sessionId;
6693
7116
  const unpackedSecrets = unpackAllSecrets();
6694
7117
  if (unpackedSecrets > 0) {
@@ -73,6 +73,7 @@
73
73
  { "script": "loadTaskState" },
74
74
  { "script": "loadConventions" },
75
75
  { "script": "loadPriorArt" },
76
+ { "script": "loadVaultContext" },
76
77
  { "script": "loadCoverageRules" },
77
78
  { "script": "composePrompt" }
78
79
  ],
@@ -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.
@@ -63,6 +63,9 @@
63
63
  {
64
64
  "script": "loadConventions"
65
65
  },
66
+ {
67
+ "script": "loadVaultContext"
68
+ },
66
69
  {
67
70
  "script": "loadCoverageRules"
68
71
  },
@@ -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
 
@@ -46,6 +46,7 @@
46
46
  { "script": "loadTaskState" },
47
47
  { "script": "resolveArtifacts" },
48
48
  { "script": "loadPriorArt" },
49
+ { "script": "loadVaultContext" },
49
50
  { "script": "loadConventions" },
50
51
  { "script": "loadCoverageRules" },
51
52
  { "script": "composePrompt" }
@@ -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.42",
3
+ "version": "0.3.43",
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",
@@ -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: