@kody-ade/kody-engine-lite 0.1.80 → 0.1.82
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 +551 -328
- package/package.json +1 -1
- package/templates/kody.yml +3 -2
- 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
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
2
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
4
|
var __esm = (fn, res) => function __init() {
|
|
4
5
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
6
|
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
6
11
|
|
|
7
12
|
// src/agent-runner.ts
|
|
8
13
|
import { spawn, execFileSync } from "child_process";
|
|
@@ -132,7 +137,7 @@ var init_agent_runner = __esm({
|
|
|
132
137
|
"src/agent-runner.ts"() {
|
|
133
138
|
"use strict";
|
|
134
139
|
SIGKILL_GRACE_MS = 5e3;
|
|
135
|
-
STDERR_TAIL_CHARS =
|
|
140
|
+
STDERR_TAIL_CHARS = 2e3;
|
|
136
141
|
RUNNER_FACTORIES = {
|
|
137
142
|
"claude-code": createClaudeCodeRunner
|
|
138
143
|
};
|
|
@@ -277,7 +282,11 @@ function getProjectConfig() {
|
|
|
277
282
|
quality: { ...DEFAULT_CONFIG.quality, ...raw.quality },
|
|
278
283
|
git: { ...DEFAULT_CONFIG.git, ...raw.git },
|
|
279
284
|
github: { ...DEFAULT_CONFIG.github, ...raw.github },
|
|
280
|
-
agent: {
|
|
285
|
+
agent: {
|
|
286
|
+
...DEFAULT_CONFIG.agent,
|
|
287
|
+
...raw.agent,
|
|
288
|
+
modelMap: { ...DEFAULT_CONFIG.agent.modelMap, ...raw.agent?.modelMap }
|
|
289
|
+
},
|
|
281
290
|
contextTiers: raw.contextTiers ? { ...DEFAULT_CONFIG.contextTiers, ...raw.contextTiers } : DEFAULT_CONFIG.contextTiers,
|
|
282
291
|
mcp: raw.mcp ? { enabled: false, servers: {}, stages: ["build", "verify", "review", "review-fix"], ...raw.mcp } : void 0
|
|
283
292
|
};
|
|
@@ -290,7 +299,7 @@ function getProjectConfig() {
|
|
|
290
299
|
}
|
|
291
300
|
return _config;
|
|
292
301
|
}
|
|
293
|
-
var DEFAULT_CONFIG, LITELLM_DEFAULT_PORT, LITELLM_DEFAULT_URL,
|
|
302
|
+
var DEFAULT_CONFIG, LITELLM_DEFAULT_PORT, LITELLM_DEFAULT_URL, VERIFY_COMMAND_TIMEOUT_MS, FIX_COMMAND_TIMEOUT_MS, _config, _configDir;
|
|
294
303
|
var init_config = __esm({
|
|
295
304
|
"src/config.ts"() {
|
|
296
305
|
"use strict";
|
|
@@ -320,11 +329,6 @@ var init_config = __esm({
|
|
|
320
329
|
};
|
|
321
330
|
LITELLM_DEFAULT_PORT = 4e3;
|
|
322
331
|
LITELLM_DEFAULT_URL = `http://localhost:${LITELLM_DEFAULT_PORT}`;
|
|
323
|
-
TIER_TO_ANTHROPIC_IDS = {
|
|
324
|
-
cheap: ["claude-haiku-4-5-20251001", "claude-haiku-4-5", "haiku"],
|
|
325
|
-
mid: ["claude-sonnet-4-6-20250514", "claude-sonnet-4-6", "sonnet"],
|
|
326
|
-
strong: ["claude-opus-4-6-20250514", "claude-opus-4-6", "opus"]
|
|
327
|
-
};
|
|
328
332
|
VERIFY_COMMAND_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
329
333
|
FIX_COMMAND_TIMEOUT_MS = 2 * 60 * 1e3;
|
|
330
334
|
_config = null;
|
|
@@ -444,6 +448,41 @@ function syncWithDefault(cwd) {
|
|
|
444
448
|
logger.warn(` Merge conflict with origin/${defaultBranch} \u2014 skipping sync`);
|
|
445
449
|
}
|
|
446
450
|
}
|
|
451
|
+
function mergeDefault(cwd) {
|
|
452
|
+
const defaultBranch = getDefaultBranch(cwd);
|
|
453
|
+
const current = getCurrentBranch(cwd);
|
|
454
|
+
if (current === defaultBranch) return "clean";
|
|
455
|
+
try {
|
|
456
|
+
git(["fetch", "origin", defaultBranch], { cwd, timeout: 3e4 });
|
|
457
|
+
} catch {
|
|
458
|
+
logger.warn(" Failed to fetch latest from origin");
|
|
459
|
+
return "error";
|
|
460
|
+
}
|
|
461
|
+
try {
|
|
462
|
+
git(["merge", `origin/${defaultBranch}`, "--no-edit"], { cwd, timeout: 3e4 });
|
|
463
|
+
logger.info(` Merged origin/${defaultBranch} cleanly`);
|
|
464
|
+
return "clean";
|
|
465
|
+
} catch {
|
|
466
|
+
try {
|
|
467
|
+
const unmerged = git(["diff", "--name-only", "--diff-filter=U"], { cwd });
|
|
468
|
+
if (unmerged.trim()) return "conflict";
|
|
469
|
+
} catch {
|
|
470
|
+
}
|
|
471
|
+
try {
|
|
472
|
+
git(["merge", "--abort"], { cwd });
|
|
473
|
+
} catch {
|
|
474
|
+
}
|
|
475
|
+
return "error";
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
function getConflictedFiles(cwd) {
|
|
479
|
+
try {
|
|
480
|
+
const output = git(["diff", "--name-only", "--diff-filter=U"], { cwd });
|
|
481
|
+
return output ? output.split("\n") : [];
|
|
482
|
+
} catch {
|
|
483
|
+
return [];
|
|
484
|
+
}
|
|
485
|
+
}
|
|
447
486
|
function commitAll(message, cwd) {
|
|
448
487
|
const status = git(["status", "--porcelain"], { cwd });
|
|
449
488
|
if (!status) {
|
|
@@ -1483,9 +1522,6 @@ ${prompt}` : prompt;
|
|
|
1483
1522
|
}
|
|
1484
1523
|
function resolveModel(modelTier, stageName) {
|
|
1485
1524
|
const config = getProjectConfig();
|
|
1486
|
-
if (config.agent.provider && config.agent.provider !== "anthropic") {
|
|
1487
|
-
return DEFAULT_MODEL_MAP[modelTier] ?? "sonnet";
|
|
1488
|
-
}
|
|
1489
1525
|
const mapped = config.agent.modelMap[modelTier];
|
|
1490
1526
|
if (mapped) return mapped;
|
|
1491
1527
|
return DEFAULT_MODEL_MAP[modelTier] ?? "sonnet";
|
|
@@ -1836,8 +1872,8 @@ async function diagnoseFailure(stageName, errorOutput, modifiedFiles, runner, mo
|
|
|
1836
1872
|
`Stage: ${stageName}`,
|
|
1837
1873
|
``,
|
|
1838
1874
|
`Error output:`,
|
|
1839
|
-
errorOutput.slice(-
|
|
1840
|
-
// Last
|
|
1875
|
+
errorOutput.slice(-5e3),
|
|
1876
|
+
// Last 5000 chars of error for accurate diagnosis
|
|
1841
1877
|
``,
|
|
1842
1878
|
modifiedFiles.length > 0 ? `Files modified by build stage:
|
|
1843
1879
|
${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (build may not have run yet)."
|
|
@@ -1884,13 +1920,21 @@ ${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (bui
|
|
|
1884
1920
|
}
|
|
1885
1921
|
function getModifiedFiles(projectDir) {
|
|
1886
1922
|
try {
|
|
1887
|
-
const
|
|
1923
|
+
const staged = execFileSync5("git", ["diff", "--name-only", "--cached"], {
|
|
1888
1924
|
encoding: "utf-8",
|
|
1889
1925
|
cwd: projectDir,
|
|
1890
1926
|
timeout: 5e3,
|
|
1891
1927
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1892
1928
|
}).trim();
|
|
1893
|
-
|
|
1929
|
+
const unstaged = execFileSync5("git", ["diff", "--name-only"], {
|
|
1930
|
+
encoding: "utf-8",
|
|
1931
|
+
cwd: projectDir,
|
|
1932
|
+
timeout: 5e3,
|
|
1933
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1934
|
+
}).trim();
|
|
1935
|
+
const all = `${staged}
|
|
1936
|
+
${unstaged}`.split("\n").filter(Boolean);
|
|
1937
|
+
return [...new Set(all)];
|
|
1894
1938
|
} catch {
|
|
1895
1939
|
return [];
|
|
1896
1940
|
}
|
|
@@ -2090,55 +2134,198 @@ var init_verify = __esm({
|
|
|
2090
2134
|
}
|
|
2091
2135
|
});
|
|
2092
2136
|
|
|
2093
|
-
// src/
|
|
2137
|
+
// src/cli/task-resolution.ts
|
|
2094
2138
|
import * as fs9 from "fs";
|
|
2095
2139
|
import * as path9 from "path";
|
|
2140
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
2141
|
+
function findLatestTaskForIssue(issueNumber, projectDir) {
|
|
2142
|
+
const tasksDir = path9.join(projectDir, ".kody", "tasks");
|
|
2143
|
+
if (!fs9.existsSync(tasksDir)) return null;
|
|
2144
|
+
const allDirs = fs9.readdirSync(tasksDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
|
|
2145
|
+
const prefix = `${issueNumber}-`;
|
|
2146
|
+
const direct = allDirs.find((d) => d.startsWith(prefix));
|
|
2147
|
+
if (direct) return direct;
|
|
2148
|
+
try {
|
|
2149
|
+
const branch = execFileSync7("git", ["branch", "--show-current"], {
|
|
2150
|
+
encoding: "utf-8",
|
|
2151
|
+
cwd: projectDir,
|
|
2152
|
+
timeout: 5e3,
|
|
2153
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2154
|
+
}).trim();
|
|
2155
|
+
const branchIssueMatch = branch.match(/^(\d+)-/);
|
|
2156
|
+
if (branchIssueMatch) {
|
|
2157
|
+
const branchIssueNum = branchIssueMatch[1];
|
|
2158
|
+
const branchPrefix = `${branchIssueNum}-`;
|
|
2159
|
+
const fromBranch = allDirs.find((d) => d.startsWith(branchPrefix));
|
|
2160
|
+
if (fromBranch) return fromBranch;
|
|
2161
|
+
}
|
|
2162
|
+
} catch {
|
|
2163
|
+
}
|
|
2164
|
+
return null;
|
|
2165
|
+
}
|
|
2166
|
+
function generateTaskId() {
|
|
2167
|
+
const now = /* @__PURE__ */ new Date();
|
|
2168
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
2169
|
+
return `${String(now.getFullYear()).slice(2)}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
2170
|
+
}
|
|
2171
|
+
var init_task_resolution = __esm({
|
|
2172
|
+
"src/cli/task-resolution.ts"() {
|
|
2173
|
+
"use strict";
|
|
2174
|
+
}
|
|
2175
|
+
});
|
|
2176
|
+
|
|
2177
|
+
// src/review-standalone.ts
|
|
2178
|
+
import * as fs10 from "fs";
|
|
2179
|
+
import * as path10 from "path";
|
|
2180
|
+
function resolveReviewTarget(input) {
|
|
2181
|
+
if (input.prs.length === 0) {
|
|
2182
|
+
return {
|
|
2183
|
+
action: "none",
|
|
2184
|
+
message: `Issue #${input.issueNumber} has no open PRs. Nothing to review.`
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
if (input.prs.length === 1) {
|
|
2188
|
+
return { action: "review", prNumber: input.prs[0].number };
|
|
2189
|
+
}
|
|
2190
|
+
const prList = input.prs.map((pr) => ` - #${pr.number}: ${pr.title}`).join("\n");
|
|
2191
|
+
return {
|
|
2192
|
+
action: "pick",
|
|
2193
|
+
prs: input.prs,
|
|
2194
|
+
message: `\u26A0\uFE0F Issue #${input.issueNumber} has ${input.prs.length} open PRs:
|
|
2195
|
+
${prList}
|
|
2196
|
+
|
|
2197
|
+
Run: \`pnpm kody review --pr-number <n>\`
|
|
2198
|
+
Or comment on the specific PR: \`@kody review\``
|
|
2199
|
+
};
|
|
2200
|
+
}
|
|
2201
|
+
async function runStandaloneReview(input) {
|
|
2202
|
+
const taskId = input.taskId ?? `review-${generateTaskId()}`;
|
|
2203
|
+
const taskDir = path10.join(input.projectDir, ".kody", "tasks", taskId);
|
|
2204
|
+
fs10.mkdirSync(taskDir, { recursive: true });
|
|
2205
|
+
const taskContent = `# ${input.prTitle}
|
|
2206
|
+
|
|
2207
|
+
${input.prBody ?? ""}`;
|
|
2208
|
+
fs10.writeFileSync(path10.join(taskDir, "task.md"), taskContent);
|
|
2209
|
+
const reviewDef = STAGES.find((s) => s.name === "review");
|
|
2210
|
+
const ctx = {
|
|
2211
|
+
taskId,
|
|
2212
|
+
taskDir,
|
|
2213
|
+
projectDir: input.projectDir,
|
|
2214
|
+
runners: input.runners,
|
|
2215
|
+
sessions: {},
|
|
2216
|
+
input: {
|
|
2217
|
+
mode: "full",
|
|
2218
|
+
local: input.local
|
|
2219
|
+
}
|
|
2220
|
+
};
|
|
2221
|
+
logger.info(`[review] standalone review for: ${input.prTitle}`);
|
|
2222
|
+
const result = await executeAgentStage(ctx, reviewDef);
|
|
2223
|
+
if (result.outcome !== "completed") {
|
|
2224
|
+
return {
|
|
2225
|
+
outcome: "failed",
|
|
2226
|
+
taskDir,
|
|
2227
|
+
error: result.error ?? "Review stage failed"
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
2230
|
+
const reviewPath = path10.join(taskDir, "review.md");
|
|
2231
|
+
let reviewContent;
|
|
2232
|
+
if (fs10.existsSync(reviewPath)) {
|
|
2233
|
+
reviewContent = fs10.readFileSync(reviewPath, "utf-8");
|
|
2234
|
+
}
|
|
2235
|
+
return {
|
|
2236
|
+
outcome: "completed",
|
|
2237
|
+
reviewContent,
|
|
2238
|
+
taskDir
|
|
2239
|
+
};
|
|
2240
|
+
}
|
|
2241
|
+
function detectReviewVerdict(reviewContent) {
|
|
2242
|
+
const verdictMatch = reviewContent.match(/##\s*Verdict:\s*(PASS|FAIL)/i);
|
|
2243
|
+
if (verdictMatch) {
|
|
2244
|
+
return verdictMatch[1].toLowerCase();
|
|
2245
|
+
}
|
|
2246
|
+
const hasCritical = /###\s*Critical\s*\n(?!None\.)/i.test(reviewContent);
|
|
2247
|
+
const hasMajor = /###\s*Major\s*\n(?!None\.)/i.test(reviewContent);
|
|
2248
|
+
if (hasCritical || hasMajor) return "fail";
|
|
2249
|
+
return "pass";
|
|
2250
|
+
}
|
|
2251
|
+
function formatReviewComment(reviewContent, taskId) {
|
|
2252
|
+
const verdict = detectReviewVerdict(reviewContent);
|
|
2253
|
+
const cta = verdict === "fail" ? "\n\n> To fix these issues, comment: `@kody fix`\n> The review findings will be used automatically as context." : "";
|
|
2254
|
+
return `## \u{1F50D} Kody Review (\`${taskId}\`)
|
|
2255
|
+
|
|
2256
|
+
${reviewContent}${cta}
|
|
2257
|
+
|
|
2258
|
+
---
|
|
2259
|
+
\u{1F916} Generated by Kody`;
|
|
2260
|
+
}
|
|
2261
|
+
var init_review_standalone = __esm({
|
|
2262
|
+
"src/review-standalone.ts"() {
|
|
2263
|
+
"use strict";
|
|
2264
|
+
init_definitions();
|
|
2265
|
+
init_agent();
|
|
2266
|
+
init_task_resolution();
|
|
2267
|
+
init_logger();
|
|
2268
|
+
}
|
|
2269
|
+
});
|
|
2270
|
+
|
|
2271
|
+
// src/stages/review.ts
|
|
2272
|
+
import * as fs11 from "fs";
|
|
2273
|
+
import * as path11 from "path";
|
|
2096
2274
|
async function executeReviewWithFix(ctx, def) {
|
|
2097
2275
|
if (ctx.input.dryRun) {
|
|
2098
2276
|
return { outcome: "completed", retries: 0 };
|
|
2099
2277
|
}
|
|
2100
2278
|
const reviewDef = STAGES.find((s) => s.name === "review");
|
|
2101
2279
|
const reviewFixDef = STAGES.find((s) => s.name === "review-fix");
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2280
|
+
for (let iteration = 0; iteration <= MAX_REVIEW_FIX_ITERATIONS; iteration++) {
|
|
2281
|
+
const label = iteration === 0 ? "initial review" : `review after fix #${iteration}`;
|
|
2282
|
+
logger.info(` running ${label}...`);
|
|
2283
|
+
const reviewResult = await executeAgentStage(ctx, reviewDef);
|
|
2284
|
+
if (reviewResult.outcome !== "completed") {
|
|
2285
|
+
return reviewResult;
|
|
2286
|
+
}
|
|
2287
|
+
const reviewFile = path11.join(ctx.taskDir, "review.md");
|
|
2288
|
+
if (!fs11.existsSync(reviewFile)) {
|
|
2289
|
+
return { outcome: "failed", retries: iteration, error: "review.md not found" };
|
|
2290
|
+
}
|
|
2291
|
+
const content = fs11.readFileSync(reviewFile, "utf-8");
|
|
2292
|
+
if (detectReviewVerdict(content) !== "fail") {
|
|
2293
|
+
return { ...reviewResult, retries: iteration };
|
|
2294
|
+
}
|
|
2295
|
+
if (iteration === MAX_REVIEW_FIX_ITERATIONS) {
|
|
2296
|
+
logger.warn(` review still failing after ${MAX_REVIEW_FIX_ITERATIONS} fix attempts`);
|
|
2297
|
+
return { ...reviewResult, retries: iteration };
|
|
2298
|
+
}
|
|
2299
|
+
logger.info(` review found issues (iteration ${iteration + 1}/${MAX_REVIEW_FIX_ITERATIONS}), running review-fix...`);
|
|
2300
|
+
const fixResult = await executeAgentStage(ctx, reviewFixDef);
|
|
2301
|
+
if (fixResult.outcome !== "completed") {
|
|
2302
|
+
return fixResult;
|
|
2303
|
+
}
|
|
2119
2304
|
}
|
|
2120
|
-
|
|
2121
|
-
return executeAgentStage(ctx, reviewDef);
|
|
2305
|
+
return { outcome: "failed", retries: MAX_REVIEW_FIX_ITERATIONS, error: "Unexpected review-fix loop exit" };
|
|
2122
2306
|
}
|
|
2307
|
+
var MAX_REVIEW_FIX_ITERATIONS;
|
|
2123
2308
|
var init_review = __esm({
|
|
2124
2309
|
"src/stages/review.ts"() {
|
|
2125
2310
|
"use strict";
|
|
2126
2311
|
init_definitions();
|
|
2127
2312
|
init_logger();
|
|
2128
2313
|
init_agent();
|
|
2314
|
+
init_review_standalone();
|
|
2315
|
+
MAX_REVIEW_FIX_ITERATIONS = 2;
|
|
2129
2316
|
}
|
|
2130
2317
|
});
|
|
2131
2318
|
|
|
2132
2319
|
// src/stages/ship.ts
|
|
2133
|
-
import * as
|
|
2134
|
-
import * as
|
|
2135
|
-
import { execFileSync as
|
|
2320
|
+
import * as fs12 from "fs";
|
|
2321
|
+
import * as path12 from "path";
|
|
2322
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
2136
2323
|
function buildPrBody(ctx) {
|
|
2137
2324
|
const sections = [];
|
|
2138
|
-
const taskJsonPath =
|
|
2139
|
-
if (
|
|
2325
|
+
const taskJsonPath = path12.join(ctx.taskDir, "task.json");
|
|
2326
|
+
if (fs12.existsSync(taskJsonPath)) {
|
|
2140
2327
|
try {
|
|
2141
|
-
const raw =
|
|
2328
|
+
const raw = fs12.readFileSync(taskJsonPath, "utf-8");
|
|
2142
2329
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
2143
2330
|
const task = JSON.parse(cleaned);
|
|
2144
2331
|
if (task.description) {
|
|
@@ -2157,9 +2344,9 @@ ${task.scope.map((s) => `- \`${s}\``).join("\n")}`);
|
|
|
2157
2344
|
} catch {
|
|
2158
2345
|
}
|
|
2159
2346
|
}
|
|
2160
|
-
const reviewPath =
|
|
2161
|
-
if (
|
|
2162
|
-
const review =
|
|
2347
|
+
const reviewPath = path12.join(ctx.taskDir, "review.md");
|
|
2348
|
+
if (fs12.existsSync(reviewPath)) {
|
|
2349
|
+
const review = fs12.readFileSync(reviewPath, "utf-8");
|
|
2163
2350
|
const summaryMatch = review.match(/## Summary\s*\n([\s\S]*?)(?=\n## |\n*$)/);
|
|
2164
2351
|
if (summaryMatch) {
|
|
2165
2352
|
const summary = summaryMatch[1].trim();
|
|
@@ -2176,14 +2363,14 @@ ${summary}`);
|
|
|
2176
2363
|
**Review:** ${verdictMatch[1].toUpperCase() === "PASS" ? "\u2705 PASS" : "\u274C FAIL"}`);
|
|
2177
2364
|
}
|
|
2178
2365
|
}
|
|
2179
|
-
const verifyPath =
|
|
2180
|
-
if (
|
|
2181
|
-
const verify =
|
|
2366
|
+
const verifyPath = path12.join(ctx.taskDir, "verify.md");
|
|
2367
|
+
if (fs12.existsSync(verifyPath)) {
|
|
2368
|
+
const verify = fs12.readFileSync(verifyPath, "utf-8");
|
|
2182
2369
|
if (/PASS/i.test(verify)) sections.push(`**Verify:** \u2705 typecheck + tests + lint passed`);
|
|
2183
2370
|
}
|
|
2184
|
-
const planPath =
|
|
2185
|
-
if (
|
|
2186
|
-
const plan =
|
|
2371
|
+
const planPath = path12.join(ctx.taskDir, "plan.md");
|
|
2372
|
+
if (fs12.existsSync(planPath)) {
|
|
2373
|
+
const plan = fs12.readFileSync(planPath, "utf-8").trim();
|
|
2187
2374
|
if (plan) {
|
|
2188
2375
|
const truncated = plan.length > 800 ? plan.slice(0, 800) + "\n..." : plan;
|
|
2189
2376
|
sections.push(`
|
|
@@ -2203,25 +2390,25 @@ Closes #${ctx.input.issueNumber}`);
|
|
|
2203
2390
|
return sections.join("\n");
|
|
2204
2391
|
}
|
|
2205
2392
|
function executeShipStage(ctx, _def) {
|
|
2206
|
-
const shipPath =
|
|
2393
|
+
const shipPath = path12.join(ctx.taskDir, "ship.md");
|
|
2207
2394
|
if (ctx.input.dryRun) {
|
|
2208
|
-
|
|
2395
|
+
fs12.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
|
|
2209
2396
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
2210
2397
|
}
|
|
2211
2398
|
if (ctx.input.local && !ctx.input.issueNumber) {
|
|
2212
|
-
|
|
2399
|
+
fs12.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
|
|
2213
2400
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
2214
2401
|
}
|
|
2215
2402
|
try {
|
|
2216
2403
|
const head = getCurrentBranch(ctx.projectDir);
|
|
2217
2404
|
const base = getDefaultBranch(ctx.projectDir);
|
|
2218
2405
|
try {
|
|
2219
|
-
|
|
2406
|
+
execFileSync8("git", ["add", ctx.taskDir], {
|
|
2220
2407
|
cwd: ctx.projectDir,
|
|
2221
2408
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
2222
2409
|
stdio: "pipe"
|
|
2223
2410
|
});
|
|
2224
|
-
|
|
2411
|
+
execFileSync8("git", ["commit", "--no-gpg-sign", "-m", `chore: add kody task artifacts [skip ci]`], {
|
|
2225
2412
|
cwd: ctx.projectDir,
|
|
2226
2413
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
2227
2414
|
stdio: "pipe"
|
|
@@ -2235,7 +2422,7 @@ function executeShipStage(ctx, _def) {
|
|
|
2235
2422
|
let repo = config.github?.repo;
|
|
2236
2423
|
if (!owner || !repo) {
|
|
2237
2424
|
try {
|
|
2238
|
-
const remoteUrl =
|
|
2425
|
+
const remoteUrl = execFileSync8("git", ["remote", "get-url", "origin"], {
|
|
2239
2426
|
encoding: "utf-8",
|
|
2240
2427
|
cwd: ctx.projectDir
|
|
2241
2428
|
}).trim();
|
|
@@ -2255,10 +2442,10 @@ function executeShipStage(ctx, _def) {
|
|
|
2255
2442
|
docs: "docs",
|
|
2256
2443
|
chore: "chore"
|
|
2257
2444
|
};
|
|
2258
|
-
const taskJsonPath =
|
|
2259
|
-
if (
|
|
2445
|
+
const taskJsonPath = path12.join(ctx.taskDir, "task.json");
|
|
2446
|
+
if (fs12.existsSync(taskJsonPath)) {
|
|
2260
2447
|
try {
|
|
2261
|
-
const raw =
|
|
2448
|
+
const raw = fs12.readFileSync(taskJsonPath, "utf-8");
|
|
2262
2449
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
2263
2450
|
const task = JSON.parse(cleaned);
|
|
2264
2451
|
const prefix = TYPE_PREFIX[task.task_type] ?? "chore";
|
|
@@ -2268,9 +2455,9 @@ function executeShipStage(ctx, _def) {
|
|
|
2268
2455
|
}
|
|
2269
2456
|
}
|
|
2270
2457
|
if (title === "Update") {
|
|
2271
|
-
const taskMdPath =
|
|
2272
|
-
if (
|
|
2273
|
-
const content =
|
|
2458
|
+
const taskMdPath = path12.join(ctx.taskDir, "task.md");
|
|
2459
|
+
if (fs12.existsSync(taskMdPath)) {
|
|
2460
|
+
const content = fs12.readFileSync(taskMdPath, "utf-8");
|
|
2274
2461
|
const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#") && !l.startsWith("*"));
|
|
2275
2462
|
if (firstLine) title = `chore: ${firstLine.trim()}`.slice(0, 72);
|
|
2276
2463
|
}
|
|
@@ -2290,7 +2477,7 @@ function executeShipStage(ctx, _def) {
|
|
|
2290
2477
|
} catch {
|
|
2291
2478
|
}
|
|
2292
2479
|
}
|
|
2293
|
-
|
|
2480
|
+
fs12.writeFileSync(shipPath, `# Ship
|
|
2294
2481
|
|
|
2295
2482
|
Updated existing PR: ${existingPr.url}
|
|
2296
2483
|
PR #${existingPr.number}
|
|
@@ -2311,19 +2498,19 @@ PR #${existingPr.number}
|
|
|
2311
2498
|
} catch {
|
|
2312
2499
|
}
|
|
2313
2500
|
}
|
|
2314
|
-
|
|
2501
|
+
fs12.writeFileSync(shipPath, `# Ship
|
|
2315
2502
|
|
|
2316
2503
|
PR created: ${pr.url}
|
|
2317
2504
|
PR #${pr.number}
|
|
2318
2505
|
`);
|
|
2319
2506
|
} else {
|
|
2320
|
-
|
|
2507
|
+
fs12.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
|
|
2321
2508
|
}
|
|
2322
2509
|
}
|
|
2323
2510
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
2324
2511
|
} catch (err) {
|
|
2325
2512
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2326
|
-
|
|
2513
|
+
fs12.writeFileSync(shipPath, `# Ship
|
|
2327
2514
|
|
|
2328
2515
|
Failed: ${msg}
|
|
2329
2516
|
`);
|
|
@@ -2369,15 +2556,15 @@ var init_executor_registry = __esm({
|
|
|
2369
2556
|
});
|
|
2370
2557
|
|
|
2371
2558
|
// src/pipeline/questions.ts
|
|
2372
|
-
import * as
|
|
2373
|
-
import * as
|
|
2559
|
+
import * as fs13 from "fs";
|
|
2560
|
+
import * as path13 from "path";
|
|
2374
2561
|
function checkForQuestions(ctx, stageName) {
|
|
2375
2562
|
if (ctx.input.local || !ctx.input.issueNumber) return false;
|
|
2376
2563
|
try {
|
|
2377
2564
|
if (stageName === "taskify") {
|
|
2378
|
-
const taskJsonPath =
|
|
2379
|
-
if (!
|
|
2380
|
-
const raw =
|
|
2565
|
+
const taskJsonPath = path13.join(ctx.taskDir, "task.json");
|
|
2566
|
+
if (!fs13.existsSync(taskJsonPath)) return false;
|
|
2567
|
+
const raw = fs13.readFileSync(taskJsonPath, "utf-8");
|
|
2381
2568
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
2382
2569
|
const taskJson = JSON.parse(cleaned);
|
|
2383
2570
|
if (taskJson.questions && Array.isArray(taskJson.questions) && taskJson.questions.length > 0) {
|
|
@@ -2392,9 +2579,9 @@ Reply with \`@kody approve\` and your answers in the comment body.`;
|
|
|
2392
2579
|
}
|
|
2393
2580
|
}
|
|
2394
2581
|
if (stageName === "plan") {
|
|
2395
|
-
const planPath =
|
|
2396
|
-
if (!
|
|
2397
|
-
const plan =
|
|
2582
|
+
const planPath = path13.join(ctx.taskDir, "plan.md");
|
|
2583
|
+
if (!fs13.existsSync(planPath)) return false;
|
|
2584
|
+
const plan = fs13.readFileSync(planPath, "utf-8");
|
|
2398
2585
|
const questionsMatch = plan.match(/## Questions\s*\n([\s\S]*?)(?=\n## |\n*$)/);
|
|
2399
2586
|
if (questionsMatch) {
|
|
2400
2587
|
const questionsText = questionsMatch[1].trim();
|
|
@@ -2423,8 +2610,8 @@ var init_questions = __esm({
|
|
|
2423
2610
|
});
|
|
2424
2611
|
|
|
2425
2612
|
// src/pipeline/hooks.ts
|
|
2426
|
-
import * as
|
|
2427
|
-
import * as
|
|
2613
|
+
import * as fs14 from "fs";
|
|
2614
|
+
import * as path14 from "path";
|
|
2428
2615
|
function applyPreStageLabel(ctx, def) {
|
|
2429
2616
|
if (!ctx.input.issueNumber || ctx.input.local) return;
|
|
2430
2617
|
if (def.name === "build") setLifecycleLabel(ctx.input.issueNumber, "building");
|
|
@@ -2450,9 +2637,9 @@ function autoDetectComplexity(ctx, def) {
|
|
|
2450
2637
|
if (def.name !== "taskify") return null;
|
|
2451
2638
|
if (ctx.input.complexity) return null;
|
|
2452
2639
|
try {
|
|
2453
|
-
const taskJsonPath =
|
|
2454
|
-
if (!
|
|
2455
|
-
const raw =
|
|
2640
|
+
const taskJsonPath = path14.join(ctx.taskDir, "task.json");
|
|
2641
|
+
if (!fs14.existsSync(taskJsonPath)) return null;
|
|
2642
|
+
const raw = fs14.readFileSync(taskJsonPath, "utf-8");
|
|
2456
2643
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
2457
2644
|
const taskJson = JSON.parse(cleaned);
|
|
2458
2645
|
if (!taskJson.risk_level || !isValidComplexity(taskJson.risk_level)) return null;
|
|
@@ -2482,8 +2669,8 @@ function checkRiskGate(ctx, def, state, complexity) {
|
|
|
2482
2669
|
if (ctx.input.dryRun || ctx.input.local) return null;
|
|
2483
2670
|
if (ctx.input.mode === "rerun") return null;
|
|
2484
2671
|
if (!ctx.input.issueNumber) return null;
|
|
2485
|
-
const planPath =
|
|
2486
|
-
const plan =
|
|
2672
|
+
const planPath = path14.join(ctx.taskDir, "plan.md");
|
|
2673
|
+
const plan = fs14.existsSync(planPath) ? fs14.readFileSync(planPath, "utf-8").slice(0, 1500) : "(plan not available)";
|
|
2487
2674
|
try {
|
|
2488
2675
|
postComment(
|
|
2489
2676
|
ctx.input.issueNumber,
|
|
@@ -2550,22 +2737,22 @@ var init_hooks = __esm({
|
|
|
2550
2737
|
});
|
|
2551
2738
|
|
|
2552
2739
|
// src/learning/auto-learn.ts
|
|
2553
|
-
import * as
|
|
2554
|
-
import * as
|
|
2740
|
+
import * as fs15 from "fs";
|
|
2741
|
+
import * as path15 from "path";
|
|
2555
2742
|
function stripAnsi(str) {
|
|
2556
2743
|
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2557
2744
|
}
|
|
2558
2745
|
function autoLearn(ctx) {
|
|
2559
2746
|
try {
|
|
2560
|
-
const memoryDir =
|
|
2561
|
-
if (!
|
|
2562
|
-
|
|
2747
|
+
const memoryDir = path15.join(ctx.projectDir, ".kody", "memory");
|
|
2748
|
+
if (!fs15.existsSync(memoryDir)) {
|
|
2749
|
+
fs15.mkdirSync(memoryDir, { recursive: true });
|
|
2563
2750
|
}
|
|
2564
2751
|
const learnings = [];
|
|
2565
2752
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2566
|
-
const verifyPath =
|
|
2567
|
-
if (
|
|
2568
|
-
const verify = stripAnsi(
|
|
2753
|
+
const verifyPath = path15.join(ctx.taskDir, "verify.md");
|
|
2754
|
+
if (fs15.existsSync(verifyPath)) {
|
|
2755
|
+
const verify = stripAnsi(fs15.readFileSync(verifyPath, "utf-8"));
|
|
2569
2756
|
if (/vitest/i.test(verify)) learnings.push("- Uses vitest for testing");
|
|
2570
2757
|
if (/jest/i.test(verify)) learnings.push("- Uses jest for testing");
|
|
2571
2758
|
if (/eslint/i.test(verify)) learnings.push("- Uses eslint for linting");
|
|
@@ -2574,18 +2761,18 @@ function autoLearn(ctx) {
|
|
|
2574
2761
|
if (/jsdom/i.test(verify)) learnings.push("- Test environment: jsdom");
|
|
2575
2762
|
if (/node/i.test(verify) && /environment/i.test(verify)) learnings.push("- Test environment: node");
|
|
2576
2763
|
}
|
|
2577
|
-
const reviewPath =
|
|
2578
|
-
if (
|
|
2579
|
-
const review =
|
|
2764
|
+
const reviewPath = path15.join(ctx.taskDir, "review.md");
|
|
2765
|
+
if (fs15.existsSync(reviewPath)) {
|
|
2766
|
+
const review = fs15.readFileSync(reviewPath, "utf-8");
|
|
2580
2767
|
if (/\.js extension/i.test(review)) learnings.push("- Imports use .js extensions (ESM)");
|
|
2581
2768
|
if (/barrel export/i.test(review)) learnings.push("- Uses barrel exports (index.ts)");
|
|
2582
2769
|
if (/timezone/i.test(review)) learnings.push("- Timezone handling is a concern in this codebase");
|
|
2583
2770
|
if (/UTC/i.test(review)) learnings.push("- Date operations should consider UTC vs local time");
|
|
2584
2771
|
}
|
|
2585
|
-
const taskJsonPath =
|
|
2586
|
-
if (
|
|
2772
|
+
const taskJsonPath = path15.join(ctx.taskDir, "task.json");
|
|
2773
|
+
if (fs15.existsSync(taskJsonPath)) {
|
|
2587
2774
|
try {
|
|
2588
|
-
const raw = stripAnsi(
|
|
2775
|
+
const raw = stripAnsi(fs15.readFileSync(taskJsonPath, "utf-8"));
|
|
2589
2776
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
2590
2777
|
const task = JSON.parse(cleaned);
|
|
2591
2778
|
if (task.scope && Array.isArray(task.scope)) {
|
|
@@ -2596,13 +2783,13 @@ function autoLearn(ctx) {
|
|
|
2596
2783
|
}
|
|
2597
2784
|
}
|
|
2598
2785
|
if (learnings.length > 0) {
|
|
2599
|
-
const conventionsPath =
|
|
2786
|
+
const conventionsPath = path15.join(memoryDir, "conventions.md");
|
|
2600
2787
|
const entry = `
|
|
2601
2788
|
## Learned ${timestamp2} (task: ${ctx.taskId})
|
|
2602
2789
|
${learnings.join("\n")}
|
|
2603
2790
|
`;
|
|
2604
|
-
|
|
2605
|
-
invalidateCache(conventionsPath,
|
|
2791
|
+
fs15.appendFileSync(conventionsPath, entry);
|
|
2792
|
+
invalidateCache(conventionsPath, path15.join(memoryDir, ".tiers"));
|
|
2606
2793
|
logger.info(`Auto-learned ${learnings.length} convention(s)`);
|
|
2607
2794
|
}
|
|
2608
2795
|
autoLearnDecisions(ctx.taskDir, memoryDir, ctx.taskId, timestamp2);
|
|
@@ -2611,13 +2798,13 @@ ${learnings.join("\n")}
|
|
|
2611
2798
|
}
|
|
2612
2799
|
}
|
|
2613
2800
|
function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
2614
|
-
const archPath =
|
|
2615
|
-
if (
|
|
2801
|
+
const archPath = path15.join(memoryDir, "architecture.md");
|
|
2802
|
+
if (fs15.existsSync(archPath)) return;
|
|
2616
2803
|
const detected = [];
|
|
2617
|
-
const pkgPath =
|
|
2618
|
-
if (
|
|
2804
|
+
const pkgPath = path15.join(projectDir, "package.json");
|
|
2805
|
+
if (fs15.existsSync(pkgPath)) {
|
|
2619
2806
|
try {
|
|
2620
|
-
const pkg = JSON.parse(
|
|
2807
|
+
const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf-8"));
|
|
2621
2808
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
2622
2809
|
if (allDeps.next) detected.push(`- Framework: Next.js ${allDeps.next}`);
|
|
2623
2810
|
else if (allDeps.react) detected.push(`- Framework: React ${allDeps.react}`);
|
|
@@ -2633,15 +2820,15 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
|
2633
2820
|
if (allDeps.payload || allDeps["@payloadcms/next"]) detected.push(`- CMS: Payload CMS`);
|
|
2634
2821
|
if (pkg.type === "module") detected.push("- Module system: ESM");
|
|
2635
2822
|
else detected.push("- Module system: CommonJS");
|
|
2636
|
-
if (
|
|
2637
|
-
else if (
|
|
2638
|
-
else if (
|
|
2823
|
+
if (fs15.existsSync(path15.join(projectDir, "pnpm-lock.yaml"))) detected.push("- Package manager: pnpm");
|
|
2824
|
+
else if (fs15.existsSync(path15.join(projectDir, "yarn.lock"))) detected.push("- Package manager: yarn");
|
|
2825
|
+
else if (fs15.existsSync(path15.join(projectDir, "package-lock.json"))) detected.push("- Package manager: npm");
|
|
2639
2826
|
} catch {
|
|
2640
2827
|
}
|
|
2641
2828
|
}
|
|
2642
2829
|
const topDirs = [];
|
|
2643
2830
|
try {
|
|
2644
|
-
const entries =
|
|
2831
|
+
const entries = fs15.readdirSync(projectDir, { withFileTypes: true });
|
|
2645
2832
|
for (const entry of entries) {
|
|
2646
2833
|
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
2647
2834
|
topDirs.push(entry.name);
|
|
@@ -2650,10 +2837,10 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
|
2650
2837
|
if (topDirs.length > 0) detected.push(`- Top-level directories: ${topDirs.join(", ")}`);
|
|
2651
2838
|
} catch {
|
|
2652
2839
|
}
|
|
2653
|
-
const srcDir =
|
|
2654
|
-
if (
|
|
2840
|
+
const srcDir = path15.join(projectDir, "src");
|
|
2841
|
+
if (fs15.existsSync(srcDir)) {
|
|
2655
2842
|
try {
|
|
2656
|
-
const srcEntries =
|
|
2843
|
+
const srcEntries = fs15.readdirSync(srcDir, { withFileTypes: true });
|
|
2657
2844
|
const srcDirs = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
2658
2845
|
if (srcDirs.length > 0) detected.push(`- src/ structure: ${srcDirs.join(", ")}`);
|
|
2659
2846
|
} catch {
|
|
@@ -2665,15 +2852,15 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
|
2665
2852
|
## Overview
|
|
2666
2853
|
${detected.join("\n")}
|
|
2667
2854
|
`;
|
|
2668
|
-
|
|
2669
|
-
invalidateCache(archPath,
|
|
2855
|
+
fs15.writeFileSync(archPath, content);
|
|
2856
|
+
invalidateCache(archPath, path15.join(memoryDir, ".tiers"));
|
|
2670
2857
|
logger.info(`Auto-detected architecture (${detected.length} items)`);
|
|
2671
2858
|
}
|
|
2672
2859
|
}
|
|
2673
2860
|
function autoLearnDecisions(taskDir, memoryDir, taskId, timestamp2) {
|
|
2674
|
-
const reviewPath =
|
|
2675
|
-
if (!
|
|
2676
|
-
const review =
|
|
2861
|
+
const reviewPath = path15.join(taskDir, "review.md");
|
|
2862
|
+
if (!fs15.existsSync(reviewPath)) return;
|
|
2863
|
+
const review = fs15.readFileSync(reviewPath, "utf-8");
|
|
2677
2864
|
const decisions = [];
|
|
2678
2865
|
const existingPatternRe = /(?:use|follow|reuse|match|adopt)\s+(?:the\s+)?existing\s+(.+?)(?:\.|$)/gim;
|
|
2679
2866
|
for (const match of review.matchAll(existingPatternRe)) {
|
|
@@ -2692,10 +2879,10 @@ function autoLearnDecisions(taskDir, memoryDir, taskId, timestamp2) {
|
|
|
2692
2879
|
decisions.push(`- Avoid ${match[1].trim()} for ${match[2].trim()}`);
|
|
2693
2880
|
}
|
|
2694
2881
|
if (decisions.length === 0) return;
|
|
2695
|
-
const decisionsPath =
|
|
2882
|
+
const decisionsPath = path15.join(memoryDir, "decisions.md");
|
|
2696
2883
|
let existing = "";
|
|
2697
|
-
if (
|
|
2698
|
-
existing =
|
|
2884
|
+
if (fs15.existsSync(decisionsPath)) {
|
|
2885
|
+
existing = fs15.readFileSync(decisionsPath, "utf-8");
|
|
2699
2886
|
} else {
|
|
2700
2887
|
existing = "# Architectural Decisions\n\nDecisions extracted from code reviews. The planning agent MUST follow these.\n";
|
|
2701
2888
|
}
|
|
@@ -2705,8 +2892,8 @@ function autoLearnDecisions(taskDir, memoryDir, taskId, timestamp2) {
|
|
|
2705
2892
|
## From task ${taskId} (${timestamp2})
|
|
2706
2893
|
${newDecisions.join("\n")}
|
|
2707
2894
|
`;
|
|
2708
|
-
|
|
2709
|
-
invalidateCache(decisionsPath,
|
|
2895
|
+
fs15.appendFileSync(decisionsPath, existing ? entry : existing + entry);
|
|
2896
|
+
invalidateCache(decisionsPath, path15.join(memoryDir, ".tiers"));
|
|
2710
2897
|
logger.info(`Auto-learned ${newDecisions.length} architectural decision(s)`);
|
|
2711
2898
|
}
|
|
2712
2899
|
var init_auto_learn = __esm({
|
|
@@ -2718,13 +2905,13 @@ var init_auto_learn = __esm({
|
|
|
2718
2905
|
});
|
|
2719
2906
|
|
|
2720
2907
|
// src/retrospective.ts
|
|
2721
|
-
import * as
|
|
2722
|
-
import * as
|
|
2908
|
+
import * as fs16 from "fs";
|
|
2909
|
+
import * as path16 from "path";
|
|
2723
2910
|
function readArtifact(taskDir, filename, maxChars) {
|
|
2724
|
-
const p =
|
|
2725
|
-
if (!
|
|
2911
|
+
const p = path16.join(taskDir, filename);
|
|
2912
|
+
if (!fs16.existsSync(p)) return null;
|
|
2726
2913
|
try {
|
|
2727
|
-
const content =
|
|
2914
|
+
const content = fs16.readFileSync(p, "utf-8");
|
|
2728
2915
|
return content.length > maxChars ? content.slice(0, maxChars) + "\n...(truncated)" : content;
|
|
2729
2916
|
} catch {
|
|
2730
2917
|
return null;
|
|
@@ -2777,13 +2964,13 @@ function collectRunContext(ctx, state, pipelineStartTime) {
|
|
|
2777
2964
|
return lines.join("\n");
|
|
2778
2965
|
}
|
|
2779
2966
|
function getLogPath(projectDir) {
|
|
2780
|
-
return
|
|
2967
|
+
return path16.join(projectDir, ".kody", "memory", "observer-log.jsonl");
|
|
2781
2968
|
}
|
|
2782
2969
|
function readPreviousRetrospectives(projectDir, limit = 10) {
|
|
2783
2970
|
const logPath = getLogPath(projectDir);
|
|
2784
|
-
if (!
|
|
2971
|
+
if (!fs16.existsSync(logPath)) return [];
|
|
2785
2972
|
try {
|
|
2786
|
-
const content =
|
|
2973
|
+
const content = fs16.readFileSync(logPath, "utf-8");
|
|
2787
2974
|
const lines = content.split("\n").filter(Boolean);
|
|
2788
2975
|
const entries = [];
|
|
2789
2976
|
const start = Math.max(0, lines.length - limit);
|
|
@@ -2810,11 +2997,11 @@ function formatPreviousEntries(entries) {
|
|
|
2810
2997
|
}
|
|
2811
2998
|
function appendRetrospectiveEntry(projectDir, entry) {
|
|
2812
2999
|
const logPath = getLogPath(projectDir);
|
|
2813
|
-
const dir =
|
|
2814
|
-
if (!
|
|
2815
|
-
|
|
3000
|
+
const dir = path16.dirname(logPath);
|
|
3001
|
+
if (!fs16.existsSync(dir)) {
|
|
3002
|
+
fs16.mkdirSync(dir, { recursive: true });
|
|
2816
3003
|
}
|
|
2817
|
-
|
|
3004
|
+
fs16.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
2818
3005
|
}
|
|
2819
3006
|
async function runRetrospective(ctx, state, pipelineStartTime) {
|
|
2820
3007
|
if (ctx.input.dryRun) return;
|
|
@@ -2925,14 +3112,22 @@ If no pipeline flaw is detected, set "pipelineFlaw" to null.
|
|
|
2925
3112
|
});
|
|
2926
3113
|
|
|
2927
3114
|
// src/pipeline.ts
|
|
2928
|
-
import * as
|
|
2929
|
-
import * as
|
|
3115
|
+
import * as fs17 from "fs";
|
|
3116
|
+
import * as path17 from "path";
|
|
2930
3117
|
function ensureFeatureBranchIfNeeded(ctx) {
|
|
2931
|
-
if (ctx.input.
|
|
2932
|
-
if (
|
|
3118
|
+
if (ctx.input.dryRun) return;
|
|
3119
|
+
if (ctx.input.prNumber) {
|
|
3120
|
+
try {
|
|
3121
|
+
syncWithDefault(ctx.projectDir);
|
|
3122
|
+
} catch (err) {
|
|
3123
|
+
logger.warn(` Failed to sync with default branch: ${err}`);
|
|
3124
|
+
}
|
|
3125
|
+
return;
|
|
3126
|
+
}
|
|
3127
|
+
if (!ctx.input.issueNumber) return;
|
|
2933
3128
|
try {
|
|
2934
|
-
const taskMdPath =
|
|
2935
|
-
const title =
|
|
3129
|
+
const taskMdPath = path17.join(ctx.taskDir, "task.md");
|
|
3130
|
+
const title = fs17.existsSync(taskMdPath) ? fs17.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
|
|
2936
3131
|
ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
|
|
2937
3132
|
syncWithDefault(ctx.projectDir);
|
|
2938
3133
|
} catch (err) {
|
|
@@ -2940,10 +3135,10 @@ function ensureFeatureBranchIfNeeded(ctx) {
|
|
|
2940
3135
|
}
|
|
2941
3136
|
}
|
|
2942
3137
|
function acquireLock(taskDir) {
|
|
2943
|
-
const lockPath =
|
|
2944
|
-
if (
|
|
3138
|
+
const lockPath = path17.join(taskDir, ".lock");
|
|
3139
|
+
if (fs17.existsSync(lockPath)) {
|
|
2945
3140
|
try {
|
|
2946
|
-
const pid = parseInt(
|
|
3141
|
+
const pid = parseInt(fs17.readFileSync(lockPath, "utf-8").trim(), 10);
|
|
2947
3142
|
try {
|
|
2948
3143
|
process.kill(pid, 0);
|
|
2949
3144
|
throw new Error(`Pipeline already running (PID ${pid})`);
|
|
@@ -2954,11 +3149,11 @@ function acquireLock(taskDir) {
|
|
|
2954
3149
|
if (e instanceof Error && e.message.startsWith("Pipeline already")) throw e;
|
|
2955
3150
|
}
|
|
2956
3151
|
}
|
|
2957
|
-
|
|
3152
|
+
fs17.writeFileSync(lockPath, String(process.pid));
|
|
2958
3153
|
}
|
|
2959
3154
|
function releaseLock(taskDir) {
|
|
2960
3155
|
try {
|
|
2961
|
-
|
|
3156
|
+
fs17.unlinkSync(path17.join(taskDir, ".lock"));
|
|
2962
3157
|
} catch {
|
|
2963
3158
|
}
|
|
2964
3159
|
}
|
|
@@ -3126,8 +3321,8 @@ var init_pipeline = __esm({
|
|
|
3126
3321
|
});
|
|
3127
3322
|
|
|
3128
3323
|
// src/preflight.ts
|
|
3129
|
-
import { execFileSync as
|
|
3130
|
-
import * as
|
|
3324
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
3325
|
+
import * as fs18 from "fs";
|
|
3131
3326
|
function check(name, fn) {
|
|
3132
3327
|
try {
|
|
3133
3328
|
const detail = fn() ?? void 0;
|
|
@@ -3139,7 +3334,7 @@ function check(name, fn) {
|
|
|
3139
3334
|
function runPreflight() {
|
|
3140
3335
|
const checks = [
|
|
3141
3336
|
check("claude CLI", () => {
|
|
3142
|
-
const v =
|
|
3337
|
+
const v = execFileSync9("claude", ["--version"], {
|
|
3143
3338
|
encoding: "utf-8",
|
|
3144
3339
|
timeout: 1e4,
|
|
3145
3340
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3147,14 +3342,14 @@ function runPreflight() {
|
|
|
3147
3342
|
return v;
|
|
3148
3343
|
}),
|
|
3149
3344
|
check("git repo", () => {
|
|
3150
|
-
|
|
3345
|
+
execFileSync9("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
3151
3346
|
encoding: "utf-8",
|
|
3152
3347
|
timeout: 5e3,
|
|
3153
3348
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3154
3349
|
});
|
|
3155
3350
|
}),
|
|
3156
3351
|
check("pnpm", () => {
|
|
3157
|
-
const v =
|
|
3352
|
+
const v = execFileSync9("pnpm", ["--version"], {
|
|
3158
3353
|
encoding: "utf-8",
|
|
3159
3354
|
timeout: 5e3,
|
|
3160
3355
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3162,7 +3357,7 @@ function runPreflight() {
|
|
|
3162
3357
|
return v;
|
|
3163
3358
|
}),
|
|
3164
3359
|
check("node >= 18", () => {
|
|
3165
|
-
const v =
|
|
3360
|
+
const v = execFileSync9("node", ["--version"], {
|
|
3166
3361
|
encoding: "utf-8",
|
|
3167
3362
|
timeout: 5e3,
|
|
3168
3363
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3172,7 +3367,7 @@ function runPreflight() {
|
|
|
3172
3367
|
return v;
|
|
3173
3368
|
}),
|
|
3174
3369
|
check("gh CLI", () => {
|
|
3175
|
-
const v =
|
|
3370
|
+
const v = execFileSync9("gh", ["--version"], {
|
|
3176
3371
|
encoding: "utf-8",
|
|
3177
3372
|
timeout: 5e3,
|
|
3178
3373
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3180,7 +3375,7 @@ function runPreflight() {
|
|
|
3180
3375
|
return v;
|
|
3181
3376
|
}),
|
|
3182
3377
|
check("package.json", () => {
|
|
3183
|
-
if (!
|
|
3378
|
+
if (!fs18.existsSync("package.json")) throw new Error("not found");
|
|
3184
3379
|
})
|
|
3185
3380
|
];
|
|
3186
3381
|
const failed = checks.filter((c) => !c.ok);
|
|
@@ -3200,140 +3395,6 @@ var init_preflight = __esm({
|
|
|
3200
3395
|
}
|
|
3201
3396
|
});
|
|
3202
3397
|
|
|
3203
|
-
// src/cli/task-resolution.ts
|
|
3204
|
-
import * as fs17 from "fs";
|
|
3205
|
-
import * as path16 from "path";
|
|
3206
|
-
import { execFileSync as execFileSync9 } from "child_process";
|
|
3207
|
-
function findLatestTaskForIssue(issueNumber, projectDir) {
|
|
3208
|
-
const tasksDir = path16.join(projectDir, ".kody", "tasks");
|
|
3209
|
-
if (!fs17.existsSync(tasksDir)) return null;
|
|
3210
|
-
const allDirs = fs17.readdirSync(tasksDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
|
|
3211
|
-
const prefix = `${issueNumber}-`;
|
|
3212
|
-
const direct = allDirs.find((d) => d.startsWith(prefix));
|
|
3213
|
-
if (direct) return direct;
|
|
3214
|
-
try {
|
|
3215
|
-
const branch = execFileSync9("git", ["branch", "--show-current"], {
|
|
3216
|
-
encoding: "utf-8",
|
|
3217
|
-
cwd: projectDir,
|
|
3218
|
-
timeout: 5e3,
|
|
3219
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3220
|
-
}).trim();
|
|
3221
|
-
const branchIssueMatch = branch.match(/^(\d+)-/);
|
|
3222
|
-
if (branchIssueMatch) {
|
|
3223
|
-
const branchIssueNum = branchIssueMatch[1];
|
|
3224
|
-
const branchPrefix = `${branchIssueNum}-`;
|
|
3225
|
-
const fromBranch = allDirs.find((d) => d.startsWith(branchPrefix));
|
|
3226
|
-
if (fromBranch) return fromBranch;
|
|
3227
|
-
}
|
|
3228
|
-
} catch {
|
|
3229
|
-
}
|
|
3230
|
-
return null;
|
|
3231
|
-
}
|
|
3232
|
-
function generateTaskId() {
|
|
3233
|
-
const now = /* @__PURE__ */ new Date();
|
|
3234
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
3235
|
-
return `${String(now.getFullYear()).slice(2)}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
3236
|
-
}
|
|
3237
|
-
var init_task_resolution = __esm({
|
|
3238
|
-
"src/cli/task-resolution.ts"() {
|
|
3239
|
-
"use strict";
|
|
3240
|
-
}
|
|
3241
|
-
});
|
|
3242
|
-
|
|
3243
|
-
// src/review-standalone.ts
|
|
3244
|
-
import * as fs18 from "fs";
|
|
3245
|
-
import * as path17 from "path";
|
|
3246
|
-
function resolveReviewTarget(input) {
|
|
3247
|
-
if (input.prs.length === 0) {
|
|
3248
|
-
return {
|
|
3249
|
-
action: "none",
|
|
3250
|
-
message: `Issue #${input.issueNumber} has no open PRs. Nothing to review.`
|
|
3251
|
-
};
|
|
3252
|
-
}
|
|
3253
|
-
if (input.prs.length === 1) {
|
|
3254
|
-
return { action: "review", prNumber: input.prs[0].number };
|
|
3255
|
-
}
|
|
3256
|
-
const prList = input.prs.map((pr) => ` - #${pr.number}: ${pr.title}`).join("\n");
|
|
3257
|
-
return {
|
|
3258
|
-
action: "pick",
|
|
3259
|
-
prs: input.prs,
|
|
3260
|
-
message: `\u26A0\uFE0F Issue #${input.issueNumber} has ${input.prs.length} open PRs:
|
|
3261
|
-
${prList}
|
|
3262
|
-
|
|
3263
|
-
Run: \`pnpm kody review --pr-number <n>\`
|
|
3264
|
-
Or comment on the specific PR: \`@kody review\``
|
|
3265
|
-
};
|
|
3266
|
-
}
|
|
3267
|
-
async function runStandaloneReview(input) {
|
|
3268
|
-
const taskId = input.taskId ?? `review-${generateTaskId()}`;
|
|
3269
|
-
const taskDir = path17.join(input.projectDir, ".kody", "tasks", taskId);
|
|
3270
|
-
fs18.mkdirSync(taskDir, { recursive: true });
|
|
3271
|
-
const taskContent = `# ${input.prTitle}
|
|
3272
|
-
|
|
3273
|
-
${input.prBody ?? ""}`;
|
|
3274
|
-
fs18.writeFileSync(path17.join(taskDir, "task.md"), taskContent);
|
|
3275
|
-
const reviewDef = STAGES.find((s) => s.name === "review");
|
|
3276
|
-
const ctx = {
|
|
3277
|
-
taskId,
|
|
3278
|
-
taskDir,
|
|
3279
|
-
projectDir: input.projectDir,
|
|
3280
|
-
runners: input.runners,
|
|
3281
|
-
sessions: {},
|
|
3282
|
-
input: {
|
|
3283
|
-
mode: "full",
|
|
3284
|
-
local: input.local
|
|
3285
|
-
}
|
|
3286
|
-
};
|
|
3287
|
-
logger.info(`[review] standalone review for: ${input.prTitle}`);
|
|
3288
|
-
const result = await executeAgentStage(ctx, reviewDef);
|
|
3289
|
-
if (result.outcome !== "completed") {
|
|
3290
|
-
return {
|
|
3291
|
-
outcome: "failed",
|
|
3292
|
-
taskDir,
|
|
3293
|
-
error: result.error ?? "Review stage failed"
|
|
3294
|
-
};
|
|
3295
|
-
}
|
|
3296
|
-
const reviewPath = path17.join(taskDir, "review.md");
|
|
3297
|
-
let reviewContent;
|
|
3298
|
-
if (fs18.existsSync(reviewPath)) {
|
|
3299
|
-
reviewContent = fs18.readFileSync(reviewPath, "utf-8");
|
|
3300
|
-
}
|
|
3301
|
-
return {
|
|
3302
|
-
outcome: "completed",
|
|
3303
|
-
reviewContent,
|
|
3304
|
-
taskDir
|
|
3305
|
-
};
|
|
3306
|
-
}
|
|
3307
|
-
function detectReviewVerdict(reviewContent) {
|
|
3308
|
-
const verdictMatch = reviewContent.match(/##\s*Verdict:\s*(PASS|FAIL)/i);
|
|
3309
|
-
if (verdictMatch) {
|
|
3310
|
-
return verdictMatch[1].toLowerCase();
|
|
3311
|
-
}
|
|
3312
|
-
const hasCritical = /###\s*Critical\s*\n(?!None\.)/i.test(reviewContent);
|
|
3313
|
-
const hasMajor = /###\s*Major\s*\n(?!None\.)/i.test(reviewContent);
|
|
3314
|
-
if (hasCritical || hasMajor) return "fail";
|
|
3315
|
-
return "pass";
|
|
3316
|
-
}
|
|
3317
|
-
function formatReviewComment(reviewContent, taskId) {
|
|
3318
|
-
const verdict = detectReviewVerdict(reviewContent);
|
|
3319
|
-
const cta = verdict === "fail" ? "\n\n> To fix these issues, comment: `@kody fix`\n> The review findings will be used automatically as context." : "";
|
|
3320
|
-
return `## \u{1F50D} Kody Review (\`${taskId}\`)
|
|
3321
|
-
|
|
3322
|
-
${reviewContent}${cta}
|
|
3323
|
-
|
|
3324
|
-
---
|
|
3325
|
-
\u{1F916} Generated by Kody`;
|
|
3326
|
-
}
|
|
3327
|
-
var init_review_standalone = __esm({
|
|
3328
|
-
"src/review-standalone.ts"() {
|
|
3329
|
-
"use strict";
|
|
3330
|
-
init_definitions();
|
|
3331
|
-
init_agent();
|
|
3332
|
-
init_task_resolution();
|
|
3333
|
-
init_logger();
|
|
3334
|
-
}
|
|
3335
|
-
});
|
|
3336
|
-
|
|
3337
3398
|
// src/cli/args.ts
|
|
3338
3399
|
function getArg(args2, flag) {
|
|
3339
3400
|
const idx = args2.indexOf(flag);
|
|
@@ -3354,12 +3415,13 @@ function parseArgs() {
|
|
|
3354
3415
|
kody fix --task-id <id> [--cwd <path>] [--issue-number <n>] [--feedback "<text>"]
|
|
3355
3416
|
kody fix-ci [--pr-number <n>] [--ci-run-id <id>] [--cwd <path>] [--issue-number <n>] [--feedback "<text>"]
|
|
3356
3417
|
kody review [--pr-number <n>] [--issue-number <n>] [--cwd <path>] [--local]
|
|
3418
|
+
kody resolve --pr-number <n> [--cwd <path>] [--local]
|
|
3357
3419
|
kody status --task-id <id> [--cwd <path>]
|
|
3358
3420
|
kody --help`);
|
|
3359
3421
|
process.exit(0);
|
|
3360
3422
|
}
|
|
3361
3423
|
const command2 = args2[0];
|
|
3362
|
-
if (!["run", "rerun", "fix", "fix-ci", "status", "review"].includes(command2)) {
|
|
3424
|
+
if (!["run", "rerun", "fix", "fix-ci", "status", "review", "resolve"].includes(command2)) {
|
|
3363
3425
|
console.error(`Unknown command: ${command2}`);
|
|
3364
3426
|
process.exit(1);
|
|
3365
3427
|
}
|
|
@@ -3437,15 +3499,14 @@ async function checkModelHealth(baseUrl, apiKey, model = "claude-haiku-4-5") {
|
|
|
3437
3499
|
function generateLitellmConfig(provider, modelMap) {
|
|
3438
3500
|
const apiKeyVar = providerApiKeyEnvVar(provider);
|
|
3439
3501
|
const entries = ["model_list:"];
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
if (
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
}
|
|
3502
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3503
|
+
for (const providerModel of Object.values(modelMap)) {
|
|
3504
|
+
if (seen.has(providerModel)) continue;
|
|
3505
|
+
seen.add(providerModel);
|
|
3506
|
+
entries.push(` - model_name: ${providerModel}`);
|
|
3507
|
+
entries.push(` litellm_params:`);
|
|
3508
|
+
entries.push(` model: ${provider}/${providerModel}`);
|
|
3509
|
+
entries.push(` api_key: os.environ/${apiKeyVar}`);
|
|
3449
3510
|
}
|
|
3450
3511
|
return entries.join("\n") + "\n";
|
|
3451
3512
|
}
|
|
@@ -3593,6 +3654,133 @@ var init_task_state = __esm({
|
|
|
3593
3654
|
}
|
|
3594
3655
|
});
|
|
3595
3656
|
|
|
3657
|
+
// src/resolve.ts
|
|
3658
|
+
var resolve_exports = {};
|
|
3659
|
+
__export(resolve_exports, {
|
|
3660
|
+
runResolve: () => runResolve
|
|
3661
|
+
});
|
|
3662
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
3663
|
+
function getConflictContext(cwd, files) {
|
|
3664
|
+
const parts = [];
|
|
3665
|
+
for (const file of files.slice(0, 10)) {
|
|
3666
|
+
try {
|
|
3667
|
+
const content = execFileSync11("git", ["diff", file], {
|
|
3668
|
+
cwd,
|
|
3669
|
+
encoding: "utf-8",
|
|
3670
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3671
|
+
}).trim();
|
|
3672
|
+
parts.push(`### ${file}
|
|
3673
|
+
\`\`\`diff
|
|
3674
|
+
${content.slice(0, 3e3)}
|
|
3675
|
+
\`\`\``);
|
|
3676
|
+
} catch {
|
|
3677
|
+
parts.push(`### ${file}
|
|
3678
|
+
(could not read diff)`);
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
return parts.join("\n\n");
|
|
3682
|
+
}
|
|
3683
|
+
async function runResolve(options) {
|
|
3684
|
+
const { prNumber, projectDir, runners, local } = options;
|
|
3685
|
+
const defaultBranch = getDefaultBranch(projectDir);
|
|
3686
|
+
logger.info(`Resolving PR #${prNumber} \u2014 merging ${defaultBranch}...`);
|
|
3687
|
+
const mergeResult = mergeDefault(projectDir);
|
|
3688
|
+
if (mergeResult === "error") {
|
|
3689
|
+
return { outcome: "failed", error: "Failed to merge default branch" };
|
|
3690
|
+
}
|
|
3691
|
+
if (mergeResult === "clean") {
|
|
3692
|
+
logger.info(" Clean merge \u2014 no conflicts");
|
|
3693
|
+
if (!local) {
|
|
3694
|
+
pushBranch(projectDir);
|
|
3695
|
+
}
|
|
3696
|
+
return { outcome: "merged" };
|
|
3697
|
+
}
|
|
3698
|
+
const conflictedFiles = getConflictedFiles(projectDir);
|
|
3699
|
+
if (conflictedFiles.length === 0) {
|
|
3700
|
+
return { outcome: "failed", error: "Merge reported conflict but no conflicted files found" };
|
|
3701
|
+
}
|
|
3702
|
+
logger.info(` ${conflictedFiles.length} conflicted file(s): ${conflictedFiles.join(", ")}`);
|
|
3703
|
+
const conflictContext = getConflictContext(projectDir, conflictedFiles);
|
|
3704
|
+
const prompt = buildResolvePrompt(conflictedFiles, conflictContext, defaultBranch);
|
|
3705
|
+
const config = getProjectConfig();
|
|
3706
|
+
const runnerName = config.agent.defaultRunner ?? Object.keys(runners)[0] ?? "claude";
|
|
3707
|
+
const runner = runners[runnerName];
|
|
3708
|
+
if (!runner) {
|
|
3709
|
+
return { outcome: "failed", error: `Runner "${runnerName}" not found` };
|
|
3710
|
+
}
|
|
3711
|
+
const model = resolveModel("mid");
|
|
3712
|
+
const extraEnv = {};
|
|
3713
|
+
if (needsLitellmProxy(config)) {
|
|
3714
|
+
extraEnv.ANTHROPIC_BASE_URL = getLitellmUrl();
|
|
3715
|
+
}
|
|
3716
|
+
logger.info(` Running agent to resolve conflicts (model=${model})...`);
|
|
3717
|
+
const result = await runner.run("resolve", prompt, model, 3e5, projectDir, {
|
|
3718
|
+
cwd: projectDir,
|
|
3719
|
+
env: extraEnv
|
|
3720
|
+
});
|
|
3721
|
+
if (result.outcome !== "completed") {
|
|
3722
|
+
return { outcome: "failed", error: `Agent failed: ${result.error}` };
|
|
3723
|
+
}
|
|
3724
|
+
logger.info(" Verifying resolution...");
|
|
3725
|
+
const verify = runQualityGates(projectDir, projectDir);
|
|
3726
|
+
if (!verify.pass) {
|
|
3727
|
+
const errorSummary = verify.errors.slice(0, 5).join("\n");
|
|
3728
|
+
logger.error(` Verification failed:
|
|
3729
|
+
${errorSummary}`);
|
|
3730
|
+
return { outcome: "failed", error: `Conflict resolution failed verification:
|
|
3731
|
+
${errorSummary}` };
|
|
3732
|
+
}
|
|
3733
|
+
logger.info(" Verification passed");
|
|
3734
|
+
commitAll(`chore: resolve merge conflicts with ${defaultBranch}`, projectDir);
|
|
3735
|
+
if (!local) {
|
|
3736
|
+
pushBranch(projectDir);
|
|
3737
|
+
try {
|
|
3738
|
+
const fileList = conflictedFiles.map((f) => `- \`${f}\``).join("\n");
|
|
3739
|
+
postPRComment(
|
|
3740
|
+
prNumber,
|
|
3741
|
+
`\u2705 **Merge conflicts resolved** with \`${defaultBranch}\`
|
|
3742
|
+
|
|
3743
|
+
**Conflicted files:**
|
|
3744
|
+
${fileList}
|
|
3745
|
+
|
|
3746
|
+
_Verification passed. Please review the resolution._`
|
|
3747
|
+
);
|
|
3748
|
+
} catch {
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
return { outcome: "resolved" };
|
|
3752
|
+
}
|
|
3753
|
+
function buildResolvePrompt(files, conflictContext, defaultBranch) {
|
|
3754
|
+
return `You are resolving merge conflicts between a feature branch and the \`${defaultBranch}\` branch.
|
|
3755
|
+
|
|
3756
|
+
## Conflicted files
|
|
3757
|
+
${files.map((f) => `- ${f}`).join("\n")}
|
|
3758
|
+
|
|
3759
|
+
## Conflict diffs
|
|
3760
|
+
${conflictContext}
|
|
3761
|
+
|
|
3762
|
+
## Instructions
|
|
3763
|
+
1. Read each conflicted file
|
|
3764
|
+
2. Resolve the conflict markers (<<<<<<< / ======= / >>>>>>>) by combining both sides correctly:
|
|
3765
|
+
- For feature/business logic: preserve the PR branch's intent
|
|
3766
|
+
- For infrastructure/config/dependencies: prefer the \`${defaultBranch}\` branch
|
|
3767
|
+
- For imports/types: merge both sides
|
|
3768
|
+
3. Write the resolved file using the Edit or Write tool
|
|
3769
|
+
4. Do NOT add new features or refactor \u2014 only resolve the conflicts
|
|
3770
|
+
5. After resolving all files, run \`git add .\` to stage the resolution`;
|
|
3771
|
+
}
|
|
3772
|
+
var init_resolve = __esm({
|
|
3773
|
+
"src/resolve.ts"() {
|
|
3774
|
+
"use strict";
|
|
3775
|
+
init_git_utils();
|
|
3776
|
+
init_github_api();
|
|
3777
|
+
init_verify_runner();
|
|
3778
|
+
init_logger();
|
|
3779
|
+
init_context();
|
|
3780
|
+
init_config();
|
|
3781
|
+
}
|
|
3782
|
+
});
|
|
3783
|
+
|
|
3596
3784
|
// src/entry.ts
|
|
3597
3785
|
var entry_exports = {};
|
|
3598
3786
|
import * as fs21 from "fs";
|
|
@@ -3638,7 +3826,7 @@ async function runModelHealthCheck(config) {
|
|
|
3638
3826
|
logger.warn(`Skipping model health check \u2014 ${keyName} not set`);
|
|
3639
3827
|
return;
|
|
3640
3828
|
}
|
|
3641
|
-
const model =
|
|
3829
|
+
const model = config.agent.modelMap.cheap;
|
|
3642
3830
|
logger.info(`Model health check (${model} via ${usesProxy ? "LiteLLM" : "Anthropic"})...`);
|
|
3643
3831
|
const result = await checkModelHealth(baseUrl, apiKey, model);
|
|
3644
3832
|
if (result.ok) {
|
|
@@ -3771,7 +3959,6 @@ async function main() {
|
|
|
3771
3959
|
console.log(result.reviewContent);
|
|
3772
3960
|
if (!input.local && prNumber) {
|
|
3773
3961
|
const comment = formatReviewComment(result.reviewContent, taskId);
|
|
3774
|
-
postPRComment(prNumber, comment);
|
|
3775
3962
|
const verdict = detectReviewVerdict(result.reviewContent);
|
|
3776
3963
|
if (verdict === "fail") {
|
|
3777
3964
|
submitPRReview(prNumber, comment, "request-changes");
|
|
@@ -3782,6 +3969,42 @@ async function main() {
|
|
|
3782
3969
|
}
|
|
3783
3970
|
process.exit(0);
|
|
3784
3971
|
}
|
|
3972
|
+
if (input.command === "resolve") {
|
|
3973
|
+
if (!input.prNumber) {
|
|
3974
|
+
console.error("--pr-number is required for resolve command");
|
|
3975
|
+
process.exit(1);
|
|
3976
|
+
}
|
|
3977
|
+
runPreflight();
|
|
3978
|
+
const config2 = getProjectConfig();
|
|
3979
|
+
const litellmProcess2 = await ensureLitellmProxy(config2, projectDir);
|
|
3980
|
+
await runModelHealthCheck(config2);
|
|
3981
|
+
const runners2 = createRunners(config2);
|
|
3982
|
+
const defaultRunnerName2 = config2.agent.defaultRunner ?? Object.keys(runners2)[0] ?? "claude";
|
|
3983
|
+
const defaultRunner2 = runners2[defaultRunnerName2];
|
|
3984
|
+
if (!defaultRunner2) {
|
|
3985
|
+
console.error(`Default runner "${defaultRunnerName2}" not configured`);
|
|
3986
|
+
process.exit(1);
|
|
3987
|
+
}
|
|
3988
|
+
const healthy2 = await defaultRunner2.healthCheck();
|
|
3989
|
+
if (!healthy2) {
|
|
3990
|
+
console.error(`Runner "${defaultRunnerName2}" health check failed`);
|
|
3991
|
+
process.exit(1);
|
|
3992
|
+
}
|
|
3993
|
+
const { runResolve: runResolve2 } = await Promise.resolve().then(() => (init_resolve(), resolve_exports));
|
|
3994
|
+
const result = await runResolve2({
|
|
3995
|
+
prNumber: input.prNumber,
|
|
3996
|
+
projectDir,
|
|
3997
|
+
runners: runners2,
|
|
3998
|
+
local: input.local ?? true
|
|
3999
|
+
});
|
|
4000
|
+
if (litellmProcess2) litellmProcess2.kill?.();
|
|
4001
|
+
if (result.outcome === "failed") {
|
|
4002
|
+
console.error(`Resolve failed: ${result.error}`);
|
|
4003
|
+
process.exit(1);
|
|
4004
|
+
}
|
|
4005
|
+
console.log(`Resolve: ${result.outcome}`);
|
|
4006
|
+
process.exit(0);
|
|
4007
|
+
}
|
|
3785
4008
|
logger.info("Preflight checks:");
|
|
3786
4009
|
runPreflight();
|
|
3787
4010
|
if (input.task) {
|
|
@@ -4001,7 +4224,7 @@ var init_entry = __esm({
|
|
|
4001
4224
|
// src/bin/cli.ts
|
|
4002
4225
|
import * as fs22 from "fs";
|
|
4003
4226
|
import * as path21 from "path";
|
|
4004
|
-
import { execFileSync as
|
|
4227
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
4005
4228
|
import { fileURLToPath } from "url";
|
|
4006
4229
|
var __dirname = path21.dirname(fileURLToPath(import.meta.url));
|
|
4007
4230
|
var PKG_ROOT = path21.resolve(__dirname, "..", "..");
|
|
@@ -4012,7 +4235,7 @@ function getVersion() {
|
|
|
4012
4235
|
}
|
|
4013
4236
|
function checkCommand2(name, args2, fix) {
|
|
4014
4237
|
try {
|
|
4015
|
-
const output =
|
|
4238
|
+
const output = execFileSync12(name, args2, {
|
|
4016
4239
|
encoding: "utf-8",
|
|
4017
4240
|
timeout: 1e4,
|
|
4018
4241
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4030,7 +4253,7 @@ function checkFile(filePath, description, fix) {
|
|
|
4030
4253
|
}
|
|
4031
4254
|
function checkGhAuth(cwd) {
|
|
4032
4255
|
try {
|
|
4033
|
-
const output =
|
|
4256
|
+
const output = execFileSync12("gh", ["auth", "status"], {
|
|
4034
4257
|
encoding: "utf-8",
|
|
4035
4258
|
timeout: 1e4,
|
|
4036
4259
|
cwd,
|
|
@@ -4048,7 +4271,7 @@ function checkGhAuth(cwd) {
|
|
|
4048
4271
|
}
|
|
4049
4272
|
function checkGhRepoAccess(cwd) {
|
|
4050
4273
|
try {
|
|
4051
|
-
const remote =
|
|
4274
|
+
const remote = execFileSync12("git", ["remote", "get-url", "origin"], {
|
|
4052
4275
|
encoding: "utf-8",
|
|
4053
4276
|
timeout: 5e3,
|
|
4054
4277
|
cwd,
|
|
@@ -4059,7 +4282,7 @@ function checkGhRepoAccess(cwd) {
|
|
|
4059
4282
|
return { name: "GitHub repo", ok: false, fix: "Set git remote origin to a GitHub URL" };
|
|
4060
4283
|
}
|
|
4061
4284
|
const repoSlug = `${match[1]}/${match[2]}`;
|
|
4062
|
-
|
|
4285
|
+
execFileSync12("gh", ["repo", "view", repoSlug, "--json", "name"], {
|
|
4063
4286
|
encoding: "utf-8",
|
|
4064
4287
|
timeout: 1e4,
|
|
4065
4288
|
cwd,
|
|
@@ -4072,7 +4295,7 @@ function checkGhRepoAccess(cwd) {
|
|
|
4072
4295
|
}
|
|
4073
4296
|
function checkGhSecret(repoSlug, secretName) {
|
|
4074
4297
|
try {
|
|
4075
|
-
const output =
|
|
4298
|
+
const output = execFileSync12("gh", ["secret", "list", "--repo", repoSlug], {
|
|
4076
4299
|
encoding: "utf-8",
|
|
4077
4300
|
timeout: 1e4,
|
|
4078
4301
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4100,7 +4323,7 @@ function detectBasicConfig(cwd) {
|
|
|
4100
4323
|
else if (!fs22.existsSync(path21.join(cwd, "pnpm-lock.yaml")) && fs22.existsSync(path21.join(cwd, "package-lock.json"))) pm = "npm";
|
|
4101
4324
|
let defaultBranch = "main";
|
|
4102
4325
|
try {
|
|
4103
|
-
const ref =
|
|
4326
|
+
const ref = execFileSync12("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
4104
4327
|
encoding: "utf-8",
|
|
4105
4328
|
timeout: 5e3,
|
|
4106
4329
|
cwd,
|
|
@@ -4109,7 +4332,7 @@ function detectBasicConfig(cwd) {
|
|
|
4109
4332
|
defaultBranch = ref.replace("refs/remotes/origin/", "");
|
|
4110
4333
|
} catch {
|
|
4111
4334
|
try {
|
|
4112
|
-
|
|
4335
|
+
execFileSync12("git", ["rev-parse", "--verify", "origin/dev"], {
|
|
4113
4336
|
encoding: "utf-8",
|
|
4114
4337
|
timeout: 5e3,
|
|
4115
4338
|
cwd,
|
|
@@ -4122,7 +4345,7 @@ function detectBasicConfig(cwd) {
|
|
|
4122
4345
|
let owner = "";
|
|
4123
4346
|
let repo = "";
|
|
4124
4347
|
try {
|
|
4125
|
-
const remote =
|
|
4348
|
+
const remote = execFileSync12("git", ["remote", "get-url", "origin"], {
|
|
4126
4349
|
encoding: "utf-8",
|
|
4127
4350
|
timeout: 5e3,
|
|
4128
4351
|
cwd,
|
|
@@ -4314,7 +4537,7 @@ function initCommand(opts) {
|
|
|
4314
4537
|
if (filesToCommit.length > 0) {
|
|
4315
4538
|
try {
|
|
4316
4539
|
const fullPaths = filesToCommit.map((f) => path21.join(cwd, f));
|
|
4317
|
-
|
|
4540
|
+
execFileSync12("npx", ["prettier", "--write", ...fullPaths], {
|
|
4318
4541
|
cwd,
|
|
4319
4542
|
encoding: "utf-8",
|
|
4320
4543
|
timeout: 3e4,
|
|
@@ -4325,13 +4548,13 @@ function initCommand(opts) {
|
|
|
4325
4548
|
}
|
|
4326
4549
|
if (filesToCommit.length > 0) {
|
|
4327
4550
|
try {
|
|
4328
|
-
|
|
4329
|
-
const staged =
|
|
4551
|
+
execFileSync12("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
|
|
4552
|
+
const staged = execFileSync12("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
|
|
4330
4553
|
if (staged) {
|
|
4331
|
-
|
|
4554
|
+
execFileSync12("git", ["commit", "-m", "chore: Add Kody Engine workflow and config\n\nAdd GitHub Actions workflow and auto-detected configuration for Kody Engine Lite."], { cwd, stdio: "pipe" });
|
|
4332
4555
|
console.log(` \u2713 Committed: ${filesToCommit.join(", ")}`);
|
|
4333
4556
|
try {
|
|
4334
|
-
|
|
4557
|
+
execFileSync12("git", ["push"], { cwd, stdio: "pipe", timeout: 6e4 });
|
|
4335
4558
|
console.log(" \u2713 Pushed to origin");
|
|
4336
4559
|
} catch {
|
|
4337
4560
|
console.log(" \u25CB Push failed \u2014 run 'git push' manually");
|
|
@@ -4425,7 +4648,7 @@ function ghComment(issueNumber, body, cwd) {
|
|
|
4425
4648
|
} catch {
|
|
4426
4649
|
}
|
|
4427
4650
|
if (!repoSlug) return;
|
|
4428
|
-
|
|
4651
|
+
execFileSync12("gh", [
|
|
4429
4652
|
"issue",
|
|
4430
4653
|
"comment",
|
|
4431
4654
|
String(issueNumber),
|
|
@@ -4558,7 +4781,7 @@ Output ONLY valid JSON. No markdown fences. No explanation.
|
|
|
4558
4781
|
${repoContext}`;
|
|
4559
4782
|
console.log(" \u23F3 Analyzing project...");
|
|
4560
4783
|
try {
|
|
4561
|
-
const output =
|
|
4784
|
+
const output = execFileSync12("claude", [
|
|
4562
4785
|
"--print",
|
|
4563
4786
|
"--model",
|
|
4564
4787
|
"haiku",
|
|
@@ -4661,7 +4884,7 @@ ${repoContext}
|
|
|
4661
4884
|
|
|
4662
4885
|
REMINDER: Output the full prompt template first (unchanged), then your three appended sections. Do NOT include "${contextPlaceholder}".`;
|
|
4663
4886
|
try {
|
|
4664
|
-
const output =
|
|
4887
|
+
const output = execFileSync12("claude", [
|
|
4665
4888
|
"--print",
|
|
4666
4889
|
"--model",
|
|
4667
4890
|
"haiku",
|
|
@@ -4718,7 +4941,7 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
4718
4941
|
];
|
|
4719
4942
|
for (const label of labels) {
|
|
4720
4943
|
try {
|
|
4721
|
-
|
|
4944
|
+
execFileSync12("gh", [
|
|
4722
4945
|
"label",
|
|
4723
4946
|
"create",
|
|
4724
4947
|
label.name,
|
|
@@ -4738,7 +4961,7 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
4738
4961
|
console.log(` \u2713 ${label.name}`);
|
|
4739
4962
|
} catch {
|
|
4740
4963
|
try {
|
|
4741
|
-
|
|
4964
|
+
execFileSync12("gh", ["label", "list", "--repo", repoSlug, "--search", label.name], {
|
|
4742
4965
|
cwd,
|
|
4743
4966
|
encoding: "utf-8",
|
|
4744
4967
|
timeout: 1e4,
|
|
@@ -4777,7 +5000,7 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
4777
5000
|
try {
|
|
4778
5001
|
const fullPaths = filesToCommit.map((f) => path21.join(cwd, f));
|
|
4779
5002
|
for (let pass = 0; pass < 2; pass++) {
|
|
4780
|
-
|
|
5003
|
+
execFileSync12("npx", ["prettier", "--write", ...fullPaths], {
|
|
4781
5004
|
cwd,
|
|
4782
5005
|
encoding: "utf-8",
|
|
4783
5006
|
timeout: 3e4,
|
|
@@ -4793,12 +5016,12 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
4793
5016
|
try {
|
|
4794
5017
|
if (isCI3) {
|
|
4795
5018
|
const branchName = `kody-bootstrap-${Date.now()}`;
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
const staged =
|
|
5019
|
+
execFileSync12("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
|
|
5020
|
+
execFileSync12("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
|
|
5021
|
+
const staged = execFileSync12("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
|
|
4799
5022
|
if (staged) {
|
|
4800
|
-
|
|
4801
|
-
|
|
5023
|
+
execFileSync12("git", ["commit", "-m", "chore: Add Kody project memory and step files\n\nBootstrap Kody Engine with project-specific architecture, conventions, and pipeline step files."], { cwd, stdio: "pipe" });
|
|
5024
|
+
execFileSync12("git", ["push", "-u", "origin", branchName], { cwd, stdio: "pipe", timeout: 6e4 });
|
|
4802
5025
|
console.log(` \u2713 Pushed branch: ${branchName}`);
|
|
4803
5026
|
let baseBranch = "main";
|
|
4804
5027
|
try {
|
|
@@ -4810,7 +5033,7 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
4810
5033
|
} catch {
|
|
4811
5034
|
}
|
|
4812
5035
|
try {
|
|
4813
|
-
const prUrl =
|
|
5036
|
+
const prUrl = execFileSync12("gh", [
|
|
4814
5037
|
"pr",
|
|
4815
5038
|
"create",
|
|
4816
5039
|
"--title",
|
|
@@ -4849,13 +5072,13 @@ Create it manually.`, cwd);
|
|
|
4849
5072
|
console.log(" \u25CB No new changes to commit");
|
|
4850
5073
|
}
|
|
4851
5074
|
} else {
|
|
4852
|
-
|
|
4853
|
-
const staged =
|
|
5075
|
+
execFileSync12("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
|
|
5076
|
+
const staged = execFileSync12("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
|
|
4854
5077
|
if (staged) {
|
|
4855
|
-
|
|
5078
|
+
execFileSync12("git", ["commit", "-m", "chore: Add Kody project memory and step files\n\nBootstrap Kody Engine with project-specific architecture, conventions, and pipeline step files."], { cwd, stdio: "pipe" });
|
|
4856
5079
|
console.log(` \u2713 Committed: ${filesToCommit.join(", ")}`);
|
|
4857
5080
|
try {
|
|
4858
|
-
|
|
5081
|
+
execFileSync12("git", ["push"], { cwd, stdio: "pipe", timeout: 6e4 });
|
|
4859
5082
|
console.log(" \u2713 Pushed to origin");
|
|
4860
5083
|
} catch {
|
|
4861
5084
|
console.log(" \u25CB Push failed \u2014 run 'git push' manually");
|
|
@@ -4976,7 +5199,7 @@ function installSkillsForProject(cwd) {
|
|
|
4976
5199
|
}
|
|
4977
5200
|
try {
|
|
4978
5201
|
console.log(` Installing: ${skill.label} (${skill.package})`);
|
|
4979
|
-
|
|
5202
|
+
execFileSync12("npx", ["skills", "add", skill.package, "--yes"], {
|
|
4980
5203
|
cwd,
|
|
4981
5204
|
encoding: "utf-8",
|
|
4982
5205
|
timeout: 6e4,
|