@kody-ade/kody-engine-lite 0.1.57 → 0.1.58

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.
Files changed (2) hide show
  1. package/dist/bin/cli.js +616 -262
  2. package/package.json +1 -1
package/dist/bin/cli.js CHANGED
@@ -267,7 +267,8 @@ function getProjectConfig() {
267
267
  git: { ...DEFAULT_CONFIG.git, ...raw.git },
268
268
  github: { ...DEFAULT_CONFIG.github, ...raw.github },
269
269
  paths: { ...DEFAULT_CONFIG.paths, ...raw.paths },
270
- agent: { ...DEFAULT_CONFIG.agent, ...raw.agent }
270
+ agent: { ...DEFAULT_CONFIG.agent, ...raw.agent },
271
+ contextTiers: raw.contextTiers ? { ...DEFAULT_CONFIG.contextTiers, ...raw.contextTiers } : DEFAULT_CONFIG.contextTiers
271
272
  };
272
273
  } catch {
273
274
  logger.warn("kody.config.json is invalid JSON \u2014 using defaults");
@@ -306,6 +307,10 @@ var init_config = __esm({
306
307
  runner: "claude-code",
307
308
  defaultRunner: "claude",
308
309
  modelMap: { cheap: "haiku", mid: "sonnet", strong: "opus" }
310
+ },
311
+ contextTiers: {
312
+ enabled: true,
313
+ tokenBudget: 8e3
309
314
  }
310
315
  };
311
316
  VERIFY_COMMAND_TIMEOUT_MS = 5 * 60 * 1e3;
@@ -775,25 +780,342 @@ var init_memory = __esm({
775
780
  }
776
781
  });
777
782
 
778
- // src/context.ts
783
+ // src/context-tiers.ts
779
784
  import * as fs4 from "fs";
780
785
  import * as path4 from "path";
