@kody-ade/kody-engine-lite 0.1.56 → 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 (74) hide show
  1. package/dist/bin/cli.js +699 -274
  2. package/kody.config.schema.json +2 -2
  3. package/package.json +1 -1
  4. package/prompts/autofix.md +27 -9
  5. package/prompts/review.md +83 -16
  6. package/templates/kody.yml +8 -8
  7. package/dist/agent-runner.d.ts +0 -4
  8. package/dist/agent-runner.js +0 -122
  9. package/dist/ci/parse-inputs.d.ts +0 -6
  10. package/dist/ci/parse-inputs.js +0 -76
  11. package/dist/ci/parse-safety.d.ts +0 -6
  12. package/dist/ci/parse-safety.js +0 -22
  13. package/dist/cli/args.d.ts +0 -13
  14. package/dist/cli/args.js +0 -42
  15. package/dist/cli/litellm.d.ts +0 -2
  16. package/dist/cli/litellm.js +0 -85
  17. package/dist/cli/task-resolution.d.ts +0 -2
  18. package/dist/cli/task-resolution.js +0 -41
  19. package/dist/config.d.ts +0 -49
  20. package/dist/config.js +0 -72
  21. package/dist/context.d.ts +0 -4
  22. package/dist/context.js +0 -83
  23. package/dist/definitions.d.ts +0 -3
  24. package/dist/definitions.js +0 -59
  25. package/dist/entry.d.ts +0 -1
  26. package/dist/entry.js +0 -236
  27. package/dist/git-utils.d.ts +0 -13
  28. package/dist/git-utils.js +0 -174
  29. package/dist/github-api.d.ts +0 -14
  30. package/dist/github-api.js +0 -114
  31. package/dist/kody-utils.d.ts +0 -1
  32. package/dist/kody-utils.js +0 -9
  33. package/dist/learning/auto-learn.d.ts +0 -2
  34. package/dist/learning/auto-learn.js +0 -169
  35. package/dist/logger.d.ts +0 -14
  36. package/dist/logger.js +0 -51
  37. package/dist/memory.d.ts +0 -1
  38. package/dist/memory.js +0 -20
  39. package/dist/observer.d.ts +0 -9
  40. package/dist/observer.js +0 -80
  41. package/dist/pipeline/complexity.d.ts +0 -3
  42. package/dist/pipeline/complexity.js +0 -12
  43. package/dist/pipeline/executor-registry.d.ts +0 -3
  44. package/dist/pipeline/executor-registry.js +0 -20
  45. package/dist/pipeline/hooks.d.ts +0 -17
  46. package/dist/pipeline/hooks.js +0 -110
  47. package/dist/pipeline/questions.d.ts +0 -2
  48. package/dist/pipeline/questions.js +0 -44
  49. package/dist/pipeline/runner-selection.d.ts +0 -2
  50. package/dist/pipeline/runner-selection.js +0 -13
  51. package/dist/pipeline/state.d.ts +0 -4
  52. package/dist/pipeline/state.js +0 -37
  53. package/dist/pipeline.d.ts +0 -3
  54. package/dist/pipeline.js +0 -213
  55. package/dist/preflight.d.ts +0 -1
  56. package/dist/preflight.js +0 -69
  57. package/dist/retrospective.d.ts +0 -26
  58. package/dist/retrospective.js +0 -211
  59. package/dist/stages/agent.d.ts +0 -2
  60. package/dist/stages/agent.js +0 -94
  61. package/dist/stages/gate.d.ts +0 -2
  62. package/dist/stages/gate.js +0 -32
  63. package/dist/stages/review.d.ts +0 -2
  64. package/dist/stages/review.js +0 -32
  65. package/dist/stages/ship.d.ts +0 -3
  66. package/dist/stages/ship.js +0 -154
  67. package/dist/stages/verify.d.ts +0 -2
  68. package/dist/stages/verify.js +0 -94
  69. package/dist/types.d.ts +0 -61
  70. package/dist/types.js +0 -1
  71. package/dist/validators.d.ts +0 -8
  72. package/dist/validators.js +0 -42
  73. package/dist/verify-runner.d.ts +0 -11
  74. package/dist/verify-runner.js +0 -110
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");
@@ -300,12 +301,16 @@ var init_config = __esm({
300
301
  repo: ""
301
302
  },
302
303
  paths: {
303
- taskDir: ".tasks"
304
+ taskDir: ".kody/tasks"
304
305
  },
305
306
  agent: {
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({
@@ -1101,7 +1446,6 @@ var init_agent = __esm({
1101
1446
  taskify: "explore",
1102
1447
  plan: "explore",
1103
1448
  build: "build",
1104
- autofix: "build",
1105
1449
  "review-fix": "build",
1106
1450
  review: "review"
1107
1451
  };
@@ -1309,8 +1653,8 @@ Error context:
1309
1653
  });
1310
1654
 
1311
1655
  // src/stages/gate.ts
1312
- import * as fs6 from "fs";
1313
- import * as path6 from "path";
1656
+ import * as fs7 from "fs";
1657
+ import * as path7 from "path";
1314
1658
  function executeGateStage(ctx, def) {
1315
1659
  if (ctx.input.dryRun) {
1316
1660
  logger.info(` [dry-run] skipping ${def.name}`);
@@ -1353,7 +1697,7 @@ ${output}
1353
1697
  `);
1354
1698
  }
1355
1699
  }
1356
- fs6.writeFileSync(path6.join(ctx.taskDir, "verify.md"), lines.join(""));
1700
+ fs7.writeFileSync(path7.join(ctx.taskDir, "verify.md"), lines.join(""));
1357
1701
  return {
1358
1702
  outcome: verifyResult.pass ? "completed" : "failed",
1359
1703
  retries: 0
@@ -1368,8 +1712,8 @@ var init_gate = __esm({
1368
1712
  });
1369
1713
 
1370
1714
  // src/stages/verify.ts
1371
- import * as fs7 from "fs";
1372
- import * as path7 from "path";
1715
+ import * as fs8 from "fs";
1716
+ import * as path8 from "path";
1373
1717
  import { execFileSync as execFileSync6 } from "child_process";
1374
1718
  async function executeVerifyWithAutofix(ctx, def) {
1375
1719
  const maxAttempts = def.maxRetries ?? 2;
@@ -1380,8 +1724,8 @@ async function executeVerifyWithAutofix(ctx, def) {
1380
1724
  return { ...gateResult, retries: attempt };
1381
1725
  }
1382
1726
  if (attempt < maxAttempts) {
1383
- const verifyPath = path7.join(ctx.taskDir, "verify.md");
1384
- 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";
1385
1729
  const modifiedFiles = getModifiedFiles(ctx.projectDir);
1386
1730
  const defaultRunner = getRunnerForStage(ctx, "taskify");
1387
1731
  const diagConfig = getProjectConfig();
@@ -1477,8 +1821,8 @@ var init_verify = __esm({
1477
1821
  });
1478
1822
 
1479
1823
  // src/stages/review.ts
1480
- import * as fs8 from "fs";
1481
- import * as path8 from "path";
1824
+ import * as fs9 from "fs";
1825
+ import * as path9 from "path";
1482
1826
  async function executeReviewWithFix(ctx, def) {
1483
1827
  if (ctx.input.dryRun) {
1484
1828
  return { outcome: "completed", retries: 0 };
@@ -1489,11 +1833,11 @@ async function executeReviewWithFix(ctx, def) {
1489
1833
  if (reviewResult.outcome !== "completed") {
1490
1834
  return reviewResult;
1491
1835
  }
1492
- const reviewFile = path8.join(ctx.taskDir, "review.md");
1493
- if (!fs8.existsSync(reviewFile)) {
1836
+ const reviewFile = path9.join(ctx.taskDir, "review.md");
1837
+ if (!fs9.existsSync(reviewFile)) {
1494
1838
  return { outcome: "failed", retries: 0, error: "review.md not found" };
1495
1839
  }
1496
- const content = fs8.readFileSync(reviewFile, "utf-8");
1840
+ const content = fs9.readFileSync(reviewFile, "utf-8");
1497
1841
  const hasIssues = /\bfail\b/i.test(content) && !/pass/i.test(content);
1498
1842
  if (!hasIssues) {
1499
1843
  return reviewResult;
@@ -1516,15 +1860,15 @@ var init_review = __esm({
1516
1860
  });
1517
1861
 
1518
1862
  // src/stages/ship.ts
1519
- import * as fs9 from "fs";
1520
- import * as path9 from "path";
1863
+ import * as fs10 from "fs";
1864
+ import * as path10 from "path";
1521
1865
  import { execFileSync as execFileSync7 } from "child_process";
1522
1866
  function buildPrBody(ctx) {
1523
1867
  const sections = [];
1524
- const taskJsonPath = path9.join(ctx.taskDir, "task.json");
1525
- if (fs9.existsSync(taskJsonPath)) {
1868
+ const taskJsonPath = path10.join(ctx.taskDir, "task.json");
1869
+ if (fs10.existsSync(taskJsonPath)) {
1526
1870
  try {
1527
- const raw = fs9.readFileSync(taskJsonPath, "utf-8");
1871
+ const raw = fs10.readFileSync(taskJsonPath, "utf-8");
1528
1872
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
1529
1873
  const task = JSON.parse(cleaned);
1530
1874
  if (task.description) {
@@ -1543,9 +1887,9 @@ ${task.scope.map((s) => `- \`${s}\``).join("\n")}`);
1543
1887
  } catch {
1544
1888
  }
1545
1889
  }
1546
- const reviewPath = path9.join(ctx.taskDir, "review.md");
1547
- if (fs9.existsSync(reviewPath)) {
1548
- 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");
1549
1893
  const summaryMatch = review.match(/## Summary\s*\n([\s\S]*?)(?=\n## |\n*$)/);
1550
1894
  if (summaryMatch) {
1551
1895
  const summary = summaryMatch[1].trim();
@@ -1562,14 +1906,14 @@ ${summary}`);
1562
1906
  **Review:** ${verdictMatch[1].toUpperCase() === "PASS" ? "\u2705 PASS" : "\u274C FAIL"}`);
1563
1907
  }
1564
1908
  }
1565
- const verifyPath = path9.join(ctx.taskDir, "verify.md");
1566
- if (fs9.existsSync(verifyPath)) {
1567
- 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");
1568
1912
  if (/PASS/i.test(verify)) sections.push(`**Verify:** \u2705 typecheck + tests + lint passed`);
1569
1913
  }
1570
- const planPath = path9.join(ctx.taskDir, "plan.md");
1571
- if (fs9.existsSync(planPath)) {
1572
- 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();
1573
1917
  if (plan) {
1574
1918
  const truncated = plan.length > 800 ? plan.slice(0, 800) + "\n..." : plan;
1575
1919
  sections.push(`
@@ -1589,18 +1933,32 @@ Closes #${ctx.input.issueNumber}`);
1589
1933
  return sections.join("\n");
1590
1934
  }
1591
1935
  function executeShipStage(ctx, _def) {
1592
- const shipPath = path9.join(ctx.taskDir, "ship.md");
1936
+ const shipPath = path10.join(ctx.taskDir, "ship.md");
1593
1937
  if (ctx.input.dryRun) {
1594
- 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");
1595
1939
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
1596
1940
  }
1597
1941
  if (ctx.input.local && !ctx.input.issueNumber) {
1598
- 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");
1599
1943
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
1600
1944
  }
1601
1945
  try {
1602
1946
  const head = getCurrentBranch(ctx.projectDir);
1603
1947
  const base = getDefaultBranch(ctx.projectDir);
1948
+ try {
1949
+ execFileSync7("git", ["add", ctx.taskDir], {
1950
+ cwd: ctx.projectDir,
1951
+ env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
1952
+ stdio: "pipe"
1953
+ });
1954
+ execFileSync7("git", ["commit", "--no-gpg-sign", "-m", `chore: add kody task artifacts [skip ci]`], {
1955
+ cwd: ctx.projectDir,
1956
+ env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
1957
+ stdio: "pipe"
1958
+ });
1959
+ logger.info(" Committed task artifacts");
1960
+ } catch {
1961
+ }
1604
1962
  pushBranch(ctx.projectDir);
1605
1963
  const config = getProjectConfig();
1606
1964
  let owner = config.github?.owner;
@@ -1627,10 +1985,10 @@ function executeShipStage(ctx, _def) {
1627
1985
  docs: "docs",
1628
1986
  chore: "chore"
1629
1987
  };
1630
- const taskJsonPath = path9.join(ctx.taskDir, "task.json");
1631
- if (fs9.existsSync(taskJsonPath)) {
1988
+ const taskJsonPath = path10.join(ctx.taskDir, "task.json");
1989
+ if (fs10.existsSync(taskJsonPath)) {
1632
1990
  try {
1633
- const raw = fs9.readFileSync(taskJsonPath, "utf-8");
1991
+ const raw = fs10.readFileSync(taskJsonPath, "utf-8");
1634
1992
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
1635
1993
  const task = JSON.parse(cleaned);
1636
1994
  const prefix = TYPE_PREFIX[task.task_type] ?? "chore";
@@ -1640,9 +1998,9 @@ function executeShipStage(ctx, _def) {
1640
1998
  }
1641
1999
  }
1642
2000
  if (title === "Update") {
1643
- const taskMdPath = path9.join(ctx.taskDir, "task.md");
1644
- if (fs9.existsSync(taskMdPath)) {
1645
- 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");
1646
2004
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#") && !l.startsWith("*"));
1647
2005
  if (firstLine) title = `chore: ${firstLine.trim()}`.slice(0, 72);
1648
2006
  }
@@ -1662,7 +2020,7 @@ function executeShipStage(ctx, _def) {
1662
2020
  } catch {
1663
2021
  }
1664
2022
  }
1665
- fs9.writeFileSync(shipPath, `# Ship
2023
+ fs10.writeFileSync(shipPath, `# Ship
1666
2024
 
1667
2025
  Updated existing PR: ${existingPr.url}
1668
2026
  PR #${existingPr.number}
@@ -1676,19 +2034,19 @@ PR #${existingPr.number}
1676
2034
  } catch {
1677
2035
  }
1678
2036
  }
1679
- fs9.writeFileSync(shipPath, `# Ship
2037
+ fs10.writeFileSync(shipPath, `# Ship
1680
2038
 
1681
2039
  PR created: ${pr.url}
1682
2040
  PR #${pr.number}
1683
2041
  `);
1684
2042
  } else {
1685
- 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");
1686
2044
  }
1687
2045
  }
1688
2046
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
1689
2047
  } catch (err) {
1690
2048
  const msg = err instanceof Error ? err.message : String(err);
1691
- fs9.writeFileSync(shipPath, `# Ship
2049
+ fs10.writeFileSync(shipPath, `# Ship
1692
2050
 
1693
2051
  Failed: ${msg}
1694
2052
  `);
@@ -1698,6 +2056,7 @@ Failed: ${msg}
1698
2056
  var init_ship = __esm({
1699
2057
  "src/stages/ship.ts"() {
1700
2058
  "use strict";
2059
+ init_logger();
1701
2060
  init_git_utils();
1702
2061
  init_github_api();
1703
2062
  init_config();
@@ -1733,15 +2092,15 @@ var init_executor_registry = __esm({
1733
2092
  });
1734
2093
 
1735
2094
  // src/pipeline/questions.ts
1736
- import * as fs10 from "fs";
1737
- import * as path10 from "path";
2095
+ import * as fs11 from "fs";
2096
+ import * as path11 from "path";
1738
2097
  function checkForQuestions(ctx, stageName) {
1739
2098
  if (ctx.input.local || !ctx.input.issueNumber) return false;
1740
2099
  try {
1741
2100
  if (stageName === "taskify") {
1742
- const taskJsonPath = path10.join(ctx.taskDir, "task.json");
1743
- if (!fs10.existsSync(taskJsonPath)) return false;
1744
- 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");
1745
2104
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
1746
2105
  const taskJson = JSON.parse(cleaned);
1747
2106
  if (taskJson.questions && Array.isArray(taskJson.questions) && taskJson.questions.length > 0) {
@@ -1756,9 +2115,9 @@ Reply with \`@kody approve\` and your answers in the comment body.`;
1756
2115
  }
1757
2116
  }
1758
2117
  if (stageName === "plan") {
1759
- const planPath = path10.join(ctx.taskDir, "plan.md");
1760
- if (!fs10.existsSync(planPath)) return false;
1761
- 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");
1762
2121
  const questionsMatch = plan.match(/## Questions\s*\n([\s\S]*?)(?=\n## |\n*$)/);
1763
2122
  if (questionsMatch) {
1764
2123
  const questionsText = questionsMatch[1].trim();
@@ -1787,8 +2146,8 @@ var init_questions = __esm({
1787
2146
  });
1788
2147
 
1789
2148
  // src/pipeline/hooks.ts
1790
- import * as fs11 from "fs";
1791
- import * as path11 from "path";
2149
+ import * as fs12 from "fs";
2150
+ import * as path12 from "path";
1792
2151
  function applyPreStageLabel(ctx, def) {
1793
2152
  if (!ctx.input.issueNumber || ctx.input.local) return;
1794
2153
  if (def.name === "build") setLifecycleLabel(ctx.input.issueNumber, "building");
@@ -1814,9 +2173,9 @@ function autoDetectComplexity(ctx, def) {
1814
2173
  if (def.name !== "taskify") return null;
1815
2174
  if (ctx.input.complexity) return null;
1816
2175
  try {
1817
- const taskJsonPath = path11.join(ctx.taskDir, "task.json");
1818
- if (!fs11.existsSync(taskJsonPath)) return null;
1819
- 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");
1820
2179
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
1821
2180
  const taskJson = JSON.parse(cleaned);
1822
2181
  if (!taskJson.risk_level || !isValidComplexity(taskJson.risk_level)) return null;
@@ -1846,8 +2205,8 @@ function checkRiskGate(ctx, def, state, complexity) {
1846
2205
  if (ctx.input.dryRun || ctx.input.local) return null;
1847
2206
  if (ctx.input.mode === "rerun") return null;
1848
2207
  if (!ctx.input.issueNumber) return null;
1849
- const planPath = path11.join(ctx.taskDir, "plan.md");
1850
- 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)";
1851
2210
  try {
1852
2211
  postComment(
1853
2212
  ctx.input.issueNumber,
@@ -1914,22 +2273,22 @@ var init_hooks = __esm({
1914
2273
  });
1915
2274
 
1916
2275
  // src/learning/auto-learn.ts
1917
- import * as fs12 from "fs";
1918
- import * as path12 from "path";
2276
+ import * as fs13 from "fs";
2277
+ import * as path13 from "path";
1919
2278
  function stripAnsi(str) {
1920
2279
  return str.replace(/\x1b\[[0-9;]*m/g, "");
1921
2280
  }
1922
2281
  function autoLearn(ctx) {
1923
2282
  try {
1924
- const memoryDir = path12.join(ctx.projectDir, ".kody", "memory");
1925
- if (!fs12.existsSync(memoryDir)) {
1926
- 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 });
1927
2286
  }
1928
2287
  const learnings = [];
1929
2288
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1930
- const verifyPath = path12.join(ctx.taskDir, "verify.md");
1931
- if (fs12.existsSync(verifyPath)) {
1932
- 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"));
1933
2292
  if (/vitest/i.test(verify)) learnings.push("- Uses vitest for testing");
1934
2293
  if (/jest/i.test(verify)) learnings.push("- Uses jest for testing");
1935
2294
  if (/eslint/i.test(verify)) learnings.push("- Uses eslint for linting");
@@ -1938,18 +2297,18 @@ function autoLearn(ctx) {
1938
2297
  if (/jsdom/i.test(verify)) learnings.push("- Test environment: jsdom");
1939
2298
  if (/node/i.test(verify) && /environment/i.test(verify)) learnings.push("- Test environment: node");
1940
2299
  }
1941
- const reviewPath = path12.join(ctx.taskDir, "review.md");
1942
- if (fs12.existsSync(reviewPath)) {
1943
- 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");
1944
2303
  if (/\.js extension/i.test(review)) learnings.push("- Imports use .js extensions (ESM)");
1945
2304
  if (/barrel export/i.test(review)) learnings.push("- Uses barrel exports (index.ts)");
1946
2305
  if (/timezone/i.test(review)) learnings.push("- Timezone handling is a concern in this codebase");
1947
2306
  if (/UTC/i.test(review)) learnings.push("- Date operations should consider UTC vs local time");
1948
2307
  }
1949
- const taskJsonPath = path12.join(ctx.taskDir, "task.json");
1950
- if (fs12.existsSync(taskJsonPath)) {
2308
+ const taskJsonPath = path13.join(ctx.taskDir, "task.json");
2309
+ if (fs13.existsSync(taskJsonPath)) {
1951
2310
  try {
1952
- const raw = stripAnsi(fs12.readFileSync(taskJsonPath, "utf-8"));
2311
+ const raw = stripAnsi(fs13.readFileSync(taskJsonPath, "utf-8"));
1953
2312
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
1954
2313
  const task = JSON.parse(cleaned);
1955
2314
  if (task.scope && Array.isArray(task.scope)) {
@@ -1960,12 +2319,13 @@ function autoLearn(ctx) {
1960
2319
  }
1961
2320
  }
1962
2321
  if (learnings.length > 0) {
1963
- const conventionsPath = path12.join(memoryDir, "conventions.md");
2322
+ const conventionsPath = path13.join(memoryDir, "conventions.md");
1964
2323
  const entry = `
1965
2324
  ## Learned ${timestamp2} (task: ${ctx.taskId})
1966
2325
  ${learnings.join("\n")}
1967
2326
  `;
1968
- fs12.appendFileSync(conventionsPath, entry);
2327
+ fs13.appendFileSync(conventionsPath, entry);
2328
+ invalidateCache(conventionsPath, path13.join(memoryDir, ".tiers"));
1969
2329
  logger.info(`Auto-learned ${learnings.length} convention(s)`);
1970
2330
  }
1971
2331
  autoLearnArchitecture(ctx.projectDir, memoryDir, timestamp2);
@@ -1973,13 +2333,13 @@ ${learnings.join("\n")}
1973
2333
  }
1974
2334
  }
1975
2335
  function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
1976
- const archPath = path12.join(memoryDir, "architecture.md");
1977
- if (fs12.existsSync(archPath)) return;
2336
+ const archPath = path13.join(memoryDir, "architecture.md");
2337
+ if (fs13.existsSync(archPath)) return;
1978
2338
  const detected = [];
1979
- const pkgPath = path12.join(projectDir, "package.json");
1980
- if (fs12.existsSync(pkgPath)) {
2339
+ const pkgPath = path13.join(projectDir, "package.json");
2340
+ if (fs13.existsSync(pkgPath)) {
1981
2341
  try {
1982
- const pkg = JSON.parse(fs12.readFileSync(pkgPath, "utf-8"));
2342
+ const pkg = JSON.parse(fs13.readFileSync(pkgPath, "utf-8"));
1983
2343
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1984
2344
  if (allDeps.next) detected.push(`- Framework: Next.js ${allDeps.next}`);
1985
2345
  else if (allDeps.react) detected.push(`- Framework: React ${allDeps.react}`);
@@ -1995,15 +2355,15 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
1995
2355
  if (allDeps.payload || allDeps["@payloadcms/next"]) detected.push(`- CMS: Payload CMS`);
1996
2356
  if (pkg.type === "module") detected.push("- Module system: ESM");
1997
2357
  else detected.push("- Module system: CommonJS");
1998
- if (fs12.existsSync(path12.join(projectDir, "pnpm-lock.yaml"))) detected.push("- Package manager: pnpm");
1999
- else if (fs12.existsSync(path12.join(projectDir, "yarn.lock"))) detected.push("- Package manager: yarn");
2000
- 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");
2001
2361
  } catch {
2002
2362
  }
2003
2363
  }
2004
2364
  const topDirs = [];
2005
2365
  try {
2006
- const entries = fs12.readdirSync(projectDir, { withFileTypes: true });
2366
+ const entries = fs13.readdirSync(projectDir, { withFileTypes: true });
2007
2367
  for (const entry of entries) {
2008
2368
  if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
2009
2369
  topDirs.push(entry.name);
@@ -2012,10 +2372,10 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
2012
2372
  if (topDirs.length > 0) detected.push(`- Top-level directories: ${topDirs.join(", ")}`);
2013
2373
  } catch {
2014
2374
  }
2015
- const srcDir = path12.join(projectDir, "src");
2016
- if (fs12.existsSync(srcDir)) {
2375
+ const srcDir = path13.join(projectDir, "src");
2376
+ if (fs13.existsSync(srcDir)) {
2017
2377
  try {
2018
- const srcEntries = fs12.readdirSync(srcDir, { withFileTypes: true });
2378
+ const srcEntries = fs13.readdirSync(srcDir, { withFileTypes: true });
2019
2379
  const srcDirs = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name);
2020
2380
  if (srcDirs.length > 0) detected.push(`- src/ structure: ${srcDirs.join(", ")}`);
2021
2381
  } catch {
@@ -2027,7 +2387,8 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
2027
2387
  ## Overview
2028
2388
  ${detected.join("\n")}
2029
2389
  `;
2030
- fs12.writeFileSync(archPath, content);
2390
+ fs13.writeFileSync(archPath, content);
2391
+ invalidateCache(archPath, path13.join(memoryDir, ".tiers"));
2031
2392
  logger.info(`Auto-detected architecture (${detected.length} items)`);
2032
2393
  }
2033
2394
  }
@@ -2035,17 +2396,18 @@ var init_auto_learn = __esm({
2035
2396
  "src/learning/auto-learn.ts"() {
2036
2397
  "use strict";
2037
2398
  init_logger();
2399
+ init_context_tiers();
2038
2400
  }
2039
2401
  });
2040
2402
 
2041
2403
  // src/retrospective.ts
2042
- import * as fs13 from "fs";
2043
- import * as path13 from "path";
2404
+ import * as fs14 from "fs";
2405
+ import * as path14 from "path";
2044
2406
  function readArtifact(taskDir, filename, maxChars) {
2045
- const p = path13.join(taskDir, filename);
2046
- if (!fs13.existsSync(p)) return null;
2407
+ const p = path14.join(taskDir, filename);
2408
+ if (!fs14.existsSync(p)) return null;
2047
2409
  try {
2048
- const content = fs13.readFileSync(p, "utf-8");
2410
+ const content = fs14.readFileSync(p, "utf-8");
2049
2411
  return content.length > maxChars ? content.slice(0, maxChars) + "\n...(truncated)" : content;
2050
2412
  } catch {
2051
2413
  return null;
@@ -2098,13 +2460,13 @@ function collectRunContext(ctx, state, pipelineStartTime) {
2098
2460
  return lines.join("\n");
2099
2461
  }
2100
2462
  function getLogPath(projectDir) {
2101
- return path13.join(projectDir, ".kody", "memory", "observer-log.jsonl");
2463
+ return path14.join(projectDir, ".kody", "memory", "observer-log.jsonl");
2102
2464
  }
2103
2465
  function readPreviousRetrospectives(projectDir, limit = 10) {
2104
2466
  const logPath = getLogPath(projectDir);
2105
- if (!fs13.existsSync(logPath)) return [];
2467
+ if (!fs14.existsSync(logPath)) return [];
2106
2468
  try {
2107
- const content = fs13.readFileSync(logPath, "utf-8");
2469
+ const content = fs14.readFileSync(logPath, "utf-8");
2108
2470
  const lines = content.split("\n").filter(Boolean);
2109
2471
  const entries = [];
2110
2472
  const start = Math.max(0, lines.length - limit);
@@ -2131,11 +2493,11 @@ function formatPreviousEntries(entries) {
2131
2493
  }
2132
2494
  function appendRetrospectiveEntry(projectDir, entry) {
2133
2495
  const logPath = getLogPath(projectDir);
2134
- const dir = path13.dirname(logPath);
2135
- if (!fs13.existsSync(dir)) {
2136
- fs13.mkdirSync(dir, { recursive: true });
2496
+ const dir = path14.dirname(logPath);
2497
+ if (!fs14.existsSync(dir)) {
2498
+ fs14.mkdirSync(dir, { recursive: true });
2137
2499
  }
2138
- fs13.appendFileSync(logPath, JSON.stringify(entry) + "\n");
2500
+ fs14.appendFileSync(logPath, JSON.stringify(entry) + "\n");
2139
2501
  }
2140
2502
  async function runRetrospective(ctx, state, pipelineStartTime) {
2141
2503
  if (ctx.input.dryRun) return;
@@ -2246,14 +2608,14 @@ If no pipeline flaw is detected, set "pipelineFlaw" to null.
2246
2608
  });
2247
2609
 
2248
2610
  // src/pipeline.ts
2249
- import * as fs14 from "fs";
2250
- import * as path14 from "path";
2611
+ import * as fs15 from "fs";
2612
+ import * as path15 from "path";
2251
2613
  function ensureFeatureBranchIfNeeded(ctx) {
2252
2614
  if (ctx.input.prNumber) return;
2253
2615
  if (!ctx.input.issueNumber || ctx.input.dryRun) return;
2254
2616
  try {
2255
- const taskMdPath = path14.join(ctx.taskDir, "task.md");
2256
- 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;
2257
2619
  ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
2258
2620
  syncWithDefault(ctx.projectDir);
2259
2621
  } catch (err) {
@@ -2261,10 +2623,10 @@ function ensureFeatureBranchIfNeeded(ctx) {
2261
2623
  }
2262
2624
  }
2263
2625
  function acquireLock(taskDir) {
2264
- const lockPath = path14.join(taskDir, ".lock");
2265
- if (fs14.existsSync(lockPath)) {
2626
+ const lockPath = path15.join(taskDir, ".lock");
2627
+ if (fs15.existsSync(lockPath)) {
2266
2628
  try {
2267
- const pid = parseInt(fs14.readFileSync(lockPath, "utf-8").trim(), 10);
2629
+ const pid = parseInt(fs15.readFileSync(lockPath, "utf-8").trim(), 10);
2268
2630
  try {
2269
2631
  process.kill(pid, 0);
2270
2632
  throw new Error(`Pipeline already running (PID ${pid})`);
@@ -2275,11 +2637,11 @@ function acquireLock(taskDir) {
2275
2637
  if (e instanceof Error && e.message.startsWith("Pipeline already")) throw e;
2276
2638
  }
2277
2639
  }
2278
- fs14.writeFileSync(lockPath, String(process.pid));
2640
+ fs15.writeFileSync(lockPath, String(process.pid));
2279
2641
  }
2280
2642
  function releaseLock(taskDir) {
2281
2643
  try {
2282
- fs14.unlinkSync(path14.join(taskDir, ".lock"));
2644
+ fs15.unlinkSync(path15.join(taskDir, ".lock"));
2283
2645
  } catch {
2284
2646
  }
2285
2647
  }
@@ -2448,7 +2810,7 @@ var init_pipeline = __esm({
2448
2810
 
2449
2811
  // src/preflight.ts
2450
2812
  import { execFileSync as execFileSync8 } from "child_process";
2451
- import * as fs15 from "fs";
2813
+ import * as fs16 from "fs";
2452
2814
  function check(name, fn) {
2453
2815
  try {
2454
2816
  const detail = fn() ?? void 0;
@@ -2501,7 +2863,7 @@ function runPreflight() {
2501
2863
  return v;
2502
2864
  }),
2503
2865
  check("package.json", () => {
2504
- if (!fs15.existsSync("package.json")) throw new Error("not found");
2866
+ if (!fs16.existsSync("package.json")) throw new Error("not found");
2505
2867
  })
2506
2868
  ];
2507
2869
  const failed = checks.filter((c) => !c.ok);
@@ -2522,13 +2884,13 @@ var init_preflight = __esm({
2522
2884
  });
2523
2885
 
2524
2886
  // src/cli/task-resolution.ts
2525
- import * as fs16 from "fs";
2526
- import * as path15 from "path";
2887
+ import * as fs17 from "fs";
2888
+ import * as path16 from "path";
2527
2889
  import { execFileSync as execFileSync9 } from "child_process";
2528
2890
  function findLatestTaskForIssue(issueNumber, projectDir) {
2529
- const tasksDir = path15.join(projectDir, ".tasks");
2530
- if (!fs16.existsSync(tasksDir)) return null;
2531
- 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();
2532
2894
  const prefix = `${issueNumber}-`;
2533
2895
  const direct = allDirs.find((d) => d.startsWith(prefix));
2534
2896
  if (direct) return direct;
@@ -2562,8 +2924,8 @@ var init_task_resolution = __esm({
2562
2924
  });
2563
2925
 
2564
2926
  // src/review-standalone.ts
2565
- import * as fs17 from "fs";
2566
- import * as path16 from "path";
2927
+ import * as fs18 from "fs";
2928
+ import * as path17 from "path";
2567
2929
  function resolveReviewTarget(input) {
2568
2930
  if (input.prs.length === 0) {
2569
2931
  return {
@@ -2587,12 +2949,12 @@ Or comment on the specific PR: \`@kody review\``
2587
2949
  }
2588
2950
  async function runStandaloneReview(input) {
2589
2951
  const taskId = input.taskId ?? `review-${generateTaskId()}`;
2590
- const taskDir = path16.join(input.projectDir, ".tasks", taskId);
2591
- fs17.mkdirSync(taskDir, { recursive: true });
2952
+ const taskDir = path17.join(input.projectDir, ".kody", "tasks", taskId);
2953
+ fs18.mkdirSync(taskDir, { recursive: true });
2592
2954
  const taskContent = `# ${input.prTitle}
2593
2955
 
2594
2956
  ${input.prBody ?? ""}`;
2595
- fs17.writeFileSync(path16.join(taskDir, "task.md"), taskContent);
2957
+ fs18.writeFileSync(path17.join(taskDir, "task.md"), taskContent);
2596
2958
  const reviewDef = STAGES.find((s) => s.name === "review");
2597
2959
  const ctx = {
2598
2960
  taskId,
@@ -2614,10 +2976,10 @@ ${input.prBody ?? ""}`;
2614
2976
  error: result.error ?? "Review stage failed"
2615
2977
  };
2616
2978
  }
2617
- const reviewPath = path16.join(taskDir, "review.md");
2979
+ const reviewPath = path17.join(taskDir, "review.md");
2618
2980
  let reviewContent;
2619
- if (fs17.existsSync(reviewPath)) {
2620
- reviewContent = fs17.readFileSync(reviewPath, "utf-8");
2981
+ if (fs18.existsSync(reviewPath)) {
2982
+ reviewContent = fs18.readFileSync(reviewPath, "utf-8");
2621
2983
  }
2622
2984
  return {
2623
2985
  outcome: "completed",
@@ -2709,8 +3071,8 @@ var init_args = __esm({
2709
3071
  });
2710
3072
 
2711
3073
  // src/cli/litellm.ts
2712
- import * as fs18 from "fs";
2713
- import * as path17 from "path";
3074
+ import * as fs19 from "fs";
3075
+ import * as path18 from "path";
2714
3076
  import { execFileSync as execFileSync10 } from "child_process";
2715
3077
  async function checkLitellmHealth(url) {
2716
3078
  try {
@@ -2721,8 +3083,8 @@ async function checkLitellmHealth(url) {
2721
3083
  }
2722
3084
  }
2723
3085
  async function tryStartLitellm(url, projectDir) {
2724
- const configPath = path17.join(projectDir, "litellm-config.yaml");
2725
- if (!fs18.existsSync(configPath)) {
3086
+ const configPath = path18.join(projectDir, "litellm-config.yaml");
3087
+ if (!fs19.existsSync(configPath)) {
2726
3088
  logger.warn("litellm-config.yaml not found \u2014 cannot start proxy");
2727
3089
  return null;
2728
3090
  }
@@ -2754,10 +3116,10 @@ async function tryStartLitellm(url, projectDir) {
2754
3116
  cmd = "python3";
2755
3117
  args2 = ["-m", "litellm", "--config", configPath, "--port", port];
2756
3118
  }
2757
- const dotenvPath = path17.join(projectDir, ".env");
3119
+ const dotenvPath = path18.join(projectDir, ".env");
2758
3120
  const dotenvVars = {};
2759
- if (fs18.existsSync(dotenvPath)) {
2760
- 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")) {
2761
3123
  const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
2762
3124
  if (match) dotenvVars[match[1]] = match[2];
2763
3125
  }
@@ -2797,8 +3159,8 @@ var init_litellm = __esm({
2797
3159
  });
2798
3160
 
2799
3161
  // src/cli/task-state.ts
2800
- import * as fs19 from "fs";
2801
- import * as path18 from "path";
3162
+ import * as fs20 from "fs";
3163
+ import * as path19 from "path";
2802
3164
  function resolveTaskAction(issueNumber, existingTaskId, existingState) {
2803
3165
  if (!existingTaskId || !existingState) {
2804
3166
  return { action: "start-fresh", taskId: `${issueNumber}-${generateTaskId()}` };
@@ -2830,11 +3192,11 @@ function resolveTaskAction(issueNumber, existingTaskId, existingState) {
2830
3192
  function resolveForIssue(issueNumber, projectDir) {
2831
3193
  const existingTaskId = findLatestTaskForIssue(issueNumber, projectDir);
2832
3194
  if (existingTaskId) {
2833
- const statusPath = path18.join(projectDir, ".tasks", existingTaskId, "status.json");
3195
+ const statusPath = path19.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
2834
3196
  let existingState = null;
2835
- if (fs19.existsSync(statusPath)) {
3197
+ if (fs20.existsSync(statusPath)) {
2836
3198
  try {
2837
- existingState = JSON.parse(fs19.readFileSync(statusPath, "utf-8"));
3199
+ existingState = JSON.parse(fs20.readFileSync(statusPath, "utf-8"));
2838
3200
  } catch {
2839
3201
  }
2840
3202
  }
@@ -2864,13 +3226,13 @@ var init_task_state = __esm({
2864
3226
 
2865
3227
  // src/entry.ts
2866
3228
  var entry_exports = {};
2867
- import * as fs20 from "fs";
2868
- import * as path19 from "path";
3229
+ import * as fs21 from "fs";
3230
+ import * as path20 from "path";
2869
3231
  async function main() {
2870
3232
  const input = parseArgs();
2871
- const projectDir = input.cwd ? path19.resolve(input.cwd) : process.cwd();
3233
+ const projectDir = input.cwd ? path20.resolve(input.cwd) : process.cwd();
2872
3234
  if (input.cwd) {
2873
- if (!fs20.existsSync(projectDir)) {
3235
+ if (!fs21.existsSync(projectDir)) {
2874
3236
  console.error(`--cwd path does not exist: ${projectDir}`);
2875
3237
  process.exit(1);
2876
3238
  }
@@ -2924,8 +3286,8 @@ async function main() {
2924
3286
  process.exit(1);
2925
3287
  }
2926
3288
  }
2927
- const taskDir = path19.join(projectDir, ".tasks", taskId);
2928
- fs20.mkdirSync(taskDir, { recursive: true });
3289
+ const taskDir = path20.join(projectDir, ".kody", "tasks", taskId);
3290
+ fs21.mkdirSync(taskDir, { recursive: true });
2929
3291
  if (input.command === "status") {
2930
3292
  printStatus(taskId, taskDir);
2931
3293
  return;
@@ -2963,9 +3325,14 @@ async function main() {
2963
3325
  const proxyRunning = await checkLitellmHealth(config2.agent.litellmUrl);
2964
3326
  if (!proxyRunning) {
2965
3327
  litellmProcess2 = await tryStartLitellm(config2.agent.litellmUrl, projectDir);
3328
+ if (!litellmProcess2) {
3329
+ logger.error("LiteLLM is configured (litellmUrl) but could not be started. Install it with: pip install 'litellm[proxy]'");
3330
+ process.exit(1);
3331
+ }
2966
3332
  }
2967
- if (config2.agent.litellmUrl) {
2968
- process.env.ANTHROPIC_BASE_URL = config2.agent.litellmUrl;
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";
2969
3336
  }
2970
3337
  }
2971
3338
  const runners2 = createRunners(config2);
@@ -3011,32 +3378,32 @@ async function main() {
3011
3378
  logger.info("Preflight checks:");
3012
3379
  runPreflight();
3013
3380
  if (input.task) {
3014
- fs20.writeFileSync(path19.join(taskDir, "task.md"), input.task);
3381
+ fs21.writeFileSync(path20.join(taskDir, "task.md"), input.task);
3015
3382
  }
3016
- const taskMdPath = path19.join(taskDir, "task.md");
3017
- if (!fs20.existsSync(taskMdPath) && isPRFix && input.prNumber) {
3383
+ const taskMdPath = path20.join(taskDir, "task.md");
3384
+ if (!fs21.existsSync(taskMdPath) && isPRFix && input.prNumber) {
3018
3385
  logger.info(`Fetching PR #${input.prNumber} details as task context...`);
3019
3386
  const prDetails = getPRDetails(input.prNumber);
3020
3387
  if (prDetails) {
3021
3388
  const taskContent = `# ${prDetails.title}
3022
3389
 
3023
3390
  ${prDetails.body ?? ""}`;
3024
- fs20.writeFileSync(taskMdPath, taskContent);
3391
+ fs21.writeFileSync(taskMdPath, taskContent);
3025
3392
  logger.info(` Task loaded from PR #${input.prNumber}: ${prDetails.title}`);
3026
3393
  }
3027
- } else if (!fs20.existsSync(taskMdPath) && input.issueNumber) {
3394
+ } else if (!fs21.existsSync(taskMdPath) && input.issueNumber) {
3028
3395
  logger.info(`Fetching issue #${input.issueNumber} body as task...`);
3029
3396
  const issue = getIssue(input.issueNumber);
3030
3397
  if (issue) {
3031
3398
  const taskContent = `# ${issue.title}
3032
3399
 
3033
3400
  ${issue.body ?? ""}`;
3034
- fs20.writeFileSync(taskMdPath, taskContent);
3401
+ fs21.writeFileSync(taskMdPath, taskContent);
3035
3402
  logger.info(` Task loaded from issue #${input.issueNumber}: ${issue.title}`);
3036
3403
  }
3037
3404
  }
3038
- if (!fs20.existsSync(taskMdPath)) {
3039
- console.error("No task.md found. Provide --task, --issue-number, or ensure .tasks/<id>/task.md exists.");
3405
+ if (!fs21.existsSync(taskMdPath)) {
3406
+ console.error("No task.md found. Provide --task, --issue-number, or ensure .kody/tasks/<id>/task.md exists.");
3040
3407
  process.exit(1);
3041
3408
  }
3042
3409
  if (input.command === "fix" && !input.fromStage) {
@@ -3078,15 +3445,16 @@ ${input.feedback}` : reviewContext;
3078
3445
  if (!proxyRunning) {
3079
3446
  litellmProcess = await tryStartLitellm(config.agent.litellmUrl, projectDir);
3080
3447
  if (!litellmProcess) {
3081
- logger.warn("LiteLLM not available \u2014 falling back to Anthropic models");
3082
- config.agent.litellmUrl = void 0;
3448
+ logger.error("LiteLLM is configured (litellmUrl) but could not be started. Install it with: pip install 'litellm[proxy]'");
3449
+ process.exit(1);
3083
3450
  }
3084
3451
  } else {
3085
3452
  logger.info(`LiteLLM proxy already running at ${config.agent.litellmUrl}`);
3086
3453
  }
3087
- if (config.agent.litellmUrl) {
3088
- process.env.ANTHROPIC_BASE_URL = config.agent.litellmUrl;
3089
- logger.info(`ANTHROPIC_BASE_URL set to ${config.agent.litellmUrl}`);
3454
+ process.env.ANTHROPIC_BASE_URL = config.agent.litellmUrl;
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";
3090
3458
  }
3091
3459
  }
3092
3460
  const runners = createRunners(config);
@@ -3134,7 +3502,7 @@ To rerun: \`@kody rerun ${taskId} --from <stage>\``
3134
3502
  }
3135
3503
  }
3136
3504
  const state = await runPipeline(ctx);
3137
- const files = fs20.readdirSync(taskDir);
3505
+ const files = fs21.readdirSync(taskDir);
3138
3506
  console.log(`
3139
3507
  Artifacts in ${taskDir}:`);
3140
3508
  for (const f of files) {
@@ -3197,15 +3565,15 @@ var init_entry = __esm({
3197
3565
  });
3198
3566
 
3199
3567
  // src/bin/cli.ts
3200
- import * as fs21 from "fs";
3201
- import * as path20 from "path";
3568
+ import * as fs22 from "fs";
3569
+ import * as path21 from "path";
3202
3570
  import { execFileSync as execFileSync11 } from "child_process";
3203
3571
  import { fileURLToPath } from "url";
3204
- var __dirname = path20.dirname(fileURLToPath(import.meta.url));
3205
- var PKG_ROOT = path20.resolve(__dirname, "..", "..");
3572
+ var __dirname = path21.dirname(fileURLToPath(import.meta.url));
3573
+ var PKG_ROOT = path21.resolve(__dirname, "..", "..");
3206
3574
  function getVersion() {
3207
- const pkgPath = path20.join(PKG_ROOT, "package.json");
3208
- 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"));
3209
3577
  return pkg.version;
3210
3578
  }
3211
3579
  function checkCommand2(name, args2, fix) {
@@ -3221,7 +3589,7 @@ function checkCommand2(name, args2, fix) {
3221
3589
  }
3222
3590
  }
3223
3591
  function checkFile(filePath, description, fix) {
3224
- if (fs21.existsSync(filePath)) {
3592
+ if (fs22.existsSync(filePath)) {
3225
3593
  return { name: description, ok: true, detail: filePath };
3226
3594
  }
3227
3595
  return { name: description, ok: false, fix };
@@ -3293,9 +3661,9 @@ function checkGhSecret(repoSlug, secretName) {
3293
3661
  }
3294
3662
  function detectBasicConfig(cwd) {
3295
3663
  let pm = "pnpm";
3296
- if (fs21.existsSync(path20.join(cwd, "yarn.lock"))) pm = "yarn";
3297
- else if (fs21.existsSync(path20.join(cwd, "bun.lockb"))) pm = "bun";
3298
- 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";
3299
3667
  let defaultBranch = "main";
3300
3668
  try {
3301
3669
  const ref = execFileSync11("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
@@ -3338,7 +3706,7 @@ function detectBasicConfig(cwd) {
3338
3706
  function buildConfig(cwd, basic) {
3339
3707
  const pkg = (() => {
3340
3708
  try {
3341
- 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"));
3342
3710
  } catch {
3343
3711
  return {};
3344
3712
  }
@@ -3362,7 +3730,7 @@ function buildConfig(cwd, basic) {
3362
3730
  },
3363
3731
  git: { defaultBranch: basic.defaultBranch },
3364
3732
  github: { owner: basic.owner, repo: basic.repo },
3365
- paths: { taskDir: ".tasks" },
3733
+ paths: { taskDir: ".kody/tasks" },
3366
3734
  agent: {
3367
3735
  runner: "claude-code",
3368
3736
  defaultRunner: "claude",
@@ -3378,37 +3746,38 @@ function initCommand(opts) {
3378
3746
  console.log(`Project: ${cwd}
3379
3747
  `);
3380
3748
  console.log("\u2500\u2500 Files \u2500\u2500");
3381
- const templatesDir = path20.join(PKG_ROOT, "templates");
3749
+ const templatesDir = path21.join(PKG_ROOT, "templates");
3382
3750
  const basic = detectBasicConfig(cwd);
3383
- const workflowSrc = path20.join(templatesDir, "kody.yml");
3384
- const workflowDest = path20.join(cwd, ".github", "workflows", "kody.yml");
3385
- 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)) {
3386
3754
  console.error(" \u2717 Template kody.yml not found in package");
3387
3755
  process.exit(1);
3388
3756
  }
3389
- if (fs21.existsSync(workflowDest) && !opts.force) {
3757
+ if (fs22.existsSync(workflowDest) && !opts.force) {
3390
3758
  console.log(" \u25CB .github/workflows/kody.yml (exists, use --force to overwrite)");
3391
3759
  } else {
3392
- fs21.mkdirSync(path20.dirname(workflowDest), { recursive: true });
3393
- fs21.copyFileSync(workflowSrc, workflowDest);
3760
+ fs22.mkdirSync(path21.dirname(workflowDest), { recursive: true });
3761
+ fs22.copyFileSync(workflowSrc, workflowDest);
3394
3762
  console.log(" \u2713 .github/workflows/kody.yml");
3395
3763
  }
3396
- const configDest = path20.join(cwd, "kody.config.json");
3397
- if (!fs21.existsSync(configDest) || opts.force) {
3764
+ const configDest = path21.join(cwd, "kody.config.json");
3765
+ if (!fs22.existsSync(configDest) || opts.force) {
3398
3766
  const config = buildConfig(cwd, basic);
3399
- fs21.writeFileSync(configDest, JSON.stringify(config, null, 2) + "\n");
3767
+ fs22.writeFileSync(configDest, JSON.stringify(config, null, 2) + "\n");
3400
3768
  console.log(" \u2713 kody.config.json (auto-configured)");
3401
3769
  } else {
3402
3770
  console.log(" \u25CB kody.config.json (exists)");
3403
3771
  }
3404
- const gitignorePath = path20.join(cwd, ".gitignore");
3405
- if (fs21.existsSync(gitignorePath)) {
3406
- const content = fs21.readFileSync(gitignorePath, "utf-8");
3407
- if (!content.includes(".tasks/")) {
3408
- fs21.appendFileSync(gitignorePath, "\n.tasks/\n");
3409
- console.log(" \u2713 .gitignore (added .tasks/)");
3772
+ const gitignorePath = path21.join(cwd, ".gitignore");
3773
+ if (fs22.existsSync(gitignorePath)) {
3774
+ const content = fs22.readFileSync(gitignorePath, "utf-8");
3775
+ if (content.includes(".tasks/")) {
3776
+ const updated = content.replace(/\n?\.tasks\/\n?/g, "\n");
3777
+ fs22.writeFileSync(gitignorePath, updated);
3778
+ console.log(" \u2713 .gitignore (removed legacy .tasks/ \u2014 tasks now committed in .kody/tasks/)");
3410
3779
  } else {
3411
- console.log(" \u25CB .gitignore (.tasks/ already present)");
3780
+ console.log(" \u25CB .gitignore (ok)");
3412
3781
  }
3413
3782
  }
3414
3783
  console.log("\n\u2500\u2500 Prerequisites \u2500\u2500");
@@ -3416,7 +3785,7 @@ function initCommand(opts) {
3416
3785
  checkCommand2("gh", ["--version"], "Install: https://cli.github.com"),
3417
3786
  checkCommand2("git", ["--version"], "Install git"),
3418
3787
  checkCommand2("node", ["--version"], "Install Node.js >= 22"),
3419
- 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`)
3420
3789
  ];
3421
3790
  for (const c of checks) {
3422
3791
  if (c.ok) {
@@ -3494,9 +3863,9 @@ function initCommand(opts) {
3494
3863
  }
3495
3864
  }
3496
3865
  console.log("\n\u2500\u2500 Config \u2500\u2500");
3497
- if (fs21.existsSync(configDest)) {
3866
+ if (fs22.existsSync(configDest)) {
3498
3867
  try {
3499
- const config = JSON.parse(fs21.readFileSync(configDest, "utf-8"));
3868
+ const config = JSON.parse(fs22.readFileSync(configDest, "utf-8"));
3500
3869
  const configChecks = [];
3501
3870
  if (config.github?.owner && config.github?.repo) {
3502
3871
  configChecks.push({ name: "github.owner/repo", ok: true, detail: `${config.github.owner}/${config.github.repo}` });
@@ -3526,10 +3895,10 @@ function initCommand(opts) {
3526
3895
  const filesToCommit = [
3527
3896
  ".github/workflows/kody.yml",
3528
3897
  "kody.config.json"
3529
- ].filter((f) => fs21.existsSync(path20.join(cwd, f)));
3898
+ ].filter((f) => fs22.existsSync(path21.join(cwd, f)));
3530
3899
  if (filesToCommit.length > 0) {
3531
3900
  try {
3532
- const fullPaths = filesToCommit.map((f) => path20.join(cwd, f));
3901
+ const fullPaths = filesToCommit.map((f) => path21.join(cwd, f));
3533
3902
  execFileSync11("npx", ["prettier", "--write", ...fullPaths], {
3534
3903
  cwd,
3535
3904
  encoding: "utf-8",
@@ -3589,22 +3958,22 @@ function initCommand(opts) {
3589
3958
  console.log("");
3590
3959
  }
3591
3960
  }
3592
- var STEP_STAGES = ["taskify", "plan", "build", "autofix", "review", "review-fix"];
3961
+ var STEP_STAGES = ["taskify", "plan", "build", "review", "review-fix"];
3593
3962
  function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
3594
- const srcDir = path20.join(cwd, "src");
3595
- const baseDir = fs21.existsSync(srcDir) ? srcDir : cwd;
3963
+ const srcDir = path21.join(cwd, "src");
3964
+ const baseDir = fs22.existsSync(srcDir) ? srcDir : cwd;
3596
3965
  const results = [];
3597
3966
  function walk(dir) {
3598
3967
  const entries = [];
3599
3968
  try {
3600
- for (const entry of fs21.readdirSync(dir, { withFileTypes: true })) {
3969
+ for (const entry of fs22.readdirSync(dir, { withFileTypes: true })) {
3601
3970
  if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
3602
- const full = path20.join(dir, entry.name);
3971
+ const full = path21.join(dir, entry.name);
3603
3972
  if (entry.isDirectory()) {
3604
3973
  entries.push(...walk(full));
3605
3974
  } else if (/\.(ts|js)$/.test(entry.name) && !/\.(test|spec|config|d)\.(ts|js)$/.test(entry.name)) {
3606
3975
  try {
3607
- const stat = fs21.statSync(full);
3976
+ const stat = fs22.statSync(full);
3608
3977
  if (stat.size >= 200 && stat.size <= 5e3) {
3609
3978
  entries.push({ filePath: full, size: stat.size });
3610
3979
  }
@@ -3618,8 +3987,8 @@ function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
3618
3987
  }
3619
3988
  const files = walk(baseDir).sort((a, b) => b.size - a.size).slice(0, maxFiles);
3620
3989
  for (const { filePath } of files) {
3621
- const rel = path20.relative(cwd, filePath);
3622
- 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);
3623
3992
  results.push(`### File: ${rel}
3624
3993
  \`\`\`typescript
3625
3994
  ${content}
@@ -3627,14 +3996,49 @@ ${content}
3627
3996
  }
3628
3997
  return results.join("\n\n");
3629
3998
  }
3999
+ function ghComment(issueNumber, body, cwd) {
4000
+ try {
4001
+ let repoSlug = "";
4002
+ try {
4003
+ const configPath = path21.join(cwd, "kody.config.json");
4004
+ if (fs22.existsSync(configPath)) {
4005
+ const config = JSON.parse(fs22.readFileSync(configPath, "utf-8"));
4006
+ if (config.github?.owner && config.github?.repo) {
4007
+ repoSlug = `${config.github.owner}/${config.github.repo}`;
4008
+ }
4009
+ }
4010
+ } catch {
4011
+ }
4012
+ if (!repoSlug) return;
4013
+ execFileSync11("gh", [
4014
+ "issue",
4015
+ "comment",
4016
+ String(issueNumber),
4017
+ "--repo",
4018
+ repoSlug,
4019
+ "--body",
4020
+ body
4021
+ ], {
4022
+ cwd,
4023
+ encoding: "utf-8",
4024
+ timeout: 15e3,
4025
+ stdio: ["pipe", "pipe", "pipe"]
4026
+ });
4027
+ } catch {
4028
+ }
4029
+ }
3630
4030
  function bootstrapCommand() {
3631
4031
  const cwd = process.cwd();
4032
+ const issueNumber = parseInt(process.env.ISSUE_NUMBER ?? "", 10) || 0;
3632
4033
  console.log(`
3633
4034
  \u{1F527} Kody Bootstrap \u2014 Generating project memory + step files
3634
4035
  `);
4036
+ if (issueNumber) {
4037
+ ghComment(issueNumber, "\u{1F527} **Bootstrap started** \u2014 analyzing project and generating configuration...", cwd);
4038
+ }
3635
4039
  const readIfExists = (rel, maxChars = 3e3) => {
3636
- const p = path20.join(cwd, rel);
3637
- 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);
3638
4042
  return null;
3639
4043
  };
3640
4044
  let repoContext = "";
@@ -3669,14 +4073,14 @@ ${sampleFiles}
3669
4073
 
3670
4074
  `;
3671
4075
  try {
3672
- 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);
3673
4077
  repoContext += `## Top-level directories
3674
4078
  ${topDirs.join(", ")}
3675
4079
 
3676
4080
  `;
3677
- const srcDir = path20.join(cwd, "src");
3678
- if (fs21.existsSync(srcDir)) {
3679
- 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);
3680
4084
  if (srcDirs.length > 0) repoContext += `## src/ subdirectories
3681
4085
  ${srcDirs.join(", ")}
3682
4086
 
@@ -3686,17 +4090,17 @@ ${srcDirs.join(", ")}
3686
4090
  }
3687
4091
  const existingFiles = [];
3688
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"]) {
3689
- if (fs21.existsSync(path20.join(cwd, f))) existingFiles.push(f);
4093
+ if (fs22.existsSync(path21.join(cwd, f))) existingFiles.push(f);
3690
4094
  }
3691
4095
  if (existingFiles.length) repoContext += `## Config files present
3692
4096
  ${existingFiles.join(", ")}
3693
4097
 
3694
4098
  `;
3695
4099
  console.log("\u2500\u2500 Project Memory \u2500\u2500");
3696
- const memoryDir = path20.join(cwd, ".kody", "memory");
3697
- fs21.mkdirSync(memoryDir, { recursive: true });
3698
- const archPath = path20.join(memoryDir, "architecture.md");
3699
- 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");
3700
4104
  const memoryPrompt = `You are analyzing a project to generate documentation for an autonomous SDLC pipeline.
3701
4105
 
3702
4106
  Given this project context, output ONLY a JSON object with EXACTLY this structure:
@@ -3737,12 +4141,12 @@ ${repoContext}`;
3737
4141
  const cleaned = output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
3738
4142
  const parsed = JSON.parse(cleaned);
3739
4143
  if (parsed.architecture) {
3740
- fs21.writeFileSync(archPath, parsed.architecture);
4144
+ fs22.writeFileSync(archPath, parsed.architecture);
3741
4145
  const lineCount = parsed.architecture.split("\n").length;
3742
4146
  console.log(` \u2713 .kody/memory/architecture.md (${lineCount} lines)`);
3743
4147
  }
3744
4148
  if (parsed.conventions) {
3745
- fs21.writeFileSync(conventionsPath, parsed.conventions);
4149
+ fs22.writeFileSync(conventionsPath, parsed.conventions);
3746
4150
  const lineCount = parsed.conventions.split("\n").length;
3747
4151
  console.log(` \u2713 .kody/memory/conventions.md (${lineCount} lines)`);
3748
4152
  }
@@ -3751,34 +4155,34 @@ ${repoContext}`;
3751
4155
  const detected = detectArchitectureBasic(cwd);
3752
4156
  if (detected.length > 0) {
3753
4157
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3754
- fs21.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
4158
+ fs22.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
3755
4159
 
3756
4160
  ## Overview
3757
4161
  ${detected.join("\n")}
3758
4162
  `);
3759
4163
  console.log(` \u2713 .kody/memory/architecture.md (${detected.length} items, basic detection)`);
3760
4164
  }
3761
- 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");
3762
4166
  console.log(" \u2713 .kody/memory/conventions.md (seed)");
3763
4167
  }
3764
4168
  console.log("\n\u2500\u2500 Step Files \u2500\u2500");
3765
- const stepsDir = path20.join(cwd, ".kody", "steps");
3766
- fs21.mkdirSync(stepsDir, { recursive: true });
3767
- const arch = fs21.existsSync(archPath) ? fs21.readFileSync(archPath, "utf-8") : "";
3768
- 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") : "";
3769
4173
  console.log(" \u23F3 Customizing step files...");
3770
4174
  let stepCount = 0;
3771
4175
  for (const stage of STEP_STAGES) {
3772
- const templatePath = path20.join(PKG_ROOT, "prompts", `${stage}.md`);
3773
- if (!fs21.existsSync(templatePath)) {
4176
+ const templatePath = path21.join(PKG_ROOT, "prompts", `${stage}.md`);
4177
+ if (!fs22.existsSync(templatePath)) {
3774
4178
  console.log(` \u2717 ${stage}.md \u2014 template not found in engine`);
3775
4179
  continue;
3776
4180
  }
3777
- const defaultPrompt = fs21.readFileSync(templatePath, "utf-8");
4181
+ const defaultPrompt = fs22.readFileSync(templatePath, "utf-8");
3778
4182
  const contextPlaceholder = "{{TASK_CONTEXT}}";
3779
4183
  const placeholderIdx = defaultPrompt.indexOf(contextPlaceholder);
3780
4184
  if (placeholderIdx === -1) {
3781
- fs21.copyFileSync(templatePath, path20.join(stepsDir, `${stage}.md`));
4185
+ fs22.copyFileSync(templatePath, path21.join(stepsDir, `${stage}.md`));
3782
4186
  stepCount++;
3783
4187
  console.log(` \u2713 ${stage}.md`);
3784
4188
  continue;
@@ -3835,12 +4239,12 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
3835
4239
  let cleaned = output.replace(/^```(?:markdown|md)?\s*\n?/, "").replace(/\n?```\s*$/, "");
3836
4240
  cleaned = cleaned.replace(/\n*\{\{TASK_CONTEXT\}\}\s*$/, "").trimEnd();
3837
4241
  const finalPrompt = cleaned + "\n\n" + afterPlaceholder;
3838
- fs21.writeFileSync(path20.join(stepsDir, `${stage}.md`), finalPrompt);
4242
+ fs22.writeFileSync(path21.join(stepsDir, `${stage}.md`), finalPrompt);
3839
4243
  stepCount++;
3840
4244
  console.log(` \u2713 ${stage}.md`);
3841
4245
  } catch {
3842
4246
  console.log(` \u26A0 ${stage}.md \u2014 customization failed, using default template`);
3843
- fs21.copyFileSync(templatePath, path20.join(stepsDir, `${stage}.md`));
4247
+ fs22.copyFileSync(templatePath, path21.join(stepsDir, `${stage}.md`));
3844
4248
  stepCount++;
3845
4249
  }
3846
4250
  }
@@ -3849,16 +4253,16 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
3849
4253
  const filesToCommit = [
3850
4254
  ".kody/memory/architecture.md",
3851
4255
  ".kody/memory/conventions.md"
3852
- ].filter((f) => fs21.existsSync(path20.join(cwd, f)));
4256
+ ].filter((f) => fs22.existsSync(path21.join(cwd, f)));
3853
4257
  for (const stage of STEP_STAGES) {
3854
4258
  const stepFile = `.kody/steps/${stage}.md`;
3855
- if (fs21.existsSync(path20.join(cwd, stepFile))) {
4259
+ if (fs22.existsSync(path21.join(cwd, stepFile))) {
3856
4260
  filesToCommit.push(stepFile);
3857
4261
  }
3858
4262
  }
3859
4263
  if (filesToCommit.length > 0) {
3860
4264
  try {
3861
- const fullPaths = filesToCommit.map((f) => path20.join(cwd, f));
4265
+ const fullPaths = filesToCommit.map((f) => path21.join(cwd, f));
3862
4266
  for (let pass = 0; pass < 2; pass++) {
3863
4267
  execFileSync11("npx", ["prettier", "--write", ...fullPaths], {
3864
4268
  cwd,
@@ -3885,9 +4289,9 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
3885
4289
  console.log(` \u2713 Pushed branch: ${branchName}`);
3886
4290
  let baseBranch = "main";
3887
4291
  try {
3888
- const configPath = path20.join(cwd, "kody.config.json");
3889
- if (fs21.existsSync(configPath)) {
3890
- 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"));
3891
4295
  baseBranch = config.git?.defaultBranch ?? "main";
3892
4296
  }
3893
4297
  } catch {
@@ -3911,8 +4315,16 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
3911
4315
  stdio: ["pipe", "pipe", "pipe"]
3912
4316
  }).trim();
3913
4317
  console.log(` \u2713 Created PR: ${prUrl}`);
4318
+ if (issueNumber) {
4319
+ ghComment(issueNumber, `\u2705 **Bootstrap complete** \u2014 PR created: ${prUrl}
4320
+
4321
+ Review and merge to activate project-specific pipeline configuration.`, cwd);
4322
+ }
3914
4323
  } catch (prErr) {
3915
4324
  console.log(` \u25CB PR creation failed: ${prErr instanceof Error ? prErr.message : prErr}`);
4325
+ if (issueNumber) {
4326
+ ghComment(issueNumber, `\u26A0\uFE0F **Bootstrap complete** \u2014 files generated and pushed to branch \`${branchName}\`, but PR creation failed. Create it manually.`, cwd);
4327
+ }
3916
4328
  }
3917
4329
  } else {
3918
4330
  console.log(" \u25CB No new changes to commit");
@@ -3935,6 +4347,9 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
3935
4347
  }
3936
4348
  } catch (err) {
3937
4349
  console.log(` \u25CB Git commit skipped: ${err instanceof Error ? err.message : err}`);
4350
+ if (issueNumber) {
4351
+ ghComment(issueNumber, `\u274C **Bootstrap failed** \u2014 git operation error: ${err instanceof Error ? err.message : err}`, cwd);
4352
+ }
3938
4353
  }
3939
4354
  }
3940
4355
  console.log("\n\u2500\u2500 Done \u2500\u2500");
@@ -3943,10 +4358,10 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
3943
4358
  }
3944
4359
  function detectArchitectureBasic(cwd) {
3945
4360
  const detected = [];
3946
- const pkgPath = path20.join(cwd, "package.json");
3947
- if (fs21.existsSync(pkgPath)) {
4361
+ const pkgPath = path21.join(cwd, "package.json");
4362
+ if (fs22.existsSync(pkgPath)) {
3948
4363
  try {
3949
- const pkg = JSON.parse(fs21.readFileSync(pkgPath, "utf-8"));
4364
+ const pkg = JSON.parse(fs22.readFileSync(pkgPath, "utf-8"));
3950
4365
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
3951
4366
  if (allDeps.next) detected.push(`- Framework: Next.js ${allDeps.next}`);
3952
4367
  else if (allDeps.react) detected.push(`- Framework: React ${allDeps.react}`);
@@ -3962,10 +4377,10 @@ function detectArchitectureBasic(cwd) {
3962
4377
  if (allDeps["drizzle-orm"]) detected.push("- ORM: Drizzle");
3963
4378
  if (allDeps.payload || allDeps["@payloadcms/next"]) detected.push("- CMS: Payload CMS");
3964
4379
  if (allDeps.tailwindcss) detected.push(`- CSS: Tailwind CSS ${allDeps.tailwindcss}`);
3965
- if (fs21.existsSync(path20.join(cwd, "pnpm-lock.yaml"))) detected.push("- Package manager: pnpm");
3966
- else if (fs21.existsSync(path20.join(cwd, "yarn.lock"))) detected.push("- Package manager: yarn");
3967
- else if (fs21.existsSync(path20.join(cwd, "bun.lockb"))) detected.push("- Package manager: bun");
3968
- 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");
3969
4384
  } catch {
3970
4385
  }
3971
4386
  }
@@ -3982,3 +4397,13 @@ if (command === "init") {
3982
4397
  } else {
3983
4398
  Promise.resolve().then(() => init_entry());
3984
4399
  }
4400
+ export {
4401
+ buildConfig,
4402
+ checkCommand2 as checkCommand,
4403
+ checkFile,
4404
+ checkGhAuth,
4405
+ checkGhRepoAccess,
4406
+ checkGhSecret,
4407
+ detectArchitectureBasic,
4408
+ detectBasicConfig
4409
+ };