@kody-ade/kody-engine 0.4.19 → 0.4.20

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.4.19",
6
+ version: "0.4.20",
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",
@@ -51,9 +51,9 @@ var package_default = {
51
51
  };
52
52
 
53
53
  // src/chat-cli.ts
54
- import { execFileSync as execFileSync31 } from "child_process";
55
- import * as fs29 from "fs";
56
- import * as path26 from "path";
54
+ import { execFileSync as execFileSync30 } from "child_process";
55
+ import * as fs28 from "fs";
56
+ import * as path25 from "path";
57
57
 
58
58
  // src/chat/events.ts
59
59
  import * as fs from "fs";
@@ -155,7 +155,7 @@ function loadConfig(projectDir = process.cwd()) {
155
155
  throw new Error(`kody.config.json is invalid JSON: ${msg}`);
156
156
  }
157
157
  const quality = raw.quality ?? {};
158
- const git5 = raw.git ?? {};
158
+ const git4 = raw.git ?? {};
159
159
  const github = raw.github ?? {};
160
160
  const agent = raw.agent ?? {};
161
161
  if (!agent.model || typeof agent.model !== "string") {
@@ -172,7 +172,7 @@ function loadConfig(projectDir = process.cwd()) {
172
172
  testUnit: typeof quality.testUnit === "string" ? quality.testUnit : ""
173
173
  },
174
174
  git: {
175
- defaultBranch: typeof git5.defaultBranch === "string" ? git5.defaultBranch : "main"
175
+ defaultBranch: typeof git4.defaultBranch === "string" ? git4.defaultBranch : "main"
176
176
  },
177
177
  github: {
178
178
  owner: String(github.owner),
@@ -912,9 +912,9 @@ async function emit2(sink, type, sessionId, suffix, payload) {
912
912
  }
913
913
 
914
914
  // src/kody-cli.ts
915
- import { execFileSync as execFileSync30 } from "child_process";
916
- import * as fs28 from "fs";
917
- import * as path25 from "path";
915
+ import { execFileSync as execFileSync29 } from "child_process";
916
+ import * as fs27 from "fs";
917
+ import * as path24 from "path";
918
918
 
919
919
  // src/dispatch.ts
920
920
  import * as fs7 from "fs";
@@ -1002,6 +1002,32 @@ function getExecutablesRoot() {
1002
1002
  function getProjectExecutablesRoot() {
1003
1003
  return path6.join(process.cwd(), ".kody", "executables");
1004
1004
  }
1005
+ function getBuiltinJobsRoot() {
1006
+ const here = path6.dirname(new URL(import.meta.url).pathname);
1007
+ const candidates = [
1008
+ path6.join(here, "jobs"),
1009
+ // dev: src/
1010
+ path6.join(here, "..", "jobs"),
1011
+ // built: dist/bin → dist/jobs
1012
+ path6.join(here, "..", "src", "jobs")
1013
+ // fallback
1014
+ ];
1015
+ for (const c of candidates) {
1016
+ if (fs6.existsSync(c) && fs6.statSync(c).isDirectory()) return c;
1017
+ }
1018
+ return candidates[0];
1019
+ }
1020
+ function listBuiltinJobs(root = getBuiltinJobsRoot()) {
1021
+ if (!fs6.existsSync(root) || !fs6.statSync(root).isDirectory()) return [];
1022
+ const out = [];
1023
+ for (const ent of fs6.readdirSync(root, { withFileTypes: true })) {
1024
+ if (!ent.isFile() || !ent.name.endsWith(".md")) continue;
1025
+ const slug = ent.name.slice(0, -3);
1026
+ out.push({ slug, filePath: path6.join(root, ent.name) });
1027
+ }
1028
+ out.sort((a, b) => a.slug.localeCompare(b.slug));
1029
+ return out;
1030
+ }
1005
1031
  function getExecutableRoots() {
1006
1032
  return [getProjectExecutablesRoot(), getExecutablesRoot()];
1007
1033
  }
@@ -1273,9 +1299,9 @@ function coerceBare(spec, value) {
1273
1299
  }
1274
1300
 
1275
1301
  // src/executor.ts
1276
- import { execFileSync as execFileSync29, spawn as spawn5 } from "child_process";
1277
- import * as fs27 from "fs";
1278
- import * as path24 from "path";
1302
+ import { execFileSync as execFileSync28, spawn as spawn5 } from "child_process";
1303
+ import * as fs26 from "fs";
1304
+ import * as path23 from "path";
1279
1305
 
1280
1306
  // src/litellm.ts
1281
1307
  import { execFileSync as execFileSync3, spawn } from "child_process";
@@ -1706,7 +1732,7 @@ var FORBIDDEN_PATH_PREFIXES = [
1706
1732
  "dist/",
1707
1733
  "build/"
1708
1734
  ];
1709
- var ALLOWED_PATH_PREFIXES = [".kody/vault/"];
1735
+ var ALLOWED_PATH_PREFIXES = [".kody/memory/"];
1710
1736
  var FORBIDDEN_PATH_EXACT = /* @__PURE__ */ new Set([".env", ".kody-pip-requirements.txt"]);
1711
1737
  var FORBIDDEN_PATH_SUFFIXES = [".log"];
1712
1738
  var CONVENTIONAL_PREFIXES = [
@@ -4632,102 +4658,6 @@ function ensurePr(opts) {
4632
4658
  return { url, number, draft: opts.draft, action: "created" };
4633
4659
  }
4634
4660
 
4635
- // src/scripts/ensureMemorizePr.ts
4636
- var TITLE_MAX2 = 72;
4637
- var ensureMemorizePr = async (ctx) => {
4638
- if (ctx.skipAgent && ctx.output.exitCode !== void 0 && ctx.output.exitCode !== 0) {
4639
- return;
4640
- }
4641
- const commitResult = ctx.data.commitResult;
4642
- const hasCommits = Boolean(ctx.data.hasCommitsAhead);
4643
- if (!commitResult?.committed && !hasCommits) {
4644
- process.stdout.write("[kody memorize] no vault changes \u2014 skipping PR\n");
4645
- ctx.output.exitCode = 0;
4646
- ctx.output.reason = "no vault changes";
4647
- return;
4648
- }
4649
- if (commitResult?.committed && commitResult.pushed === false) {
4650
- const reason = ctx.data.commitCrash ?? "push failed";
4651
- ctx.output.exitCode = 4;
4652
- ctx.output.reason = `memorize: branch not pushed to origin \u2014 ${reason}`;
4653
- process.stderr.write(`[kody memorize] not opening PR: ${ctx.output.reason}
4654
- `);
4655
- return;
4656
- }
4657
- const branch = ctx.data.branch;
4658
- if (!branch) {
4659
- ctx.output.exitCode = 4;
4660
- ctx.output.reason = "memorize: no branch on ctx.data.branch";
4661
- return;
4662
- }
4663
- const datestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4664
- const titleBase = `kody memorize: vault update ${datestamp}`;
4665
- const title = titleBase.length <= TITLE_MAX2 ? titleBase : `${titleBase.slice(0, TITLE_MAX2 - 1)}\u2026`;
4666
- const body = buildBody(ctx, branch, datestamp);
4667
- const existing = findExistingPr(branch, ctx.cwd);
4668
- if (existing) {
4669
- const stripped = existing.url.replace(/^https:\/\/github\.com\//, "");
4670
- const [owner, repo] = stripped.split("/");
4671
- try {
4672
- gh2(["api", "--method", "PATCH", `repos/${owner}/${repo}/pulls/${existing.number}`, "-f", `body=${body}`], {
4673
- cwd: ctx.cwd
4674
- });
4675
- ctx.output.prUrl = existing.url;
4676
- ctx.data.prResult = { url: existing.url, number: existing.number, action: "updated" };
4677
- process.stdout.write(`[kody memorize] updated PR ${existing.url}
4678
- `);
4679
- } catch (err) {
4680
- ctx.output.exitCode = 4;
4681
- ctx.output.reason = `gh api PATCH #${existing.number} failed: ${err instanceof Error ? err.message : String(err)}`;
4682
- }
4683
- return;
4684
- }
4685
- try {
4686
- const output = gh2(
4687
- ["pr", "create", "--head", branch, "--base", ctx.config.git.defaultBranch, "--title", title, "--body-file", "-"],
4688
- { input: body, cwd: ctx.cwd }
4689
- );
4690
- const url = output.trim();
4691
- const match = url.match(/\/pull\/(\d+)$/);
4692
- const number = match ? parseInt(match[1], 10) : 0;
4693
- ctx.output.prUrl = url;
4694
- ctx.data.prResult = { url, number, action: "created" };
4695
- process.stdout.write(`[kody memorize] opened PR ${url}
4696
- `);
4697
- } catch (err) {
4698
- ctx.output.exitCode = 4;
4699
- ctx.output.reason = `PR creation failed: ${err instanceof Error ? err.message : String(err)}`;
4700
- }
4701
- };
4702
- function buildBody(ctx, branch, datestamp) {
4703
- const lines = [];
4704
- lines.push("## Summary");
4705
- lines.push("");
4706
- const summary = ctx.data.prSummary?.trim();
4707
- if (summary) {
4708
- lines.push(summary);
4709
- } else {
4710
- lines.push(`Vault knowledge base update for ${datestamp}.`);
4711
- }
4712
- lines.push("");
4713
- const changedFiles = ctx.data.changedFiles ?? [];
4714
- if (changedFiles.length > 0) {
4715
- lines.push("## Changes");
4716
- lines.push("");
4717
- for (const f of changedFiles.slice(0, 50)) lines.push(`- \`${f}\``);
4718
- if (changedFiles.length > 50) lines.push(`- \u2026 and ${changedFiles.length - 50} more`);
4719
- lines.push("");
4720
- }
4721
- const recentCount = ctx.data.recentPrCount;
4722
- if (typeof recentCount === "number") {
4723
- lines.push(`Synthesized from ${recentCount} merged PR(s) since ${ctx.data.vaultSinceIso ?? "(unknown)"}.`);
4724
- lines.push("");
4725
- }
4726
- lines.push("---");
4727
- lines.push(`_Opened by kody memorize on branch \`${branch}\`._`);
4728
- return lines.join("\n");
4729
- }
4730
-
4731
4661
  // src/scripts/ensurePr.ts
4732
4662
  var ensurePr2 = async (ctx) => {
4733
4663
  if (ctx.skipAgent && ctx.output.exitCode !== void 0) {
@@ -5557,6 +5487,21 @@ function performInit(cwd, force) {
5557
5487
  wrote.push(QA_GUIDE_REL_PATH);
5558
5488
  }
5559
5489
  }
5490
+ const builtinJobs = listBuiltinJobs();
5491
+ if (builtinJobs.length > 0) {
5492
+ const jobsDir = path20.join(cwd, ".kody", "jobs");
5493
+ fs22.mkdirSync(jobsDir, { recursive: true });
5494
+ for (const job of builtinJobs) {
5495
+ const rel = path20.join(".kody", "jobs", `${job.slug}.md`);
5496
+ const target = path20.join(cwd, rel);
5497
+ if (fs22.existsSync(target) && !force) {
5498
+ skipped.push(rel);
5499
+ continue;
5500
+ }
5501
+ fs22.writeFileSync(target, fs22.readFileSync(job.filePath, "utf-8"));
5502
+ wrote.push(rel);
5503
+ }
5504
+ }
5560
5505
  for (const exe of listExecutables()) {
5561
5506
  let profile;
5562
5507
  try {
@@ -5752,141 +5697,39 @@ function humanizeSlug(slug) {
5752
5697
  return slug.split(/[-_]+/).filter((s) => s.length > 0).map((s) => s[0].toUpperCase() + s.slice(1)).join(" ");
5753
5698
  }
5754
5699
 
5755
- // src/scripts/loadPriorArt.ts
5756
- var PER_PR_DIFF_MAX_BYTES = 8e3;
5757
- var TOTAL_MAX_BYTES = 3e4;
5758
- var TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
5759
- var loadPriorArt = async (ctx, _profile, args) => {
5760
- const artifactName = typeof args?.artifactName === "string" ? args.artifactName : "priorArt";
5761
- const state = ctx.data.taskState;
5762
- const artifact = state?.artifacts?.[artifactName];
5763
- const prNumbers = parsePrNumbers(artifact?.content);
5764
- if (prNumbers.length === 0) {
5765
- ctx.data.priorArt = "";
5766
- return;
5767
- }
5768
- const blocks = [];
5769
- for (const n of prNumbers) {
5770
- const block = fetchPrBlock(n, ctx.cwd);
5771
- if (block) blocks.push(block);
5772
- }
5773
- const joined = blocks.join("\n\n---\n\n");
5774
- ctx.data.priorArt = joined.length > TOTAL_MAX_BYTES ? joined.slice(0, TOTAL_MAX_BYTES) + TRUNCATED_SUFFIX : joined;
5775
- };
5776
- function parsePrNumbers(raw) {
5777
- if (!raw) return [];
5778
- const trimmed = raw.trim();
5779
- if (!trimmed) return [];
5780
- try {
5781
- const parsed = JSON.parse(trimmed);
5782
- if (!Array.isArray(parsed)) return [];
5783
- return parsed.filter((n) => typeof n === "number" && Number.isInteger(n) && n > 0);
5784
- } catch {
5785
- return [];
5786
- }
5787
- }
5788
- function fetchPrBlock(prNumber, cwd) {
5789
- try {
5790
- const metaRaw = gh2(["pr", "view", String(prNumber), "--json", "title,state,url,mergedAt,closedAt"], { cwd });
5791
- const meta = JSON.parse(metaRaw);
5792
- const diff = truncate3(safeGh(["pr", "diff", String(prNumber)], cwd), PER_PR_DIFF_MAX_BYTES);
5793
- const commentsRaw = safeGh(["pr", "view", String(prNumber), "--json", "comments,reviews"], cwd);
5794
- const commentsBlock = formatReviewComments(commentsRaw);
5795
- const lines = [
5796
- `## Prior art: PR #${prNumber} \u2014 ${meta.title ?? "(no title)"} [${meta.state ?? "unknown"}]`,
5797
- meta.url ? meta.url : "",
5798
- "",
5799
- "### Diff",
5800
- "```diff",
5801
- diff || "(empty)",
5802
- "```"
5803
- ];
5804
- if (commentsBlock) {
5805
- lines.push("");
5806
- lines.push("### Review comments");
5807
- lines.push(commentsBlock);
5808
- }
5809
- return lines.filter((l) => l !== "").join("\n");
5810
- } catch (err) {
5811
- return `## Prior art: PR #${prNumber}
5812
- _Could not fetch \u2014 ${err instanceof Error ? err.message : String(err)}_`;
5813
- }
5814
- }
5815
- function safeGh(args, cwd) {
5816
- try {
5817
- return gh2(args, { cwd });
5818
- } catch {
5819
- return "";
5820
- }
5821
- }
5822
- function truncate3(s, max) {
5823
- return s.length <= max ? s : s.slice(0, max) + TRUNCATED_SUFFIX;
5824
- }
5825
- function formatReviewComments(raw) {
5826
- if (!raw) return "";
5827
- try {
5828
- const parsed = JSON.parse(raw);
5829
- const out = [];
5830
- for (const c of parsed.comments ?? []) {
5831
- if (!c.body) continue;
5832
- out.push(`- **${c.author?.login ?? "unknown"}**: ${c.body.replace(/\n/g, " ").slice(0, 500)}`);
5833
- }
5834
- for (const r of parsed.reviews ?? []) {
5835
- if (!r.body && !r.state) continue;
5836
- const state = r.state ? ` (${r.state})` : "";
5837
- const body = r.body ? `: ${r.body.replace(/\n/g, " ").slice(0, 500)}` : "";
5838
- out.push(`- **${r.author?.login ?? "unknown"}**${state}${body}`);
5839
- }
5840
- return out.join("\n");
5841
- } catch {
5842
- return "";
5843
- }
5844
- }
5845
-
5846
- // src/scripts/loadTaskState.ts
5847
- var loadTaskState = async (ctx) => {
5848
- const target = ctx.data.commentTargetType;
5849
- const number = ctx.data.commentTargetNumber;
5850
- if (!target || !number) {
5851
- ctx.data.taskState = emptyState();
5852
- return;
5853
- }
5854
- ctx.data.taskState = readTaskState(target, number, ctx.cwd);
5855
- };
5856
-
5857
- // src/scripts/loadVaultContext.ts
5700
+ // src/scripts/loadMemoryContext.ts
5858
5701
  import * as fs24 from "fs";
5859
5702
  import * as path22 from "path";
5860
- var VAULT_DIR_RELATIVE = ".kody/vault";
5703
+ var MEMORY_DIR_RELATIVE = ".kody/memory";
5861
5704
  var MAX_PAGES = 8;
5862
5705
  var PER_PAGE_MAX_BYTES = 4e3;
5863
- var TOTAL_MAX_BYTES2 = 24e3;
5864
- var TRUNCATED_SUFFIX2 = "\n\n\u2026 (truncated)";
5865
- var loadVaultContext = async (ctx) => {
5866
- const vaultAbs = path22.join(ctx.cwd, VAULT_DIR_RELATIVE);
5867
- if (!fs24.existsSync(vaultAbs)) {
5868
- ctx.data.vaultContext = "";
5706
+ var TOTAL_MAX_BYTES = 24e3;
5707
+ var TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
5708
+ var loadMemoryContext = async (ctx) => {
5709
+ const memoryAbs = path22.join(ctx.cwd, MEMORY_DIR_RELATIVE);
5710
+ if (!fs24.existsSync(memoryAbs)) {
5711
+ ctx.data.memoryContext = "";
5869
5712
  return;
5870
5713
  }
5871
5714
  let pages = [];
5872
5715
  try {
5873
- pages = collectPages(vaultAbs);
5716
+ pages = collectPages(memoryAbs);
5874
5717
  } catch {
5875
- ctx.data.vaultContext = "";
5718
+ ctx.data.memoryContext = "";
5876
5719
  return;
5877
5720
  }
5878
5721
  if (pages.length === 0) {
5879
- ctx.data.vaultContext = "";
5722
+ ctx.data.memoryContext = "";
5880
5723
  return;
5881
5724
  }
5882
5725
  const queryTerms = extractQueryTerms(ctx);
5883
5726
  const ranked = queryTerms.length > 0 ? scorePages(pages, queryTerms) : sortByRecency(pages);
5884
5727
  const top = ranked.slice(0, MAX_PAGES);
5885
- ctx.data.vaultContext = formatBlock(top);
5728
+ ctx.data.memoryContext = formatBlock(top);
5886
5729
  };
5887
- function collectPages(vaultAbs) {
5730
+ function collectPages(memoryAbs) {
5888
5731
  const out = [];
5889
- walkMd(vaultAbs, (file) => {
5732
+ walkMd(memoryAbs, (file) => {
5890
5733
  let stat;
5891
5734
  try {
5892
5735
  stat = fs24.statSync(file);
@@ -5903,10 +5746,10 @@ function collectPages(vaultAbs) {
5903
5746
  const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path22.basename(file, ".md");
5904
5747
  const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
5905
5748
  out.push({
5906
- relPath: path22.relative(vaultAbs, file),
5749
+ relPath: path22.relative(memoryAbs, file),
5907
5750
  title,
5908
5751
  updated,
5909
- content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX2 : raw,
5752
+ content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX : raw,
5910
5753
  mtime: stat.mtimeMs
5911
5754
  });
5912
5755
  });
@@ -5947,7 +5790,7 @@ function sortByRecency(pages) {
5947
5790
  function formatBlock(pages) {
5948
5791
  if (pages.length === 0) return "";
5949
5792
  const lines = [
5950
- "# Project memory (`.kody/vault/`)",
5793
+ "# Project memory (`.kody/memory/`)",
5951
5794
  "",
5952
5795
  "Pages from prior memorize ticks. Treat as advisory context \u2014 confirm against the codebase before acting.",
5953
5796
  ""
@@ -5955,7 +5798,7 @@ function formatBlock(pages) {
5955
5798
  let total = 0;
5956
5799
  for (const p of pages) {
5957
5800
  const block = [`## ${p.title} \u2014 \`${p.relPath}\``, "", p.content].join("\n");
5958
- if (total + block.length > TOTAL_MAX_BYTES2) {
5801
+ if (total + block.length > TOTAL_MAX_BYTES) {
5959
5802
  lines.push("_\u2026 (further pages truncated to fit budget)_");
5960
5803
  break;
5961
5804
  }
@@ -5993,209 +5836,118 @@ function walkMd(root, visit) {
5993
5836
  }
5994
5837
  }
5995
5838
 
5996
- // src/scripts/markFlowSuccess.ts
5997
- var markFlowSuccess = async (ctx) => {
5998
- const exit = ctx.output.exitCode;
5999
- if (exit === void 0 || exit === 0) {
6000
- ctx.data.agentDone = true;
5839
+ // src/scripts/loadPriorArt.ts
5840
+ var PER_PR_DIFF_MAX_BYTES = 8e3;
5841
+ var TOTAL_MAX_BYTES2 = 3e4;
5842
+ var TRUNCATED_SUFFIX2 = "\n\n\u2026 (truncated)";
5843
+ var loadPriorArt = async (ctx, _profile, args) => {
5844
+ const artifactName = typeof args?.artifactName === "string" ? args.artifactName : "priorArt";
5845
+ const state = ctx.data.taskState;
5846
+ const artifact = state?.artifacts?.[artifactName];
5847
+ const prNumbers = parsePrNumbers(artifact?.content);
5848
+ if (prNumbers.length === 0) {
5849
+ ctx.data.priorArt = "";
5850
+ return;
6001
5851
  }
6002
- };
6003
-
6004
- // src/scripts/memorizeFlow.ts
6005
- import { execFileSync as execFileSync18 } from "child_process";
6006
- import * as fs25 from "fs";
6007
- import * as path23 from "path";
6008
- var VAULT_DIR_RELATIVE2 = ".kody/vault";
6009
- var DEFAULT_LOOKBACK_HOURS = 36;
6010
- var MAX_RECENT_PRS = 25;
6011
- var MAX_VAULT_INDEX_ENTRIES = 200;
6012
- var PR_BODY_TRUNC = 2e3;
6013
- var memorizeFlow = async (ctx) => {
6014
- const vaultAbs = path23.join(ctx.cwd, VAULT_DIR_RELATIVE2);
6015
- ensureBranch(ctx, vaultAbs);
6016
- if (ctx.skipAgent) return;
6017
- const sinceIso = computeSinceIso(vaultAbs);
6018
- ctx.data.vaultSinceIso = sinceIso;
6019
- ctx.data.vaultUpdatedIso = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6020
- ctx.data.vaultDir = VAULT_DIR_RELATIVE2;
6021
- const recent = fetchRecentPrs(ctx.cwd, sinceIso);
6022
- ctx.data.recentPrs = formatRecentPrs(recent);
6023
- ctx.data.recentPrCount = recent.length;
6024
- if (!fs25.existsSync(vaultAbs)) {
6025
- fs25.mkdirSync(vaultAbs, { recursive: true });
6026
- }
6027
- ctx.data.vaultIndex = formatVaultIndex(vaultAbs);
6028
- if (recent.length === 0) {
6029
- process.stdout.write(`[kody memorize] no merged PRs since ${sinceIso}; agent may emit no changes
6030
- `);
6031
- } else {
6032
- process.stdout.write(
6033
- `[kody memorize] ${recent.length} merged PR(s) since ${sinceIso} \u2192 branch ${ctx.data.branch}
6034
- `
6035
- );
5852
+ const blocks = [];
5853
+ for (const n of prNumbers) {
5854
+ const block = fetchPrBlock(n, ctx.cwd);
5855
+ if (block) blocks.push(block);
6036
5856
  }
5857
+ const joined = blocks.join("\n\n---\n\n");
5858
+ ctx.data.priorArt = joined.length > TOTAL_MAX_BYTES2 ? joined.slice(0, TOTAL_MAX_BYTES2) + TRUNCATED_SUFFIX2 : joined;
6037
5859
  };
6038
- function ensureBranch(ctx, vaultAbs) {
6039
- const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replace(/-/g, "");
6040
- const branch = `kody-memorize-${stamp}`;
6041
- const defaultBranch = ctx.config.git.defaultBranch;
5860
+ function parsePrNumbers(raw) {
5861
+ if (!raw) return [];
5862
+ const trimmed = raw.trim();
5863
+ if (!trimmed) return [];
6042
5864
  try {
6043
- git3(["fetch", "origin", defaultBranch], ctx.cwd);
5865
+ const parsed = JSON.parse(trimmed);
5866
+ if (!Array.isArray(parsed)) return [];
5867
+ return parsed.filter((n) => typeof n === "number" && Number.isInteger(n) && n > 0);
6044
5868
  } catch {
5869
+ return [];
6045
5870
  }
5871
+ }
5872
+ function fetchPrBlock(prNumber, cwd) {
6046
5873
  try {
6047
- git3(["rev-parse", "--verify", `origin/${branch}`], ctx.cwd);
6048
- git3(["checkout", "-B", branch, `origin/${branch}`], ctx.cwd);
6049
- } catch {
6050
- try {
6051
- git3(["checkout", "-B", branch, `origin/${defaultBranch}`], ctx.cwd);
6052
- } catch {
6053
- git3(["checkout", "-B", branch], ctx.cwd);
5874
+ const metaRaw = gh2(["pr", "view", String(prNumber), "--json", "title,state,url,mergedAt,closedAt"], { cwd });
5875
+ const meta = JSON.parse(metaRaw);
5876
+ const diff = truncate3(safeGh(["pr", "diff", String(prNumber)], cwd), PER_PR_DIFF_MAX_BYTES);
5877
+ const commentsRaw = safeGh(["pr", "view", String(prNumber), "--json", "comments,reviews"], cwd);
5878
+ const commentsBlock = formatReviewComments(commentsRaw);
5879
+ const lines = [
5880
+ `## Prior art: PR #${prNumber} \u2014 ${meta.title ?? "(no title)"} [${meta.state ?? "unknown"}]`,
5881
+ meta.url ? meta.url : "",
5882
+ "",
5883
+ "### Diff",
5884
+ "```diff",
5885
+ diff || "(empty)",
5886
+ "```"
5887
+ ];
5888
+ if (commentsBlock) {
5889
+ lines.push("");
5890
+ lines.push("### Review comments");
5891
+ lines.push(commentsBlock);
6054
5892
  }
6055
- }
6056
- ctx.data.branch = branch;
6057
- if (!fs25.existsSync(vaultAbs)) {
6058
- fs25.mkdirSync(vaultAbs, { recursive: true });
5893
+ return lines.filter((l) => l !== "").join("\n");
5894
+ } catch (err) {
5895
+ return `## Prior art: PR #${prNumber}
5896
+ _Could not fetch \u2014 ${err instanceof Error ? err.message : String(err)}_`;
6059
5897
  }
6060
5898
  }
6061
- function computeSinceIso(vaultAbs) {
6062
- const fallback = new Date(Date.now() - DEFAULT_LOOKBACK_HOURS * 60 * 60 * 1e3).toISOString();
6063
- if (!fs25.existsSync(vaultAbs)) return fallback;
6064
- let latest = "";
6065
- walkMd2(vaultAbs, (file) => {
6066
- let raw;
6067
- try {
6068
- raw = fs25.readFileSync(file, "utf-8");
6069
- } catch {
6070
- return;
6071
- }
6072
- const m = raw.match(/^---\s*\n([\s\S]*?)\n---/);
6073
- if (!m) return;
6074
- const updated = m[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m);
6075
- if (!updated) return;
6076
- const ts = new Date(updated[1].trim()).toISOString();
6077
- if (ts > latest) latest = ts;
6078
- });
6079
- return latest || fallback;
6080
- }
6081
- function fetchRecentPrs(cwd, sinceIso) {
6082
- let raw;
6083
- try {
6084
- raw = gh2(
6085
- [
6086
- "pr",
6087
- "list",
6088
- "--state",
6089
- "merged",
6090
- "--limit",
6091
- String(MAX_RECENT_PRS * 2),
6092
- "--json",
6093
- "number,title,url,mergedAt,body"
6094
- ],
6095
- { cwd }
6096
- );
6097
- } catch {
6098
- return [];
6099
- }
6100
- let arr;
5899
+ function safeGh(args, cwd) {
6101
5900
  try {
6102
- arr = JSON.parse(raw);
5901
+ return gh2(args, { cwd });
6103
5902
  } catch {
6104
- return [];
6105
- }
6106
- if (!Array.isArray(arr)) return [];
6107
- const since = Date.parse(sinceIso);
6108
- const filtered = [];
6109
- for (const p of arr) {
6110
- if (typeof p.number !== "number") continue;
6111
- const merged = p.mergedAt ? Date.parse(p.mergedAt) : NaN;
6112
- if (!Number.isFinite(merged) || merged <= since) continue;
6113
- filtered.push({
6114
- number: p.number,
6115
- title: p.title ?? "(no title)",
6116
- url: p.url ?? "",
6117
- mergedAt: p.mergedAt ?? "",
6118
- body: (p.body ?? "").slice(0, PR_BODY_TRUNC)
6119
- });
6120
- if (filtered.length >= MAX_RECENT_PRS) break;
6121
- }
6122
- return filtered;
6123
- }
6124
- function formatRecentPrs(prs) {
6125
- if (prs.length === 0) return "_(no merged PRs since the last memorize tick)_";
6126
- const lines = [];
6127
- for (const p of prs) {
6128
- lines.push(`### PR #${p.number} \u2014 ${p.title}`);
6129
- if (p.url) lines.push(p.url);
6130
- lines.push(`Merged: ${p.mergedAt}`);
6131
- lines.push("");
6132
- if (p.body.trim()) {
6133
- lines.push(p.body.trim());
6134
- } else {
6135
- lines.push("_(no PR body)_");
6136
- }
6137
- lines.push("");
5903
+ return "";
6138
5904
  }
6139
- return lines.join("\n");
6140
5905
  }
6141
- function formatVaultIndex(vaultAbs) {
6142
- const entries = [];
6143
- walkMd2(vaultAbs, (file) => {
6144
- if (entries.length >= MAX_VAULT_INDEX_ENTRIES) return;
6145
- const rel = path23.relative(vaultAbs, file);
6146
- let title = rel;
6147
- try {
6148
- const raw = fs25.readFileSync(file, "utf-8");
6149
- const m = raw.match(/^---\s*\n([\s\S]*?)\n---/);
6150
- const titleMatch = m?.[1]?.match(/^title:\s*(.+)$/m);
6151
- if (titleMatch) title = `${titleMatch[1].trim()} (${rel})`;
6152
- } catch {
6153
- }
6154
- entries.push(`- ${title}`);
6155
- });
6156
- if (entries.length === 0) return "_(vault is empty)_";
6157
- return entries.join("\n");
5906
+ function truncate3(s, max) {
5907
+ return s.length <= max ? s : s.slice(0, max) + TRUNCATED_SUFFIX2;
6158
5908
  }
6159
- function walkMd2(root, visit) {
6160
- if (!fs25.existsSync(root)) return;
6161
- const stack = [root];
6162
- while (stack.length > 0) {
6163
- const dir = stack.pop();
6164
- let names;
6165
- try {
6166
- names = fs25.readdirSync(dir);
6167
- } catch {
6168
- continue;
5909
+ function formatReviewComments(raw) {
5910
+ if (!raw) return "";
5911
+ try {
5912
+ const parsed = JSON.parse(raw);
5913
+ const out = [];
5914
+ for (const c of parsed.comments ?? []) {
5915
+ if (!c.body) continue;
5916
+ out.push(`- **${c.author?.login ?? "unknown"}**: ${c.body.replace(/\n/g, " ").slice(0, 500)}`);
6169
5917
  }
6170
- for (const name of names) {
6171
- if (name.startsWith(".")) continue;
6172
- const full = path23.join(dir, name);
6173
- let stat;
6174
- try {
6175
- stat = fs25.statSync(full);
6176
- } catch {
6177
- continue;
6178
- }
6179
- if (stat.isDirectory()) {
6180
- stack.push(full);
6181
- continue;
6182
- }
6183
- if (stat.isFile() && full.endsWith(".md")) visit(full);
5918
+ for (const r of parsed.reviews ?? []) {
5919
+ if (!r.body && !r.state) continue;
5920
+ const state = r.state ? ` (${r.state})` : "";
5921
+ const body = r.body ? `: ${r.body.replace(/\n/g, " ").slice(0, 500)}` : "";
5922
+ out.push(`- **${r.author?.login ?? "unknown"}**${state}${body}`);
6184
5923
  }
5924
+ return out.join("\n");
5925
+ } catch {
5926
+ return "";
6185
5927
  }
6186
5928
  }
6187
- function git3(args, cwd) {
6188
- return execFileSync18("git", args, {
6189
- encoding: "utf-8",
6190
- timeout: 3e4,
6191
- cwd,
6192
- env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
6193
- stdio: ["pipe", "pipe", "pipe"]
6194
- }).trim();
6195
- }
5929
+
5930
+ // src/scripts/loadTaskState.ts
5931
+ var loadTaskState = async (ctx) => {
5932
+ const target = ctx.data.commentTargetType;
5933
+ const number = ctx.data.commentTargetNumber;
5934
+ if (!target || !number) {
5935
+ ctx.data.taskState = emptyState();
5936
+ return;
5937
+ }
5938
+ ctx.data.taskState = readTaskState(target, number, ctx.cwd);
5939
+ };
5940
+
5941
+ // src/scripts/markFlowSuccess.ts
5942
+ var markFlowSuccess = async (ctx) => {
5943
+ const exit = ctx.output.exitCode;
5944
+ if (exit === void 0 || exit === 0) {
5945
+ ctx.data.agentDone = true;
5946
+ }
5947
+ };
6196
5948
 
6197
5949
  // src/scripts/mergeReleasePr.ts
6198
- import { execFileSync as execFileSync19 } from "child_process";
5950
+ import { execFileSync as execFileSync18 } from "child_process";
6199
5951
  var API_TIMEOUT_MS7 = 6e4;
6200
5952
  var mergeReleasePr = async (ctx) => {
6201
5953
  const state = ctx.data.taskState;
@@ -6214,7 +5966,7 @@ var mergeReleasePr = async (ctx) => {
6214
5966
  process.stderr.write(`[kody mergeReleasePr] merging PR #${prNumber} (${prUrl})
6215
5967
  `);
6216
5968
  try {
6217
- const out = execFileSync19("gh", ["pr", "merge", String(prNumber), "--merge"], {
5969
+ const out = execFileSync18("gh", ["pr", "merge", String(prNumber), "--merge"], {
6218
5970
  timeout: API_TIMEOUT_MS7,
6219
5971
  cwd: ctx.cwd,
6220
5972
  stdio: ["ignore", "pipe", "pipe"]
@@ -6790,7 +6542,7 @@ ${body}`;
6790
6542
  }
6791
6543
 
6792
6544
  // src/scripts/recordClassification.ts
6793
- import { execFileSync as execFileSync20 } from "child_process";
6545
+ import { execFileSync as execFileSync19 } from "child_process";
6794
6546
  var API_TIMEOUT_MS8 = 3e4;
6795
6547
  var VALID_CLASSES3 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
6796
6548
  var recordClassification = async (ctx) => {
@@ -6838,7 +6590,7 @@ function parseClassification(prSummary) {
6838
6590
  }
6839
6591
  function tryAuditComment(issueNumber, body, cwd) {
6840
6592
  try {
6841
- execFileSync20("gh", ["issue", "comment", String(issueNumber), "--body", body], {
6593
+ execFileSync19("gh", ["issue", "comment", String(issueNumber), "--body", body], {
6842
6594
  cwd,
6843
6595
  timeout: API_TIMEOUT_MS8,
6844
6596
  stdio: ["ignore", "pipe", "pipe"]
@@ -6966,7 +6718,7 @@ var resolveArtifacts = async (ctx, profile) => {
6966
6718
  };
6967
6719
 
6968
6720
  // src/scripts/resolveFlow.ts
6969
- import { execFileSync as execFileSync21 } from "child_process";
6721
+ import { execFileSync as execFileSync20 } from "child_process";
6970
6722
  var CONFLICT_DIFF_MAX_BYTES = 4e4;
6971
6723
  var resolveFlow = async (ctx) => {
6972
6724
  const prNumber = ctx.args.pr;
@@ -7036,7 +6788,7 @@ function buildPreferBlock(prefer, baseBranch) {
7036
6788
  }
7037
6789
  function getConflictedFiles(cwd) {
7038
6790
  try {
7039
- const out = execFileSync21("git", ["diff", "--name-only", "--diff-filter=U"], {
6791
+ const out = execFileSync20("git", ["diff", "--name-only", "--diff-filter=U"], {
7040
6792
  encoding: "utf-8",
7041
6793
  cwd,
7042
6794
  env: { ...process.env, HUSKY: "0" }
@@ -7051,7 +6803,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
7051
6803
  let total = 0;
7052
6804
  for (const f of files) {
7053
6805
  try {
7054
- const content = execFileSync21("cat", [f], { encoding: "utf-8", cwd }).toString();
6806
+ const content = execFileSync20("cat", [f], { encoding: "utf-8", cwd }).toString();
7055
6807
  const snippet = `### ${f}
7056
6808
 
7057
6809
  \`\`\`
@@ -7152,10 +6904,10 @@ var resolvePreviewUrl = async (ctx) => {
7152
6904
  };
7153
6905
 
7154
6906
  // src/scripts/resolveQaUrl.ts
7155
- import { execFileSync as execFileSync22 } from "child_process";
6907
+ import { execFileSync as execFileSync21 } from "child_process";
7156
6908
  function ghQuery(args, cwd) {
7157
6909
  try {
7158
- const out = execFileSync22("gh", args, {
6910
+ const out = execFileSync21("gh", args, {
7159
6911
  cwd,
7160
6912
  stdio: ["ignore", "pipe", "pipe"],
7161
6913
  encoding: "utf-8",
@@ -7225,7 +6977,7 @@ var resolveQaUrl = async (ctx) => {
7225
6977
  };
7226
6978
 
7227
6979
  // src/scripts/revertFlow.ts
7228
- import { execFileSync as execFileSync23 } from "child_process";
6980
+ import { execFileSync as execFileSync22 } from "child_process";
7229
6981
  var SHA_RE = /^[0-9a-f]{4,40}$/i;
7230
6982
  var revertFlow = async (ctx) => {
7231
6983
  const prNumber = ctx.args.pr;
@@ -7263,7 +7015,7 @@ var revertFlow = async (ctx) => {
7263
7015
  for (const s of requested) {
7264
7016
  let full;
7265
7017
  try {
7266
- full = git4(["rev-parse", "--verify", `${s}^{commit}`], ctx.cwd);
7018
+ full = git3(["rev-parse", "--verify", `${s}^{commit}`], ctx.cwd);
7267
7019
  } catch {
7268
7020
  unreachable.push(s);
7269
7021
  continue;
@@ -7274,7 +7026,7 @@ var revertFlow = async (ctx) => {
7274
7026
  }
7275
7027
  let subject = "";
7276
7028
  try {
7277
- subject = git4(["log", "-1", "--format=%s", full], ctx.cwd);
7029
+ subject = git3(["log", "-1", "--format=%s", full], ctx.cwd);
7278
7030
  } catch {
7279
7031
  }
7280
7032
  resolved.push({ input: s, full, subject });
@@ -7306,8 +7058,8 @@ function buildCommitMessage(resolved) {
7306
7058
  function buildPrSummary(resolved) {
7307
7059
  return resolved.map((r) => `- Reverted \`${r.full.slice(0, 7)}\`${r.subject ? ` \u2014 ${r.subject}` : ""}`).join("\n");
7308
7060
  }
7309
- function git4(args, cwd) {
7310
- return execFileSync23("git", args, {
7061
+ function git3(args, cwd) {
7062
+ return execFileSync22("git", args, {
7311
7063
  encoding: "utf-8",
7312
7064
  timeout: 3e4,
7313
7065
  cwd,
@@ -7317,7 +7069,7 @@ function git4(args, cwd) {
7317
7069
  }
7318
7070
  function isAncestorOfHead(sha, cwd) {
7319
7071
  try {
7320
- execFileSync23("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
7072
+ execFileSync22("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
7321
7073
  cwd,
7322
7074
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
7323
7075
  stdio: ["ignore", "ignore", "ignore"]
@@ -7495,11 +7247,11 @@ var skipAgent = async (ctx) => {
7495
7247
  };
7496
7248
 
7497
7249
  // src/scripts/stageMergeConflicts.ts
7498
- import { execFileSync as execFileSync24 } from "child_process";
7250
+ import { execFileSync as execFileSync23 } from "child_process";
7499
7251
  var stageMergeConflicts = async (ctx) => {
7500
7252
  if (ctx.data.agentDone === false) return;
7501
7253
  try {
7502
- execFileSync24("git", ["add", "-A"], {
7254
+ execFileSync23("git", ["add", "-A"], {
7503
7255
  cwd: ctx.cwd,
7504
7256
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
7505
7257
  stdio: "pipe"
@@ -7509,7 +7261,7 @@ var stageMergeConflicts = async (ctx) => {
7509
7261
  };
7510
7262
 
7511
7263
  // src/scripts/startFlow.ts
7512
- import { execFileSync as execFileSync25 } from "child_process";
7264
+ import { execFileSync as execFileSync24 } from "child_process";
7513
7265
  var API_TIMEOUT_MS9 = 3e4;
7514
7266
  var startFlow = async (ctx, profile, _agentResult, args) => {
7515
7267
  const entry = args?.entry;
@@ -7543,7 +7295,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
7543
7295
  const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
7544
7296
  const body = `@kody ${next}`;
7545
7297
  try {
7546
- execFileSync25("gh", [sub, "comment", String(targetNumber), "--body", body], {
7298
+ execFileSync24("gh", [sub, "comment", String(targetNumber), "--body", body], {
7547
7299
  timeout: API_TIMEOUT_MS9,
7548
7300
  cwd,
7549
7301
  stdio: ["ignore", "pipe", "pipe"]
@@ -7557,7 +7309,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
7557
7309
  }
7558
7310
 
7559
7311
  // src/scripts/syncFlow.ts
7560
- import { execFileSync as execFileSync26 } from "child_process";
7312
+ import { execFileSync as execFileSync25 } from "child_process";
7561
7313
  var syncFlow = async (ctx, _profile, args) => {
7562
7314
  const announceOnSuccess = Boolean(args?.announceOnSuccess);
7563
7315
  const prNumber = ctx.args.pr;
@@ -7629,7 +7381,7 @@ function bail2(ctx, prNumber, reason) {
7629
7381
  }
7630
7382
  function revParseHead(cwd) {
7631
7383
  try {
7632
- return execFileSync26("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
7384
+ return execFileSync25("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
7633
7385
  } catch {
7634
7386
  return "";
7635
7387
  }
@@ -7637,9 +7389,9 @@ function revParseHead(cwd) {
7637
7389
  function pushBranch(branch, cwd) {
7638
7390
  const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
7639
7391
  try {
7640
- execFileSync26("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
7392
+ execFileSync25("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
7641
7393
  } catch {
7642
- execFileSync26("git", ["push", "--force-with-lease", "-u", "origin", branch], {
7394
+ execFileSync25("git", ["push", "--force-with-lease", "-u", "origin", branch], {
7643
7395
  cwd,
7644
7396
  env,
7645
7397
  stdio: ["ignore", "pipe", "pipe"]
@@ -7866,7 +7618,7 @@ function downgrade2(ctx, reason) {
7866
7618
  }
7867
7619
 
7868
7620
  // src/scripts/waitForCi.ts
7869
- import { execFileSync as execFileSync27 } from "child_process";
7621
+ import { execFileSync as execFileSync26 } from "child_process";
7870
7622
  var API_TIMEOUT_MS10 = 3e4;
7871
7623
  var waitForCi = async (ctx, _profile, _agentResult, args) => {
7872
7624
  const timeoutMinutes = numArg(args, "timeoutMinutes", 30);
@@ -7944,7 +7696,7 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
7944
7696
  };
7945
7697
  function fetchChecks(prNumber, cwd) {
7946
7698
  try {
7947
- const raw = execFileSync27("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
7699
+ const raw = execFileSync26("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
7948
7700
  encoding: "utf-8",
7949
7701
  timeout: API_TIMEOUT_MS10,
7950
7702
  cwd,
@@ -8135,72 +7887,6 @@ function lineStream(stream) {
8135
7887
  };
8136
7888
  }
8137
7889
 
8138
- // src/scripts/watchStalePrsFlow.ts
8139
- function readWatchConfig(ctx) {
8140
- const cfg = ctx.config.watch;
8141
- if (!cfg || typeof cfg !== "object") return {};
8142
- const r = cfg;
8143
- return {
8144
- staleDays: typeof r.staleDays === "number" && r.staleDays > 0 ? Math.floor(r.staleDays) : void 0,
8145
- reportIssueNumber: typeof r.reportIssueNumber === "number" && r.reportIssueNumber > 0 ? Math.floor(r.reportIssueNumber) : void 0
8146
- };
8147
- }
8148
- function findStalePrs(cwd, staleDays, now = /* @__PURE__ */ new Date()) {
8149
- let raw = "";
8150
- try {
8151
- raw = gh2(["pr", "list", "--state", "open", "--limit", "100", "--json", "number,title,url,updatedAt"], { cwd });
8152
- } catch {
8153
- return [];
8154
- }
8155
- let list;
8156
- try {
8157
- list = JSON.parse(raw);
8158
- } catch {
8159
- return [];
8160
- }
8161
- if (!Array.isArray(list)) return [];
8162
- const cutoffMs = now.getTime() - staleDays * 24 * 60 * 60 * 1e3;
8163
- const stale = [];
8164
- for (const pr of list) {
8165
- const ts = Date.parse(pr.updatedAt);
8166
- if (!Number.isFinite(ts) || ts > cutoffMs) continue;
8167
- const daysStale = Math.floor((now.getTime() - ts) / (24 * 60 * 60 * 1e3));
8168
- stale.push({ number: pr.number, title: pr.title, url: pr.url, updatedAt: pr.updatedAt, daysStale });
8169
- }
8170
- return stale.sort((a, b) => b.daysStale - a.daysStale);
8171
- }
8172
- function formatStaleReport(stale, staleDays) {
8173
- if (stale.length === 0) {
8174
- return `\u{1F7E2} **kody watch-stale-prs** \u2014 no open PRs untouched for more than ${staleDays} days. \u2728`;
8175
- }
8176
- const lines = [`\u{1F7E1} **kody watch-stale-prs** \u2014 ${stale.length} PR(s) untouched for > ${staleDays} days:`, ""];
8177
- for (const pr of stale.slice(0, 50)) {
8178
- lines.push(`- [#${pr.number}](${pr.url}) \u2014 *${truncate2(pr.title, 80)}* (${pr.daysStale} days stale)`);
8179
- }
8180
- if (stale.length > 50) lines.push(`- \u2026 and ${stale.length - 50} more`);
8181
- return lines.join("\n");
8182
- }
8183
- var watchStalePrsFlow = async (ctx) => {
8184
- ctx.skipAgent = true;
8185
- const { staleDays = 7, reportIssueNumber } = readWatchConfig(ctx);
8186
- const stale = findStalePrs(ctx.cwd, staleDays);
8187
- const report = formatStaleReport(stale, staleDays);
8188
- process.stdout.write(`${report}
8189
- `);
8190
- if (reportIssueNumber) {
8191
- try {
8192
- postIssueComment(reportIssueNumber, report, ctx.cwd);
8193
- } catch (err) {
8194
- process.stderr.write(
8195
- `[kody watch] failed to post to issue #${reportIssueNumber}: ${err instanceof Error ? err.message : String(err)}
8196
- `
8197
- );
8198
- }
8199
- }
8200
- ctx.output.exitCode = 0;
8201
- ctx.data.staleCount = stale.length;
8202
- };
8203
-
8204
7890
  // src/scripts/writeIssueStateComment.ts
8205
7891
  var writeIssueStateComment = async (ctx, _profile, _agentResult, args) => {
8206
7892
  const marker = String(args?.marker ?? "");
@@ -8268,7 +7954,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
8268
7954
  };
8269
7955
 
8270
7956
  // src/scripts/writeRunSummary.ts
8271
- import * as fs26 from "fs";
7957
+ import * as fs25 from "fs";
8272
7958
  var writeRunSummary = async (ctx, profile) => {
8273
7959
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
8274
7960
  if (!summaryPath) return;
@@ -8290,7 +7976,7 @@ var writeRunSummary = async (ctx, profile) => {
8290
7976
  if (reason) lines.push(`- **Reason:** ${reason}`);
8291
7977
  lines.push("");
8292
7978
  try {
8293
- fs26.appendFileSync(summaryPath, `${lines.join("\n")}
7979
+ fs25.appendFileSync(summaryPath, `${lines.join("\n")}
8294
7980
  `);
8295
7981
  } catch {
8296
7982
  }
@@ -8306,15 +7992,13 @@ var preflightScripts = {
8306
7992
  reviewFlow,
8307
7993
  syncFlow,
8308
7994
  initFlow,
8309
- watchStalePrsFlow,
8310
- memorizeFlow,
8311
7995
  loadTaskState,
8312
- loadVaultContext,
8313
7996
  loadIssueContext,
8314
7997
  loadIssueStateComment,
8315
7998
  loadJobFromFile,
8316
7999
  loadConventions,
8317
8000
  loadCoverageRules,
8001
+ loadMemoryContext,
8318
8002
  loadPriorArt,
8319
8003
  loadQaGuide,
8320
8004
  buildSyntheticPlugin,
@@ -8348,7 +8032,6 @@ var postflightScripts = {
8348
8032
  stageMergeConflicts,
8349
8033
  commitAndPush: commitAndPush2,
8350
8034
  ensurePr: ensurePr2,
8351
- ensureMemorizePr,
8352
8035
  postIssueComment: postIssueComment2,
8353
8036
  postPlanComment,
8354
8037
  postResearchComment,
@@ -8378,7 +8061,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
8378
8061
  ]);
8379
8062
 
8380
8063
  // src/tools.ts
8381
- import { execFileSync as execFileSync28 } from "child_process";
8064
+ import { execFileSync as execFileSync27 } from "child_process";
8382
8065
  function verifyCliTools(tools, cwd) {
8383
8066
  const out = [];
8384
8067
  for (const t of tools) out.push(verifyOne(t, cwd));
@@ -8411,7 +8094,7 @@ function verifyOne(tool, cwd) {
8411
8094
  }
8412
8095
  function runShell(cmd, cwd, timeoutMs = 3e4) {
8413
8096
  try {
8414
- execFileSync28("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
8097
+ execFileSync27("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
8415
8098
  return true;
8416
8099
  } catch {
8417
8100
  return false;
@@ -8480,9 +8163,9 @@ async function runExecutable(profileName, input) {
8480
8163
  data: {},
8481
8164
  output: { exitCode: 0 }
8482
8165
  };
8483
- const ndjsonDir = path24.join(input.cwd, ".kody");
8166
+ const ndjsonDir = path23.join(input.cwd, ".kody");
8484
8167
  const invokeAgent = async (prompt) => {
8485
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path24.isAbsolute(p) ? p : path24.resolve(profile.dir, p)).filter((p) => p.length > 0);
8168
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path23.isAbsolute(p) ? p : path23.resolve(profile.dir, p)).filter((p) => p.length > 0);
8486
8169
  const syntheticPath = ctx.data.syntheticPluginPath;
8487
8170
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
8488
8171
  return runAgent({
@@ -8577,17 +8260,17 @@ async function runExecutable(profileName, input) {
8577
8260
  function resolveProfilePath(profileName) {
8578
8261
  const found = resolveExecutable(profileName);
8579
8262
  if (found) return found;
8580
- const here = path24.dirname(new URL(import.meta.url).pathname);
8263
+ const here = path23.dirname(new URL(import.meta.url).pathname);
8581
8264
  const candidates = [
8582
- path24.join(here, "executables", profileName, "profile.json"),
8265
+ path23.join(here, "executables", profileName, "profile.json"),
8583
8266
  // same-dir sibling (dev)
8584
- path24.join(here, "..", "executables", profileName, "profile.json"),
8267
+ path23.join(here, "..", "executables", profileName, "profile.json"),
8585
8268
  // up one (prod: dist/bin → dist/executables)
8586
- path24.join(here, "..", "src", "executables", profileName, "profile.json")
8269
+ path23.join(here, "..", "src", "executables", profileName, "profile.json")
8587
8270
  // fallback
8588
8271
  ];
8589
8272
  for (const c of candidates) {
8590
- if (fs27.existsSync(c)) return c;
8273
+ if (fs26.existsSync(c)) return c;
8591
8274
  }
8592
8275
  return candidates[0];
8593
8276
  }
@@ -8691,8 +8374,8 @@ function resolveShellTimeoutMs(entry) {
8691
8374
  var SIGKILL_GRACE_MS = 5e3;
8692
8375
  async function runShellEntry(entry, ctx, profile) {
8693
8376
  const shellName = entry.shell;
8694
- const shellPath = path24.join(profile.dir, shellName);
8695
- if (!fs27.existsSync(shellPath)) {
8377
+ const shellPath = path23.join(profile.dir, shellName);
8378
+ if (!fs26.existsSync(shellPath)) {
8696
8379
  ctx.skipAgent = true;
8697
8380
  ctx.output.exitCode = 99;
8698
8381
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -8953,7 +8636,7 @@ async function runContainerLoop(profile, ctx, input) {
8953
8636
  }
8954
8637
  function resetWorkingTree(cwd) {
8955
8638
  try {
8956
- execFileSync29("git", ["reset", "--hard", "HEAD"], {
8639
+ execFileSync28("git", ["reset", "--hard", "HEAD"], {
8957
8640
  cwd,
8958
8641
  stdio: ["ignore", "pipe", "pipe"],
8959
8642
  timeout: 3e4
@@ -9105,14 +8788,14 @@ function resolveAuthToken(env = process.env) {
9105
8788
  return token;
9106
8789
  }
9107
8790
  function detectPackageManager2(cwd) {
9108
- if (fs28.existsSync(path25.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
9109
- if (fs28.existsSync(path25.join(cwd, "yarn.lock"))) return "yarn";
9110
- if (fs28.existsSync(path25.join(cwd, "bun.lockb"))) return "bun";
8791
+ if (fs27.existsSync(path24.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
8792
+ if (fs27.existsSync(path24.join(cwd, "yarn.lock"))) return "yarn";
8793
+ if (fs27.existsSync(path24.join(cwd, "bun.lockb"))) return "bun";
9111
8794
  return "npm";
9112
8795
  }
9113
8796
  function shellOut(cmd, args, cwd, stream = true) {
9114
8797
  try {
9115
- execFileSync30(cmd, args, {
8798
+ execFileSync29(cmd, args, {
9116
8799
  cwd,
9117
8800
  stdio: stream ? "inherit" : "pipe",
9118
8801
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
@@ -9125,7 +8808,7 @@ function shellOut(cmd, args, cwd, stream = true) {
9125
8808
  }
9126
8809
  function isOnPath(bin) {
9127
8810
  try {
9128
- execFileSync30("which", [bin], { stdio: "pipe" });
8811
+ execFileSync29("which", [bin], { stdio: "pipe" });
9129
8812
  return true;
9130
8813
  } catch {
9131
8814
  return false;
@@ -9166,7 +8849,7 @@ function installLitellmIfNeeded(cwd) {
9166
8849
  } catch {
9167
8850
  }
9168
8851
  try {
9169
- execFileSync30("python3", ["-c", "import litellm"], { stdio: "pipe" });
8852
+ execFileSync29("python3", ["-c", "import litellm"], { stdio: "pipe" });
9170
8853
  process.stdout.write("\u2192 kody: litellm already installed\n");
9171
8854
  return 0;
9172
8855
  } catch {
@@ -9176,16 +8859,16 @@ function installLitellmIfNeeded(cwd) {
9176
8859
  }
9177
8860
  function configureGitIdentity(cwd) {
9178
8861
  try {
9179
- const name = execFileSync30("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
8862
+ const name = execFileSync29("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
9180
8863
  if (name) return;
9181
8864
  } catch {
9182
8865
  }
9183
8866
  try {
9184
- execFileSync30("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
8867
+ execFileSync29("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
9185
8868
  } catch {
9186
8869
  }
9187
8870
  try {
9188
- execFileSync30("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
8871
+ execFileSync29("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
9189
8872
  cwd,
9190
8873
  stdio: "pipe"
9191
8874
  });
@@ -9194,11 +8877,11 @@ function configureGitIdentity(cwd) {
9194
8877
  }
9195
8878
  function postFailureTail(issueNumber, cwd, reason) {
9196
8879
  if (!issueNumber) return;
9197
- const logPath = path25.join(cwd, ".kody", "last-run.jsonl");
8880
+ const logPath = path24.join(cwd, ".kody", "last-run.jsonl");
9198
8881
  let tail = "";
9199
8882
  try {
9200
- if (fs28.existsSync(logPath)) {
9201
- const content = fs28.readFileSync(logPath, "utf-8");
8883
+ if (fs27.existsSync(logPath)) {
8884
+ const content = fs27.readFileSync(logPath, "utf-8");
9202
8885
  tail = content.slice(-3e3);
9203
8886
  }
9204
8887
  } catch {
@@ -9223,7 +8906,7 @@ async function runCi(argv) {
9223
8906
  return 0;
9224
8907
  }
9225
8908
  const args = parseCiArgs(argv);
9226
- const cwd = args.cwd ? path25.resolve(args.cwd) : process.cwd();
8909
+ const cwd = args.cwd ? path24.resolve(args.cwd) : process.cwd();
9227
8910
  let earlyConfig;
9228
8911
  try {
9229
8912
  earlyConfig = loadConfig(cwd);
@@ -9233,9 +8916,9 @@ async function runCi(argv) {
9233
8916
  const eventName = process.env.GITHUB_EVENT_NAME;
9234
8917
  const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
9235
8918
  let manualWorkflowDispatch = false;
9236
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs28.existsSync(dispatchEventPath)) {
8919
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs27.existsSync(dispatchEventPath)) {
9237
8920
  try {
9238
- const evt = JSON.parse(fs28.readFileSync(dispatchEventPath, "utf-8"));
8921
+ const evt = JSON.parse(fs27.readFileSync(dispatchEventPath, "utf-8"));
9239
8922
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
9240
8923
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
9241
8924
  manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
@@ -9450,15 +9133,15 @@ function parseChatArgs(argv, env = process.env) {
9450
9133
  return result;
9451
9134
  }
9452
9135
  function commitChatFiles(cwd, sessionId, verbose) {
9453
- const sessionFile = path26.relative(cwd, sessionFilePath(cwd, sessionId));
9454
- const eventsFile = path26.relative(cwd, eventsFilePath(cwd, sessionId));
9455
- const paths = [sessionFile, eventsFile].filter((p) => fs29.existsSync(path26.join(cwd, p)));
9136
+ const sessionFile = path25.relative(cwd, sessionFilePath(cwd, sessionId));
9137
+ const eventsFile = path25.relative(cwd, eventsFilePath(cwd, sessionId));
9138
+ const paths = [sessionFile, eventsFile].filter((p) => fs28.existsSync(path25.join(cwd, p)));
9456
9139
  if (paths.length === 0) return;
9457
9140
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
9458
9141
  try {
9459
- execFileSync31("git", ["add", "-f", ...paths], opts);
9460
- execFileSync31("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
9461
- execFileSync31("git", ["push", "--quiet", "origin", "HEAD"], opts);
9142
+ execFileSync30("git", ["add", "-f", ...paths], opts);
9143
+ execFileSync30("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
9144
+ execFileSync30("git", ["push", "--quiet", "origin", "HEAD"], opts);
9462
9145
  } catch (err) {
9463
9146
  const msg = err instanceof Error ? err.message : String(err);
9464
9147
  process.stderr.write(`[kody:chat] commit/push skipped: ${msg}
@@ -9490,7 +9173,7 @@ async function runChat(argv) {
9490
9173
  ${CHAT_HELP}`);
9491
9174
  return 64;
9492
9175
  }
9493
- const cwd = args.cwd ? path26.resolve(args.cwd) : process.cwd();
9176
+ const cwd = args.cwd ? path25.resolve(args.cwd) : process.cwd();
9494
9177
  const sessionId = args.sessionId;
9495
9178
  const unpackedSecrets = unpackAllSecrets();
9496
9179
  if (unpackedSecrets > 0) {
@@ -9542,7 +9225,7 @@ ${CHAT_HELP}`);
9542
9225
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
9543
9226
  const meta = readMeta(sessionFile);
9544
9227
  process.stdout.write(
9545
- `\u2192 kody:chat: session file=${sessionFile} exists=${fs29.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
9228
+ `\u2192 kody:chat: session file=${sessionFile} exists=${fs28.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
9546
9229
  `
9547
9230
  );
9548
9231
  try {