786
+ import * as crypto2 from "crypto";
787
+ function estimateTokens(text) {
788
+ return Math.ceil(text.length / 4);
789
+ }
790
+ function contentHash(content) {
791
+ return crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
792
+ }
793
+ function resolveStagePolicy(stageName, stageOverrides) {
794
+ const defaults = DEFAULT_STAGE_POLICIES[stageName] ?? DEFAULT_STAGE_POLICIES.build;
795
+ const overrides = stageOverrides?.[stageName];
796
+ return overrides ? { ...defaults, ...overrides } : { ...defaults };
797
+ }
798
+ function generateL0(content, filename) {
799
+ if (!content.trim()) return "";
800
+ if (filename.endsWith(".json")) {
801
+ return generateL0Json(content);
802
+ }
803
+ const lines = content.split("\n");
804
+ const parts = [];
805
+ const headingLine = lines.find((l) => l.startsWith("#"));
806
+ if (headingLine) parts.push(headingLine);
807
+ for (const line of lines) {
808
+ if (!line.trim() || line.startsWith("#")) continue;
809
+ if (line.startsWith("-") || line.startsWith("*")) continue;
810
+ const sentence = line.split(/\.\s/)[0];
811
+ if (sentence && sentence.length > 10) {
812
+ parts.push(sentence.endsWith(".") ? sentence : sentence + ".");
813
+ break;
814
+ }
815
+ }
816
+ const result = parts.join("\n");
817
+ return result.slice(0, L0_MAX_CHARS);
818
+ }
819
+ function generateL0Json(content) {
820
+ try {
821
+ const cleaned = content.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
822
+ const obj = JSON.parse(cleaned);
823
+ const fields = [];
824
+ if (obj.title) fields.push(`Title: ${obj.title}`);
825
+ if (obj.task_type) fields.push(`Type: ${obj.task_type}`);
826
+ if (obj.risk_level) fields.push(`Risk: ${obj.risk_level}`);
827
+ if (obj.estimated_complexity) fields.push(`Complexity: ${obj.estimated_complexity}`);
828
+ return fields.join(" | ") || content.slice(0, L0_MAX_CHARS);
829
+ } catch {
830
+ return content.slice(0, L0_MAX_CHARS);
831
+ }
832
+ }
833
+ function generateL1(content, filename) {
834
+ if (!content.trim()) return "";
835
+ if (filename.endsWith(".json")) {
836
+ return generateL1Json(content);
837
+ }
838
+ const lines = content.split("\n");
839
+ const parts = [];
840
+ let inSection = false;
841
+ for (const line of lines) {
842
+ if (line.startsWith("#")) {
843
+ parts.push(line);
844
+ inSection = true;
845
+ continue;
846
+ }
847
+ if (line.match(/^\s*[-*]\s/)) {
848
+ const recentBullets = parts.slice(-5).filter((p) => p.match(/^\s*[-*]\s/)).length;
849
+ if (recentBullets < 5) {
850
+ parts.push(line);
851
+ }
852
+ continue;
853
+ }
854
+ if (inSection && line.trim()) {
855
+ parts.push(line);
856
+ inSection = false;
857
+ }
858
+ }
859
+ const result = parts.join("\n");
860
+ return result.slice(0, L1_MAX_CHARS);
861
+ }
862
+ function generateL1Json(content) {
863
+ try {
864
+ const cleaned = content.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
865
+ const obj = JSON.parse(cleaned);
866
+ const lines = [];
867
+ for (const [key, value] of Object.entries(obj)) {
868
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
869
+ lines.push(`- ${key}: ${value}`);
870
+ } else if (Array.isArray(value)) {
871
+ lines.push(`- ${key}: [${value.length} items] ${value.slice(0, 3).join(", ")}`);
872
+ }
873
+ }
874
+ return lines.join("\n").slice(0, L1_MAX_CHARS);
875
+ } catch {
876
+ return content.slice(0, L1_MAX_CHARS);
877
+ }
878
+ }
879
+ function readCache(cacheDir) {
880
+ const cachePath = path4.join(cacheDir, "tier-cache.json");
881
+ if (!fs4.existsSync(cachePath)) return { version: 1, entries: {} };
882
+ try {
883
+ return JSON.parse(fs4.readFileSync(cachePath, "utf-8"));
884
+ } catch {
885
+ return { version: 1, entries: {} };
886
+ }
887
+ }
888
+ function writeCache(cacheDir, cache) {
889
+ fs4.mkdirSync(cacheDir, { recursive: true });
890
+ fs4.writeFileSync(path4.join(cacheDir, "tier-cache.json"), JSON.stringify(cache, null, 2));
891
+ }
892
+ function getTieredContent(filePath, content, cacheDir) {
893
+ const hash = contentHash(content);
894
+ const key = path4.basename(filePath);
895
+ const cache = readCache(cacheDir);
896
+ if (cache.entries[key] && cache.entries[key].hash === hash) {
897
+ return cache.entries[key];
898
+ }
899
+ const tiered = {
900
+ source: filePath,
901
+ hash,
902
+ L0: generateL0(content, key),
903
+ L1: generateL1(content, key),
904
+ L2: content
905
+ };
906
+ cache.entries[key] = tiered;
907
+ writeCache(cacheDir, cache);
908
+ return tiered;
909
+ }
910
+ function invalidateCache(filePath, cacheDir) {
911
+ const key = path4.basename(filePath);
912
+ const cache = readCache(cacheDir);
913
+ if (cache.entries[key]) {
914
+ delete cache.entries[key];
915
+ writeCache(cacheDir, cache);
916
+ }
917
+ }
918
+ function selectTier(tiered, tier) {
919
+ return tiered[tier];
920
+ }
921
+ function readProjectMemoryTiered(projectDir, tier) {
922
+ const memoryDir = path4.join(projectDir, ".kody", "memory");
923
+ if (!fs4.existsSync(memoryDir)) return "";
924
+ const files = fs4.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
925
+ if (files.length === 0) return "";
926
+ const cacheDir = path4.join(memoryDir, ".tiers");
927
+ const tierLabel2 = tier === "L2" ? "full" : tier === "L1" ? "overview" : "abstract";
928
+ const sections = [];
929
+ for (const file of files) {
930
+ const filePath = path4.join(memoryDir, file);
931
+ const content = fs4.readFileSync(filePath, "utf-8").trim();
932
+ if (!content) continue;
933
+ const tiered = getTieredContent(filePath, content, cacheDir);
934
+ const selected = selectTier(tiered, tier);
935
+ if (selected) {
936
+ sections.push(`## ${file.replace(".md", "")}
937
+ ${selected}`);
938
+ }
939
+ }
940
+ if (sections.length === 0) return "";
941
+ const header = tier === "L2" ? "# Project Memory\n" : `# Project Memory (${tierLabel2} \u2014 use Read tool for full details)
942
+ `;
943
+ return `${header}
944
+ ${sections.join("\n\n")}
945
+ `;
946
+ }
947
+ function injectTaskContextTiered(prompt, taskId, taskDir, policy, feedback) {
948
+ const cacheDir = path4.join(taskDir, ".tiers");
949
+ let context = `## Task Context
950
+ `;
951
+ context += `Task ID: ${taskId}
952
+ `;
953
+ context += `Task Directory: ${taskDir}
954
+ `;
955
+ const taskMdPath = path4.join(taskDir, "task.md");
956
+ if (fs4.existsSync(taskMdPath)) {
957
+ const content = fs4.readFileSync(taskMdPath, "utf-8");
958
+ const selected = selectContent(taskMdPath, content, cacheDir, policy.taskDescription);
959
+ const label = tierLabel("Task Description", policy.taskDescription);
960
+ context += `
961
+ ## ${label}
962
+ ${selected}
963
+ `;
964
+ }
965
+ const taskJsonPath = path4.join(taskDir, "task.json");
966
+ if (fs4.existsSync(taskJsonPath)) {
967
+ const content = fs4.readFileSync(taskJsonPath, "utf-8");
968
+ if (policy.taskClassification === "L2") {
969
+ try {
970
+ const taskDef = JSON.parse(content.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, ""));
971
+ context += `
972
+ ## Task Classification
973
+ `;
974
+ context += `Type: ${taskDef.task_type ?? "unknown"}
975
+ `;
976
+ context += `Title: ${taskDef.title ?? "unknown"}
977
+ `;
978
+ context += `Risk: ${taskDef.risk_level ?? "unknown"}
979
+ `;
980
+ } catch {
981
+ }
982
+ } else {
983
+ const selected = selectContent(taskJsonPath, content, cacheDir, policy.taskClassification);
984
+ if (selected) {
985
+ const label = tierLabel("Task Classification", policy.taskClassification);
986
+ context += `
987
+ ## ${label}
988
+ ${selected}
989
+ `;
990
+ }
991
+ }
992
+ }
993
+ const specPath = path4.join(taskDir, "spec.md");
994
+ if (fs4.existsSync(specPath)) {
995
+ const content = fs4.readFileSync(specPath, "utf-8");
996
+ const selected = selectContent(specPath, content, cacheDir, policy.spec);
997
+ const label = tierLabel("Spec", policy.spec);
998
+ context += `
999
+ ## ${label}
1000
+ ${selected}
1001
+ `;
1002
+ }
1003
+ const planPath = path4.join(taskDir, "plan.md");
1004
+ if (fs4.existsSync(planPath)) {
1005
+ const content = fs4.readFileSync(planPath, "utf-8");
1006
+ const selected = selectContent(planPath, content, cacheDir, policy.plan);
1007
+ const label = tierLabel("Plan", policy.plan);
1008
+ context += `
1009
+ ## ${label}
1010
+ ${selected}
1011
+ `;
1012
+ }
1013
+ const contextMdPath = path4.join(taskDir, "context.md");
1014
+ if (fs4.existsSync(contextMdPath)) {
1015
+ const content = fs4.readFileSync(contextMdPath, "utf-8");
1016
+ const selected = selectContent(contextMdPath, content, cacheDir, policy.accumulatedContext);
1017
+ const label = tierLabel("Previous Stage Context", policy.accumulatedContext);
1018
+ context += `
1019
+ ## ${label}
1020
+ ${selected}
1021
+ `;
1022
+ }
1023
+ if (feedback) {
1024
+ context += `
1025
+ ## Human Feedback
1026
+ ${feedback}
1027
+ `;
1028
+ }
1029
+ return prompt.replace("{{TASK_CONTEXT}}", context);
1030
+ }
1031
+ function selectContent(filePath, content, cacheDir, tier) {
1032
+ if (tier === "L2") return content;
1033
+ const tiered = getTieredContent(filePath, content, cacheDir);
1034
+ return selectTier(tiered, tier);
1035
+ }
1036
+ function tierLabel(sectionName, tier) {
1037
+ if (tier === "L2") return sectionName;
1038
+ if (tier === "L1") return `${sectionName} (overview)`;
1039
+ return `${sectionName} (abstract)`;
1040
+ }
1041
+ var DEFAULT_STAGE_POLICIES, L0_MAX_CHARS, L1_MAX_CHARS;
1042
+ var init_context_tiers = __esm({
1043
+ "src/context-tiers.ts"() {
1044
+ "use strict";
1045
+ DEFAULT_STAGE_POLICIES = {
1046
+ taskify: {
1047
+ memory: "L1",
1048
+ taskDescription: "L2",
1049
+ taskClassification: "L0",
1050
+ spec: "L0",
1051
+ plan: "L0",
1052
+ accumulatedContext: "L0"
1053
+ },
1054
+ plan: {
1055
+ memory: "L1",
1056
+ taskDescription: "L2",
1057
+ taskClassification: "L2",
1058
+ spec: "L0",
1059
+ plan: "L0",
1060
+ accumulatedContext: "L1"
1061
+ },
1062
+ build: {
1063
+ memory: "L1",
1064
+ taskDescription: "L1",
1065
+ taskClassification: "L1",
1066
+ spec: "L1",
1067
+ plan: "L2",
1068
+ accumulatedContext: "L1"
1069
+ },
1070
+ autofix: {
1071
+ memory: "L0",
1072
+ taskDescription: "L0",
1073
+ taskClassification: "L0",
1074
+ spec: "L0",
1075
+ plan: "L1",
1076
+ accumulatedContext: "L2"
1077
+ },
1078
+ review: {
1079
+ memory: "L1",
1080
+ taskDescription: "L1",
1081
+ taskClassification: "L1",
1082
+ spec: "L0",
1083
+ plan: "L2",
1084
+ accumulatedContext: "L1"
1085
+ },
1086
+ "review-fix": {
1087
+ memory: "L0",
1088
+ taskDescription: "L0",
1089
+ taskClassification: "L0",
1090
+ spec: "L0",
1091
+ plan: "L1",
1092
+ accumulatedContext: "L2"
1093
+ }
1094
+ };
1095
+ L0_MAX_CHARS = 400;
1096
+ L1_MAX_CHARS = 1600;
1097
+ }
1098
+ });
1099
+
1100
+ // src/context.ts
1101
+ import * as fs5 from "fs";
1102
+ import * as path5 from "path";
781
1103
  function readPromptFile(stageName, projectDir) {
782
1104
  if (projectDir) {
783
- const stepFile = path4.join(projectDir, ".kody", "steps", `${stageName}.md`);
784
- if (fs4.existsSync(stepFile)) {
785
- return fs4.readFileSync(stepFile, "utf-8");
1105
+ const stepFile = path5.join(projectDir, ".kody", "steps", `${stageName}.md`);
1106
+ if (fs5.existsSync(stepFile)) {
1107
+ return fs5.readFileSync(stepFile, "utf-8");
786
1108
  }
787
1109
  console.warn(` \u26A0 No step file at ${stepFile}, falling back to engine defaults. Run 'kody-engine-lite init --force' to generate step files.`);
788
1110
  }
789
1111
  const scriptDir = new URL(".", import.meta.url).pathname;
790
1112
  const candidates = [
791
- path4.resolve(scriptDir, "..", "prompts", `${stageName}.md`),
792
- path4.resolve(scriptDir, "..", "..", "prompts", `${stageName}.md`)
1113
+ path5.resolve(scriptDir, "..", "prompts", `${stageName}.md`),
1114
+ path5.resolve(scriptDir, "..", "..", "prompts", `${stageName}.md`)
793
1115
  ];
794
1116
  for (const candidate of candidates) {
795
- if (fs4.existsSync(candidate)) {
796
- return fs4.readFileSync(candidate, "utf-8");
1117
+ if (fs5.existsSync(candidate)) {
1118
+ return fs5.readFileSync(candidate, "utf-8");
797
1119
  }
798
1120
  }
799
1121
  throw new Error(`Prompt file not found: tried ${candidates.join(", ")}`);
@@ -805,18 +1127,18 @@ function injectTaskContext(prompt, taskId, taskDir, feedback) {
805
1127
  `;
806
1128
  context += `Task Directory: ${taskDir}
807
1129
  `;
808
- const taskMdPath = path4.join(taskDir, "task.md");
809
- if (fs4.existsSync(taskMdPath)) {
810
- const taskMd = fs4.readFileSync(taskMdPath, "utf-8");
1130
+ const taskMdPath = path5.join(taskDir, "task.md");
1131
+ if (fs5.existsSync(taskMdPath)) {
1132
+ const taskMd = fs5.readFileSync(taskMdPath, "utf-8");
811
1133
  context += `
812
1134
  ## Task Description
813
1135
  ${taskMd}
814
1136
  `;
815
1137
  }
816
- const taskJsonPath = path4.join(taskDir, "task.json");
817
- if (fs4.existsSync(taskJsonPath)) {
1138
+ const taskJsonPath = path5.join(taskDir, "task.json");
1139
+ if (fs5.existsSync(taskJsonPath)) {
818
1140
  try {
819
- const taskDef = JSON.parse(fs4.readFileSync(taskJsonPath, "utf-8"));
1141
+ const taskDef = JSON.parse(fs5.readFileSync(taskJsonPath, "utf-8"));
820
1142
  context += `
821
1143
  ## Task Classification
822
1144
  `;
@@ -829,27 +1151,27 @@ ${taskMd}
829
1151
  } catch {
830
1152
  }
831
1153
  }
832
- const specPath = path4.join(taskDir, "spec.md");
833
- if (fs4.existsSync(specPath)) {
834
- const spec = fs4.readFileSync(specPath, "utf-8");
1154
+ const specPath = path5.join(taskDir, "spec.md");
1155
+ if (fs5.existsSync(specPath)) {
1156
+ const spec = fs5.readFileSync(specPath, "utf-8");
835
1157
  const truncated = spec.slice(0, MAX_TASK_CONTEXT_SPEC);
836
1158
  context += `
837
1159
  ## Spec Summary
838
1160
  ${truncated}${spec.length > MAX_TASK_CONTEXT_SPEC ? "\n..." : ""}
839
1161
  `;
840
1162
  }
841
- const planPath = path4.join(taskDir, "plan.md");
842
- if (fs4.existsSync(planPath)) {
843
- const plan = fs4.readFileSync(planPath, "utf-8");
1163
+ const planPath = path5.join(taskDir, "plan.md");
1164
+ if (fs5.existsSync(planPath)) {
1165
+ const plan = fs5.readFileSync(planPath, "utf-8");
844
1166
  const truncated = plan.slice(0, MAX_TASK_CONTEXT_PLAN);
845
1167
  context += `
846
1168
  ## Plan Summary
847
1169
  ${truncated}${plan.length > MAX_TASK_CONTEXT_PLAN ? "\n..." : ""}
848
1170
  `;
849
1171
  }
850
- const contextMdPath = path4.join(taskDir, "context.md");
851
- if (fs4.existsSync(contextMdPath)) {
852
- const accumulated = fs4.readFileSync(contextMdPath, "utf-8");
1172
+ const contextMdPath = path5.join(taskDir, "context.md");
1173
+ if (fs5.existsSync(contextMdPath)) {
1174
+ const accumulated = fs5.readFileSync(contextMdPath, "utf-8");
853
1175
  const truncated = accumulated.slice(-MAX_ACCUMULATED_CONTEXT);
854
1176
  const prefix = accumulated.length > MAX_ACCUMULATED_CONTEXT ? "...(earlier context truncated)\n" : "";
855
1177
  context += `
@@ -866,6 +1188,10 @@ ${feedback}
866
1188
  return prompt.replace("{{TASK_CONTEXT}}", context);
867
1189
  }
868
1190
  function buildFullPrompt(stageName, taskId, taskDir, projectDir, feedback) {
1191
+ const config = getProjectConfig();
1192
+ if (config.contextTiers?.enabled) {
1193
+ return buildFullPromptTiered(stageName, taskId, taskDir, projectDir, feedback);
1194
+ }
869
1195
  const memory = readProjectMemory(projectDir);
870
1196
  const promptTemplate = readPromptFile(stageName, projectDir);
871
1197
  const prompt = injectTaskContext(promptTemplate, taskId, taskDir, feedback);
@@ -874,6 +1200,24 @@ function buildFullPrompt(stageName, taskId, taskDir, projectDir, feedback) {
874
1200
 
875
1201
  ${prompt}` : prompt;
876
1202
  }
1203
+ function buildFullPromptTiered(stageName, taskId, taskDir, projectDir, feedback) {
1204
+ const config = getProjectConfig();
1205
+ const policy = resolveStagePolicy(stageName, config.contextTiers?.stageOverrides);
1206
+ const tokenBudget = config.contextTiers?.tokenBudget ?? 8e3;
1207
+ const memory = readProjectMemoryTiered(projectDir, policy.memory);
1208
+ const promptTemplate = readPromptFile(stageName, projectDir);
1209
+ const prompt = injectTaskContextTiered(promptTemplate, taskId, taskDir, policy, feedback);
1210
+ let assembled = memory ? `${memory}
1211
+ ---
1212
+
1213
+ ${prompt}` : prompt;
1214
+ const tokens = estimateTokens(assembled);
1215
+ if (tokens > tokenBudget) {
1216
+ const maxChars = tokenBudget * 4;
1217
+ assembled = assembled.slice(0, maxChars) + "\n...(context truncated to fit token budget)";
1218
+ }
1219
+ return assembled;
1220
+ }
877
1221
  function resolveModel(modelTier, stageName) {
878
1222
  const config = getProjectConfig();
879
1223
  if (config.agent.usePerStageRouting && stageName) {
@@ -889,6 +1233,7 @@ var init_context = __esm({
889
1233
  "use strict";
890
1234
  init_memory();
891
1235
  init_config();
1236
+ init_context_tiers();
892
1237
  DEFAULT_MODEL_MAP = {
893
1238
  cheap: "haiku",
894
1239
  mid: "sonnet",
@@ -969,8 +1314,8 @@ var init_runner_selection = __esm({
969
1314
  });
970
1315
 
971
1316
  // src/stages/agent.ts
972
- import * as fs5 from "fs";
973
- import * as path5 from "path";
1317
+ import * as fs6 from "fs";
1318
+ import * as path6 from "path";
974
1319
  function getSessionInfo(stageName, sessions) {
975
1320
  const group = SESSION_GROUP[stageName];
976
1321
  if (!group) return void 0;
@@ -1023,27 +1368,27 @@ async function executeAgentStage(ctx, def) {
1023
1368
  return { outcome: result.outcome, error: result.error, retries: 0 };
1024
1369
  }
1025
1370
  if (def.outputFile && result.output) {
1026
- fs5.writeFileSync(path5.join(ctx.taskDir, def.outputFile), result.output);
1371
+ fs6.writeFileSync(path6.join(ctx.taskDir, def.outputFile), result.output);
1027
1372
  }
1028
1373
  if (def.outputFile) {
1029
- const outputPath = path5.join(ctx.taskDir, def.outputFile);
1030
- if (!fs5.existsSync(outputPath)) {
1031
- const ext = path5.extname(def.outputFile);
1032
- const base = path5.basename(def.outputFile, ext);
1033
- const files = fs5.readdirSync(ctx.taskDir);
1374
+ const outputPath = path6.join(ctx.taskDir, def.outputFile);
1375
+ if (!fs6.existsSync(outputPath)) {
1376
+ const ext = path6.extname(def.outputFile);
1377
+ const base = path6.basename(def.outputFile, ext);
1378
+ const files = fs6.readdirSync(ctx.taskDir);
1034
1379
  const variant = files.find(
1035
1380
  (f) => f.startsWith(base + "-") && f.endsWith(ext)
1036
1381
  );
1037
1382
  if (variant) {
1038
- fs5.renameSync(path5.join(ctx.taskDir, variant), outputPath);
1383
+ fs6.renameSync(path6.join(ctx.taskDir, variant), outputPath);
1039
1384
  logger.info(` Renamed variant ${variant} \u2192 ${def.outputFile}`);
1040
1385
  }
1041
1386
  }
1042
1387
  }
1043
1388
  if (def.outputFile) {
1044
- const outputPath = path5.join(ctx.taskDir, def.outputFile);
1045
- if (fs5.existsSync(outputPath)) {
1046
- const content = fs5.readFileSync(outputPath, "utf-8");
1389
+ const outputPath = path6.join(ctx.taskDir, def.outputFile);
1390
+ if (fs6.existsSync(outputPath)) {
1391
+ const content = fs6.readFileSync(outputPath, "utf-8");
1047
1392
  const validation = validateStageOutput(def.name, content);
1048
1393
  if (!validation.valid) {
1049
1394
  if (def.name === "taskify") {
@@ -1057,7 +1402,7 @@ async function executeAgentStage(ctx, def) {
1057
1402
  const stripped = stripFences(retryResult.output);
1058
1403
  const retryValidation = validateTaskJson(stripped);
1059
1404
  if (retryValidation.valid) {
1060
- fs5.writeFileSync(outputPath, retryResult.output);
1405
+ fs6.writeFileSync(outputPath, retryResult.output);
1061
1406
  logger.info(` taskify retry produced valid JSON`);
1062
1407
  } else {
1063
1408
  logger.warn(` taskify retry still invalid: ${retryValidation.error}`);
@@ -1073,7 +1418,7 @@ async function executeAgentStage(ctx, def) {
1073
1418
  return { outcome: "completed", outputFile: def.outputFile, retries: 0 };
1074
1419
  }
1075
1420
  function appendStageContext(taskDir, stageName, output) {
1076
- const contextPath = path5.join(taskDir, "context.md");
1421
+ const contextPath = path6.join(taskDir, "context.md");
1077
1422
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19);
1078
1423
  let summary;
1079
1424
  if (output && output.trim()) {
@@ -1086,7 +1431,7 @@ function appendStageContext(taskDir, stageName, output) {
1086
1431
  ### ${stageName} (${timestamp2})
1087
1432
  ${summary}
1088
1433
  `;
1089
- fs5.appendFileSync(contextPath, entry);
1434
+ fs6.appendFileSync(contextPath, entry);
1090
1435
  }
1091
1436
  var SESSION_GROUP;
1092
1437
  var init_agent = __esm({
@@ -1308,8 +1653,8 @@ Error context:
1308
1653
  });
1309
1654
 
1310
1655
  // src/stages/gate.ts
1311
- import * as fs6 from "fs";
1312
- import * as path6 from "path";
1656
+ import * as fs7 from "fs";
1657
+ import * as path7 from "path";
1313
1658
  function executeGateStage(ctx, def) {
1314
1659
  if (ctx.input.dryRun) {
1315
1660
  logger.info(` [dry-run] skipping ${def.name}`);
@@ -1352,7 +1697,7 @@ ${output}
1352
1697
  `);
1353
1698
  }
1354
1699
  }
1355
- fs6.writeFileSync(path6.join(ctx.taskDir, "verify.md"), lines.join(""));
1700
+ fs7.writeFileSync(path7.join(ctx.taskDir, "verify.md"), lines.join(""));
1356
1701
  return {
1357
1702
  outcome: verifyResult.pass ? "completed" : "failed",
1358
1703
  retries: 0
@@ -1367,8 +1712,8 @@ var init_gate = __esm({
1367
1712
  });
1368
1713
 
1369
1714
  // src/stages/verify.ts
1370
- import * as fs7 from "fs";
1371
- import * as path7 from "path";
1715
+ import * as fs8 from "fs";
1716
+ import * as path8 from "path";
1372
1717
  import { execFileSync as execFileSync6 } from "child_process";
1373
1718
  async function executeVerifyWithAutofix(ctx, def) {
1374
1719
  const maxAttempts = def.maxRetries ?? 2;
@@ -1379,8 +1724,8 @@ async function executeVerifyWithAutofix(ctx, def) {
1379
1724
  return { ...gateResult, retries: attempt };
1380
1725
  }
1381
1726
  if (attempt < maxAttempts) {
1382
- const verifyPath = path7.join(ctx.taskDir, "verify.md");
1383
- const errorOutput = fs7.existsSync(verifyPath) ? fs7.readFileSync(verifyPath, "utf-8") : "Unknown error";
1727
+ const verifyPath = path8.join(ctx.taskDir, "verify.md");
1728
+ const errorOutput = fs8.existsSync(verifyPath) ? fs8.readFileSync(verifyPath, "utf-8") : "Unknown error";
1384
1729
  const modifiedFiles = getModifiedFiles(ctx.projectDir);
1385
1730
  const defaultRunner = getRunnerForStage(ctx, "taskify");
1386
1731
  const diagConfig = getProjectConfig();
@@ -1476,8 +1821,8 @@ var init_verify = __esm({
1476
1821
  });
1477
1822
 
1478
1823
  // src/stages/review.ts
1479
- import * as fs8 from "fs";
1480
- import * as path8 from "path";
1824
+ import * as fs9 from "fs";
1825
+ import * as path9 from "path";
1481
1826
  async function executeReviewWithFix(ctx, def) {
1482
1827
  if (ctx.input.dryRun) {
1483
1828
  return { outcome: "completed", retries: 0 };
@@ -1488,11 +1833,11 @@ async function executeReviewWithFix(ctx, def) {
1488
1833
  if (reviewResult.outcome !== "completed") {
1489
1834
  return reviewResult;
1490
1835
  }
1491
- const reviewFile = path8.join(ctx.taskDir, "review.md");
1492
- if (!fs8.existsSync(reviewFile)) {
1836
+ const reviewFile = path9.join(ctx.taskDir, "review.md");
1837
+ if (!fs9.existsSync(reviewFile)) {
1493
1838
  return { outcome: "failed", retries: 0, error: "review.md not found" };
1494
1839
  }
1495
- const content = fs8.readFileSync(reviewFile, "utf-8");
1840
+ const content = fs9.readFileSync(reviewFile, "utf-8");
1496
1841
  const hasIssues = /\bfail\b/i.test(content) && !/pass/i.test(content);
1497
1842
  if (!hasIssues) {
1498
1843
  return reviewResult;
@@ -1515,15 +1860,15 @@ var init_review = __esm({
1515
1860
  });
1516
1861
 
1517
1862
  // src/stages/ship.ts
1518
- import * as fs9 from "fs";
1519
- import * as path9 from "path";
1863
+ import * as fs10 from "fs";
1864
+ import * as path10 from "path";
1520
1865
  import { execFileSync as execFileSync7 } from "child_process";
1521
1866
  function buildPrBody(ctx) {
1522
1867
  const sections = [];
1523
- const taskJsonPath = path9.join(ctx.taskDir, "task.json");
1524
- if (fs9.existsSync(taskJsonPath)) {
1868
+ const taskJsonPath = path10.join(ctx.taskDir, "task.json");
1869
+ if (fs10.existsSync(taskJsonPath)) {
1525
1870
  try {
1526
- const raw = fs9.readFileSync(taskJsonPath, "utf-8");
1871
+ const raw = fs10.readFileSync(taskJsonPath, "utf-8");
1527
1872
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
1528
1873
  const task = JSON.parse(cleaned);
1529
1874
  if (task.description) {
@@ -1542,9 +1887,9 @@ ${task.scope.map((s) => `- \`${s}\``).join("\n")}`);
1542
1887
  } catch {
1543
1888
  }
1544
1889
  }
1545
- const reviewPath = path9.join(ctx.taskDir, "review.md");
1546
- if (fs9.existsSync(reviewPath)) {
1547
- const review = fs9.readFileSync(reviewPath, "utf-8");
1890
+ const reviewPath = path10.join(ctx.taskDir, "review.md");
1891
+ if (fs10.existsSync(reviewPath)) {
1892
+ const review = fs10.readFileSync(reviewPath, "utf-8");
1548
1893
  const summaryMatch = review.match(/## Summary\s*\n([\s\S]*?)(?=\n## |\n*$)/);
1549
1894
  if (summaryMatch) {
1550
1895
  const summary = summaryMatch[1].trim();
@@ -1561,14 +1906,14 @@ ${summary}`);
1561
1906
  **Review:** ${verdictMatch[1].toUpperCase() === "PASS" ? "\u2705 PASS" : "\u274C FAIL"}`);
1562
1907
  }
1563
1908
  }
1564
- const verifyPath = path9.join(ctx.taskDir, "verify.md");
1565
- if (fs9.existsSync(verifyPath)) {
1566
- const verify = fs9.readFileSync(verifyPath, "utf-8");
1909
+ const verifyPath = path10.join(ctx.taskDir, "verify.md");
1910
+ if (fs10.existsSync(verifyPath)) {
1911
+ const verify = fs10.readFileSync(verifyPath, "utf-8");
1567
1912
  if (/PASS/i.test(verify)) sections.push(`**Verify:** \u2705 typecheck + tests + lint passed`);
1568
1913
  }
1569
- const planPath = path9.join(ctx.taskDir, "plan.md");
1570
- if (fs9.existsSync(planPath)) {
1571
- const plan = fs9.readFileSync(planPath, "utf-8").trim();
1914
+ const planPath = path10.join(ctx.taskDir, "plan.md");
1915
+ if (fs10.existsSync(planPath)) {
1916
+ const plan = fs10.readFileSync(planPath, "utf-8").trim();
1572
1917
  if (plan) {
1573
1918
  const truncated = plan.length > 800 ? plan.slice(0, 800) + "\n..." : plan;
1574
1919
  sections.push(`
@@ -1588,13 +1933,13 @@ Closes #${ctx.input.issueNumber}`);
1588
1933
  return sections.join("\n");
1589
1934
  }
1590
1935
  function executeShipStage(ctx, _def) {
1591
- const shipPath = path9.join(ctx.taskDir, "ship.md");
1936
+ const shipPath = path10.join(ctx.taskDir, "ship.md");
1592
1937
  if (ctx.input.dryRun) {
1593
- fs9.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
1938
+ fs10.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
1594
1939
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
1595
1940
  }
1596
1941
  if (ctx.input.local && !ctx.input.issueNumber) {
1597
- fs9.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
1942
+ fs10.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
1598
1943
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
1599
1944
  }
1600
1945
  try {
@@ -1640,10 +1985,10 @@ function executeShipStage(ctx, _def) {
1640
1985
  docs: "docs",
1641
1986
  chore: "chore"
1642
1987
  };
1643
- const taskJsonPath = path9.join(ctx.taskDir, "task.json");
1644
- if (fs9.existsSync(taskJsonPath)) {
1988
+ const taskJsonPath = path10.join(ctx.taskDir, "task.json");
1989
+ if (fs10.existsSync(taskJsonPath)) {
1645
1990
  try {
1646
- const raw = fs9.readFileSync(taskJsonPath, "utf-8");
1991
+ const raw = fs10.readFileSync(taskJsonPath, "utf-8");
1647
1992
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
1648
1993
  const task = JSON.parse(cleaned);
1649
1994
  const prefix = TYPE_PREFIX[task.task_type] ?? "chore";
@@ -1653,9 +1998,9 @@ function executeShipStage(ctx, _def) {
1653
1998
  }
1654
1999
  }
1655
2000
  if (title === "Update") {
1656
- const taskMdPath = path9.join(ctx.taskDir, "task.md");
1657
- if (fs9.existsSync(taskMdPath)) {
1658
- const content = fs9.readFileSync(taskMdPath, "utf-8");
2001
+ const taskMdPath = path10.join(ctx.taskDir, "task.md");
2002
+ if (fs10.existsSync(taskMdPath)) {
2003
+ const content = fs10.readFileSync(taskMdPath, "utf-8");
1659
2004
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#") && !l.startsWith("*"));
1660
2005
  if (firstLine) title = `chore: ${firstLine.trim()}`.slice(0, 72);
1661
2006
  }
@@ -1675,7 +2020,7 @@ function executeShipStage(ctx, _def) {
1675
2020
  } catch {
1676
2021
  }
1677
2022
  }
1678
- fs9.writeFileSync(shipPath, `# Ship
2023
+ fs10.writeFileSync(shipPath, `# Ship
1679
2024
 
1680
2025
  Updated existing PR: ${existingPr.url}
1681
2026
  PR #${existingPr.number}
@@ -1689,19 +2034,19 @@ PR #${existingPr.number}
1689
2034
  } catch {
1690
2035
  }
1691
2036
  }
1692
- fs9.writeFileSync(shipPath, `# Ship
2037
+ fs10.writeFileSync(shipPath, `# Ship
1693
2038
 
1694
2039
  PR created: ${pr.url}
1695
2040
  PR #${pr.number}
1696
2041
  `);
1697
2042
  } else {
1698
- fs9.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
2043
+ fs10.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
1699
2044
  }
1700
2045
  }
1701
2046
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
1702
2047
  } catch (err) {
1703
2048
  const msg = err instanceof Error ? err.message : String(err);
1704
- fs9.writeFileSync(shipPath, `# Ship
2049
+ fs10.writeFileSync(shipPath, `# Ship
1705
2050
 
1706
2051
  Failed: ${msg}
1707
2052
  `);
@@ -1747,15 +2092,15 @@ var init_executor_registry = __esm({
1747
2092
  });
1748
2093
 
1749
2094
  // src/pipeline/questions.ts
1750
- import * as fs10 from "fs";
1751
- import * as path10 from "path";
2095
+ import * as fs11 from "fs";
2096
+ import * as path11 from "path";
1752
2097
  function checkForQuestions(ctx, stageName) {
1753
2098
  if (ctx.input.local || !ctx.input.issueNumber) return false;
1754
2099
  try {
1755
2100
  if (stageName === "taskify") {
1756
- const taskJsonPath = path10.join(ctx.taskDir, "task.json");
1757
- if (!fs10.existsSync(taskJsonPath)) return false;
1758
- const raw = fs10.readFileSync(taskJsonPath, "utf-8");
2101
+ const taskJsonPath = path11.join(ctx.taskDir, "task.json");
2102
+ if (!fs11.existsSync(taskJsonPath)) return false;
2103
+ const raw = fs11.readFileSync(taskJsonPath, "utf-8");
1759
2104
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
1760
2105
  const taskJson = JSON.parse(cleaned);
1761
2106
  if (taskJson.questions && Array.isArray(taskJson.questions) && taskJson.questions.length > 0) {
@@ -1770,9 +2115,9 @@ Reply with \`@kody approve\` and your answers in the comment body.`;
1770
2115
  }
1771
2116
  }
1772
2117
  if (stageName === "plan") {
1773
- const planPath = path10.join(ctx.taskDir, "plan.md");
1774
- if (!fs10.existsSync(planPath)) return false;
1775
- const plan = fs10.readFileSync(planPath, "utf-8");
2118
+ const planPath = path11.join(ctx.taskDir, "plan.md");
2119
+ if (!fs11.existsSync(planPath)) return false;
2120
+ const plan = fs11.readFileSync(planPath, "utf-8");
1776
2121
  const questionsMatch = plan.match(/## Questions\s*\n([\s\S]*?)(?=\n## |\n*$)/);
1777
2122
  if (questionsMatch) {
1778
2123
  const questionsText = questionsMatch[1].trim();
@@ -1801,8 +2146,8 @@ var init_questions = __esm({
1801
2146
  });
1802
2147
 
1803
2148
  // src/pipeline/hooks.ts
1804
- import * as fs11 from "fs";
1805
- import * as path11 from "path";
2149
+ import * as fs12 from "fs";
2150
+ import * as path12 from "path";
1806
2151
  function applyPreStageLabel(ctx, def) {
1807
2152
  if (!ctx.input.issueNumber || ctx.input.local) return;
1808
2153
  if (def.name === "build") setLifecycleLabel(ctx.input.issueNumber, "building");
@@ -1828,9 +2173,9 @@ function autoDetectComplexity(ctx, def) {
1828
2173
  if (def.name !== "taskify") return null;
1829
2174
  if (ctx.input.complexity) return null;
1830
2175
  try {
1831
- const taskJsonPath = path11.join(ctx.taskDir, "task.json");
1832
- if (!fs11.existsSync(taskJsonPath)) return null;
1833
- const raw = fs11.readFileSync(taskJsonPath, "utf-8");
2176
+ const taskJsonPath = path12.join(ctx.taskDir, "task.json");
2177
+ if (!fs12.existsSync(taskJsonPath)) return null;
2178
+ const raw = fs12.readFileSync(taskJsonPath, "utf-8");
1834
2179
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
1835
2180
  const taskJson = JSON.parse(cleaned);
1836
2181
  if (!taskJson.risk_level || !isValidComplexity(taskJson.risk_level)) return null;
@@ -1860,8 +2205,8 @@ function checkRiskGate(ctx, def, state, complexity) {
1860
2205
  if (ctx.input.dryRun || ctx.input.local) return null;
1861
2206
  if (ctx.input.mode === "rerun") return null;
1862
2207
  if (!ctx.input.issueNumber) return null;
1863
- const planPath = path11.join(ctx.taskDir, "plan.md");
1864
- const plan = fs11.existsSync(planPath) ? fs11.readFileSync(planPath, "utf-8").slice(0, 1500) : "(plan not available)";
2208
+ const planPath = path12.join(ctx.taskDir, "plan.md");
2209
+ const plan = fs12.existsSync(planPath) ? fs12.readFileSync(planPath, "utf-8").slice(0, 1500) : "(plan not available)";
1865
2210
  try {
1866
2211
  postComment(
1867
2212
  ctx.input.issueNumber,
@@ -1928,22 +2273,22 @@ var init_hooks = __esm({
1928
2273
  });
1929
2274
 
1930
2275
  // src/learning/auto-learn.ts
1931
- import * as fs12 from "fs";
1932
- import * as path12 from "path";
2276
+ import * as fs13 from "fs";
2277
+ import * as path13 from "path";
1933
2278
  function stripAnsi(str) {
1934
2279
  return str.replace(/\x1b\[[0-9;]*m/g, "");
1935
2280
  }
1936
2281
  function autoLearn(ctx) {
1937
2282
  try {
1938
- const memoryDir = path12.join(ctx.projectDir, ".kody", "memory");
1939
- if (!fs12.existsSync(memoryDir)) {
1940
- fs12.mkdirSync(memoryDir, { recursive: true });
2283
+ const memoryDir = path13.join(ctx.projectDir, ".kody", "memory");
2284
+ if (!fs13.existsSync(memoryDir)) {
2285
+ fs13.mkdirSync(memoryDir, { recursive: true });
1941
2286
  }
1942
2287
  const learnings = [];
1943
2288
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1944
- const verifyPath = path12.join(ctx.taskDir, "verify.md");
1945
- if (fs12.existsSync(verifyPath)) {
1946
- const verify = stripAnsi(fs12.readFileSync(verifyPath, "utf-8"));
2289
+ const verifyPath = path13.join(ctx.taskDir, "verify.md");
2290
+ if (fs13.existsSync(verifyPath)) {
2291
+ const verify = stripAnsi(fs13.readFileSync(verifyPath, "utf-8"));
1947
2292
  if (/vitest/i.test(verify)) learnings.push("- Uses vitest for testing");
1948
2293
  if (/jest/i.test(verify)) learnings.push("- Uses jest for testing");
1949
2294
  if (/eslint/i.test(verify)) learnings.push("- Uses eslint for linting");
@@ -1952,18 +2297,18 @@ function autoLearn(ctx) {
1952
2297
  if (/jsdom/i.test(verify)) learnings.push("- Test environment: jsdom");
1953
2298
  if (/node/i.test(verify) && /environment/i.test(verify)) learnings.push("- Test environment: node");
1954
2299
  }
1955
- const reviewPath = path12.join(ctx.taskDir, "review.md");
1956
- if (fs12.existsSync(reviewPath)) {
1957
- const review = fs12.readFileSync(reviewPath, "utf-8");
2300
+ const reviewPath = path13.join(ctx.taskDir, "review.md");
2301
+ if (fs13.existsSync(reviewPath)) {
2302
+ const review = fs13.readFileSync(reviewPath, "utf-8");
1958
2303
  if (/\.js extension/i.test(review)) learnings.push("- Imports use .js extensions (ESM)");
1959
2304
  if (/barrel export/i.test(review)) learnings.push("- Uses barrel exports (index.ts)");
1960
2305
  if (/timezone/i.test(review)) learnings.push("- Timezone handling is a concern in this codebase");
1961
2306
  if (/UTC/i.test(review)) learnings.push("- Date operations should consider UTC vs local time");
1962
2307
  }
1963
- const taskJsonPath = path12.join(ctx.taskDir, "task.json");
1964
- if (fs12.existsSync(taskJsonPath)) {
2308
+ const taskJsonPath = path13.join(ctx.taskDir, "task.json");
2309
+ if (fs13.existsSync(taskJsonPath)) {
1965
2310
  try {
1966
- const raw = stripAnsi(fs12.readFileSync(taskJsonPath, "utf-8"));
2311
+ const raw = stripAnsi(fs13.readFileSync(taskJsonPath, "utf-8"));
1967
2312
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
1968
2313
  const task = JSON.parse(cleaned);
1969
2314
  if (task.scope && Array.isArray(task.scope)) {
@@ -1974,12 +2319,13 @@ function autoLearn(ctx) {
1974
2319
  }
1975
2320
  }
1976
2321
  if (learnings.length > 0) {
1977
- const conventionsPath = path12.join(memoryDir, "conventions.md");
2322
+ const conventionsPath = path13.join(memoryDir, "conventions.md");
1978
2323
  const entry = `
1979
2324
  ## Learned ${timestamp2} (task: ${ctx.taskId})
1980
2325
  ${learnings.join("\n")}
1981
2326
  `;
1982
- fs12.appendFileSync(conventionsPath, entry);
2327
+ fs13.appendFileSync(conventionsPath, entry);
2328
+ invalidateCache(conventionsPath, path13.join(memoryDir, ".tiers"));
1983
2329
  logger.info(`Auto-learned ${learnings.length} convention(s)`);
1984
2330
  }
1985
2331
  autoLearnArchitecture(ctx.projectDir, memoryDir, timestamp2);
@@ -1987,13 +2333,13 @@ ${learnings.join("\n")}
1987
2333
  }
1988
2334
  }
1989
2335
  function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
1990
- const archPath = path12.join(memoryDir, "architecture.md");
1991
- if (fs12.existsSync(archPath)) return;
2336
+ const archPath = path13.join(memoryDir, "architecture.md");
2337
+ if (fs13.existsSync(archPath)) return;
1992
2338
  const detected = [];
1993
- const pkgPath = path12.join(projectDir, "package.json");
1994
- if (fs12.existsSync(pkgPath)) {
2339
+ const pkgPath = path13.join(projectDir, "package.json");
2340
+ if (fs13.existsSync(pkgPath)) {
1995
2341
  try {
1996
- const pkg = JSON.parse(fs12.readFileSync(pkgPath, "utf-8"));
2342
+ const pkg = JSON.parse(fs13.readFileSync(pkgPath, "utf-8"));
1997
2343
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1998
2344
  if (allDeps.next) detected.push(`- Framework: Next.js ${allDeps.next}`);
1999
2345
  else if (allDeps.react) detected.push(`- Framework: React ${allDeps.react}`);
@@ -2009,15 +2355,15 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
2009
2355
  if (allDeps.payload || allDeps["@payloadcms/next"]) detected.push(`- CMS: Payload CMS`);
2010
2356
  if (pkg.type === "module") detected.push("- Module system: ESM");
2011
2357
  else detected.push("- Module system: CommonJS");
2012
- if (fs12.existsSync(path12.join(projectDir, "pnpm-lock.yaml"))) detected.push("- Package manager: pnpm");
2013
- else if (fs12.existsSync(path12.join(projectDir, "yarn.lock"))) detected.push("- Package manager: yarn");
2014
- else if (fs12.existsSync(path12.join(projectDir, "package-lock.json"))) detected.push("- Package manager: npm");
2358
+ if (fs13.existsSync(path13.join(projectDir, "pnpm-lock.yaml"))) detected.push("- Package manager: pnpm");
2359
+ else if (fs13.existsSync(path13.join(projectDir, "yarn.lock"))) detected.push("- Package manager: yarn");
2360
+ else if (fs13.existsSync(path13.join(projectDir, "package-lock.json"))) detected.push("- Package manager: npm");
2015
2361
  } catch {
2016
2362
  }
2017
2363
  }
2018
2364
  const topDirs = [];
2019
2365
  try {
2020
- const entries = fs12.readdirSync(projectDir, { withFileTypes: true });
2366
+ const entries = fs13.readdirSync(projectDir, { withFileTypes: true });
2021
2367
  for (const entry of entries) {
2022
2368
  if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
2023
2369
  topDirs.push(entry.name);
@@ -2026,10 +2372,10 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
2026
2372
  if (topDirs.length > 0) detected.push(`- Top-level directories: ${topDirs.join(", ")}`);
2027
2373
  } catch {
2028
2374
  }
2029
- const srcDir = path12.join(projectDir, "src");
2030
- if (fs12.existsSync(srcDir)) {
2375
+ const srcDir = path13.join(projectDir, "src");
2376
+ if (fs13.existsSync(srcDir)) {
2031
2377
  try {
2032
- const srcEntries = fs12.readdirSync(srcDir, { withFileTypes: true });
2378
+ const srcEntries = fs13.readdirSync(srcDir, { withFileTypes: true });
2033
2379
  const srcDirs = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name);
2034
2380
  if (srcDirs.length > 0) detected.push(`- src/ structure: ${srcDirs.join(", ")}`);
2035
2381
  } catch {
@@ -2041,7 +2387,8 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
2041
2387
  ## Overview
2042
2388
  ${detected.join("\n")}
2043
2389
  `;
2044
- fs12.writeFileSync(archPath, content);
2390
+ fs13.writeFileSync(archPath, content);
2391
+ invalidateCache(archPath, path13.join(memoryDir, ".tiers"));
2045
2392
  logger.info(`Auto-detected architecture (${detected.length} items)`);
2046
2393
  }
2047
2394
  }
@@ -2049,17 +2396,18 @@ var init_auto_learn = __esm({
2049
2396
  "src/learning/auto-learn.ts"() {
2050
2397
  "use strict";
2051
2398
  init_logger();
2399
+ init_context_tiers();
2052
2400
  }
2053
2401
  });
2054
2402
 
2055
2403
  // src/retrospective.ts
2056
- import * as fs13 from "fs";
2057
- import * as path13 from "path";
2404
+ import * as fs14 from "fs";
2405
+ import * as path14 from "path";
2058
2406
  function readArtifact(taskDir, filename, maxChars) {
2059
- const p = path13.join(taskDir, filename);
2060
- if (!fs13.existsSync(p)) return null;
2407
+ const p = path14.join(taskDir, filename);
2408
+ if (!fs14.existsSync(p)) return null;
2061
2409
  try {
2062
- const content = fs13.readFileSync(p, "utf-8");
2410
+ const content = fs14.readFileSync(p, "utf-8");
2063
2411
  return content.length > maxChars ? content.slice(0, maxChars) + "\n...(truncated)" : content;
2064
2412
  } catch {
2065
2413
  return null;
@@ -2112,13 +2460,13 @@ function collectRunContext(ctx, state, pipelineStartTime) {
2112
2460
  return lines.join("\n");
2113
2461
  }
2114
2462
  function getLogPath(projectDir) {
2115
- return path13.join(projectDir, ".kody", "memory", "observer-log.jsonl");
2463
+ return path14.join(projectDir, ".kody", "memory", "observer-log.jsonl");
2116
2464
  }
2117
2465
  function readPreviousRetrospectives(projectDir, limit = 10) {
2118
2466
  const logPath = getLogPath(projectDir);
2119
- if (!fs13.existsSync(logPath)) return [];
2467
+ if (!fs14.existsSync(logPath)) return [];
2120
2468
  try {
2121
- const content = fs13.readFileSync(logPath, "utf-8");
2469
+ const content = fs14.readFileSync(logPath, "utf-8");
2122
2470
  const lines = content.split("\n").filter(Boolean);
2123
2471
  const entries = [];
2124
2472
  const start = Math.max(0, lines.length - limit);
@@ -2145,11 +2493,11 @@ function formatPreviousEntries(entries) {
2145
2493
  }
2146
2494
  function appendRetrospectiveEntry(projectDir, entry) {
2147
2495
  const logPath = getLogPath(projectDir);
2148
- const dir = path13.dirname(logPath);
2149
- if (!fs13.existsSync(dir)) {
2150
- fs13.mkdirSync(dir, { recursive: true });
2496
+ const dir = path14.dirname(logPath);
2497
+ if (!fs14.existsSync(dir)) {
2498
+ fs14.mkdirSync(dir, { recursive: true });
2151
2499
  }
2152
- fs13.appendFileSync(logPath, JSON.stringify(entry) + "\n");
2500
+ fs14.appendFileSync(logPath, JSON.stringify(entry) + "\n");
2153
2501
  }
2154
2502
  async function runRetrospective(ctx, state, pipelineStartTime) {
2155
2503
  if (ctx.input.dryRun) return;
@@ -2260,14 +2608,14 @@ If no pipeline flaw is detected, set "pipelineFlaw" to null.
2260
2608
  });
2261
2609
 
2262
2610
  // src/pipeline.ts
2263
- import * as fs14 from "fs";
2264
- import * as path14 from "path";
2611
+ import * as fs15 from "fs";
2612
+ import * as path15 from "path";
2265
2613
  function ensureFeatureBranchIfNeeded(ctx) {
2266
2614
  if (ctx.input.prNumber) return;
2267
2615
  if (!ctx.input.issueNumber || ctx.input.dryRun) return;
2268
2616
  try {
2269
- const taskMdPath = path14.join(ctx.taskDir, "task.md");
2270
- const title = fs14.existsSync(taskMdPath) ? fs14.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
2617
+ const taskMdPath = path15.join(ctx.taskDir, "task.md");
2618
+ const title = fs15.existsSync(taskMdPath) ? fs15.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
2271
2619
  ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
2272
2620
  syncWithDefault(ctx.projectDir);
2273
2621
  } catch (err) {
@@ -2275,10 +2623,10 @@ function ensureFeatureBranchIfNeeded(ctx) {
2275
2623
  }
2276
2624
  }
2277
2625
  function acquireLock(taskDir) {
2278
- const lockPath = path14.join(taskDir, ".lock");
2279
- if (fs14.existsSync(lockPath)) {
2626
+ const lockPath = path15.join(taskDir, ".lock");
2627
+ if (fs15.existsSync(lockPath)) {
2280
2628
  try {
2281
- const pid = parseInt(fs14.readFileSync(lockPath, "utf-8").trim(), 10);
2629
+ const pid = parseInt(fs15.readFileSync(lockPath, "utf-8").trim(), 10);
2282
2630
  try {
2283
2631
  process.kill(pid, 0);
2284
2632
  throw new Error(`Pipeline already running (PID ${pid})`);
@@ -2289,11 +2637,11 @@ function acquireLock(taskDir) {
2289
2637
  if (e instanceof Error && e.message.startsWith("Pipeline already")) throw e;
2290
2638
  }
2291
2639
  }
2292
- fs14.writeFileSync(lockPath, String(process.pid));
2640
+ fs15.writeFileSync(lockPath, String(process.pid));
2293
2641
  }
2294
2642
  function releaseLock(taskDir) {
2295
2643
  try {
2296
- fs14.unlinkSync(path14.join(taskDir, ".lock"));
2644
+ fs15.unlinkSync(path15.join(taskDir, ".lock"));
2297
2645
  } catch {
2298
2646
  }
2299
2647
  }
@@ -2462,7 +2810,7 @@ var init_pipeline = __esm({
2462
2810
 
2463
2811
  // src/preflight.ts
2464
2812
  import { execFileSync as execFileSync8 } from "child_process";
2465
- import * as fs15 from "fs";
2813
+ import * as fs16 from "fs";
2466
2814
  function check(name, fn) {
2467
2815
  try {
2468
2816
  const detail = fn() ?? void 0;
@@ -2515,7 +2863,7 @@ function runPreflight() {
2515
2863
  return v;
2516
2864
  }),
2517
2865
  check("package.json", () => {
2518
- if (!fs15.existsSync("package.json")) throw new Error("not found");
2866
+ if (!fs16.existsSync("package.json")) throw new Error("not found");
2519
2867
  })
2520
2868
  ];
2521
2869
  const failed = checks.filter((c) => !c.ok);
@@ -2536,13 +2884,13 @@ var init_preflight = __esm({
2536
2884
  });
2537
2885
 
2538
2886
  // src/cli/task-resolution.ts
2539
- import * as fs16 from "fs";
2540
- import * as path15 from "path";
2887
+ import * as fs17 from "fs";
2888
+ import * as path16 from "path";
2541
2889
  import { execFileSync as execFileSync9 } from "child_process";
2542
2890
  function findLatestTaskForIssue(issueNumber, projectDir) {
2543
- const tasksDir = path15.join(projectDir, ".kody", "tasks");
2544
- if (!fs16.existsSync(tasksDir)) return null;
2545
- const allDirs = fs16.readdirSync(tasksDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
2891
+ const tasksDir = path16.join(projectDir, ".kody", "tasks");
2892
+ if (!fs17.existsSync(tasksDir)) return null;
2893
+ const allDirs = fs17.readdirSync(tasksDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
2546
2894
  const prefix = `${issueNumber}-`;
2547
2895
  const direct = allDirs.find((d) => d.startsWith(prefix));
2548
2896
  if (direct) return direct;
@@ -2576,8 +2924,8 @@ var init_task_resolution = __esm({
2576
2924
  });
2577
2925
 
2578
2926
  // src/review-standalone.ts
2579
- import * as fs17 from "fs";
2580
- import * as path16 from "path";
2927
+ import * as fs18 from "fs";
2928
+ import * as path17 from "path";
2581
2929
  function resolveReviewTarget(input) {
2582
2930
  if (input.prs.length === 0) {
2583
2931
  return {
@@ -2601,12 +2949,12 @@ Or comment on the specific PR: \`@kody review\``
2601
2949
  }
2602
2950
  async function runStandaloneReview(input) {
2603
2951
  const taskId = input.taskId ?? `review-${generateTaskId()}`;
2604
- const taskDir = path16.join(input.projectDir, ".kody", "tasks", taskId);
2605
- fs17.mkdirSync(taskDir, { recursive: true });
2952
+ const taskDir = path17.join(input.projectDir, ".kody", "tasks", taskId);
2953
+ fs18.mkdirSync(taskDir, { recursive: true });
2606
2954
  const taskContent = `# ${input.prTitle}
2607
2955
 
2608
2956
  ${input.prBody ?? ""}`;
2609
- fs17.writeFileSync(path16.join(taskDir, "task.md"), taskContent);
2957
+ fs18.writeFileSync(path17.join(taskDir, "task.md"), taskContent);
2610
2958
  const reviewDef = STAGES.find((s) => s.name === "review");
2611
2959
  const ctx = {
2612
2960
  taskId,
@@ -2628,10 +2976,10 @@ ${input.prBody ?? ""}`;
2628
2976
  error: result.error ?? "Review stage failed"
2629
2977
  };
2630
2978
  }
2631
- const reviewPath = path16.join(taskDir, "review.md");
2979
+ const reviewPath = path17.join(taskDir, "review.md");
2632
2980
  let reviewContent;
2633
- if (fs17.existsSync(reviewPath)) {
2634
- reviewContent = fs17.readFileSync(reviewPath, "utf-8");
2981
+ if (fs18.existsSync(reviewPath)) {
2982
+ reviewContent = fs18.readFileSync(reviewPath, "utf-8");
2635
2983
  }
2636
2984
  return {
2637
2985
  outcome: "completed",
@@ -2723,8 +3071,8 @@ var init_args = __esm({
2723
3071
  });
2724
3072
 
2725
3073
  // src/cli/litellm.ts
2726
- import * as fs18 from "fs";
2727
- import * as path17 from "path";
3074
+ import * as fs19 from "fs";
3075
+ import * as path18 from "path";
2728
3076
  import { execFileSync as execFileSync10 } from "child_process";
2729
3077
  async function checkLitellmHealth(url) {
2730
3078
  try {
@@ -2735,8 +3083,8 @@ async function checkLitellmHealth(url) {
2735
3083
  }
2736
3084
  }
2737
3085
  async function tryStartLitellm(url, projectDir) {
2738
- const configPath = path17.join(projectDir, "litellm-config.yaml");
2739
- if (!fs18.existsSync(configPath)) {
3086
+ const configPath = path18.join(projectDir, "litellm-config.yaml");
3087
+ if (!fs19.existsSync(configPath)) {
2740
3088
  logger.warn("litellm-config.yaml not found \u2014 cannot start proxy");
2741
3089
  return null;
2742
3090
  }
@@ -2768,10 +3116,10 @@ async function tryStartLitellm(url, projectDir) {
2768
3116
  cmd = "python3";
2769
3117
  args2 = ["-m", "litellm", "--config", configPath, "--port", port];
2770
3118
  }
2771
- const dotenvPath = path17.join(projectDir, ".env");
3119
+ const dotenvPath = path18.join(projectDir, ".env");
2772
3120
  const dotenvVars = {};
2773
- if (fs18.existsSync(dotenvPath)) {
2774
- for (const line of fs18.readFileSync(dotenvPath, "utf-8").split("\n")) {
3121
+ if (fs19.existsSync(dotenvPath)) {
3122
+ for (const line of fs19.readFileSync(dotenvPath, "utf-8").split("\n")) {
2775
3123
  const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
2776
3124
  if (match) dotenvVars[match[1]] = match[2];
2777
3125
  }
@@ -2811,8 +3159,8 @@ var init_litellm = __esm({
2811
3159
  });
2812
3160
 
2813
3161
  // src/cli/task-state.ts
2814
- import * as fs19 from "fs";
2815
- import * as path18 from "path";
3162
+ import * as fs20 from "fs";
3163
+ import * as path19 from "path";
2816
3164
  function resolveTaskAction(issueNumber, existingTaskId, existingState) {
2817
3165
  if (!existingTaskId || !existingState) {
2818
3166
  return { action: "start-fresh", taskId: `${issueNumber}-${generateTaskId()}` };
@@ -2844,11 +3192,11 @@ function resolveTaskAction(issueNumber, existingTaskId, existingState) {
2844
3192
  function resolveForIssue(issueNumber, projectDir) {
2845
3193
  const existingTaskId = findLatestTaskForIssue(issueNumber, projectDir);
2846
3194
  if (existingTaskId) {
2847
- const statusPath = path18.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
3195
+ const statusPath = path19.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
2848
3196
  let existingState = null;
2849
- if (fs19.existsSync(statusPath)) {
3197
+ if (fs20.existsSync(statusPath)) {
2850
3198
  try {
2851
- existingState = JSON.parse(fs19.readFileSync(statusPath, "utf-8"));
3199
+ existingState = JSON.parse(fs20.readFileSync(statusPath, "utf-8"));
2852
3200
  } catch {
2853
3201
  }
2854
3202
  }
@@ -2878,13 +3226,13 @@ var init_task_state = __esm({
2878
3226
 
2879
3227
  // src/entry.ts
2880
3228
  var entry_exports = {};
2881
- import * as fs20 from "fs";
2882
- import * as path19 from "path";
3229
+ import * as fs21 from "fs";
3230
+ import * as path20 from "path";
2883
3231
  async function main() {
2884
3232
  const input = parseArgs();
2885
- const projectDir = input.cwd ? path19.resolve(input.cwd) : process.cwd();
3233
+ const projectDir = input.cwd ? path20.resolve(input.cwd) : process.cwd();
2886
3234
  if (input.cwd) {
2887
- if (!fs20.existsSync(projectDir)) {
3235
+ if (!fs21.existsSync(projectDir)) {
2888
3236
  console.error(`--cwd path does not exist: ${projectDir}`);
2889
3237
  process.exit(1);
2890
3238
  }
@@ -2938,8 +3286,8 @@ async function main() {
2938
3286
  process.exit(1);
2939
3287
  }
2940
3288
  }
2941
- const taskDir = path19.join(projectDir, ".kody", "tasks", taskId);
2942
- fs20.mkdirSync(taskDir, { recursive: true });
3289
+ const taskDir = path20.join(projectDir, ".kody", "tasks", taskId);
3290
+ fs21.mkdirSync(taskDir, { recursive: true });
2943
3291
  if (input.command === "status") {
2944
3292
  printStatus(taskId, taskDir);
2945
3293
  return;
@@ -2983,6 +3331,9 @@ async function main() {
2983
3331
  }
2984
3332
  }
2985
3333
  process.env.ANTHROPIC_BASE_URL = config2.agent.litellmUrl;
3334
+ if (!process.env.ANTHROPIC_API_KEY || !process.env.ANTHROPIC_API_KEY.startsWith("sk-ant-")) {
3335
+ process.env.ANTHROPIC_API_KEY = "sk-ant-api03-litellm-proxy-key-00000000000000000000000000000000000000000000000000000000000000000000";
3336
+ }
2986
3337
  }
2987
3338
  const runners2 = createRunners(config2);
2988
3339
  const defaultRunnerName2 = config2.agent.defaultRunner ?? Object.keys(runners2)[0] ?? "claude";
@@ -3027,31 +3378,31 @@ async function main() {
3027
3378
  logger.info("Preflight checks:");
3028
3379
  runPreflight();
3029
3380
  if (input.task) {
3030
- fs20.writeFileSync(path19.join(taskDir, "task.md"), input.task);
3381
+ fs21.writeFileSync(path20.join(taskDir, "task.md"), input.task);
3031
3382
  }
3032
- const taskMdPath = path19.join(taskDir, "task.md");
3033
- if (!fs20.existsSync(taskMdPath) && isPRFix && input.prNumber) {
3383
+ const taskMdPath = path20.join(taskDir, "task.md");
3384
+ if (!fs21.existsSync(taskMdPath) && isPRFix && input.prNumber) {
3034
3385
  logger.info(`Fetching PR #${input.prNumber} details as task context...`);
3035
3386
  const prDetails = getPRDetails(input.prNumber);
3036
3387
  if (prDetails) {
3037
3388
  const taskContent = `# ${prDetails.title}
3038
3389
 
3039
3390
  ${prDetails.body ?? ""}`;
3040
- fs20.writeFileSync(taskMdPath, taskContent);
3391
+ fs21.writeFileSync(taskMdPath, taskContent);
3041
3392
  logger.info(` Task loaded from PR #${input.prNumber}: ${prDetails.title}`);
3042
3393
  }
3043
- } else if (!fs20.existsSync(taskMdPath) && input.issueNumber) {
3394
+ } else if (!fs21.existsSync(taskMdPath) && input.issueNumber) {
3044
3395
  logger.info(`Fetching issue #${input.issueNumber} body as task...`);
3045
3396
  const issue = getIssue(input.issueNumber);
3046
3397
  if (issue) {
3047
3398
  const taskContent = `# ${issue.title}
3048
3399
 
3049
3400
  ${issue.body ?? ""}`;
3050
- fs20.writeFileSync(taskMdPath, taskContent);
3401
+ fs21.writeFileSync(taskMdPath, taskContent);
3051
3402
  logger.info(` Task loaded from issue #${input.issueNumber}: ${issue.title}`);
3052
3403
  }
3053
3404
  }
3054
- if (!fs20.existsSync(taskMdPath)) {
3405
+ if (!fs21.existsSync(taskMdPath)) {
3055
3406
  console.error("No task.md found. Provide --task, --issue-number, or ensure .kody/tasks/<id>/task.md exists.");
3056
3407
  process.exit(1);
3057
3408
  }
@@ -3102,6 +3453,9 @@ ${input.feedback}` : reviewContext;
3102
3453
  }
3103
3454
  process.env.ANTHROPIC_BASE_URL = config.agent.litellmUrl;
3104
3455
  logger.info(`ANTHROPIC_BASE_URL set to ${config.agent.litellmUrl}`);
3456
+ if (!process.env.ANTHROPIC_API_KEY || !process.env.ANTHROPIC_API_KEY.startsWith("sk-ant-")) {
3457
+ process.env.ANTHROPIC_API_KEY = "sk-ant-api03-litellm-proxy-key-00000000000000000000000000000000000000000000000000000000000000000000";
3458
+ }
3105
3459
  }
3106
3460
  const runners = createRunners(config);
3107
3461
  const defaultRunnerName = config.agent.defaultRunner ?? Object.keys(runners)[0] ?? "claude";
@@ -3148,7 +3502,7 @@ To rerun: \`@kody rerun ${taskId} --from <stage>\``
3148
3502
  }
3149
3503
  }
3150
3504
  const state = await runPipeline(ctx);
3151
- const files = fs20.readdirSync(taskDir);
3505
+ const files = fs21.readdirSync(taskDir);
3152
3506
  console.log(`
3153
3507
  Artifacts in ${taskDir}:`);
3154
3508
  for (const f of files) {
@@ -3211,15 +3565,15 @@ var init_entry = __esm({
3211
3565
  });
3212
3566
 
3213
3567
  // src/bin/cli.ts
3214
- import * as fs21 from "fs";
3215
- import * as path20 from "path";
3568
+ import * as fs22 from "fs";
3569
+ import * as path21 from "path";
3216
3570
  import { execFileSync as execFileSync11 } from "child_process";
3217
3571
  import { fileURLToPath } from "url";
3218
- var __dirname = path20.dirname(fileURLToPath(import.meta.url));
3219
- var PKG_ROOT = path20.resolve(__dirname, "..", "..");
3572
+ var __dirname = path21.dirname(fileURLToPath(import.meta.url));
3573
+ var PKG_ROOT = path21.resolve(__dirname, "..", "..");
3220
3574
  function getVersion() {
3221
- const pkgPath = path20.join(PKG_ROOT, "package.json");
3222
- const pkg = JSON.parse(fs21.readFileSync(pkgPath, "utf-8"));
3575
+ const pkgPath = path21.join(PKG_ROOT, "package.json");
3576
+ const pkg = JSON.parse(fs22.readFileSync(pkgPath, "utf-8"));
3223
3577
  return pkg.version;
3224
3578
  }
3225
3579
  function checkCommand2(name, args2, fix) {
@@ -3235,7 +3589,7 @@ function checkCommand2(name, args2, fix) {
3235
3589
  }
3236
3590
  }
3237
3591
  function checkFile(filePath, description, fix) {
3238
- if (fs21.existsSync(filePath)) {
3592
+ if (fs22.existsSync(filePath)) {
3239
3593
  return { name: description, ok: true, detail: filePath };
3240
3594
  }
3241
3595
  return { name: description, ok: false, fix };
@@ -3307,9 +3661,9 @@ function checkGhSecret(repoSlug, secretName) {
3307
3661
  }
3308
3662
  function detectBasicConfig(cwd) {
3309
3663
  let pm = "pnpm";
3310
- if (fs21.existsSync(path20.join(cwd, "yarn.lock"))) pm = "yarn";
3311
- else if (fs21.existsSync(path20.join(cwd, "bun.lockb"))) pm = "bun";
3312
- else if (!fs21.existsSync(path20.join(cwd, "pnpm-lock.yaml")) && fs21.existsSync(path20.join(cwd, "package-lock.json"))) pm = "npm";
3664
+ if (fs22.existsSync(path21.join(cwd, "yarn.lock"))) pm = "yarn";
3665
+ else if (fs22.existsSync(path21.join(cwd, "bun.lockb"))) pm = "bun";
3666
+ else if (!fs22.existsSync(path21.join(cwd, "pnpm-lock.yaml")) && fs22.existsSync(path21.join(cwd, "package-lock.json"))) pm = "npm";
3313
3667
  let defaultBranch = "main";
3314
3668
  try {
3315
3669
  const ref = execFileSync11("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
@@ -3352,7 +3706,7 @@ function detectBasicConfig(cwd) {
3352
3706
  function buildConfig(cwd, basic) {
3353
3707
  const pkg = (() => {
3354
3708
  try {
3355
- return JSON.parse(fs21.readFileSync(path20.join(cwd, "package.json"), "utf-8"));
3709
+ return JSON.parse(fs22.readFileSync(path21.join(cwd, "package.json"), "utf-8"));
3356
3710
  } catch {
3357
3711
  return {};
3358
3712
  }
@@ -3392,35 +3746,35 @@ function initCommand(opts) {
3392
3746
  console.log(`Project: ${cwd}
3393
3747
  `);
3394
3748
  console.log("\u2500\u2500 Files \u2500\u2500");
3395
- const templatesDir = path20.join(PKG_ROOT, "templates");
3749
+ const templatesDir = path21.join(PKG_ROOT, "templates");
3396
3750
  const basic = detectBasicConfig(cwd);
3397
- const workflowSrc = path20.join(templatesDir, "kody.yml");
3398
- const workflowDest = path20.join(cwd, ".github", "workflows", "kody.yml");
3399
- if (!fs21.existsSync(workflowSrc)) {
3751
+ const workflowSrc = path21.join(templatesDir, "kody.yml");
3752
+ const workflowDest = path21.join(cwd, ".github", "workflows", "kody.yml");
3753
+ if (!fs22.existsSync(workflowSrc)) {
3400
3754
  console.error(" \u2717 Template kody.yml not found in package");
3401
3755
  process.exit(1);
3402
3756
  }
3403
- if (fs21.existsSync(workflowDest) && !opts.force) {
3757
+ if (fs22.existsSync(workflowDest) && !opts.force) {
3404
3758
  console.log(" \u25CB .github/workflows/kody.yml (exists, use --force to overwrite)");
3405
3759
  } else {
3406
- fs21.mkdirSync(path20.dirname(workflowDest), { recursive: true });
3407
- fs21.copyFileSync(workflowSrc, workflowDest);
3760
+ fs22.mkdirSync(path21.dirname(workflowDest), { recursive: true });
3761
+ fs22.copyFileSync(workflowSrc, workflowDest);
3408
3762
  console.log(" \u2713 .github/workflows/kody.yml");
3409
3763
  }
3410
- const configDest = path20.join(cwd, "kody.config.json");
3411
- if (!fs21.existsSync(configDest) || opts.force) {
3764
+ const configDest = path21.join(cwd, "kody.config.json");
3765
+ if (!fs22.existsSync(configDest) || opts.force) {
3412
3766
  const config = buildConfig(cwd, basic);
3413
- fs21.writeFileSync(configDest, JSON.stringify(config, null, 2) + "\n");
3767
+ fs22.writeFileSync(configDest, JSON.stringify(config, null, 2) + "\n");
3414
3768
  console.log(" \u2713 kody.config.json (auto-configured)");
3415
3769
  } else {
3416
3770
  console.log(" \u25CB kody.config.json (exists)");
3417
3771
  }
3418
- const gitignorePath = path20.join(cwd, ".gitignore");
3419
- if (fs21.existsSync(gitignorePath)) {
3420
- const content = fs21.readFileSync(gitignorePath, "utf-8");
3772
+ const gitignorePath = path21.join(cwd, ".gitignore");
3773
+ if (fs22.existsSync(gitignorePath)) {
3774
+ const content = fs22.readFileSync(gitignorePath, "utf-8");
3421
3775
  if (content.includes(".tasks/")) {
3422
3776
  const updated = content.replace(/\n?\.tasks\/\n?/g, "\n");
3423
- fs21.writeFileSync(gitignorePath, updated);
3777
+ fs22.writeFileSync(gitignorePath, updated);
3424
3778
  console.log(" \u2713 .gitignore (removed legacy .tasks/ \u2014 tasks now committed in .kody/tasks/)");
3425
3779
  } else {
3426
3780
  console.log(" \u25CB .gitignore (ok)");
@@ -3431,7 +3785,7 @@ function initCommand(opts) {
3431
3785
  checkCommand2("gh", ["--version"], "Install: https://cli.github.com"),
3432
3786
  checkCommand2("git", ["--version"], "Install git"),
3433
3787
  checkCommand2("node", ["--version"], "Install Node.js >= 22"),
3434
- checkFile(path20.join(cwd, "package.json"), "package.json", `Run: ${basic.pm} init`)
3788
+ checkFile(path21.join(cwd, "package.json"), "package.json", `Run: ${basic.pm} init`)
3435
3789
  ];
3436
3790
  for (const c of checks) {
3437
3791
  if (c.ok) {
@@ -3509,9 +3863,9 @@ function initCommand(opts) {
3509
3863
  }
3510
3864
  }
3511
3865
  console.log("\n\u2500\u2500 Config \u2500\u2500");
3512
- if (fs21.existsSync(configDest)) {
3866
+ if (fs22.existsSync(configDest)) {
3513
3867
  try {
3514
- const config = JSON.parse(fs21.readFileSync(configDest, "utf-8"));
3868
+ const config = JSON.parse(fs22.readFileSync(configDest, "utf-8"));
3515
3869
  const configChecks = [];
3516
3870
  if (config.github?.owner && config.github?.repo) {
3517
3871
  configChecks.push({ name: "github.owner/repo", ok: true, detail: `${config.github.owner}/${config.github.repo}` });
@@ -3541,10 +3895,10 @@ function initCommand(opts) {
3541
3895
  const filesToCommit = [
3542
3896
  ".github/workflows/kody.yml",
3543
3897
  "kody.config.json"
3544
- ].filter((f) => fs21.existsSync(path20.join(cwd, f)));
3898
+ ].filter((f) => fs22.existsSync(path21.join(cwd, f)));
3545
3899
  if (filesToCommit.length > 0) {
3546
3900
  try {
3547
- const fullPaths = filesToCommit.map((f) => path20.join(cwd, f));
3901
+ const fullPaths = filesToCommit.map((f) => path21.join(cwd, f));
3548
3902
  execFileSync11("npx", ["prettier", "--write", ...fullPaths], {
3549
3903
  cwd,
3550
3904
  encoding: "utf-8",
@@ -3606,20 +3960,20 @@ function initCommand(opts) {
3606
3960
  }
3607
3961
  var STEP_STAGES = ["taskify", "plan", "build", "review", "review-fix"];
3608
3962
  function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
3609
- const srcDir = path20.join(cwd, "src");
3610
- const baseDir = fs21.existsSync(srcDir) ? srcDir : cwd;
3963
+ const srcDir = path21.join(cwd, "src");
3964
+ const baseDir = fs22.existsSync(srcDir) ? srcDir : cwd;
3611
3965
  const results = [];
3612
3966
  function walk(dir) {
3613
3967
  const entries = [];
3614
3968
  try {
3615
- for (const entry of fs21.readdirSync(dir, { withFileTypes: true })) {
3969
+ for (const entry of fs22.readdirSync(dir, { withFileTypes: true })) {
3616
3970
  if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
3617
- const full = path20.join(dir, entry.name);
3971
+ const full = path21.join(dir, entry.name);
3618
3972
  if (entry.isDirectory()) {
3619
3973
  entries.push(...walk(full));
3620
3974
  } else if (/\.(ts|js)$/.test(entry.name) && !/\.(test|spec|config|d)\.(ts|js)$/.test(entry.name)) {
3621
3975
  try {
3622
- const stat = fs21.statSync(full);
3976
+ const stat = fs22.statSync(full);
3623
3977
  if (stat.size >= 200 && stat.size <= 5e3) {
3624
3978
  entries.push({ filePath: full, size: stat.size });
3625
3979
  }
@@ -3633,8 +3987,8 @@ function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
3633
3987
  }
3634
3988
  const files = walk(baseDir).sort((a, b) => b.size - a.size).slice(0, maxFiles);
3635
3989
  for (const { filePath } of files) {
3636
- const rel = path20.relative(cwd, filePath);
3637
- const content = fs21.readFileSync(filePath, "utf-8").slice(0, maxCharsEach);
3990
+ const rel = path21.relative(cwd, filePath);
3991
+ const content = fs22.readFileSync(filePath, "utf-8").slice(0, maxCharsEach);
3638
3992
  results.push(`### File: ${rel}
3639
3993
  \`\`\`typescript
3640
3994
  ${content}
@@ -3646,9 +4000,9 @@ function ghComment(issueNumber, body, cwd) {
3646
4000
  try {
3647
4001
  let repoSlug = "";
3648
4002
  try {
3649
- const configPath = path20.join(cwd, "kody.config.json");
3650
- if (fs21.existsSync(configPath)) {
3651
- const config = JSON.parse(fs21.readFileSync(configPath, "utf-8"));
4003
+ const configPath = path21.join(cwd, "kody.config.json");
4004
+ if (fs22.existsSync(configPath)) {
4005
+ const config = JSON.parse(fs22.readFileSync(configPath, "utf-8"));
3652
4006
  if (config.github?.owner && config.github?.repo) {
3653
4007
  repoSlug = `${config.github.owner}/${config.github.repo}`;
3654
4008
  }
@@ -3683,8 +4037,8 @@ function bootstrapCommand() {
3683
4037
  ghComment(issueNumber, "\u{1F527} **Bootstrap started** \u2014 analyzing project and generating configuration...", cwd);
3684
4038
  }
3685
4039
  const readIfExists = (rel, maxChars = 3e3) => {
3686
- const p = path20.join(cwd, rel);
3687
- if (fs21.existsSync(p)) return fs21.readFileSync(p, "utf-8").slice(0, maxChars);
4040
+ const p = path21.join(cwd, rel);
4041
+ if (fs22.existsSync(p)) return fs22.readFileSync(p, "utf-8").slice(0, maxChars);
3688
4042
  return null;
3689
4043
  };
3690
4044
  let repoContext = "";
@@ -3719,14 +4073,14 @@ ${sampleFiles}
3719
4073
 
3720
4074
  `;
3721
4075
  try {
3722
- const topDirs = fs21.readdirSync(cwd, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
4076
+ const topDirs = fs22.readdirSync(cwd, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
3723
4077
  repoContext += `## Top-level directories
3724
4078
  ${topDirs.join(", ")}
3725
4079
 
3726
4080
  `;
3727
- const srcDir = path20.join(cwd, "src");
3728
- if (fs21.existsSync(srcDir)) {
3729
- const srcDirs = fs21.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
4081
+ const srcDir = path21.join(cwd, "src");
4082
+ if (fs22.existsSync(srcDir)) {
4083
+ const srcDirs = fs22.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
3730
4084
  if (srcDirs.length > 0) repoContext += `## src/ subdirectories
3731
4085
  ${srcDirs.join(", ")}
3732
4086
 
@@ -3736,17 +4090,17 @@ ${srcDirs.join(", ")}
3736
4090
  }
3737
4091
  const existingFiles = [];
3738
4092
  for (const f of [".env.example", "CLAUDE.md", ".ai-docs", "vitest.config.ts", "vitest.config.mts", "jest.config.ts", "playwright.config.ts", ".eslintrc.js", "eslint.config.mjs", ".prettierrc"]) {
3739
- if (fs21.existsSync(path20.join(cwd, f))) existingFiles.push(f);
4093
+ if (fs22.existsSync(path21.join(cwd, f))) existingFiles.push(f);
3740
4094
  }
3741
4095
  if (existingFiles.length) repoContext += `## Config files present
3742
4096
  ${existingFiles.join(", ")}
3743
4097
 
3744
4098
  `;
3745
4099
  console.log("\u2500\u2500 Project Memory \u2500\u2500");
3746
- const memoryDir = path20.join(cwd, ".kody", "memory");
3747
- fs21.mkdirSync(memoryDir, { recursive: true });
3748
- const archPath = path20.join(memoryDir, "architecture.md");
3749
- const conventionsPath = path20.join(memoryDir, "conventions.md");
4100
+ const memoryDir = path21.join(cwd, ".kody", "memory");
4101
+ fs22.mkdirSync(memoryDir, { recursive: true });
4102
+ const archPath = path21.join(memoryDir, "architecture.md");
4103
+ const conventionsPath = path21.join(memoryDir, "conventions.md");
3750
4104
  const memoryPrompt = `You are analyzing a project to generate documentation for an autonomous SDLC pipeline.
3751
4105
 
3752
4106
  Given this project context, output ONLY a JSON object with EXACTLY this structure:
@@ -3787,12 +4141,12 @@ ${repoContext}`;
3787
4141
  const cleaned = output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
3788
4142
  const parsed = JSON.parse(cleaned);
3789
4143
  if (parsed.architecture) {
3790
- fs21.writeFileSync(archPath, parsed.architecture);
4144
+ fs22.writeFileSync(archPath, parsed.architecture);
3791
4145
  const lineCount = parsed.architecture.split("\n").length;
3792
4146
  console.log(` \u2713 .kody/memory/architecture.md (${lineCount} lines)`);
3793
4147
  }
3794
4148
  if (parsed.conventions) {
3795
- fs21.writeFileSync(conventionsPath, parsed.conventions);
4149
+ fs22.writeFileSync(conventionsPath, parsed.conventions);
3796
4150
  const lineCount = parsed.conventions.split("\n").length;
3797
4151
  console.log(` \u2713 .kody/memory/conventions.md (${lineCount} lines)`);
3798
4152
  }
@@ -3801,34 +4155,34 @@ ${repoContext}`;
3801
4155
  const detected = detectArchitectureBasic(cwd);
3802
4156
  if (detected.length > 0) {
3803
4157
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3804
- fs21.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
4158
+ fs22.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
3805
4159
 
3806
4160
  ## Overview
3807
4161
  ${detected.join("\n")}
3808
4162
  `);
3809
4163
  console.log(` \u2713 .kody/memory/architecture.md (${detected.length} items, basic detection)`);
3810
4164
  }
3811
- fs21.writeFileSync(conventionsPath, "# Conventions\n\n<!-- Auto-learned conventions will be appended here -->\n");
4165
+ fs22.writeFileSync(conventionsPath, "# Conventions\n\n<!-- Auto-learned conventions will be appended here -->\n");
3812
4166
  console.log(" \u2713 .kody/memory/conventions.md (seed)");
3813
4167
  }
3814
4168
  console.log("\n\u2500\u2500 Step Files \u2500\u2500");
3815
- const stepsDir = path20.join(cwd, ".kody", "steps");
3816
- fs21.mkdirSync(stepsDir, { recursive: true });
3817
- const arch = fs21.existsSync(archPath) ? fs21.readFileSync(archPath, "utf-8") : "";
3818
- const conv = fs21.existsSync(conventionsPath) ? fs21.readFileSync(conventionsPath, "utf-8") : "";
4169
+ const stepsDir = path21.join(cwd, ".kody", "steps");
4170
+ fs22.mkdirSync(stepsDir, { recursive: true });
4171
+ const arch = fs22.existsSync(archPath) ? fs22.readFileSync(archPath, "utf-8") : "";
4172
+ const conv = fs22.existsSync(conventionsPath) ? fs22.readFileSync(conventionsPath, "utf-8") : "";
3819
4173
  console.log(" \u23F3 Customizing step files...");
3820
4174
  let stepCount = 0;
3821
4175
  for (const stage of STEP_STAGES) {
3822
- const templatePath = path20.join(PKG_ROOT, "prompts", `${stage}.md`);
3823
- if (!fs21.existsSync(templatePath)) {
4176
+ const templatePath = path21.join(PKG_ROOT, "prompts", `${stage}.md`);
4177
+ if (!fs22.existsSync(templatePath)) {
3824
4178
  console.log(` \u2717 ${stage}.md \u2014 template not found in engine`);
3825
4179
  continue;
3826
4180
  }
3827
- const defaultPrompt = fs21.readFileSync(templatePath, "utf-8");
4181
+ const defaultPrompt = fs22.readFileSync(templatePath, "utf-8");
3828
4182
  const contextPlaceholder = "{{TASK_CONTEXT}}";
3829
4183
  const placeholderIdx = defaultPrompt.indexOf(contextPlaceholder);
3830
4184
  if (placeholderIdx === -1) {
3831
- fs21.copyFileSync(templatePath, path20.join(stepsDir, `${stage}.md`));
4185
+ fs22.copyFileSync(templatePath, path21.join(stepsDir, `${stage}.md`));
3832
4186
  stepCount++;
3833
4187
  console.log(` \u2713 ${stage}.md`);
3834
4188
  continue;
@@ -3885,12 +4239,12 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
3885
4239
  let cleaned = output.replace(/^```(?:markdown|md)?\s*\n?/, "").replace(/\n?```\s*$/, "");
3886
4240
  cleaned = cleaned.replace(/\n*\{\{TASK_CONTEXT\}\}\s*$/, "").trimEnd();
3887
4241
  const finalPrompt = cleaned + "\n\n" + afterPlaceholder;
3888
- fs21.writeFileSync(path20.join(stepsDir, `${stage}.md`), finalPrompt);
4242
+ fs22.writeFileSync(path21.join(stepsDir, `${stage}.md`), finalPrompt);
3889
4243
  stepCount++;
3890
4244
  console.log(` \u2713 ${stage}.md`);
3891
4245
  } catch {
3892
4246
  console.log(` \u26A0 ${stage}.md \u2014 customization failed, using default template`);
3893
- fs21.copyFileSync(templatePath, path20.join(stepsDir, `${stage}.md`));
4247
+ fs22.copyFileSync(templatePath, path21.join(stepsDir, `${stage}.md`));
3894
4248
  stepCount++;
3895
4249
  }
3896
4250
  }
@@ -3899,16 +4253,16 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
3899
4253
  const filesToCommit = [
3900
4254
  ".kody/memory/architecture.md",
3901
4255
  ".kody/memory/conventions.md"
3902
- ].filter((f) => fs21.existsSync(path20.join(cwd, f)));
4256
+ ].filter((f) => fs22.existsSync(path21.join(cwd, f)));
3903
4257
  for (const stage of STEP_STAGES) {
3904
4258
  const stepFile = `.kody/steps/${stage}.md`;
3905
- if (fs21.existsSync(path20.join(cwd, stepFile))) {
4259
+ if (fs22.existsSync(path21.join(cwd, stepFile))) {
3906
4260
  filesToCommit.push(stepFile);
3907
4261
  }
3908
4262
  }
3909
4263
  if (filesToCommit.length > 0) {
3910
4264
  try {
3911
- const fullPaths = filesToCommit.map((f) => path20.join(cwd, f));
4265
+ const fullPaths = filesToCommit.map((f) => path21.join(cwd, f));
3912
4266
  for (let pass = 0; pass < 2; pass++) {
3913
4267
  execFileSync11("npx", ["prettier", "--write", ...fullPaths], {
3914
4268
  cwd,
@@ -3935,9 +4289,9 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
3935
4289
  console.log(` \u2713 Pushed branch: ${branchName}`);
3936
4290
  let baseBranch = "main";
3937
4291
  try {
3938
- const configPath = path20.join(cwd, "kody.config.json");
3939
- if (fs21.existsSync(configPath)) {
3940
- const config = JSON.parse(fs21.readFileSync(configPath, "utf-8"));
4292
+ const configPath = path21.join(cwd, "kody.config.json");
4293
+ if (fs22.existsSync(configPath)) {
4294
+ const config = JSON.parse(fs22.readFileSync(configPath, "utf-8"));
3941
4295
  baseBranch = config.git?.defaultBranch ?? "main";
3942
4296
  }
3943
4297
  } catch {
@@ -4004,10 +4358,10 @@ Review and merge to activate project-specific pipeline configuration.`, cwd);
4004
4358
  }
4005
4359
  function detectArchitectureBasic(cwd) {
4006
4360
  const detected = [];
4007
- const pkgPath = path20.join(cwd, "package.json");
4008
- if (fs21.existsSync(pkgPath)) {
4361
+ const pkgPath = path21.join(cwd, "package.json");
4362
+ if (fs22.existsSync(pkgPath)) {
4009
4363
  try {
4010
- const pkg = JSON.parse(fs21.readFileSync(pkgPath, "utf-8"));
4364
+ const pkg = JSON.parse(fs22.readFileSync(pkgPath, "utf-8"));
4011
4365
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
4012
4366
  if (allDeps.next) detected.push(`- Framework: Next.js ${allDeps.next}`);
4013
4367
  else if (allDeps.react) detected.push(`- Framework: React ${allDeps.react}`);
@@ -4023,10 +4377,10 @@ function detectArchitectureBasic(cwd) {
4023
4377
  if (allDeps["drizzle-orm"]) detected.push("- ORM: Drizzle");
4024
4378
  if (allDeps.payload || allDeps["@payloadcms/next"]) detected.push("- CMS: Payload CMS");
4025
4379
  if (allDeps.tailwindcss) detected.push(`- CSS: Tailwind CSS ${allDeps.tailwindcss}`);
4026
- if (fs21.existsSync(path20.join(cwd, "pnpm-lock.yaml"))) detected.push("- Package manager: pnpm");
4027
- else if (fs21.existsSync(path20.join(cwd, "yarn.lock"))) detected.push("- Package manager: yarn");
4028
- else if (fs21.existsSync(path20.join(cwd, "bun.lockb"))) detected.push("- Package manager: bun");
4029
- else if (fs21.existsSync(path20.join(cwd, "package-lock.json"))) detected.push("- Package manager: npm");
4380
+ if (fs22.existsSync(path21.join(cwd, "pnpm-lock.yaml"))) detected.push("- Package manager: pnpm");
4381
+ else if (fs22.existsSync(path21.join(cwd, "yarn.lock"))) detected.push("- Package manager: yarn");
4382
+ else if (fs22.existsSync(path21.join(cwd, "bun.lockb"))) detected.push("- Package manager: bun");
4383
+ else if (fs22.existsSync(path21.join(cwd, "package-lock.json"))) detected.push("- Package manager: npm");
4030
4384
  } catch {
4031
4385
  }
4032
4386
  }