@kody-ade/kody-engine-lite 0.1.25 → 0.1.27
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/README.md +74 -224
- package/dist/agent-runner.d.ts +4 -0
- package/dist/agent-runner.js +122 -0
- package/dist/bin/cli.js +365 -100
- package/dist/ci/parse-inputs.d.ts +6 -0
- package/dist/ci/parse-inputs.js +76 -0
- package/dist/ci/parse-safety.d.ts +6 -0
- package/dist/ci/parse-safety.js +22 -0
- package/dist/cli/args.d.ts +13 -0
- package/dist/cli/args.js +42 -0
- package/dist/cli/litellm.d.ts +2 -0
- package/dist/cli/litellm.js +85 -0
- package/dist/cli/task-resolution.d.ts +2 -0
- package/dist/cli/task-resolution.js +41 -0
- package/dist/config.d.ts +49 -0
- package/dist/config.js +72 -0
- package/dist/context.d.ts +4 -0
- package/dist/context.js +83 -0
- package/dist/definitions.d.ts +3 -0
- package/dist/definitions.js +59 -0
- package/dist/entry.d.ts +1 -0
- package/dist/entry.js +236 -0
- package/dist/git-utils.d.ts +13 -0
- package/dist/git-utils.js +174 -0
- package/dist/github-api.d.ts +14 -0
- package/dist/github-api.js +114 -0
- package/dist/kody-utils.d.ts +1 -0
- package/dist/kody-utils.js +9 -0
- package/dist/learning/auto-learn.d.ts +2 -0
- package/dist/learning/auto-learn.js +169 -0
- package/dist/logger.d.ts +14 -0
- package/dist/logger.js +51 -0
- package/dist/memory.d.ts +1 -0
- package/dist/memory.js +20 -0
- package/dist/observer.d.ts +9 -0
- package/dist/observer.js +80 -0
- package/dist/pipeline/complexity.d.ts +3 -0
- package/dist/pipeline/complexity.js +12 -0
- package/dist/pipeline/executor-registry.d.ts +3 -0
- package/dist/pipeline/executor-registry.js +20 -0
- package/dist/pipeline/hooks.d.ts +17 -0
- package/dist/pipeline/hooks.js +110 -0
- package/dist/pipeline/questions.d.ts +2 -0
- package/dist/pipeline/questions.js +44 -0
- package/dist/pipeline/runner-selection.d.ts +2 -0
- package/dist/pipeline/runner-selection.js +13 -0
- package/dist/pipeline/state.d.ts +4 -0
- package/dist/pipeline/state.js +37 -0
- package/dist/pipeline.d.ts +3 -0
- package/dist/pipeline.js +213 -0
- package/dist/preflight.d.ts +1 -0
- package/dist/preflight.js +69 -0
- package/dist/retrospective.d.ts +26 -0
- package/dist/retrospective.js +211 -0
- package/dist/stages/agent.d.ts +2 -0
- package/dist/stages/agent.js +94 -0
- package/dist/stages/gate.d.ts +2 -0
- package/dist/stages/gate.js +32 -0
- package/dist/stages/review.d.ts +2 -0
- package/dist/stages/review.js +32 -0
- package/dist/stages/ship.d.ts +3 -0
- package/dist/stages/ship.js +154 -0
- package/dist/stages/verify.d.ts +2 -0
- package/dist/stages/verify.js +94 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.js +1 -0
- package/dist/validators.d.ts +8 -0
- package/dist/validators.js +42 -0
- package/dist/verify-runner.d.ts +11 -0
- package/dist/verify-runner.js +110 -0
- package/package.json +1 -1
package/dist/bin/cli.js
CHANGED
|
@@ -698,6 +698,16 @@ ${truncated}${spec.length > MAX_TASK_CONTEXT_SPEC ? "\n..." : ""}
|
|
|
698
698
|
context += `
|
|
699
699
|
## Plan Summary
|
|
700
700
|
${truncated}${plan.length > MAX_TASK_CONTEXT_PLAN ? "\n..." : ""}
|
|
701
|
+
`;
|
|
702
|
+
}
|
|
703
|
+
const contextMdPath = path4.join(taskDir, "context.md");
|
|
704
|
+
if (fs4.existsSync(contextMdPath)) {
|
|
705
|
+
const accumulated = fs4.readFileSync(contextMdPath, "utf-8");
|
|
706
|
+
const truncated = accumulated.slice(-MAX_ACCUMULATED_CONTEXT);
|
|
707
|
+
const prefix = accumulated.length > MAX_ACCUMULATED_CONTEXT ? "...(earlier context truncated)\n" : "";
|
|
708
|
+
context += `
|
|
709
|
+
## Previous Stage Context
|
|
710
|
+
${prefix}${truncated}
|
|
701
711
|
`;
|
|
702
712
|
}
|
|
703
713
|
if (feedback) {
|
|
@@ -726,7 +736,7 @@ function resolveModel(modelTier, stageName) {
|
|
|
726
736
|
if (mapped) return mapped;
|
|
727
737
|
return DEFAULT_MODEL_MAP[modelTier] ?? "sonnet";
|
|
728
738
|
}
|
|
729
|
-
var DEFAULT_MODEL_MAP, MAX_TASK_CONTEXT_PLAN, MAX_TASK_CONTEXT_SPEC;
|
|
739
|
+
var DEFAULT_MODEL_MAP, MAX_TASK_CONTEXT_PLAN, MAX_TASK_CONTEXT_SPEC, MAX_ACCUMULATED_CONTEXT;
|
|
730
740
|
var init_context = __esm({
|
|
731
741
|
"src/context.ts"() {
|
|
732
742
|
"use strict";
|
|
@@ -739,6 +749,7 @@ var init_context = __esm({
|
|
|
739
749
|
};
|
|
740
750
|
MAX_TASK_CONTEXT_PLAN = 1500;
|
|
741
751
|
MAX_TASK_CONTEXT_SPEC = 2e3;
|
|
752
|
+
MAX_ACCUMULATED_CONTEXT = 4e3;
|
|
742
753
|
}
|
|
743
754
|
});
|
|
744
755
|
|
|
@@ -894,8 +905,25 @@ async function executeAgentStage(ctx, def) {
|
|
|
894
905
|
}
|
|
895
906
|
}
|
|
896
907
|
}
|
|
908
|
+
appendStageContext(ctx.taskDir, def.name, result.output);
|
|
897
909
|
return { outcome: "completed", outputFile: def.outputFile, retries: 0 };
|
|
898
910
|
}
|
|
911
|
+
function appendStageContext(taskDir, stageName, output) {
|
|
912
|
+
const contextPath = path5.join(taskDir, "context.md");
|
|
913
|
+
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19);
|
|
914
|
+
let summary;
|
|
915
|
+
if (output && output.trim()) {
|
|
916
|
+
summary = output.slice(0, 500);
|
|
917
|
+
if (output.length > 500) summary += "\n...(truncated)";
|
|
918
|
+
} else {
|
|
919
|
+
summary = "(stage completed via tool use \u2014 no text output)";
|
|
920
|
+
}
|
|
921
|
+
const entry = `
|
|
922
|
+
### ${stageName} (${timestamp2})
|
|
923
|
+
${summary}
|
|
924
|
+
`;
|
|
925
|
+
fs5.appendFileSync(contextPath, entry);
|
|
926
|
+
}
|
|
899
927
|
var init_agent = __esm({
|
|
900
928
|
"src/stages/agent.ts"() {
|
|
901
929
|
"use strict";
|
|
@@ -1596,6 +1624,39 @@ function autoDetectComplexity(ctx, def) {
|
|
|
1596
1624
|
return null;
|
|
1597
1625
|
}
|
|
1598
1626
|
}
|
|
1627
|
+
function checkRiskGate(ctx, def, state, complexity) {
|
|
1628
|
+
if (def.name !== "plan") return null;
|
|
1629
|
+
if (complexity !== "high") return null;
|
|
1630
|
+
if (ctx.input.dryRun || ctx.input.local) return null;
|
|
1631
|
+
if (ctx.input.mode === "rerun") return null;
|
|
1632
|
+
if (!ctx.input.issueNumber) return null;
|
|
1633
|
+
const planPath = path11.join(ctx.taskDir, "plan.md");
|
|
1634
|
+
const plan = fs11.existsSync(planPath) ? fs11.readFileSync(planPath, "utf-8").slice(0, 1500) : "(plan not available)";
|
|
1635
|
+
try {
|
|
1636
|
+
postComment(
|
|
1637
|
+
ctx.input.issueNumber,
|
|
1638
|
+
`\u{1F6D1} **Risk gate: HIGH complexity \u2014 awaiting approval**
|
|
1639
|
+
|
|
1640
|
+
<details><summary>\u{1F4CB} Plan summary</summary>
|
|
1641
|
+
|
|
1642
|
+
${plan}
|
|
1643
|
+
</details>
|
|
1644
|
+
|
|
1645
|
+
To approve: \`@kody approve\``
|
|
1646
|
+
);
|
|
1647
|
+
setLifecycleLabel(ctx.input.issueNumber, "waiting");
|
|
1648
|
+
} catch {
|
|
1649
|
+
}
|
|
1650
|
+
state.state = "failed";
|
|
1651
|
+
state.stages[def.name] = {
|
|
1652
|
+
...state.stages[def.name],
|
|
1653
|
+
state: "completed",
|
|
1654
|
+
error: "paused: risk gate \u2014 awaiting approval"
|
|
1655
|
+
};
|
|
1656
|
+
writeState(state, ctx.taskDir);
|
|
1657
|
+
logger.info(` Pipeline paused \u2014 HIGH risk gate, awaiting approval on issue`);
|
|
1658
|
+
return state;
|
|
1659
|
+
}
|
|
1599
1660
|
function commitAfterStage(ctx, def) {
|
|
1600
1661
|
if (ctx.input.dryRun || !ctx.input.issueNumber) return;
|
|
1601
1662
|
if (def.name === "build") {
|
|
@@ -1761,14 +1822,212 @@ var init_auto_learn = __esm({
|
|
|
1761
1822
|
}
|
|
1762
1823
|
});
|
|
1763
1824
|
|
|
1764
|
-
// src/
|
|
1825
|
+
// src/retrospective.ts
|
|
1765
1826
|
import * as fs13 from "fs";
|
|
1766
1827
|
import * as path13 from "path";
|
|
1828
|
+
function readArtifact(taskDir, filename, maxChars) {
|
|
1829
|
+
const p = path13.join(taskDir, filename);
|
|
1830
|
+
if (!fs13.existsSync(p)) return null;
|
|
1831
|
+
try {
|
|
1832
|
+
const content = fs13.readFileSync(p, "utf-8");
|
|
1833
|
+
return content.length > maxChars ? content.slice(0, maxChars) + "\n...(truncated)" : content;
|
|
1834
|
+
} catch {
|
|
1835
|
+
return null;
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
function computeStageDuration(stage) {
|
|
1839
|
+
if (!stage.startedAt) return void 0;
|
|
1840
|
+
const end = stage.completedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1841
|
+
return new Date(end).getTime() - new Date(stage.startedAt).getTime();
|
|
1842
|
+
}
|
|
1843
|
+
function collectRunContext(ctx, state, pipelineStartTime) {
|
|
1844
|
+
const durationMs = Date.now() - pipelineStartTime;
|
|
1845
|
+
const lines = [];
|
|
1846
|
+
lines.push(`## This Run`);
|
|
1847
|
+
lines.push(`Task: ${state.taskId}`);
|
|
1848
|
+
lines.push(`Outcome: ${state.state}`);
|
|
1849
|
+
lines.push(`Duration: ${durationMs}ms (${Math.round(durationMs / 1e3)}s)`);
|
|
1850
|
+
lines.push(`Mode: ${ctx.input.mode}`);
|
|
1851
|
+
lines.push(``);
|
|
1852
|
+
lines.push(`### Stage Results`);
|
|
1853
|
+
let failedStage;
|
|
1854
|
+
for (const def of STAGES) {
|
|
1855
|
+
const s = state.stages[def.name];
|
|
1856
|
+
const duration = computeStageDuration(s);
|
|
1857
|
+
const durationStr = duration != null ? `, ${duration}ms` : "";
|
|
1858
|
+
const errorStr = s.error ? ` \u2014 ${s.error}` : "";
|
|
1859
|
+
lines.push(`${def.name}: ${s.state} (${s.retries} retries${durationStr})${errorStr}`);
|
|
1860
|
+
if (s.state === "failed" || s.state === "timeout") {
|
|
1861
|
+
failedStage = def.name;
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
lines.push(``);
|
|
1865
|
+
const artifacts = [
|
|
1866
|
+
["task.md", 300],
|
|
1867
|
+
["task.json", 500],
|
|
1868
|
+
["plan.md", 500],
|
|
1869
|
+
["verify.md", 800],
|
|
1870
|
+
["review.md", 500],
|
|
1871
|
+
["ship.md", 300]
|
|
1872
|
+
];
|
|
1873
|
+
lines.push(`### Artifacts`);
|
|
1874
|
+
for (const [filename, maxChars] of artifacts) {
|
|
1875
|
+
const content = readArtifact(ctx.taskDir, filename, maxChars);
|
|
1876
|
+
if (content) {
|
|
1877
|
+
lines.push(`#### ${filename}`);
|
|
1878
|
+
lines.push(content);
|
|
1879
|
+
lines.push(``);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
return lines.join("\n");
|
|
1883
|
+
}
|
|
1884
|
+
function getLogPath(projectDir) {
|
|
1885
|
+
return path13.join(projectDir, ".kody", "memory", "observer-log.jsonl");
|
|
1886
|
+
}
|
|
1887
|
+
function readPreviousRetrospectives(projectDir, limit = 10) {
|
|
1888
|
+
const logPath = getLogPath(projectDir);
|
|
1889
|
+
if (!fs13.existsSync(logPath)) return [];
|
|
1890
|
+
try {
|
|
1891
|
+
const content = fs13.readFileSync(logPath, "utf-8");
|
|
1892
|
+
const lines = content.split("\n").filter(Boolean);
|
|
1893
|
+
const entries = [];
|
|
1894
|
+
const start = Math.max(0, lines.length - limit);
|
|
1895
|
+
for (let i = start; i < lines.length; i++) {
|
|
1896
|
+
try {
|
|
1897
|
+
entries.push(JSON.parse(lines[i]));
|
|
1898
|
+
} catch {
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
return entries;
|
|
1902
|
+
} catch {
|
|
1903
|
+
return [];
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
function formatPreviousEntries(entries) {
|
|
1907
|
+
if (entries.length === 0) return "No previous runs recorded.";
|
|
1908
|
+
return entries.map((e) => {
|
|
1909
|
+
const failed = e.failedStage ? ` at ${e.failedStage}` : "";
|
|
1910
|
+
const pattern = e.patternMatch ? `Pattern: ${e.patternMatch}` : "Pattern: none";
|
|
1911
|
+
const flaw = e.pipelineFlaw ? ` | Flaw: ${e.pipelineFlaw.component} \u2014 ${e.pipelineFlaw.issue}` : "";
|
|
1912
|
+
return `[${e.timestamp.slice(0, 10)}] ${e.taskId}: ${e.outcome}${failed} \u2014 "${e.observation.slice(0, 120)}"
|
|
1913
|
+
${pattern} | Suggestion: "${e.suggestion}"${flaw}`;
|
|
1914
|
+
}).join("\n");
|
|
1915
|
+
}
|
|
1916
|
+
function appendRetrospectiveEntry(projectDir, entry) {
|
|
1917
|
+
const logPath = getLogPath(projectDir);
|
|
1918
|
+
const dir = path13.dirname(logPath);
|
|
1919
|
+
if (!fs13.existsSync(dir)) {
|
|
1920
|
+
fs13.mkdirSync(dir, { recursive: true });
|
|
1921
|
+
}
|
|
1922
|
+
fs13.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
1923
|
+
}
|
|
1924
|
+
async function runRetrospective(ctx, state, pipelineStartTime) {
|
|
1925
|
+
if (ctx.input.dryRun) return;
|
|
1926
|
+
try {
|
|
1927
|
+
const durationMs = Date.now() - pipelineStartTime;
|
|
1928
|
+
const runContext = collectRunContext(ctx, state, pipelineStartTime);
|
|
1929
|
+
const previous = readPreviousRetrospectives(ctx.projectDir);
|
|
1930
|
+
const previousText = formatPreviousEntries(previous);
|
|
1931
|
+
const prompt = RETROSPECTIVE_PROMPT + `## Run Context
|
|
1932
|
+
${runContext}
|
|
1933
|
+
|
|
1934
|
+
## Previous Retrospectives (last ${previous.length} runs)
|
|
1935
|
+
${previousText}
|
|
1936
|
+
`;
|
|
1937
|
+
const runner = getRunnerForStage(ctx, "taskify");
|
|
1938
|
+
const model = resolveModel("cheap");
|
|
1939
|
+
const result = await runner.run("retrospective", prompt, model, 3e4, "");
|
|
1940
|
+
let observation = "Retrospective analysis unavailable";
|
|
1941
|
+
let patternMatch = null;
|
|
1942
|
+
let suggestion = "No suggestion";
|
|
1943
|
+
let pipelineFlaw = null;
|
|
1944
|
+
if (result.outcome === "completed" && result.output) {
|
|
1945
|
+
const cleaned = result.output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "").trim();
|
|
1946
|
+
try {
|
|
1947
|
+
const parsed = JSON.parse(cleaned);
|
|
1948
|
+
observation = parsed.observation ?? observation;
|
|
1949
|
+
patternMatch = parsed.patternMatch ?? null;
|
|
1950
|
+
suggestion = parsed.suggestion ?? suggestion;
|
|
1951
|
+
if (parsed.pipelineFlaw && parsed.pipelineFlaw.component) {
|
|
1952
|
+
pipelineFlaw = {
|
|
1953
|
+
component: parsed.pipelineFlaw.component,
|
|
1954
|
+
issue: parsed.pipelineFlaw.issue ?? "Unknown",
|
|
1955
|
+
evidence: parsed.pipelineFlaw.evidence ?? ""
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
} catch {
|
|
1959
|
+
logger.warn(" Retrospective: failed to parse LLM output");
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
const stageResults = {};
|
|
1963
|
+
let failedStage;
|
|
1964
|
+
for (const def of STAGES) {
|
|
1965
|
+
const s = state.stages[def.name];
|
|
1966
|
+
stageResults[def.name] = {
|
|
1967
|
+
state: s.state,
|
|
1968
|
+
retries: s.retries,
|
|
1969
|
+
durationMs: computeStageDuration(s),
|
|
1970
|
+
error: s.error
|
|
1971
|
+
};
|
|
1972
|
+
if (s.state === "failed" || s.state === "timeout") {
|
|
1973
|
+
failedStage = def.name;
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
const entry = {
|
|
1977
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1978
|
+
taskId: state.taskId,
|
|
1979
|
+
outcome: state.state === "completed" ? "completed" : "failed",
|
|
1980
|
+
durationMs,
|
|
1981
|
+
stageResults,
|
|
1982
|
+
failedStage,
|
|
1983
|
+
observation,
|
|
1984
|
+
patternMatch,
|
|
1985
|
+
suggestion,
|
|
1986
|
+
pipelineFlaw
|
|
1987
|
+
};
|
|
1988
|
+
appendRetrospectiveEntry(ctx.projectDir, entry);
|
|
1989
|
+
logger.info(`Retrospective: ${observation.slice(0, 120)}`);
|
|
1990
|
+
} catch (err) {
|
|
1991
|
+
logger.warn(`Retrospective failed: ${err instanceof Error ? err.message : err}`);
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
var RETROSPECTIVE_PROMPT;
|
|
1995
|
+
var init_retrospective = __esm({
|
|
1996
|
+
"src/retrospective.ts"() {
|
|
1997
|
+
"use strict";
|
|
1998
|
+
init_definitions();
|
|
1999
|
+
init_context();
|
|
2000
|
+
init_runner_selection();
|
|
2001
|
+
init_logger();
|
|
2002
|
+
RETROSPECTIVE_PROMPT = `You are a pipeline retrospective analyst. You observe automated software development pipeline runs and identify flaws, patterns, and improvement opportunities.
|
|
2003
|
+
|
|
2004
|
+
Output ONLY valid JSON. No markdown fences. No explanation.
|
|
2005
|
+
|
|
2006
|
+
{
|
|
2007
|
+
"observation": "One paragraph: what happened in this run, what went well, what went wrong",
|
|
2008
|
+
"patternMatch": "If this matches a pattern seen in previous runs, describe the pattern. Otherwise null",
|
|
2009
|
+
"suggestion": "One specific, actionable change to improve pipeline reliability or efficiency",
|
|
2010
|
+
"pipelineFlaw": {
|
|
2011
|
+
"component": "pipeline component name (e.g., verify, build, autofix, taskify prompt, review prompt, model selection, timeout config)",
|
|
2012
|
+
"issue": "concise description of the flaw",
|
|
2013
|
+
"evidence": "specific data from this run that supports this conclusion"
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
If no pipeline flaw is detected, set "pipelineFlaw" to null.
|
|
2018
|
+
|
|
2019
|
+
`;
|
|
2020
|
+
}
|
|
2021
|
+
});
|
|
2022
|
+
|
|
2023
|
+
// src/pipeline.ts
|
|
2024
|
+
import * as fs14 from "fs";
|
|
2025
|
+
import * as path14 from "path";
|
|
1767
2026
|
function ensureFeatureBranchIfNeeded(ctx) {
|
|
1768
2027
|
if (!ctx.input.issueNumber || ctx.input.dryRun) return;
|
|
1769
2028
|
try {
|
|
1770
|
-
const taskMdPath =
|
|
1771
|
-
const title =
|
|
2029
|
+
const taskMdPath = path14.join(ctx.taskDir, "task.md");
|
|
2030
|
+
const title = fs14.existsSync(taskMdPath) ? fs14.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
|
|
1772
2031
|
ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
|
|
1773
2032
|
syncWithDefault(ctx.projectDir);
|
|
1774
2033
|
} catch (err) {
|
|
@@ -1776,10 +2035,10 @@ function ensureFeatureBranchIfNeeded(ctx) {
|
|
|
1776
2035
|
}
|
|
1777
2036
|
}
|
|
1778
2037
|
function acquireLock(taskDir) {
|
|
1779
|
-
const lockPath =
|
|
1780
|
-
if (
|
|
2038
|
+
const lockPath = path14.join(taskDir, ".lock");
|
|
2039
|
+
if (fs14.existsSync(lockPath)) {
|
|
1781
2040
|
try {
|
|
1782
|
-
const pid = parseInt(
|
|
2041
|
+
const pid = parseInt(fs14.readFileSync(lockPath, "utf-8").trim(), 10);
|
|
1783
2042
|
try {
|
|
1784
2043
|
process.kill(pid, 0);
|
|
1785
2044
|
throw new Error(`Pipeline already running (PID ${pid})`);
|
|
@@ -1790,11 +2049,11 @@ function acquireLock(taskDir) {
|
|
|
1790
2049
|
if (e instanceof Error && e.message.startsWith("Pipeline already")) throw e;
|
|
1791
2050
|
}
|
|
1792
2051
|
}
|
|
1793
|
-
|
|
2052
|
+
fs14.writeFileSync(lockPath, String(process.pid));
|
|
1794
2053
|
}
|
|
1795
2054
|
function releaseLock(taskDir) {
|
|
1796
2055
|
try {
|
|
1797
|
-
|
|
2056
|
+
fs14.unlinkSync(path14.join(taskDir, ".lock"));
|
|
1798
2057
|
} catch {
|
|
1799
2058
|
}
|
|
1800
2059
|
}
|
|
@@ -1807,6 +2066,7 @@ async function runPipeline(ctx) {
|
|
|
1807
2066
|
}
|
|
1808
2067
|
}
|
|
1809
2068
|
async function runPipelineInner(ctx) {
|
|
2069
|
+
const pipelineStartTime = Date.now();
|
|
1810
2070
|
let state = loadState(ctx.taskId, ctx.taskDir);
|
|
1811
2071
|
if (!state) {
|
|
1812
2072
|
state = initState(ctx.taskId);
|
|
@@ -1888,6 +2148,8 @@ async function runPipelineInner(ctx) {
|
|
|
1888
2148
|
complexity = detected.complexity;
|
|
1889
2149
|
activeStages = detected.activeStages;
|
|
1890
2150
|
}
|
|
2151
|
+
const gated = checkRiskGate(ctx, def, state, complexity);
|
|
2152
|
+
if (gated) return gated;
|
|
1891
2153
|
commitAfterStage(ctx, def);
|
|
1892
2154
|
} else {
|
|
1893
2155
|
const isTimeout = result.outcome === "timed_out";
|
|
@@ -1916,6 +2178,8 @@ async function runPipelineInner(ctx) {
|
|
|
1916
2178
|
}
|
|
1917
2179
|
autoLearn(ctx);
|
|
1918
2180
|
}
|
|
2181
|
+
await runRetrospective(ctx, state, pipelineStartTime).catch(() => {
|
|
2182
|
+
});
|
|
1919
2183
|
return state;
|
|
1920
2184
|
}
|
|
1921
2185
|
function printStatus(taskId, taskDir) {
|
|
@@ -1949,12 +2213,13 @@ var init_pipeline = __esm({
|
|
|
1949
2213
|
init_executor_registry();
|
|
1950
2214
|
init_hooks();
|
|
1951
2215
|
init_auto_learn();
|
|
2216
|
+
init_retrospective();
|
|
1952
2217
|
}
|
|
1953
2218
|
});
|
|
1954
2219
|
|
|
1955
2220
|
// src/preflight.ts
|
|
1956
2221
|
import { execFileSync as execFileSync8 } from "child_process";
|
|
1957
|
-
import * as
|
|
2222
|
+
import * as fs15 from "fs";
|
|
1958
2223
|
function check(name, fn) {
|
|
1959
2224
|
try {
|
|
1960
2225
|
const detail = fn() ?? void 0;
|
|
@@ -2007,7 +2272,7 @@ function runPreflight() {
|
|
|
2007
2272
|
return v;
|
|
2008
2273
|
}),
|
|
2009
2274
|
check("package.json", () => {
|
|
2010
|
-
if (!
|
|
2275
|
+
if (!fs15.existsSync("package.json")) throw new Error("not found");
|
|
2011
2276
|
})
|
|
2012
2277
|
];
|
|
2013
2278
|
const failed = checks.filter((c) => !c.ok);
|
|
@@ -2078,8 +2343,8 @@ var init_args = __esm({
|
|
|
2078
2343
|
});
|
|
2079
2344
|
|
|
2080
2345
|
// src/cli/litellm.ts
|
|
2081
|
-
import * as
|
|
2082
|
-
import * as
|
|
2346
|
+
import * as fs16 from "fs";
|
|
2347
|
+
import * as path15 from "path";
|
|
2083
2348
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
2084
2349
|
async function checkLitellmHealth(url) {
|
|
2085
2350
|
try {
|
|
@@ -2090,8 +2355,8 @@ async function checkLitellmHealth(url) {
|
|
|
2090
2355
|
}
|
|
2091
2356
|
}
|
|
2092
2357
|
async function tryStartLitellm(url, projectDir) {
|
|
2093
|
-
const configPath =
|
|
2094
|
-
if (!
|
|
2358
|
+
const configPath = path15.join(projectDir, "litellm-config.yaml");
|
|
2359
|
+
if (!fs16.existsSync(configPath)) {
|
|
2095
2360
|
logger.warn("litellm-config.yaml not found \u2014 cannot start proxy");
|
|
2096
2361
|
return null;
|
|
2097
2362
|
}
|
|
@@ -2118,10 +2383,10 @@ async function tryStartLitellm(url, projectDir) {
|
|
|
2118
2383
|
cmd = "python3";
|
|
2119
2384
|
args2 = ["-m", "litellm", "--config", configPath, "--port", port];
|
|
2120
2385
|
}
|
|
2121
|
-
const dotenvPath =
|
|
2386
|
+
const dotenvPath = path15.join(projectDir, ".env");
|
|
2122
2387
|
const dotenvVars = {};
|
|
2123
|
-
if (
|
|
2124
|
-
for (const line of
|
|
2388
|
+
if (fs16.existsSync(dotenvPath)) {
|
|
2389
|
+
for (const line of fs16.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
2125
2390
|
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
2126
2391
|
if (match) dotenvVars[match[1]] = match[2];
|
|
2127
2392
|
}
|
|
@@ -2161,13 +2426,13 @@ var init_litellm = __esm({
|
|
|
2161
2426
|
});
|
|
2162
2427
|
|
|
2163
2428
|
// src/cli/task-resolution.ts
|
|
2164
|
-
import * as
|
|
2165
|
-
import * as
|
|
2429
|
+
import * as fs17 from "fs";
|
|
2430
|
+
import * as path16 from "path";
|
|
2166
2431
|
import { execFileSync as execFileSync10 } from "child_process";
|
|
2167
2432
|
function findLatestTaskForIssue(issueNumber, projectDir) {
|
|
2168
|
-
const tasksDir =
|
|
2169
|
-
if (!
|
|
2170
|
-
const allDirs =
|
|
2433
|
+
const tasksDir = path16.join(projectDir, ".tasks");
|
|
2434
|
+
if (!fs17.existsSync(tasksDir)) return null;
|
|
2435
|
+
const allDirs = fs17.readdirSync(tasksDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
|
|
2171
2436
|
const prefix = `${issueNumber}-`;
|
|
2172
2437
|
const direct = allDirs.find((d) => d.startsWith(prefix));
|
|
2173
2438
|
if (direct) return direct;
|
|
@@ -2202,13 +2467,13 @@ var init_task_resolution = __esm({
|
|
|
2202
2467
|
|
|
2203
2468
|
// src/entry.ts
|
|
2204
2469
|
var entry_exports = {};
|
|
2205
|
-
import * as
|
|
2206
|
-
import * as
|
|
2470
|
+
import * as fs18 from "fs";
|
|
2471
|
+
import * as path17 from "path";
|
|
2207
2472
|
async function main() {
|
|
2208
2473
|
const input = parseArgs();
|
|
2209
|
-
const projectDir = input.cwd ?
|
|
2474
|
+
const projectDir = input.cwd ? path17.resolve(input.cwd) : process.cwd();
|
|
2210
2475
|
if (input.cwd) {
|
|
2211
|
-
if (!
|
|
2476
|
+
if (!fs18.existsSync(projectDir)) {
|
|
2212
2477
|
console.error(`--cwd path does not exist: ${projectDir}`);
|
|
2213
2478
|
process.exit(1);
|
|
2214
2479
|
}
|
|
@@ -2235,8 +2500,8 @@ async function main() {
|
|
|
2235
2500
|
process.exit(1);
|
|
2236
2501
|
}
|
|
2237
2502
|
}
|
|
2238
|
-
const taskDir =
|
|
2239
|
-
|
|
2503
|
+
const taskDir = path17.join(projectDir, ".tasks", taskId);
|
|
2504
|
+
fs18.mkdirSync(taskDir, { recursive: true });
|
|
2240
2505
|
if (input.command === "status") {
|
|
2241
2506
|
printStatus(taskId, taskDir);
|
|
2242
2507
|
return;
|
|
@@ -2244,22 +2509,22 @@ async function main() {
|
|
|
2244
2509
|
logger.info("Preflight checks:");
|
|
2245
2510
|
runPreflight();
|
|
2246
2511
|
if (input.task) {
|
|
2247
|
-
|
|
2512
|
+
fs18.writeFileSync(path17.join(taskDir, "task.md"), input.task);
|
|
2248
2513
|
}
|
|
2249
|
-
const taskMdPath =
|
|
2250
|
-
if (!
|
|
2514
|
+
const taskMdPath = path17.join(taskDir, "task.md");
|
|
2515
|
+
if (!fs18.existsSync(taskMdPath) && input.issueNumber) {
|
|
2251
2516
|
logger.info(`Fetching issue #${input.issueNumber} body as task...`);
|
|
2252
2517
|
const issue = getIssue(input.issueNumber);
|
|
2253
2518
|
if (issue) {
|
|
2254
2519
|
const taskContent = `# ${issue.title}
|
|
2255
2520
|
|
|
2256
2521
|
${issue.body ?? ""}`;
|
|
2257
|
-
|
|
2522
|
+
fs18.writeFileSync(taskMdPath, taskContent);
|
|
2258
2523
|
logger.info(` Task loaded from issue #${input.issueNumber}: ${issue.title}`);
|
|
2259
2524
|
}
|
|
2260
2525
|
}
|
|
2261
2526
|
if (input.command === "run") {
|
|
2262
|
-
if (!
|
|
2527
|
+
if (!fs18.existsSync(taskMdPath)) {
|
|
2263
2528
|
console.error("No task.md found. Provide --task, --issue-number, or ensure .tasks/<id>/task.md exists.");
|
|
2264
2529
|
process.exit(1);
|
|
2265
2530
|
}
|
|
@@ -2268,10 +2533,10 @@ ${issue.body ?? ""}`;
|
|
|
2268
2533
|
input.fromStage = "build";
|
|
2269
2534
|
}
|
|
2270
2535
|
if (input.command === "rerun" && !input.fromStage) {
|
|
2271
|
-
const statusPath =
|
|
2272
|
-
if (
|
|
2536
|
+
const statusPath = path17.join(taskDir, "status.json");
|
|
2537
|
+
if (fs18.existsSync(statusPath)) {
|
|
2273
2538
|
try {
|
|
2274
|
-
const status = JSON.parse(
|
|
2539
|
+
const status = JSON.parse(fs18.readFileSync(statusPath, "utf-8"));
|
|
2275
2540
|
const stageNames = ["taskify", "plan", "build", "verify", "review", "review-fix", "ship"];
|
|
2276
2541
|
let foundPaused = false;
|
|
2277
2542
|
for (const name of stageNames) {
|
|
@@ -2378,7 +2643,7 @@ To rerun: \`@kody rerun ${taskId} --from <stage>\``
|
|
|
2378
2643
|
}
|
|
2379
2644
|
}
|
|
2380
2645
|
const state = await runPipeline(ctx);
|
|
2381
|
-
const files =
|
|
2646
|
+
const files = fs18.readdirSync(taskDir);
|
|
2382
2647
|
console.log(`
|
|
2383
2648
|
Artifacts in ${taskDir}:`);
|
|
2384
2649
|
for (const f of files) {
|
|
@@ -2437,15 +2702,15 @@ var init_entry = __esm({
|
|
|
2437
2702
|
});
|
|
2438
2703
|
|
|
2439
2704
|
// src/bin/cli.ts
|
|
2440
|
-
import * as
|
|
2441
|
-
import * as
|
|
2705
|
+
import * as fs19 from "fs";
|
|
2706
|
+
import * as path18 from "path";
|
|
2442
2707
|
import { execFileSync as execFileSync11 } from "child_process";
|
|
2443
2708
|
import { fileURLToPath } from "url";
|
|
2444
|
-
var __dirname =
|
|
2445
|
-
var PKG_ROOT =
|
|
2709
|
+
var __dirname = path18.dirname(fileURLToPath(import.meta.url));
|
|
2710
|
+
var PKG_ROOT = path18.resolve(__dirname, "..", "..");
|
|
2446
2711
|
function getVersion() {
|
|
2447
|
-
const pkgPath =
|
|
2448
|
-
const pkg = JSON.parse(
|
|
2712
|
+
const pkgPath = path18.join(PKG_ROOT, "package.json");
|
|
2713
|
+
const pkg = JSON.parse(fs19.readFileSync(pkgPath, "utf-8"));
|
|
2449
2714
|
return pkg.version;
|
|
2450
2715
|
}
|
|
2451
2716
|
function checkCommand2(name, args2, fix) {
|
|
@@ -2461,7 +2726,7 @@ function checkCommand2(name, args2, fix) {
|
|
|
2461
2726
|
}
|
|
2462
2727
|
}
|
|
2463
2728
|
function checkFile(filePath, description, fix) {
|
|
2464
|
-
if (
|
|
2729
|
+
if (fs19.existsSync(filePath)) {
|
|
2465
2730
|
return { name: description, ok: true, detail: filePath };
|
|
2466
2731
|
}
|
|
2467
2732
|
return { name: description, ok: false, fix };
|
|
@@ -2533,10 +2798,10 @@ function checkGhSecret(repoSlug, secretName) {
|
|
|
2533
2798
|
}
|
|
2534
2799
|
function detectArchitecture(cwd) {
|
|
2535
2800
|
const detected = [];
|
|
2536
|
-
const pkgPath =
|
|
2537
|
-
if (
|
|
2801
|
+
const pkgPath = path18.join(cwd, "package.json");
|
|
2802
|
+
if (fs19.existsSync(pkgPath)) {
|
|
2538
2803
|
try {
|
|
2539
|
-
const pkg = JSON.parse(
|
|
2804
|
+
const pkg = JSON.parse(fs19.readFileSync(pkgPath, "utf-8"));
|
|
2540
2805
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
2541
2806
|
if (allDeps.next) detected.push(`- Framework: Next.js ${allDeps.next}`);
|
|
2542
2807
|
else if (allDeps.react) detected.push(`- Framework: React ${allDeps.react}`);
|
|
@@ -2559,41 +2824,41 @@ function detectArchitecture(cwd) {
|
|
|
2559
2824
|
if (allDeps.tailwindcss) detected.push(`- CSS: Tailwind CSS ${allDeps.tailwindcss}`);
|
|
2560
2825
|
if (pkg.type === "module") detected.push("- Module system: ESM");
|
|
2561
2826
|
else detected.push("- Module system: CommonJS");
|
|
2562
|
-
if (
|
|
2563
|
-
else if (
|
|
2564
|
-
else if (
|
|
2565
|
-
else if (
|
|
2827
|
+
if (fs19.existsSync(path18.join(cwd, "pnpm-lock.yaml"))) detected.push("- Package manager: pnpm");
|
|
2828
|
+
else if (fs19.existsSync(path18.join(cwd, "yarn.lock"))) detected.push("- Package manager: yarn");
|
|
2829
|
+
else if (fs19.existsSync(path18.join(cwd, "bun.lockb"))) detected.push("- Package manager: bun");
|
|
2830
|
+
else if (fs19.existsSync(path18.join(cwd, "package-lock.json"))) detected.push("- Package manager: npm");
|
|
2566
2831
|
} catch {
|
|
2567
2832
|
}
|
|
2568
2833
|
}
|
|
2569
2834
|
try {
|
|
2570
|
-
const entries =
|
|
2835
|
+
const entries = fs19.readdirSync(cwd, { withFileTypes: true });
|
|
2571
2836
|
const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
|
|
2572
2837
|
if (dirs.length > 0) detected.push(`- Top-level directories: ${dirs.join(", ")}`);
|
|
2573
2838
|
} catch {
|
|
2574
2839
|
}
|
|
2575
|
-
const srcDir =
|
|
2576
|
-
if (
|
|
2840
|
+
const srcDir = path18.join(cwd, "src");
|
|
2841
|
+
if (fs19.existsSync(srcDir)) {
|
|
2577
2842
|
try {
|
|
2578
|
-
const srcEntries =
|
|
2843
|
+
const srcEntries = fs19.readdirSync(srcDir, { withFileTypes: true });
|
|
2579
2844
|
const srcDirs = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
2580
2845
|
if (srcDirs.length > 0) detected.push(`- src/ structure: ${srcDirs.join(", ")}`);
|
|
2581
2846
|
} catch {
|
|
2582
2847
|
}
|
|
2583
2848
|
}
|
|
2584
2849
|
const configs = [];
|
|
2585
|
-
if (
|
|
2586
|
-
if (
|
|
2587
|
-
if (
|
|
2588
|
-
if (
|
|
2850
|
+
if (fs19.existsSync(path18.join(cwd, "tsconfig.json"))) configs.push("tsconfig.json");
|
|
2851
|
+
if (fs19.existsSync(path18.join(cwd, "docker-compose.yml")) || fs19.existsSync(path18.join(cwd, "docker-compose.yaml"))) configs.push("docker-compose");
|
|
2852
|
+
if (fs19.existsSync(path18.join(cwd, "Dockerfile"))) configs.push("Dockerfile");
|
|
2853
|
+
if (fs19.existsSync(path18.join(cwd, ".env")) || fs19.existsSync(path18.join(cwd, ".env.local"))) configs.push(".env");
|
|
2589
2854
|
if (configs.length > 0) detected.push(`- Config files: ${configs.join(", ")}`);
|
|
2590
2855
|
return detected;
|
|
2591
2856
|
}
|
|
2592
2857
|
function detectBasicConfig(cwd) {
|
|
2593
2858
|
let pm = "pnpm";
|
|
2594
|
-
if (
|
|
2595
|
-
else if (
|
|
2596
|
-
else if (!
|
|
2859
|
+
if (fs19.existsSync(path18.join(cwd, "yarn.lock"))) pm = "yarn";
|
|
2860
|
+
else if (fs19.existsSync(path18.join(cwd, "bun.lockb"))) pm = "bun";
|
|
2861
|
+
else if (!fs19.existsSync(path18.join(cwd, "pnpm-lock.yaml")) && fs19.existsSync(path18.join(cwd, "package-lock.json"))) pm = "npm";
|
|
2597
2862
|
let defaultBranch = "main";
|
|
2598
2863
|
try {
|
|
2599
2864
|
const ref = execFileSync11("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
@@ -2637,9 +2902,9 @@ function smartInit(cwd) {
|
|
|
2637
2902
|
const basic = detectBasicConfig(cwd);
|
|
2638
2903
|
let context = "";
|
|
2639
2904
|
const readIfExists = (rel, maxChars = 3e3) => {
|
|
2640
|
-
const p =
|
|
2641
|
-
if (
|
|
2642
|
-
const content =
|
|
2905
|
+
const p = path18.join(cwd, rel);
|
|
2906
|
+
if (fs19.existsSync(p)) {
|
|
2907
|
+
const content = fs19.readFileSync(p, "utf-8");
|
|
2643
2908
|
return content.slice(0, maxChars);
|
|
2644
2909
|
}
|
|
2645
2910
|
return null;
|
|
@@ -2665,14 +2930,14 @@ ${claudeMd}
|
|
|
2665
2930
|
|
|
2666
2931
|
`;
|
|
2667
2932
|
try {
|
|
2668
|
-
const topDirs =
|
|
2933
|
+
const topDirs = fs19.readdirSync(cwd, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
|
|
2669
2934
|
context += `## Top-level directories
|
|
2670
2935
|
${topDirs.join(", ")}
|
|
2671
2936
|
|
|
2672
2937
|
`;
|
|
2673
|
-
const srcDir =
|
|
2674
|
-
if (
|
|
2675
|
-
const srcDirs =
|
|
2938
|
+
const srcDir = path18.join(cwd, "src");
|
|
2939
|
+
if (fs19.existsSync(srcDir)) {
|
|
2940
|
+
const srcDirs = fs19.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
2676
2941
|
context += `## src/ subdirectories
|
|
2677
2942
|
${srcDirs.join(", ")}
|
|
2678
2943
|
|
|
@@ -2682,7 +2947,7 @@ ${srcDirs.join(", ")}
|
|
|
2682
2947
|
}
|
|
2683
2948
|
const existingFiles = [];
|
|
2684
2949
|
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"]) {
|
|
2685
|
-
if (
|
|
2950
|
+
if (fs19.existsSync(path18.join(cwd, f))) existingFiles.push(f);
|
|
2686
2951
|
}
|
|
2687
2952
|
if (existingFiles.length) context += `## Config files present
|
|
2688
2953
|
${existingFiles.join(", ")}
|
|
@@ -2788,7 +3053,7 @@ ${context}`;
|
|
|
2788
3053
|
function validateQualityCommands(cwd, config, pm) {
|
|
2789
3054
|
let scripts = {};
|
|
2790
3055
|
try {
|
|
2791
|
-
const pkg = JSON.parse(
|
|
3056
|
+
const pkg = JSON.parse(fs19.readFileSync(path18.join(cwd, "package.json"), "utf-8"));
|
|
2792
3057
|
scripts = pkg.scripts ?? {};
|
|
2793
3058
|
} catch {
|
|
2794
3059
|
return;
|
|
@@ -2822,7 +3087,7 @@ function validateQualityCommands(cwd, config, pm) {
|
|
|
2822
3087
|
function buildFallbackConfig(cwd, basic) {
|
|
2823
3088
|
const pkg = (() => {
|
|
2824
3089
|
try {
|
|
2825
|
-
return JSON.parse(
|
|
3090
|
+
return JSON.parse(fs19.readFileSync(path18.join(cwd, "package.json"), "utf-8"));
|
|
2826
3091
|
} catch {
|
|
2827
3092
|
return {};
|
|
2828
3093
|
}
|
|
@@ -2862,34 +3127,34 @@ function initCommand(opts) {
|
|
|
2862
3127
|
console.log(`Project: ${cwd}
|
|
2863
3128
|
`);
|
|
2864
3129
|
console.log("\u2500\u2500 Files \u2500\u2500");
|
|
2865
|
-
const templatesDir =
|
|
2866
|
-
const workflowSrc =
|
|
2867
|
-
const workflowDest =
|
|
2868
|
-
if (!
|
|
3130
|
+
const templatesDir = path18.join(PKG_ROOT, "templates");
|
|
3131
|
+
const workflowSrc = path18.join(templatesDir, "kody.yml");
|
|
3132
|
+
const workflowDest = path18.join(cwd, ".github", "workflows", "kody.yml");
|
|
3133
|
+
if (!fs19.existsSync(workflowSrc)) {
|
|
2869
3134
|
console.error(" \u2717 Template kody.yml not found in package");
|
|
2870
3135
|
process.exit(1);
|
|
2871
3136
|
}
|
|
2872
|
-
if (
|
|
3137
|
+
if (fs19.existsSync(workflowDest) && !opts.force) {
|
|
2873
3138
|
console.log(" \u25CB .github/workflows/kody.yml (exists, use --force to overwrite)");
|
|
2874
3139
|
} else {
|
|
2875
|
-
|
|
2876
|
-
|
|
3140
|
+
fs19.mkdirSync(path18.dirname(workflowDest), { recursive: true });
|
|
3141
|
+
fs19.copyFileSync(workflowSrc, workflowDest);
|
|
2877
3142
|
console.log(" \u2713 .github/workflows/kody.yml");
|
|
2878
3143
|
}
|
|
2879
|
-
const configDest =
|
|
3144
|
+
const configDest = path18.join(cwd, "kody.config.json");
|
|
2880
3145
|
let smartResult = null;
|
|
2881
|
-
if (!
|
|
3146
|
+
if (!fs19.existsSync(configDest) || opts.force) {
|
|
2882
3147
|
smartResult = smartInit(cwd);
|
|
2883
|
-
|
|
3148
|
+
fs19.writeFileSync(configDest, JSON.stringify(smartResult.config, null, 2) + "\n");
|
|
2884
3149
|
console.log(" \u2713 kody.config.json (auto-configured)");
|
|
2885
3150
|
} else {
|
|
2886
3151
|
console.log(" \u25CB kody.config.json (exists)");
|
|
2887
3152
|
}
|
|
2888
|
-
const gitignorePath =
|
|
2889
|
-
if (
|
|
2890
|
-
const content =
|
|
3153
|
+
const gitignorePath = path18.join(cwd, ".gitignore");
|
|
3154
|
+
if (fs19.existsSync(gitignorePath)) {
|
|
3155
|
+
const content = fs19.readFileSync(gitignorePath, "utf-8");
|
|
2891
3156
|
if (!content.includes(".tasks/")) {
|
|
2892
|
-
|
|
3157
|
+
fs19.appendFileSync(gitignorePath, "\n.tasks/\n");
|
|
2893
3158
|
console.log(" \u2713 .gitignore (added .tasks/)");
|
|
2894
3159
|
} else {
|
|
2895
3160
|
console.log(" \u25CB .gitignore (.tasks/ already present)");
|
|
@@ -2902,7 +3167,7 @@ function initCommand(opts) {
|
|
|
2902
3167
|
checkCommand2("git", ["--version"], "Install git"),
|
|
2903
3168
|
checkCommand2("node", ["--version"], "Install Node.js >= 22"),
|
|
2904
3169
|
checkCommand2("pnpm", ["--version"], "Install: npm i -g pnpm"),
|
|
2905
|
-
checkFile(
|
|
3170
|
+
checkFile(path18.join(cwd, "package.json"), "package.json", "Run: pnpm init")
|
|
2906
3171
|
];
|
|
2907
3172
|
for (const c of checks) {
|
|
2908
3173
|
if (c.ok) {
|
|
@@ -2979,9 +3244,9 @@ function initCommand(opts) {
|
|
|
2979
3244
|
}
|
|
2980
3245
|
}
|
|
2981
3246
|
console.log("\n\u2500\u2500 Config \u2500\u2500");
|
|
2982
|
-
if (
|
|
3247
|
+
if (fs19.existsSync(configDest)) {
|
|
2983
3248
|
try {
|
|
2984
|
-
const config = JSON.parse(
|
|
3249
|
+
const config = JSON.parse(fs19.readFileSync(configDest, "utf-8"));
|
|
2985
3250
|
const configChecks = [];
|
|
2986
3251
|
if (config.github?.owner && config.github?.repo) {
|
|
2987
3252
|
configChecks.push({ name: "github.owner/repo", ok: true, detail: `${config.github.owner}/${config.github.repo}` });
|
|
@@ -3008,21 +3273,21 @@ function initCommand(opts) {
|
|
|
3008
3273
|
}
|
|
3009
3274
|
}
|
|
3010
3275
|
console.log("\n\u2500\u2500 Project Memory \u2500\u2500");
|
|
3011
|
-
const memoryDir =
|
|
3012
|
-
|
|
3013
|
-
const archPath =
|
|
3014
|
-
const conventionsPath =
|
|
3015
|
-
if (
|
|
3276
|
+
const memoryDir = path18.join(cwd, ".kody", "memory");
|
|
3277
|
+
fs19.mkdirSync(memoryDir, { recursive: true });
|
|
3278
|
+
const archPath = path18.join(memoryDir, "architecture.md");
|
|
3279
|
+
const conventionsPath = path18.join(memoryDir, "conventions.md");
|
|
3280
|
+
if (fs19.existsSync(archPath) && !opts.force) {
|
|
3016
3281
|
console.log(" \u25CB .kody/memory/architecture.md (exists, use --force to regenerate)");
|
|
3017
3282
|
} else if (smartResult?.architecture) {
|
|
3018
|
-
|
|
3283
|
+
fs19.writeFileSync(archPath, smartResult.architecture);
|
|
3019
3284
|
const lineCount = smartResult.architecture.split("\n").length;
|
|
3020
3285
|
console.log(` \u2713 .kody/memory/architecture.md (${lineCount} lines, LLM-generated)`);
|
|
3021
3286
|
} else {
|
|
3022
3287
|
const archItems = detectArchitecture(cwd);
|
|
3023
3288
|
if (archItems.length > 0) {
|
|
3024
3289
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3025
|
-
|
|
3290
|
+
fs19.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
|
|
3026
3291
|
|
|
3027
3292
|
## Overview
|
|
3028
3293
|
${archItems.join("\n")}
|
|
@@ -3032,14 +3297,14 @@ ${archItems.join("\n")}
|
|
|
3032
3297
|
console.log(" \u25CB No architecture detected");
|
|
3033
3298
|
}
|
|
3034
3299
|
}
|
|
3035
|
-
if (
|
|
3300
|
+
if (fs19.existsSync(conventionsPath) && !opts.force) {
|
|
3036
3301
|
console.log(" \u25CB .kody/memory/conventions.md (exists, use --force to regenerate)");
|
|
3037
3302
|
} else if (smartResult?.conventions) {
|
|
3038
|
-
|
|
3303
|
+
fs19.writeFileSync(conventionsPath, smartResult.conventions);
|
|
3039
3304
|
const lineCount = smartResult.conventions.split("\n").length;
|
|
3040
3305
|
console.log(` \u2713 .kody/memory/conventions.md (${lineCount} lines, LLM-generated)`);
|
|
3041
3306
|
} else {
|
|
3042
|
-
|
|
3307
|
+
fs19.writeFileSync(conventionsPath, "# Conventions\n\n<!-- Auto-learned conventions will be appended here -->\n");
|
|
3043
3308
|
console.log(" \u2713 .kody/memory/conventions.md (seed)");
|
|
3044
3309
|
}
|
|
3045
3310
|
const allChecks = [...checks, ghAuth, ghRepo];
|