@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.
- package/dist/bin/cli.js +616 -262
- 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 =
|
|
784
|
-
if (
|
|
785
|
-
return
|
|
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
|
-
|
|
792
|
-
|
|
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 (
|
|
796
|
-
return
|
|
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 =
|
|
809
|
-
if (
|
|
810
|
-
const taskMd =
|
|
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 =
|
|
817
|
-
if (
|
|
1138
|
+
const taskJsonPath = path5.join(taskDir, "task.json");
|
|
1139
|
+
if (fs5.existsSync(taskJsonPath)) {
|
|
818
1140
|
try {
|
|
819
|
-
const taskDef = JSON.parse(
|
|
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 =
|
|
833
|
-
if (
|
|
834
|
-
const spec =
|
|
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 =
|
|
842
|
-
if (
|
|
843
|
-
const plan =
|
|
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 =
|
|
851
|
-
if (
|
|
852
|
-
const accumulated =
|
|
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
|
|
973
|
-
import * as
|
|
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
|
-
|
|
1371
|
+
fs6.writeFileSync(path6.join(ctx.taskDir, def.outputFile), result.output);
|
|
1027
1372
|
}
|
|
1028
1373
|
if (def.outputFile) {
|
|
1029
|
-
const outputPath =
|
|
1030
|
-
if (!
|
|
1031
|
-
const ext =
|
|
1032
|
-
const base =
|
|
1033
|
-
const files =
|
|
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
|
-
|
|
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 =
|
|
1045
|
-
if (
|
|
1046
|
-
const content =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
1312
|
-
import * as
|
|
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
|
-
|
|
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
|
|
1371
|
-
import * as
|
|
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 =
|
|
1383
|
-
const errorOutput =
|
|
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
|
|
1480
|
-
import * as
|
|
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 =
|
|
1492
|
-
if (!
|
|
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 =
|
|
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
|
|
1519
|
-
import * as
|
|
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 =
|
|
1524
|
-
if (
|
|
1868
|
+
const taskJsonPath = path10.join(ctx.taskDir, "task.json");
|
|
1869
|
+
if (fs10.existsSync(taskJsonPath)) {
|
|
1525
1870
|
try {
|
|
1526
|
-
const raw =
|
|
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 =
|
|
1546
|
-
if (
|
|
1547
|
-
const review =
|
|
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 =
|
|
1565
|
-
if (
|
|
1566
|
-
const verify =
|
|
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 =
|
|
1570
|
-
if (
|
|
1571
|
-
const plan =
|
|
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 =
|
|
1936
|
+
const shipPath = path10.join(ctx.taskDir, "ship.md");
|
|
1592
1937
|
if (ctx.input.dryRun) {
|
|
1593
|
-
|
|
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
|
-
|
|
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 =
|
|
1644
|
-
if (
|
|
1988
|
+
const taskJsonPath = path10.join(ctx.taskDir, "task.json");
|
|
1989
|
+
if (fs10.existsSync(taskJsonPath)) {
|
|
1645
1990
|
try {
|
|
1646
|
-
const raw =
|
|
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 =
|
|
1657
|
-
if (
|
|
1658
|
-
const content =
|
|
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
|
-
|
|
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
|
-
|
|
2037
|
+
fs10.writeFileSync(shipPath, `# Ship
|
|
1693
2038
|
|
|
1694
2039
|
PR created: ${pr.url}
|
|
1695
2040
|
PR #${pr.number}
|
|
1696
2041
|
`);
|
|
1697
2042
|
} else {
|
|
1698
|
-
|
|
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
|
-
|
|
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
|
|
1751
|
-
import * as
|
|
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 =
|
|
1757
|
-
if (!
|
|
1758
|
-
const raw =
|
|
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 =
|
|
1774
|
-
if (!
|
|
1775
|
-
const plan =
|
|
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
|
|
1805
|
-
import * as
|
|
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 =
|
|
1832
|
-
if (!
|
|
1833
|
-
const raw =
|
|
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 =
|
|
1864
|
-
const plan =
|
|
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
|
|
1932
|
-
import * as
|
|
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 =
|
|
1939
|
-
if (!
|
|
1940
|
-
|
|
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 =
|
|
1945
|
-
if (
|
|
1946
|
-
const verify = stripAnsi(
|
|
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 =
|
|
1956
|
-
if (
|
|
1957
|
-
const review =
|
|
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 =
|
|
1964
|
-
if (
|
|
2308
|
+
const taskJsonPath = path13.join(ctx.taskDir, "task.json");
|
|
2309
|
+
if (fs13.existsSync(taskJsonPath)) {
|
|
1965
2310
|
try {
|
|
1966
|
-
const raw = stripAnsi(
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
1991
|
-
if (
|
|
2336
|
+
const archPath = path13.join(memoryDir, "architecture.md");
|
|
2337
|
+
if (fs13.existsSync(archPath)) return;
|
|
1992
2338
|
const detected = [];
|
|
1993
|
-
const pkgPath =
|
|
1994
|
-
if (
|
|
2339
|
+
const pkgPath = path13.join(projectDir, "package.json");
|
|
2340
|
+
if (fs13.existsSync(pkgPath)) {
|
|
1995
2341
|
try {
|
|
1996
|
-
const pkg = JSON.parse(
|
|
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 (
|
|
2013
|
-
else if (
|
|
2014
|
-
else if (
|
|
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 =
|
|
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 =
|
|
2030
|
-
if (
|
|
2375
|
+
const srcDir = path13.join(projectDir, "src");
|
|
2376
|
+
if (fs13.existsSync(srcDir)) {
|
|
2031
2377
|
try {
|
|
2032
|
-
const srcEntries =
|
|
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
|
-
|
|
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
|
|
2057
|
-
import * as
|
|
2404
|
+
import * as fs14 from "fs";
|
|
2405
|
+
import * as path14 from "path";
|
|
2058
2406
|
function readArtifact(taskDir, filename, maxChars) {
|
|
2059
|
-
const p =
|
|
2060
|
-
if (!
|
|
2407
|
+
const p = path14.join(taskDir, filename);
|
|
2408
|
+
if (!fs14.existsSync(p)) return null;
|
|
2061
2409
|
try {
|
|
2062
|
-
const content =
|
|
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
|
|
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 (!
|
|
2467
|
+
if (!fs14.existsSync(logPath)) return [];
|
|
2120
2468
|
try {
|
|
2121
|
-
const content =
|
|
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 =
|
|
2149
|
-
if (!
|
|
2150
|
-
|
|
2496
|
+
const dir = path14.dirname(logPath);
|
|
2497
|
+
if (!fs14.existsSync(dir)) {
|
|
2498
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
2151
2499
|
}
|
|
2152
|
-
|
|
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
|
|
2264
|
-
import * as
|
|
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 =
|
|
2270
|
-
const title =
|
|
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 =
|
|
2279
|
-
if (
|
|
2626
|
+
const lockPath = path15.join(taskDir, ".lock");
|
|
2627
|
+
if (fs15.existsSync(lockPath)) {
|
|
2280
2628
|
try {
|
|
2281
|
-
const pid = parseInt(
|
|
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
|
-
|
|
2640
|
+
fs15.writeFileSync(lockPath, String(process.pid));
|
|
2293
2641
|
}
|
|
2294
2642
|
function releaseLock(taskDir) {
|
|
2295
2643
|
try {
|
|
2296
|
-
|
|
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
|
|
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 (!
|
|
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
|
|
2540
|
-
import * as
|
|
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 =
|
|
2544
|
-
if (!
|
|
2545
|
-
const allDirs =
|
|
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
|
|
2580
|
-
import * as
|
|
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 =
|
|
2605
|
-
|
|
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
|
-
|
|
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 =
|
|
2979
|
+
const reviewPath = path17.join(taskDir, "review.md");
|
|
2632
2980
|
let reviewContent;
|
|
2633
|
-
if (
|
|
2634
|
-
reviewContent =
|
|
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
|
|
2727
|
-
import * as
|
|
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 =
|
|
2739
|
-
if (!
|
|
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 =
|
|
3119
|
+
const dotenvPath = path18.join(projectDir, ".env");
|
|
2772
3120
|
const dotenvVars = {};
|
|
2773
|
-
if (
|
|
2774
|
-
for (const line of
|
|
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
|
|
2815
|
-
import * as
|
|
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 =
|
|
3195
|
+
const statusPath = path19.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
|
|
2848
3196
|
let existingState = null;
|
|
2849
|
-
if (
|
|
3197
|
+
if (fs20.existsSync(statusPath)) {
|
|
2850
3198
|
try {
|
|
2851
|
-
existingState = JSON.parse(
|
|
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
|
|
2882
|
-
import * as
|
|
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 ?
|
|
3233
|
+
const projectDir = input.cwd ? path20.resolve(input.cwd) : process.cwd();
|
|
2886
3234
|
if (input.cwd) {
|
|
2887
|
-
if (!
|
|
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 =
|
|
2942
|
-
|
|
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
|
-
|
|
3381
|
+
fs21.writeFileSync(path20.join(taskDir, "task.md"), input.task);
|
|
3031
3382
|
}
|
|
3032
|
-
const taskMdPath =
|
|
3033
|
-
if (!
|
|
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
|
-
|
|
3391
|
+
fs21.writeFileSync(taskMdPath, taskContent);
|
|
3041
3392
|
logger.info(` Task loaded from PR #${input.prNumber}: ${prDetails.title}`);
|
|
3042
3393
|
}
|
|
3043
|
-
} else if (!
|
|
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
|
-
|
|
3401
|
+
fs21.writeFileSync(taskMdPath, taskContent);
|
|
3051
3402
|
logger.info(` Task loaded from issue #${input.issueNumber}: ${issue.title}`);
|
|
3052
3403
|
}
|
|
3053
3404
|
}
|
|
3054
|
-
if (!
|
|
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 =
|
|
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
|
|
3215
|
-
import * as
|
|
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 =
|
|
3219
|
-
var PKG_ROOT =
|
|
3572
|
+
var __dirname = path21.dirname(fileURLToPath(import.meta.url));
|
|
3573
|
+
var PKG_ROOT = path21.resolve(__dirname, "..", "..");
|
|
3220
3574
|
function getVersion() {
|
|
3221
|
-
const pkgPath =
|
|
3222
|
-
const pkg = JSON.parse(
|
|
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 (
|
|
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 (
|
|
3311
|
-
else if (
|
|
3312
|
-
else if (!
|
|
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(
|
|
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 =
|
|
3749
|
+
const templatesDir = path21.join(PKG_ROOT, "templates");
|
|
3396
3750
|
const basic = detectBasicConfig(cwd);
|
|
3397
|
-
const workflowSrc =
|
|
3398
|
-
const workflowDest =
|
|
3399
|
-
if (!
|
|
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 (
|
|
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
|
-
|
|
3407
|
-
|
|
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 =
|
|
3411
|
-
if (!
|
|
3764
|
+
const configDest = path21.join(cwd, "kody.config.json");
|
|
3765
|
+
if (!fs22.existsSync(configDest) || opts.force) {
|
|
3412
3766
|
const config = buildConfig(cwd, basic);
|
|
3413
|
-
|
|
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 =
|
|
3419
|
-
if (
|
|
3420
|
-
const content =
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
3866
|
+
if (fs22.existsSync(configDest)) {
|
|
3513
3867
|
try {
|
|
3514
|
-
const config = JSON.parse(
|
|
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) =>
|
|
3898
|
+
].filter((f) => fs22.existsSync(path21.join(cwd, f)));
|
|
3545
3899
|
if (filesToCommit.length > 0) {
|
|
3546
3900
|
try {
|
|
3547
|
-
const fullPaths = filesToCommit.map((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 =
|
|
3610
|
-
const baseDir =
|
|
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
|
|
3969
|
+
for (const entry of fs22.readdirSync(dir, { withFileTypes: true })) {
|
|
3616
3970
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
3617
|
-
const full =
|
|
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 =
|
|
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 =
|
|
3637
|
-
const content =
|
|
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 =
|
|
3650
|
-
if (
|
|
3651
|
-
const config = JSON.parse(
|
|
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 =
|
|
3687
|
-
if (
|
|
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 =
|
|
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 =
|
|
3728
|
-
if (
|
|
3729
|
-
const srcDirs =
|
|
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 (
|
|
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 =
|
|
3747
|
-
|
|
3748
|
-
const archPath =
|
|
3749
|
-
const conventionsPath =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
3816
|
-
|
|
3817
|
-
const arch =
|
|
3818
|
-
const conv =
|
|
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 =
|
|
3823
|
-
if (!
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
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 (
|
|
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) =>
|
|
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 =
|
|
3939
|
-
if (
|
|
3940
|
-
const config = JSON.parse(
|
|
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 =
|
|
4008
|
-
if (
|
|
4361
|
+
const pkgPath = path21.join(cwd, "package.json");
|
|
4362
|
+
if (fs22.existsSync(pkgPath)) {
|
|
4009
4363
|
try {
|
|
4010
|
-
const pkg = JSON.parse(
|
|
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 (
|
|
4027
|
-
else if (
|
|
4028
|
-
else if (
|
|
4029
|
-
else if (
|
|
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
|
}
|