@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.
- package/dist/bin/cli.js +699 -274
- package/kody.config.schema.json +2 -2
- package/package.json +1 -1
- package/prompts/autofix.md +27 -9
- package/prompts/review.md +83 -16
- package/templates/kody.yml +8 -8
- package/dist/agent-runner.d.ts +0 -4
- package/dist/agent-runner.js +0 -122
- package/dist/ci/parse-inputs.d.ts +0 -6
- package/dist/ci/parse-inputs.js +0 -76
- package/dist/ci/parse-safety.d.ts +0 -6
- package/dist/ci/parse-safety.js +0 -22
- package/dist/cli/args.d.ts +0 -13
- package/dist/cli/args.js +0 -42
- package/dist/cli/litellm.d.ts +0 -2
- package/dist/cli/litellm.js +0 -85
- package/dist/cli/task-resolution.d.ts +0 -2
- package/dist/cli/task-resolution.js +0 -41
- package/dist/config.d.ts +0 -49
- package/dist/config.js +0 -72
- package/dist/context.d.ts +0 -4
- package/dist/context.js +0 -83
- package/dist/definitions.d.ts +0 -3
- package/dist/definitions.js +0 -59
- package/dist/entry.d.ts +0 -1
- package/dist/entry.js +0 -236
- package/dist/git-utils.d.ts +0 -13
- package/dist/git-utils.js +0 -174
- package/dist/github-api.d.ts +0 -14
- package/dist/github-api.js +0 -114
- package/dist/kody-utils.d.ts +0 -1
- package/dist/kody-utils.js +0 -9
- package/dist/learning/auto-learn.d.ts +0 -2
- package/dist/learning/auto-learn.js +0 -169
- package/dist/logger.d.ts +0 -14
- package/dist/logger.js +0 -51
- package/dist/memory.d.ts +0 -1
- package/dist/memory.js +0 -20
- package/dist/observer.d.ts +0 -9
- package/dist/observer.js +0 -80
- package/dist/pipeline/complexity.d.ts +0 -3
- package/dist/pipeline/complexity.js +0 -12
- package/dist/pipeline/executor-registry.d.ts +0 -3
- package/dist/pipeline/executor-registry.js +0 -20
- package/dist/pipeline/hooks.d.ts +0 -17
- package/dist/pipeline/hooks.js +0 -110
- package/dist/pipeline/questions.d.ts +0 -2
- package/dist/pipeline/questions.js +0 -44
- package/dist/pipeline/runner-selection.d.ts +0 -2
- package/dist/pipeline/runner-selection.js +0 -13
- package/dist/pipeline/state.d.ts +0 -4
- package/dist/pipeline/state.js +0 -37
- package/dist/pipeline.d.ts +0 -3
- package/dist/pipeline.js +0 -213
- package/dist/preflight.d.ts +0 -1
- package/dist/preflight.js +0 -69
- package/dist/retrospective.d.ts +0 -26
- package/dist/retrospective.js +0 -211
- package/dist/stages/agent.d.ts +0 -2
- package/dist/stages/agent.js +0 -94
- package/dist/stages/gate.d.ts +0 -2
- package/dist/stages/gate.js +0 -32
- package/dist/stages/review.d.ts +0 -2
- package/dist/stages/review.js +0 -32
- package/dist/stages/ship.d.ts +0 -3
- package/dist/stages/ship.js +0 -154
- package/dist/stages/verify.d.ts +0 -2
- package/dist/stages/verify.js +0 -94
- package/dist/types.d.ts +0 -61
- package/dist/types.js +0 -1
- package/dist/validators.d.ts +0 -8
- package/dist/validators.js +0 -42
- package/dist/verify-runner.d.ts +0 -11
- 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 =
|
|
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({
|
|
@@ -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
|
|
1313
|
-
import * as
|
|
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
|
-
|
|
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
|
|
1372
|
-
import * as
|
|
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 =
|
|
1384
|
-
const errorOutput =
|
|
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
|
|
1481
|
-
import * as
|
|
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 =
|
|
1493
|
-
if (!
|
|
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 =
|
|
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
|
|
1520
|
-
import * as
|
|
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 =
|
|
1525
|
-
if (
|
|
1868
|
+
const taskJsonPath = path10.join(ctx.taskDir, "task.json");
|
|
1869
|
+
if (fs10.existsSync(taskJsonPath)) {
|
|
1526
1870
|
try {
|
|
1527
|
-
const raw =
|
|
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 =
|
|
1547
|
-
if (
|
|
1548
|
-
const review =
|
|
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 =
|
|
1566
|
-
if (
|
|
1567
|
-
const verify =
|
|
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 =
|
|
1571
|
-
if (
|
|
1572
|
-
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();
|
|
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 =
|
|
1936
|
+
const shipPath = path10.join(ctx.taskDir, "ship.md");
|
|
1593
1937
|
if (ctx.input.dryRun) {
|
|
1594
|
-
|
|
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
|
-
|
|
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 =
|
|
1631
|
-
if (
|
|
1988
|
+
const taskJsonPath = path10.join(ctx.taskDir, "task.json");
|
|
1989
|
+
if (fs10.existsSync(taskJsonPath)) {
|
|
1632
1990
|
try {
|
|
1633
|
-
const raw =
|
|
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 =
|
|
1644
|
-
if (
|
|
1645
|
-
const content =
|
|
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
|
-
|
|
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
|
-
|
|
2037
|
+
fs10.writeFileSync(shipPath, `# Ship
|
|
1680
2038
|
|
|
1681
2039
|
PR created: ${pr.url}
|
|
1682
2040
|
PR #${pr.number}
|
|
1683
2041
|
`);
|
|
1684
2042
|
} else {
|
|
1685
|
-
|
|
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
|
-
|
|
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
|
|
1737
|
-
import * as
|
|
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 =
|
|
1743
|
-
if (!
|
|
1744
|
-
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");
|
|
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 =
|
|
1760
|
-
if (!
|
|
1761
|
-
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");
|
|
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
|
|
1791
|
-
import * as
|
|
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 =
|
|
1818
|
-
if (!
|
|
1819
|
-
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");
|
|
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 =
|
|
1850
|
-
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)";
|
|
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
|
|
1918
|
-
import * as
|
|
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 =
|
|
1925
|
-
if (!
|
|
1926
|
-
|
|
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 =
|
|
1931
|
-
if (
|
|
1932
|
-
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"));
|
|
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 =
|
|
1942
|
-
if (
|
|
1943
|
-
const review =
|
|
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 =
|
|
1950
|
-
if (
|
|
2308
|
+
const taskJsonPath = path13.join(ctx.taskDir, "task.json");
|
|
2309
|
+
if (fs13.existsSync(taskJsonPath)) {
|
|
1951
2310
|
try {
|
|
1952
|
-
const raw = stripAnsi(
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
1977
|
-
if (
|
|
2336
|
+
const archPath = path13.join(memoryDir, "architecture.md");
|
|
2337
|
+
if (fs13.existsSync(archPath)) return;
|
|
1978
2338
|
const detected = [];
|
|
1979
|
-
const pkgPath =
|
|
1980
|
-
if (
|
|
2339
|
+
const pkgPath = path13.join(projectDir, "package.json");
|
|
2340
|
+
if (fs13.existsSync(pkgPath)) {
|
|
1981
2341
|
try {
|
|
1982
|
-
const pkg = JSON.parse(
|
|
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 (
|
|
1999
|
-
else if (
|
|
2000
|
-
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");
|
|
2001
2361
|
} catch {
|
|
2002
2362
|
}
|
|
2003
2363
|
}
|
|
2004
2364
|
const topDirs = [];
|
|
2005
2365
|
try {
|
|
2006
|
-
const entries =
|
|
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 =
|
|
2016
|
-
if (
|
|
2375
|
+
const srcDir = path13.join(projectDir, "src");
|
|
2376
|
+
if (fs13.existsSync(srcDir)) {
|
|
2017
2377
|
try {
|
|
2018
|
-
const srcEntries =
|
|
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
|
-
|
|
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
|
|
2043
|
-
import * as
|
|
2404
|
+
import * as fs14 from "fs";
|
|
2405
|
+
import * as path14 from "path";
|
|
2044
2406
|
function readArtifact(taskDir, filename, maxChars) {
|
|
2045
|
-
const p =
|
|
2046
|
-
if (!
|
|
2407
|
+
const p = path14.join(taskDir, filename);
|
|
2408
|
+
if (!fs14.existsSync(p)) return null;
|
|
2047
2409
|
try {
|
|
2048
|
-
const content =
|
|
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
|
|
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 (!
|
|
2467
|
+
if (!fs14.existsSync(logPath)) return [];
|
|
2106
2468
|
try {
|
|
2107
|
-
const content =
|
|
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 =
|
|
2135
|
-
if (!
|
|
2136
|
-
|
|
2496
|
+
const dir = path14.dirname(logPath);
|
|
2497
|
+
if (!fs14.existsSync(dir)) {
|
|
2498
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
2137
2499
|
}
|
|
2138
|
-
|
|
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
|
|
2250
|
-
import * as
|
|
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 =
|
|
2256
|
-
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;
|
|
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 =
|
|
2265
|
-
if (
|
|
2626
|
+
const lockPath = path15.join(taskDir, ".lock");
|
|
2627
|
+
if (fs15.existsSync(lockPath)) {
|
|
2266
2628
|
try {
|
|
2267
|
-
const pid = parseInt(
|
|
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
|
-
|
|
2640
|
+
fs15.writeFileSync(lockPath, String(process.pid));
|
|
2279
2641
|
}
|
|
2280
2642
|
function releaseLock(taskDir) {
|
|
2281
2643
|
try {
|
|
2282
|
-
|
|
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
|
|
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 (!
|
|
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
|
|
2526
|
-
import * as
|
|
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 =
|
|
2530
|
-
if (!
|
|
2531
|
-
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();
|
|
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
|
|
2566
|
-
import * as
|
|
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 =
|
|
2591
|
-
|
|
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
|
-
|
|
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 =
|
|
2979
|
+
const reviewPath = path17.join(taskDir, "review.md");
|
|
2618
2980
|
let reviewContent;
|
|
2619
|
-
if (
|
|
2620
|
-
reviewContent =
|
|
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
|
|
2713
|
-
import * as
|
|
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 =
|
|
2725
|
-
if (!
|
|
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 =
|
|
3119
|
+
const dotenvPath = path18.join(projectDir, ".env");
|
|
2758
3120
|
const dotenvVars = {};
|
|
2759
|
-
if (
|
|
2760
|
-
for (const line of
|
|
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
|
|
2801
|
-
import * as
|
|
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 =
|
|
3195
|
+
const statusPath = path19.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
|
|
2834
3196
|
let existingState = null;
|
|
2835
|
-
if (
|
|
3197
|
+
if (fs20.existsSync(statusPath)) {
|
|
2836
3198
|
try {
|
|
2837
|
-
existingState = JSON.parse(
|
|
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
|
|
2868
|
-
import * as
|
|
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 ?
|
|
3233
|
+
const projectDir = input.cwd ? path20.resolve(input.cwd) : process.cwd();
|
|
2872
3234
|
if (input.cwd) {
|
|
2873
|
-
if (!
|
|
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 =
|
|
2928
|
-
|
|
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
|
-
|
|
2968
|
-
|
|
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
|
-
|
|
3381
|
+
fs21.writeFileSync(path20.join(taskDir, "task.md"), input.task);
|
|
3015
3382
|
}
|
|
3016
|
-
const taskMdPath =
|
|
3017
|
-
if (!
|
|
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
|
-
|
|
3391
|
+
fs21.writeFileSync(taskMdPath, taskContent);
|
|
3025
3392
|
logger.info(` Task loaded from PR #${input.prNumber}: ${prDetails.title}`);
|
|
3026
3393
|
}
|
|
3027
|
-
} else if (!
|
|
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
|
-
|
|
3401
|
+
fs21.writeFileSync(taskMdPath, taskContent);
|
|
3035
3402
|
logger.info(` Task loaded from issue #${input.issueNumber}: ${issue.title}`);
|
|
3036
3403
|
}
|
|
3037
3404
|
}
|
|
3038
|
-
if (!
|
|
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.
|
|
3082
|
-
|
|
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
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
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 =
|
|
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
|
|
3201
|
-
import * as
|
|
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 =
|
|
3205
|
-
var PKG_ROOT =
|
|
3572
|
+
var __dirname = path21.dirname(fileURLToPath(import.meta.url));
|
|
3573
|
+
var PKG_ROOT = path21.resolve(__dirname, "..", "..");
|
|
3206
3574
|
function getVersion() {
|
|
3207
|
-
const pkgPath =
|
|
3208
|
-
const pkg = JSON.parse(
|
|
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 (
|
|
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 (
|
|
3297
|
-
else if (
|
|
3298
|
-
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";
|
|
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(
|
|
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 =
|
|
3749
|
+
const templatesDir = path21.join(PKG_ROOT, "templates");
|
|
3382
3750
|
const basic = detectBasicConfig(cwd);
|
|
3383
|
-
const workflowSrc =
|
|
3384
|
-
const workflowDest =
|
|
3385
|
-
if (!
|
|
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 (
|
|
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
|
-
|
|
3393
|
-
|
|
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 =
|
|
3397
|
-
if (!
|
|
3764
|
+
const configDest = path21.join(cwd, "kody.config.json");
|
|
3765
|
+
if (!fs22.existsSync(configDest) || opts.force) {
|
|
3398
3766
|
const config = buildConfig(cwd, basic);
|
|
3399
|
-
|
|
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 =
|
|
3405
|
-
if (
|
|
3406
|
-
const content =
|
|
3407
|
-
if (
|
|
3408
|
-
|
|
3409
|
-
|
|
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 (
|
|
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(
|
|
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 (
|
|
3866
|
+
if (fs22.existsSync(configDest)) {
|
|
3498
3867
|
try {
|
|
3499
|
-
const config = JSON.parse(
|
|
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) =>
|
|
3898
|
+
].filter((f) => fs22.existsSync(path21.join(cwd, f)));
|
|
3530
3899
|
if (filesToCommit.length > 0) {
|
|
3531
3900
|
try {
|
|
3532
|
-
const fullPaths = filesToCommit.map((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", "
|
|
3961
|
+
var STEP_STAGES = ["taskify", "plan", "build", "review", "review-fix"];
|
|
3593
3962
|
function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
|
|
3594
|
-
const srcDir =
|
|
3595
|
-
const baseDir =
|
|
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
|
|
3969
|
+
for (const entry of fs22.readdirSync(dir, { withFileTypes: true })) {
|
|
3601
3970
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
3602
|
-
const full =
|
|
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 =
|
|
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 =
|
|
3622
|
-
const content =
|
|
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 =
|
|
3637
|
-
if (
|
|
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 =
|
|
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 =
|
|
3678
|
-
if (
|
|
3679
|
-
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);
|
|
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 (
|
|
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 =
|
|
3697
|
-
|
|
3698
|
-
const archPath =
|
|
3699
|
-
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");
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
3766
|
-
|
|
3767
|
-
const arch =
|
|
3768
|
-
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") : "";
|
|
3769
4173
|
console.log(" \u23F3 Customizing step files...");
|
|
3770
4174
|
let stepCount = 0;
|
|
3771
4175
|
for (const stage of STEP_STAGES) {
|
|
3772
|
-
const templatePath =
|
|
3773
|
-
if (!
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
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 (
|
|
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) =>
|
|
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 =
|
|
3889
|
-
if (
|
|
3890
|
-
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"));
|
|
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 =
|
|
3947
|
-
if (
|
|
4361
|
+
const pkgPath = path21.join(cwd, "package.json");
|
|
4362
|
+
if (fs22.existsSync(pkgPath)) {
|
|
3948
4363
|
try {
|
|
3949
|
-
const pkg = JSON.parse(
|
|
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 (
|
|
3966
|
-
else if (
|
|
3967
|
-
else if (
|
|
3968
|
-
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");
|
|
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
|
+
};
|