@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 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.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 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";
@@ -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/mergeReleasePr.ts
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
- execFileSync14("gh", ["pr", "merge", String(prNumber), "--merge"], {
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 execFileSync15 } from "child_process";
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
- execFileSync15("gh", ["issue", "comment", String(issueNumber), "--body", `@kody ${classification}`], {
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
- execFileSync15("gh", ["issue", "comment", String(issueNumber), "--body", body], {
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 execFileSync16 } from "child_process";
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 = execFileSync16("git", ["diff", "--name-only", "--diff-filter=U"], {
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 = execFileSync16("cat", [f], { encoding: "utf-8", cwd }).toString();
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 execFileSync17 } from "child_process";
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
- execFileSync17("git", ["add", "-A"], {
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 execFileSync18 } from "child_process";
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
- execFileSync18("gh", [sub, "comment", String(targetNumber), "--body", body], {
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 execFileSync19 } from "child_process";
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 execFileSync19("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
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
- execFileSync19("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
5836
+ execFileSync20("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
5415
5837
  } catch {
5416
- execFileSync19("git", ["push", "--force-with-lease", "-u", "origin", branch], {
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 execFileSync20 } from "child_process";
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 = execFileSync20("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
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 fs21 from "fs";
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
- fs21.appendFileSync(summaryPath, `${lines.join("\n")}
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 execFileSync21 } from "child_process";
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
- execFileSync21("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
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 = path19.join(input.cwd, ".kody");
6404
+ const ndjsonDir = path21.join(input.cwd, ".kody");
5980
6405
  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);
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 = path19.dirname(new URL(import.meta.url).pathname);
6484
+ const here = path21.dirname(new URL(import.meta.url).pathname);
6060
6485
  const candidates = [
6061
- path19.join(here, "executables", profileName, "profile.json"),
6486
+ path21.join(here, "executables", profileName, "profile.json"),
6062
6487
  // same-dir sibling (dev)
6063
- path19.join(here, "..", "executables", profileName, "profile.json"),
6488
+ path21.join(here, "..", "executables", profileName, "profile.json"),
6064
6489
  // up one (prod: dist/bin → dist/executables)
6065
- path19.join(here, "..", "src", "executables", profileName, "profile.json")
6490
+ path21.join(here, "..", "src", "executables", profileName, "profile.json")
6066
6491
  // fallback
6067
6492
  ];
6068
6493
  for (const c of candidates) {
6069
- if (fs22.existsSync(c)) return c;
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 = path19.join(profile.dir, shellName);
6163
- if (!fs22.existsSync(shellPath)) {
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 (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";
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
- execFileSync22(cmd, args, {
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
- execFileSync22("which", [bin], { stdio: "pipe" });
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
- execFileSync22("python3", ["-c", "import litellm"], { stdio: "pipe" });
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 = execFileSync22("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
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
- execFileSync22("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
6807
+ execFileSync23("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
6383
6808
  } catch {
6384
6809
  }
6385
6810
  try {
6386
- execFileSync22("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
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 = path20.join(cwd, ".kody", "last-run.jsonl");
6820
+ const logPath = path22.join(cwd, ".kody", "last-run.jsonl");
6396
6821
  let tail = "";
6397
6822
  try {
6398
- if (fs23.existsSync(logPath)) {
6399
- const content = fs23.readFileSync(logPath, "utf-8");
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 ? path20.resolve(args.cwd) : process.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 && fs23.existsSync(dispatchEventPath)) {
6859
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs25.existsSync(dispatchEventPath)) {
6435
6860
  try {
6436
- const evt = JSON.parse(fs23.readFileSync(dispatchEventPath, "utf-8"));
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 = 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)));
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
- execFileSync23("git", ["add", ...paths], opts);
6658
- execFileSync23("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
6659
- execFileSync23("git", ["push", "--quiet", "origin", "HEAD"], opts);
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 ? path21.resolve(args.cwd) : process.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) {
@@ -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.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",
@@ -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: