@kody-ade/kody-engine-lite 0.1.81 → 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 +295 -275
- package/package.json +1 -1
package/dist/bin/cli.js
CHANGED
|
@@ -137,7 +137,7 @@ var init_agent_runner = __esm({
|
|
|
137
137
|
"src/agent-runner.ts"() {
|
|
138
138
|
"use strict";
|
|
139
139
|
SIGKILL_GRACE_MS = 5e3;
|
|
140
|
-
STDERR_TAIL_CHARS =
|
|
140
|
+
STDERR_TAIL_CHARS = 2e3;
|
|
141
141
|
RUNNER_FACTORIES = {
|
|
142
142
|
"claude-code": createClaudeCodeRunner
|
|
143
143
|
};
|
|
@@ -282,7 +282,11 @@ function getProjectConfig() {
|
|
|
282
282
|
quality: { ...DEFAULT_CONFIG.quality, ...raw.quality },
|
|
283
283
|
git: { ...DEFAULT_CONFIG.git, ...raw.git },
|
|
284
284
|
github: { ...DEFAULT_CONFIG.github, ...raw.github },
|
|
285
|
-
agent: {
|
|
285
|
+
agent: {
|
|
286
|
+
...DEFAULT_CONFIG.agent,
|
|
287
|
+
...raw.agent,
|
|
288
|
+
modelMap: { ...DEFAULT_CONFIG.agent.modelMap, ...raw.agent?.modelMap }
|
|
289
|
+
},
|
|
286
290
|
contextTiers: raw.contextTiers ? { ...DEFAULT_CONFIG.contextTiers, ...raw.contextTiers } : DEFAULT_CONFIG.contextTiers,
|
|
287
291
|
mcp: raw.mcp ? { enabled: false, servers: {}, stages: ["build", "verify", "review", "review-fix"], ...raw.mcp } : void 0
|
|
288
292
|
};
|
|
@@ -1868,8 +1872,8 @@ async function diagnoseFailure(stageName, errorOutput, modifiedFiles, runner, mo
|
|
|
1868
1872
|
`Stage: ${stageName}`,
|
|
1869
1873
|
``,
|
|
1870
1874
|
`Error output:`,
|
|
1871
|
-
errorOutput.slice(-
|
|
1872
|
-
// Last
|
|
1875
|
+
errorOutput.slice(-5e3),
|
|
1876
|
+
// Last 5000 chars of error for accurate diagnosis
|
|
1873
1877
|
``,
|
|
1874
1878
|
modifiedFiles.length > 0 ? `Files modified by build stage:
|
|
1875
1879
|
${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (build may not have run yet)."
|
|
@@ -1916,13 +1920,21 @@ ${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (bui
|
|
|
1916
1920
|
}
|
|
1917
1921
|
function getModifiedFiles(projectDir) {
|
|
1918
1922
|
try {
|
|
1919
|
-
const
|
|
1923
|
+
const staged = execFileSync5("git", ["diff", "--name-only", "--cached"], {
|
|
1920
1924
|
encoding: "utf-8",
|
|
1921
1925
|
cwd: projectDir,
|
|
1922
1926
|
timeout: 5e3,
|
|
1923
1927
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1924
1928
|
}).trim();
|
|
1925
|
-
|
|
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)];
|
|
1926
1938
|
} catch {
|
|
1927
1939
|
return [];
|
|
1928
1940
|
}
|
|
@@ -2122,55 +2134,198 @@ var init_verify = __esm({
|
|
|
2122
2134
|
}
|
|
2123
2135
|
});
|
|
2124
2136
|
|
|
2125
|
-
// src/
|
|
2137
|
+
// src/cli/task-resolution.ts
|
|
2126
2138
|
import * as fs9 from "fs";
|
|
2127
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";
|
|
2128
2274
|
async function executeReviewWithFix(ctx, def) {
|
|
2129
2275
|
if (ctx.input.dryRun) {
|
|
2130
2276
|
return { outcome: "completed", retries: 0 };
|
|
2131
2277
|
}
|
|
2132
2278
|
const reviewDef = STAGES.find((s) => s.name === "review");
|
|
2133
2279
|
const reviewFixDef = STAGES.find((s) => s.name === "review-fix");
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
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
|
+
}
|
|
2151
2304
|
}
|
|
2152
|
-
|
|
2153
|
-
return executeAgentStage(ctx, reviewDef);
|
|
2305
|
+
return { outcome: "failed", retries: MAX_REVIEW_FIX_ITERATIONS, error: "Unexpected review-fix loop exit" };
|
|
2154
2306
|
}
|
|
2307
|
+
var MAX_REVIEW_FIX_ITERATIONS;
|
|
2155
2308
|
var init_review = __esm({
|
|
2156
2309
|
"src/stages/review.ts"() {
|
|
2157
2310
|
"use strict";
|
|
2158
2311
|
init_definitions();
|
|
2159
2312
|
init_logger();
|
|
2160
2313
|
init_agent();
|
|
2314
|
+
init_review_standalone();
|
|
2315
|
+
MAX_REVIEW_FIX_ITERATIONS = 2;
|
|
2161
2316
|
}
|
|
2162
2317
|
});
|
|
2163
2318
|
|
|
2164
2319
|
// src/stages/ship.ts
|
|
2165
|
-
import * as
|
|
2166
|
-
import * as
|
|
2167
|
-
import { execFileSync as
|
|
2320
|
+
import * as fs12 from "fs";
|
|
2321
|
+
import * as path12 from "path";
|
|
2322
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
2168
2323
|
function buildPrBody(ctx) {
|
|
2169
2324
|
const sections = [];
|
|
2170
|
-
const taskJsonPath =
|
|
2171
|
-
if (
|
|
2325
|
+
const taskJsonPath = path12.join(ctx.taskDir, "task.json");
|
|
2326
|
+
if (fs12.existsSync(taskJsonPath)) {
|
|
2172
2327
|
try {
|
|
2173
|
-
const raw =
|
|
2328
|
+
const raw = fs12.readFileSync(taskJsonPath, "utf-8");
|
|
2174
2329
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
2175
2330
|
const task = JSON.parse(cleaned);
|
|
2176
2331
|
if (task.description) {
|
|
@@ -2189,9 +2344,9 @@ ${task.scope.map((s) => `- \`${s}\``).join("\n")}`);
|
|
|
2189
2344
|
} catch {
|
|
2190
2345
|
}
|
|
2191
2346
|
}
|
|
2192
|
-
const reviewPath =
|
|
2193
|
-
if (
|
|
2194
|
-
const review =
|
|
2347
|
+
const reviewPath = path12.join(ctx.taskDir, "review.md");
|
|
2348
|
+
if (fs12.existsSync(reviewPath)) {
|
|
2349
|
+
const review = fs12.readFileSync(reviewPath, "utf-8");
|
|
2195
2350
|
const summaryMatch = review.match(/## Summary\s*\n([\s\S]*?)(?=\n## |\n*$)/);
|
|
2196
2351
|
if (summaryMatch) {
|
|
2197
2352
|
const summary = summaryMatch[1].trim();
|
|
@@ -2208,14 +2363,14 @@ ${summary}`);
|
|
|
2208
2363
|
**Review:** ${verdictMatch[1].toUpperCase() === "PASS" ? "\u2705 PASS" : "\u274C FAIL"}`);
|
|
2209
2364
|
}
|
|
2210
2365
|
}
|
|
2211
|
-
const verifyPath =
|
|
2212
|
-
if (
|
|
2213
|
-
const verify =
|
|
2366
|
+
const verifyPath = path12.join(ctx.taskDir, "verify.md");
|
|
2367
|
+
if (fs12.existsSync(verifyPath)) {
|
|
2368
|
+
const verify = fs12.readFileSync(verifyPath, "utf-8");
|
|
2214
2369
|
if (/PASS/i.test(verify)) sections.push(`**Verify:** \u2705 typecheck + tests + lint passed`);
|
|
2215
2370
|
}
|
|
2216
|
-
const planPath =
|
|
2217
|
-
if (
|
|
2218
|
-
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();
|
|
2219
2374
|
if (plan) {
|
|
2220
2375
|
const truncated = plan.length > 800 ? plan.slice(0, 800) + "\n..." : plan;
|
|
2221
2376
|
sections.push(`
|
|
@@ -2235,25 +2390,25 @@ Closes #${ctx.input.issueNumber}`);
|
|
|
2235
2390
|
return sections.join("\n");
|
|
2236
2391
|
}
|
|
2237
2392
|
function executeShipStage(ctx, _def) {
|
|
2238
|
-
const shipPath =
|
|
2393
|
+
const shipPath = path12.join(ctx.taskDir, "ship.md");
|
|
2239
2394
|
if (ctx.input.dryRun) {
|
|
2240
|
-
|
|
2395
|
+
fs12.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
|
|
2241
2396
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
2242
2397
|
}
|
|
2243
2398
|
if (ctx.input.local && !ctx.input.issueNumber) {
|
|
2244
|
-
|
|
2399
|
+
fs12.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
|
|
2245
2400
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
2246
2401
|
}
|
|
2247
2402
|
try {
|
|
2248
2403
|
const head = getCurrentBranch(ctx.projectDir);
|
|
2249
2404
|
const base = getDefaultBranch(ctx.projectDir);
|
|
2250
2405
|
try {
|
|
2251
|
-
|
|
2406
|
+
execFileSync8("git", ["add", ctx.taskDir], {
|
|
2252
2407
|
cwd: ctx.projectDir,
|
|
2253
2408
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
2254
2409
|
stdio: "pipe"
|
|
2255
2410
|
});
|
|
2256
|
-
|
|
2411
|
+
execFileSync8("git", ["commit", "--no-gpg-sign", "-m", `chore: add kody task artifacts [skip ci]`], {
|
|
2257
2412
|
cwd: ctx.projectDir,
|
|
2258
2413
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
2259
2414
|
stdio: "pipe"
|
|
@@ -2267,7 +2422,7 @@ function executeShipStage(ctx, _def) {
|
|
|
2267
2422
|
let repo = config.github?.repo;
|
|
2268
2423
|
if (!owner || !repo) {
|
|
2269
2424
|
try {
|
|
2270
|
-
const remoteUrl =
|
|
2425
|
+
const remoteUrl = execFileSync8("git", ["remote", "get-url", "origin"], {
|
|
2271
2426
|
encoding: "utf-8",
|
|
2272
2427
|
cwd: ctx.projectDir
|
|
2273
2428
|
}).trim();
|
|
@@ -2287,10 +2442,10 @@ function executeShipStage(ctx, _def) {
|
|
|
2287
2442
|
docs: "docs",
|
|
2288
2443
|
chore: "chore"
|
|
2289
2444
|
};
|
|
2290
|
-
const taskJsonPath =
|
|
2291
|
-
if (
|
|
2445
|
+
const taskJsonPath = path12.join(ctx.taskDir, "task.json");
|
|
2446
|
+
if (fs12.existsSync(taskJsonPath)) {
|
|
2292
2447
|
try {
|
|
2293
|
-
const raw =
|
|
2448
|
+
const raw = fs12.readFileSync(taskJsonPath, "utf-8");
|
|
2294
2449
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
2295
2450
|
const task = JSON.parse(cleaned);
|
|
2296
2451
|
const prefix = TYPE_PREFIX[task.task_type] ?? "chore";
|
|
@@ -2300,9 +2455,9 @@ function executeShipStage(ctx, _def) {
|
|
|
2300
2455
|
}
|
|
2301
2456
|
}
|
|
2302
2457
|
if (title === "Update") {
|
|
2303
|
-
const taskMdPath =
|
|
2304
|
-
if (
|
|
2305
|
-
const content =
|
|
2458
|
+
const taskMdPath = path12.join(ctx.taskDir, "task.md");
|
|
2459
|
+
if (fs12.existsSync(taskMdPath)) {
|
|
2460
|
+
const content = fs12.readFileSync(taskMdPath, "utf-8");
|
|
2306
2461
|
const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#") && !l.startsWith("*"));
|
|
2307
2462
|
if (firstLine) title = `chore: ${firstLine.trim()}`.slice(0, 72);
|
|
2308
2463
|
}
|
|
@@ -2322,7 +2477,7 @@ function executeShipStage(ctx, _def) {
|
|
|
2322
2477
|
} catch {
|
|
2323
2478
|
}
|
|
2324
2479
|
}
|
|
2325
|
-
|
|
2480
|
+
fs12.writeFileSync(shipPath, `# Ship
|
|
2326
2481
|
|
|
2327
2482
|
Updated existing PR: ${existingPr.url}
|
|
2328
2483
|
PR #${existingPr.number}
|
|
@@ -2343,19 +2498,19 @@ PR #${existingPr.number}
|
|
|
2343
2498
|
} catch {
|
|
2344
2499
|
}
|
|
2345
2500
|
}
|
|
2346
|
-
|
|
2501
|
+
fs12.writeFileSync(shipPath, `# Ship
|
|
2347
2502
|
|
|
2348
2503
|
PR created: ${pr.url}
|
|
2349
2504
|
PR #${pr.number}
|
|
2350
2505
|
`);
|
|
2351
2506
|
} else {
|
|
2352
|
-
|
|
2507
|
+
fs12.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
|
|
2353
2508
|
}
|
|
2354
2509
|
}
|
|
2355
2510
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
2356
2511
|
} catch (err) {
|
|
2357
2512
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2358
|
-
|
|
2513
|
+
fs12.writeFileSync(shipPath, `# Ship
|
|
2359
2514
|
|
|
2360
2515
|
Failed: ${msg}
|
|
2361
2516
|
`);
|
|
@@ -2401,15 +2556,15 @@ var init_executor_registry = __esm({
|
|
|
2401
2556
|
});
|
|
2402
2557
|
|
|
2403
2558
|
// src/pipeline/questions.ts
|
|
2404
|
-
import * as
|
|
2405
|
-
import * as
|
|
2559
|
+
import * as fs13 from "fs";
|
|
2560
|
+
import * as path13 from "path";
|
|
2406
2561
|
function checkForQuestions(ctx, stageName) {
|
|
2407
2562
|
if (ctx.input.local || !ctx.input.issueNumber) return false;
|
|
2408
2563
|
try {
|
|
2409
2564
|
if (stageName === "taskify") {
|
|
2410
|
-
const taskJsonPath =
|
|
2411
|
-
if (!
|
|
2412
|
-
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");
|
|
2413
2568
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
2414
2569
|
const taskJson = JSON.parse(cleaned);
|
|
2415
2570
|
if (taskJson.questions && Array.isArray(taskJson.questions) && taskJson.questions.length > 0) {
|
|
@@ -2424,9 +2579,9 @@ Reply with \`@kody approve\` and your answers in the comment body.`;
|
|
|
2424
2579
|
}
|
|
2425
2580
|
}
|
|
2426
2581
|
if (stageName === "plan") {
|
|
2427
|
-
const planPath =
|
|
2428
|
-
if (!
|
|
2429
|
-
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");
|
|
2430
2585
|
const questionsMatch = plan.match(/## Questions\s*\n([\s\S]*?)(?=\n## |\n*$)/);
|
|
2431
2586
|
if (questionsMatch) {
|
|
2432
2587
|
const questionsText = questionsMatch[1].trim();
|
|
@@ -2455,8 +2610,8 @@ var init_questions = __esm({
|
|
|
2455
2610
|
});
|
|
2456
2611
|
|
|
2457
2612
|
// src/pipeline/hooks.ts
|
|
2458
|
-
import * as
|
|
2459
|
-
import * as
|
|
2613
|
+
import * as fs14 from "fs";
|
|
2614
|
+
import * as path14 from "path";
|
|
2460
2615
|
function applyPreStageLabel(ctx, def) {
|
|
2461
2616
|
if (!ctx.input.issueNumber || ctx.input.local) return;
|
|
2462
2617
|
if (def.name === "build") setLifecycleLabel(ctx.input.issueNumber, "building");
|
|
@@ -2482,9 +2637,9 @@ function autoDetectComplexity(ctx, def) {
|
|
|
2482
2637
|
if (def.name !== "taskify") return null;
|
|
2483
2638
|
if (ctx.input.complexity) return null;
|
|
2484
2639
|
try {
|
|
2485
|
-
const taskJsonPath =
|
|
2486
|
-
if (!
|
|
2487
|
-
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");
|
|
2488
2643
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
2489
2644
|
const taskJson = JSON.parse(cleaned);
|
|
2490
2645
|
if (!taskJson.risk_level || !isValidComplexity(taskJson.risk_level)) return null;
|
|
@@ -2514,8 +2669,8 @@ function checkRiskGate(ctx, def, state, complexity) {
|
|
|
2514
2669
|
if (ctx.input.dryRun || ctx.input.local) return null;
|
|
2515
2670
|
if (ctx.input.mode === "rerun") return null;
|
|
2516
2671
|
if (!ctx.input.issueNumber) return null;
|
|
2517
|
-
const planPath =
|
|
2518
|
-
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)";
|
|
2519
2674
|
try {
|
|
2520
2675
|
postComment(
|
|
2521
2676
|
ctx.input.issueNumber,
|
|
@@ -2582,22 +2737,22 @@ var init_hooks = __esm({
|
|
|
2582
2737
|
});
|
|
2583
2738
|
|
|
2584
2739
|
// src/learning/auto-learn.ts
|
|
2585
|
-
import * as
|
|
2586
|
-
import * as
|
|
2740
|
+
import * as fs15 from "fs";
|
|
2741
|
+
import * as path15 from "path";
|
|
2587
2742
|
function stripAnsi(str) {
|
|
2588
2743
|
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2589
2744
|
}
|
|
2590
2745
|
function autoLearn(ctx) {
|
|
2591
2746
|
try {
|
|
2592
|
-
const memoryDir =
|
|
2593
|
-
if (!
|
|
2594
|
-
|
|
2747
|
+
const memoryDir = path15.join(ctx.projectDir, ".kody", "memory");
|
|
2748
|
+
if (!fs15.existsSync(memoryDir)) {
|
|
2749
|
+
fs15.mkdirSync(memoryDir, { recursive: true });
|
|
2595
2750
|
}
|
|
2596
2751
|
const learnings = [];
|
|
2597
2752
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2598
|
-
const verifyPath =
|
|
2599
|
-
if (
|
|
2600
|
-
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"));
|
|
2601
2756
|
if (/vitest/i.test(verify)) learnings.push("- Uses vitest for testing");
|
|
2602
2757
|
if (/jest/i.test(verify)) learnings.push("- Uses jest for testing");
|
|
2603
2758
|
if (/eslint/i.test(verify)) learnings.push("- Uses eslint for linting");
|
|
@@ -2606,18 +2761,18 @@ function autoLearn(ctx) {
|
|
|
2606
2761
|
if (/jsdom/i.test(verify)) learnings.push("- Test environment: jsdom");
|
|
2607
2762
|
if (/node/i.test(verify) && /environment/i.test(verify)) learnings.push("- Test environment: node");
|
|
2608
2763
|
}
|
|
2609
|
-
const reviewPath =
|
|
2610
|
-
if (
|
|
2611
|
-
const review =
|
|
2764
|
+
const reviewPath = path15.join(ctx.taskDir, "review.md");
|
|
2765
|
+
if (fs15.existsSync(reviewPath)) {
|
|
2766
|
+
const review = fs15.readFileSync(reviewPath, "utf-8");
|
|
2612
2767
|
if (/\.js extension/i.test(review)) learnings.push("- Imports use .js extensions (ESM)");
|
|
2613
2768
|
if (/barrel export/i.test(review)) learnings.push("- Uses barrel exports (index.ts)");
|
|
2614
2769
|
if (/timezone/i.test(review)) learnings.push("- Timezone handling is a concern in this codebase");
|
|
2615
2770
|
if (/UTC/i.test(review)) learnings.push("- Date operations should consider UTC vs local time");
|
|
2616
2771
|
}
|
|
2617
|
-
const taskJsonPath =
|
|
2618
|
-
if (
|
|
2772
|
+
const taskJsonPath = path15.join(ctx.taskDir, "task.json");
|
|
2773
|
+
if (fs15.existsSync(taskJsonPath)) {
|
|
2619
2774
|
try {
|
|
2620
|
-
const raw = stripAnsi(
|
|
2775
|
+
const raw = stripAnsi(fs15.readFileSync(taskJsonPath, "utf-8"));
|
|
2621
2776
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
2622
2777
|
const task = JSON.parse(cleaned);
|
|
2623
2778
|
if (task.scope && Array.isArray(task.scope)) {
|
|
@@ -2628,13 +2783,13 @@ function autoLearn(ctx) {
|
|
|
2628
2783
|
}
|
|
2629
2784
|
}
|
|
2630
2785
|
if (learnings.length > 0) {
|
|
2631
|
-
const conventionsPath =
|
|
2786
|
+
const conventionsPath = path15.join(memoryDir, "conventions.md");
|
|
2632
2787
|
const entry = `
|
|
2633
2788
|
## Learned ${timestamp2} (task: ${ctx.taskId})
|
|
2634
2789
|
${learnings.join("\n")}
|
|
2635
2790
|
`;
|
|
2636
|
-
|
|
2637
|
-
invalidateCache(conventionsPath,
|
|
2791
|
+
fs15.appendFileSync(conventionsPath, entry);
|
|
2792
|
+
invalidateCache(conventionsPath, path15.join(memoryDir, ".tiers"));
|
|
2638
2793
|
logger.info(`Auto-learned ${learnings.length} convention(s)`);
|
|
2639
2794
|
}
|
|
2640
2795
|
autoLearnDecisions(ctx.taskDir, memoryDir, ctx.taskId, timestamp2);
|
|
@@ -2643,13 +2798,13 @@ ${learnings.join("\n")}
|
|
|
2643
2798
|
}
|
|
2644
2799
|
}
|
|
2645
2800
|
function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
2646
|
-
const archPath =
|
|
2647
|
-
if (
|
|
2801
|
+
const archPath = path15.join(memoryDir, "architecture.md");
|
|
2802
|
+
if (fs15.existsSync(archPath)) return;
|
|
2648
2803
|
const detected = [];
|
|
2649
|
-
const pkgPath =
|
|
2650
|
-
if (
|
|
2804
|
+
const pkgPath = path15.join(projectDir, "package.json");
|
|
2805
|
+
if (fs15.existsSync(pkgPath)) {
|
|
2651
2806
|
try {
|
|
2652
|
-
const pkg = JSON.parse(
|
|
2807
|
+
const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf-8"));
|
|
2653
2808
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
2654
2809
|
if (allDeps.next) detected.push(`- Framework: Next.js ${allDeps.next}`);
|
|
2655
2810
|
else if (allDeps.react) detected.push(`- Framework: React ${allDeps.react}`);
|
|
@@ -2665,15 +2820,15 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
|
2665
2820
|
if (allDeps.payload || allDeps["@payloadcms/next"]) detected.push(`- CMS: Payload CMS`);
|
|
2666
2821
|
if (pkg.type === "module") detected.push("- Module system: ESM");
|
|
2667
2822
|
else detected.push("- Module system: CommonJS");
|
|
2668
|
-
if (
|
|
2669
|
-
else if (
|
|
2670
|
-
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");
|
|
2671
2826
|
} catch {
|
|
2672
2827
|
}
|
|
2673
2828
|
}
|
|
2674
2829
|
const topDirs = [];
|
|
2675
2830
|
try {
|
|
2676
|
-
const entries =
|
|
2831
|
+
const entries = fs15.readdirSync(projectDir, { withFileTypes: true });
|
|
2677
2832
|
for (const entry of entries) {
|
|
2678
2833
|
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
2679
2834
|
topDirs.push(entry.name);
|
|
@@ -2682,10 +2837,10 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
|
2682
2837
|
if (topDirs.length > 0) detected.push(`- Top-level directories: ${topDirs.join(", ")}`);
|
|
2683
2838
|
} catch {
|
|
2684
2839
|
}
|
|
2685
|
-
const srcDir =
|
|
2686
|
-
if (
|
|
2840
|
+
const srcDir = path15.join(projectDir, "src");
|
|
2841
|
+
if (fs15.existsSync(srcDir)) {
|
|
2687
2842
|
try {
|
|
2688
|
-
const srcEntries =
|
|
2843
|
+
const srcEntries = fs15.readdirSync(srcDir, { withFileTypes: true });
|
|
2689
2844
|
const srcDirs = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
2690
2845
|
if (srcDirs.length > 0) detected.push(`- src/ structure: ${srcDirs.join(", ")}`);
|
|
2691
2846
|
} catch {
|
|
@@ -2697,15 +2852,15 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
|
2697
2852
|
## Overview
|
|
2698
2853
|
${detected.join("\n")}
|
|
2699
2854
|
`;
|
|
2700
|
-
|
|
2701
|
-
invalidateCache(archPath,
|
|
2855
|
+
fs15.writeFileSync(archPath, content);
|
|
2856
|
+
invalidateCache(archPath, path15.join(memoryDir, ".tiers"));
|
|
2702
2857
|
logger.info(`Auto-detected architecture (${detected.length} items)`);
|
|
2703
2858
|
}
|
|
2704
2859
|
}
|
|
2705
2860
|
function autoLearnDecisions(taskDir, memoryDir, taskId, timestamp2) {
|
|
2706
|
-
const reviewPath =
|
|
2707
|
-
if (!
|
|
2708
|
-
const review =
|
|
2861
|
+
const reviewPath = path15.join(taskDir, "review.md");
|
|
2862
|
+
if (!fs15.existsSync(reviewPath)) return;
|
|
2863
|
+
const review = fs15.readFileSync(reviewPath, "utf-8");
|
|
2709
2864
|
const decisions = [];
|
|
2710
2865
|
const existingPatternRe = /(?:use|follow|reuse|match|adopt)\s+(?:the\s+)?existing\s+(.+?)(?:\.|$)/gim;
|
|
2711
2866
|
for (const match of review.matchAll(existingPatternRe)) {
|
|
@@ -2724,10 +2879,10 @@ function autoLearnDecisions(taskDir, memoryDir, taskId, timestamp2) {
|
|
|
2724
2879
|
decisions.push(`- Avoid ${match[1].trim()} for ${match[2].trim()}`);
|
|
2725
2880
|
}
|
|
2726
2881
|
if (decisions.length === 0) return;
|
|
2727
|
-
const decisionsPath =
|
|
2882
|
+
const decisionsPath = path15.join(memoryDir, "decisions.md");
|
|
2728
2883
|
let existing = "";
|
|
2729
|
-
if (
|
|
2730
|
-
existing =
|
|
2884
|
+
if (fs15.existsSync(decisionsPath)) {
|
|
2885
|
+
existing = fs15.readFileSync(decisionsPath, "utf-8");
|
|
2731
2886
|
} else {
|
|
2732
2887
|
existing = "# Architectural Decisions\n\nDecisions extracted from code reviews. The planning agent MUST follow these.\n";
|
|
2733
2888
|
}
|
|
@@ -2737,8 +2892,8 @@ function autoLearnDecisions(taskDir, memoryDir, taskId, timestamp2) {
|
|
|
2737
2892
|
## From task ${taskId} (${timestamp2})
|
|
2738
2893
|
${newDecisions.join("\n")}
|
|
2739
2894
|
`;
|
|
2740
|
-
|
|
2741
|
-
invalidateCache(decisionsPath,
|
|
2895
|
+
fs15.appendFileSync(decisionsPath, existing ? entry : existing + entry);
|
|
2896
|
+
invalidateCache(decisionsPath, path15.join(memoryDir, ".tiers"));
|
|
2742
2897
|
logger.info(`Auto-learned ${newDecisions.length} architectural decision(s)`);
|
|
2743
2898
|
}
|
|
2744
2899
|
var init_auto_learn = __esm({
|
|
@@ -2750,13 +2905,13 @@ var init_auto_learn = __esm({
|
|
|
2750
2905
|
});
|
|
2751
2906
|
|
|
2752
2907
|
// src/retrospective.ts
|
|
2753
|
-
import * as
|
|
2754
|
-
import * as
|
|
2908
|
+
import * as fs16 from "fs";
|
|
2909
|
+
import * as path16 from "path";
|
|
2755
2910
|
function readArtifact(taskDir, filename, maxChars) {
|
|
2756
|
-
const p =
|
|
2757
|
-
if (!
|
|
2911
|
+
const p = path16.join(taskDir, filename);
|
|
2912
|
+
if (!fs16.existsSync(p)) return null;
|
|
2758
2913
|
try {
|
|
2759
|
-
const content =
|
|
2914
|
+
const content = fs16.readFileSync(p, "utf-8");
|
|
2760
2915
|
return content.length > maxChars ? content.slice(0, maxChars) + "\n...(truncated)" : content;
|
|
2761
2916
|
} catch {
|
|
2762
2917
|
return null;
|
|
@@ -2809,13 +2964,13 @@ function collectRunContext(ctx, state, pipelineStartTime) {
|
|
|
2809
2964
|
return lines.join("\n");
|
|
2810
2965
|
}
|
|
2811
2966
|
function getLogPath(projectDir) {
|
|
2812
|
-
return
|
|
2967
|
+
return path16.join(projectDir, ".kody", "memory", "observer-log.jsonl");
|
|
2813
2968
|
}
|
|
2814
2969
|
function readPreviousRetrospectives(projectDir, limit = 10) {
|
|
2815
2970
|
const logPath = getLogPath(projectDir);
|
|
2816
|
-
if (!
|
|
2971
|
+
if (!fs16.existsSync(logPath)) return [];
|
|
2817
2972
|
try {
|
|
2818
|
-
const content =
|
|
2973
|
+
const content = fs16.readFileSync(logPath, "utf-8");
|
|
2819
2974
|
const lines = content.split("\n").filter(Boolean);
|
|
2820
2975
|
const entries = [];
|
|
2821
2976
|
const start = Math.max(0, lines.length - limit);
|
|
@@ -2842,11 +2997,11 @@ function formatPreviousEntries(entries) {
|
|
|
2842
2997
|
}
|
|
2843
2998
|
function appendRetrospectiveEntry(projectDir, entry) {
|
|
2844
2999
|
const logPath = getLogPath(projectDir);
|
|
2845
|
-
const dir =
|
|
2846
|
-
if (!
|
|
2847
|
-
|
|
3000
|
+
const dir = path16.dirname(logPath);
|
|
3001
|
+
if (!fs16.existsSync(dir)) {
|
|
3002
|
+
fs16.mkdirSync(dir, { recursive: true });
|
|
2848
3003
|
}
|
|
2849
|
-
|
|
3004
|
+
fs16.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
2850
3005
|
}
|
|
2851
3006
|
async function runRetrospective(ctx, state, pipelineStartTime) {
|
|
2852
3007
|
if (ctx.input.dryRun) return;
|
|
@@ -2957,8 +3112,8 @@ If no pipeline flaw is detected, set "pipelineFlaw" to null.
|
|
|
2957
3112
|
});
|
|
2958
3113
|
|
|
2959
3114
|
// src/pipeline.ts
|
|
2960
|
-
import * as
|
|
2961
|
-
import * as
|
|
3115
|
+
import * as fs17 from "fs";
|
|
3116
|
+
import * as path17 from "path";
|
|
2962
3117
|
function ensureFeatureBranchIfNeeded(ctx) {
|
|
2963
3118
|
if (ctx.input.dryRun) return;
|
|
2964
3119
|
if (ctx.input.prNumber) {
|
|
@@ -2971,8 +3126,8 @@ function ensureFeatureBranchIfNeeded(ctx) {
|
|
|
2971
3126
|
}
|
|
2972
3127
|
if (!ctx.input.issueNumber) return;
|
|
2973
3128
|
try {
|
|
2974
|
-
const taskMdPath =
|
|
2975
|
-
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;
|
|
2976
3131
|
ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
|
|
2977
3132
|
syncWithDefault(ctx.projectDir);
|
|
2978
3133
|
} catch (err) {
|
|
@@ -2980,10 +3135,10 @@ function ensureFeatureBranchIfNeeded(ctx) {
|
|
|
2980
3135
|
}
|
|
2981
3136
|
}
|
|
2982
3137
|
function acquireLock(taskDir) {
|
|
2983
|
-
const lockPath =
|
|
2984
|
-
if (
|
|
3138
|
+
const lockPath = path17.join(taskDir, ".lock");
|
|
3139
|
+
if (fs17.existsSync(lockPath)) {
|
|
2985
3140
|
try {
|
|
2986
|
-
const pid = parseInt(
|
|
3141
|
+
const pid = parseInt(fs17.readFileSync(lockPath, "utf-8").trim(), 10);
|
|
2987
3142
|
try {
|
|
2988
3143
|
process.kill(pid, 0);
|
|
2989
3144
|
throw new Error(`Pipeline already running (PID ${pid})`);
|
|
@@ -2994,11 +3149,11 @@ function acquireLock(taskDir) {
|
|
|
2994
3149
|
if (e instanceof Error && e.message.startsWith("Pipeline already")) throw e;
|
|
2995
3150
|
}
|
|
2996
3151
|
}
|
|
2997
|
-
|
|
3152
|
+
fs17.writeFileSync(lockPath, String(process.pid));
|
|
2998
3153
|
}
|
|
2999
3154
|
function releaseLock(taskDir) {
|
|
3000
3155
|
try {
|
|
3001
|
-
|
|
3156
|
+
fs17.unlinkSync(path17.join(taskDir, ".lock"));
|
|
3002
3157
|
} catch {
|
|
3003
3158
|
}
|
|
3004
3159
|
}
|
|
@@ -3166,8 +3321,8 @@ var init_pipeline = __esm({
|
|
|
3166
3321
|
});
|
|
3167
3322
|
|
|
3168
3323
|
// src/preflight.ts
|
|
3169
|
-
import { execFileSync as
|
|
3170
|
-
import * as
|
|
3324
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
3325
|
+
import * as fs18 from "fs";
|
|
3171
3326
|
function check(name, fn) {
|
|
3172
3327
|
try {
|
|
3173
3328
|
const detail = fn() ?? void 0;
|
|
@@ -3179,7 +3334,7 @@ function check(name, fn) {
|
|
|
3179
3334
|
function runPreflight() {
|
|
3180
3335
|
const checks = [
|
|
3181
3336
|
check("claude CLI", () => {
|
|
3182
|
-
const v =
|
|
3337
|
+
const v = execFileSync9("claude", ["--version"], {
|
|
3183
3338
|
encoding: "utf-8",
|
|
3184
3339
|
timeout: 1e4,
|
|
3185
3340
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3187,14 +3342,14 @@ function runPreflight() {
|
|
|
3187
3342
|
return v;
|
|
3188
3343
|
}),
|
|
3189
3344
|
check("git repo", () => {
|
|
3190
|
-
|
|
3345
|
+
execFileSync9("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
3191
3346
|
encoding: "utf-8",
|
|
3192
3347
|
timeout: 5e3,
|
|
3193
3348
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3194
3349
|
});
|
|
3195
3350
|
}),
|
|
3196
3351
|
check("pnpm", () => {
|
|
3197
|
-
const v =
|
|
3352
|
+
const v = execFileSync9("pnpm", ["--version"], {
|
|
3198
3353
|
encoding: "utf-8",
|
|
3199
3354
|
timeout: 5e3,
|
|
3200
3355
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3202,7 +3357,7 @@ function runPreflight() {
|
|
|
3202
3357
|
return v;
|
|
3203
3358
|
}),
|
|
3204
3359
|
check("node >= 18", () => {
|
|
3205
|
-
const v =
|
|
3360
|
+
const v = execFileSync9("node", ["--version"], {
|
|
3206
3361
|
encoding: "utf-8",
|
|
3207
3362
|
timeout: 5e3,
|
|
3208
3363
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3212,7 +3367,7 @@ function runPreflight() {
|
|
|
3212
3367
|
return v;
|
|
3213
3368
|
}),
|
|
3214
3369
|
check("gh CLI", () => {
|
|
3215
|
-
const v =
|
|
3370
|
+
const v = execFileSync9("gh", ["--version"], {
|
|
3216
3371
|
encoding: "utf-8",
|
|
3217
3372
|
timeout: 5e3,
|
|
3218
3373
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3220,7 +3375,7 @@ function runPreflight() {
|
|
|
3220
3375
|
return v;
|
|
3221
3376
|
}),
|
|
3222
3377
|
check("package.json", () => {
|
|
3223
|
-
if (!
|
|
3378
|
+
if (!fs18.existsSync("package.json")) throw new Error("not found");
|
|
3224
3379
|
})
|
|
3225
3380
|
];
|
|
3226
3381
|
const failed = checks.filter((c) => !c.ok);
|
|
@@ -3240,140 +3395,6 @@ var init_preflight = __esm({
|
|
|
3240
3395
|
}
|
|
3241
3396
|
});
|
|
3242
3397
|
|
|
3243
|
-
// src/cli/task-resolution.ts
|
|
3244
|
-
import * as fs17 from "fs";
|
|
3245
|
-
import * as path16 from "path";
|
|
3246
|
-
import { execFileSync as execFileSync9 } from "child_process";
|
|
3247
|
-
function findLatestTaskForIssue(issueNumber, projectDir) {
|
|
3248
|
-
const tasksDir = path16.join(projectDir, ".kody", "tasks");
|
|
3249
|
-
if (!fs17.existsSync(tasksDir)) return null;
|
|
3250
|
-
const allDirs = fs17.readdirSync(tasksDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
|
|
3251
|
-
const prefix = `${issueNumber}-`;
|
|
3252
|
-
const direct = allDirs.find((d) => d.startsWith(prefix));
|
|
3253
|
-
if (direct) return direct;
|
|
3254
|
-
try {
|
|
3255
|
-
const branch = execFileSync9("git", ["branch", "--show-current"], {
|
|
3256
|
-
encoding: "utf-8",
|
|
3257
|
-
cwd: projectDir,
|
|
3258
|
-
timeout: 5e3,
|
|
3259
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3260
|
-
}).trim();
|
|
3261
|
-
const branchIssueMatch = branch.match(/^(\d+)-/);
|
|
3262
|
-
if (branchIssueMatch) {
|
|
3263
|
-
const branchIssueNum = branchIssueMatch[1];
|
|
3264
|
-
const branchPrefix = `${branchIssueNum}-`;
|
|
3265
|
-
const fromBranch = allDirs.find((d) => d.startsWith(branchPrefix));
|
|
3266
|
-
if (fromBranch) return fromBranch;
|
|
3267
|
-
}
|
|
3268
|
-
} catch {
|
|
3269
|
-
}
|
|
3270
|
-
return null;
|
|
3271
|
-
}
|
|
3272
|
-
function generateTaskId() {
|
|
3273
|
-
const now = /* @__PURE__ */ new Date();
|
|
3274
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
3275
|
-
return `${String(now.getFullYear()).slice(2)}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
3276
|
-
}
|
|
3277
|
-
var init_task_resolution = __esm({
|
|
3278
|
-
"src/cli/task-resolution.ts"() {
|
|
3279
|
-
"use strict";
|
|
3280
|
-
}
|
|
3281
|
-
});
|
|
3282
|
-
|
|
3283
|
-
// src/review-standalone.ts
|
|
3284
|
-
import * as fs18 from "fs";
|
|
3285
|
-
import * as path17 from "path";
|
|
3286
|
-
function resolveReviewTarget(input) {
|
|
3287
|
-
if (input.prs.length === 0) {
|
|
3288
|
-
return {
|
|
3289
|
-
action: "none",
|
|
3290
|
-
message: `Issue #${input.issueNumber} has no open PRs. Nothing to review.`
|
|
3291
|
-
};
|
|
3292
|
-
}
|
|
3293
|
-
if (input.prs.length === 1) {
|
|
3294
|
-
return { action: "review", prNumber: input.prs[0].number };
|
|
3295
|
-
}
|
|
3296
|
-
const prList = input.prs.map((pr) => ` - #${pr.number}: ${pr.title}`).join("\n");
|
|
3297
|
-
return {
|
|
3298
|
-
action: "pick",
|
|
3299
|
-
prs: input.prs,
|
|
3300
|
-
message: `\u26A0\uFE0F Issue #${input.issueNumber} has ${input.prs.length} open PRs:
|
|
3301
|
-
${prList}
|
|
3302
|
-
|
|
3303
|
-
Run: \`pnpm kody review --pr-number <n>\`
|
|
3304
|
-
Or comment on the specific PR: \`@kody review\``
|
|
3305
|
-
};
|
|
3306
|
-
}
|
|
3307
|
-
async function runStandaloneReview(input) {
|
|
3308
|
-
const taskId = input.taskId ?? `review-${generateTaskId()}`;
|
|
3309
|
-
const taskDir = path17.join(input.projectDir, ".kody", "tasks", taskId);
|
|
3310
|
-
fs18.mkdirSync(taskDir, { recursive: true });
|
|
3311
|
-
const taskContent = `# ${input.prTitle}
|
|
3312
|
-
|
|
3313
|
-
${input.prBody ?? ""}`;
|
|
3314
|
-
fs18.writeFileSync(path17.join(taskDir, "task.md"), taskContent);
|
|
3315
|
-
const reviewDef = STAGES.find((s) => s.name === "review");
|
|
3316
|
-
const ctx = {
|
|
3317
|
-
taskId,
|
|
3318
|
-
taskDir,
|
|
3319
|
-
projectDir: input.projectDir,
|
|
3320
|
-
runners: input.runners,
|
|
3321
|
-
sessions: {},
|
|
3322
|
-
input: {
|
|
3323
|
-
mode: "full",
|
|
3324
|
-
local: input.local
|
|
3325
|
-
}
|
|
3326
|
-
};
|
|
3327
|
-
logger.info(`[review] standalone review for: ${input.prTitle}`);
|
|
3328
|
-
const result = await executeAgentStage(ctx, reviewDef);
|
|
3329
|
-
if (result.outcome !== "completed") {
|
|
3330
|
-
return {
|
|
3331
|
-
outcome: "failed",
|
|
3332
|
-
taskDir,
|
|
3333
|
-
error: result.error ?? "Review stage failed"
|
|
3334
|
-
};
|
|
3335
|
-
}
|
|
3336
|
-
const reviewPath = path17.join(taskDir, "review.md");
|
|
3337
|
-
let reviewContent;
|
|
3338
|
-
if (fs18.existsSync(reviewPath)) {
|
|
3339
|
-
reviewContent = fs18.readFileSync(reviewPath, "utf-8");
|
|
3340
|
-
}
|
|
3341
|
-
return {
|
|
3342
|
-
outcome: "completed",
|
|
3343
|
-
reviewContent,
|
|
3344
|
-
taskDir
|
|
3345
|
-
};
|
|
3346
|
-
}
|
|
3347
|
-
function detectReviewVerdict(reviewContent) {
|
|
3348
|
-
const verdictMatch = reviewContent.match(/##\s*Verdict:\s*(PASS|FAIL)/i);
|
|
3349
|
-
if (verdictMatch) {
|
|
3350
|
-
return verdictMatch[1].toLowerCase();
|
|
3351
|
-
}
|
|
3352
|
-
const hasCritical = /###\s*Critical\s*\n(?!None\.)/i.test(reviewContent);
|
|
3353
|
-
const hasMajor = /###\s*Major\s*\n(?!None\.)/i.test(reviewContent);
|
|
3354
|
-
if (hasCritical || hasMajor) return "fail";
|
|
3355
|
-
return "pass";
|
|
3356
|
-
}
|
|
3357
|
-
function formatReviewComment(reviewContent, taskId) {
|
|
3358
|
-
const verdict = detectReviewVerdict(reviewContent);
|
|
3359
|
-
const cta = verdict === "fail" ? "\n\n> To fix these issues, comment: `@kody fix`\n> The review findings will be used automatically as context." : "";
|
|
3360
|
-
return `## \u{1F50D} Kody Review (\`${taskId}\`)
|
|
3361
|
-
|
|
3362
|
-
${reviewContent}${cta}
|
|
3363
|
-
|
|
3364
|
-
---
|
|
3365
|
-
\u{1F916} Generated by Kody`;
|
|
3366
|
-
}
|
|
3367
|
-
var init_review_standalone = __esm({
|
|
3368
|
-
"src/review-standalone.ts"() {
|
|
3369
|
-
"use strict";
|
|
3370
|
-
init_definitions();
|
|
3371
|
-
init_agent();
|
|
3372
|
-
init_task_resolution();
|
|
3373
|
-
init_logger();
|
|
3374
|
-
}
|
|
3375
|
-
});
|
|
3376
|
-
|
|
3377
3398
|
// src/cli/args.ts
|
|
3378
3399
|
function getArg(args2, flag) {
|
|
3379
3400
|
const idx = args2.indexOf(flag);
|
|
@@ -3938,7 +3959,6 @@ async function main() {
|
|
|
3938
3959
|
console.log(result.reviewContent);
|
|
3939
3960
|
if (!input.local && prNumber) {
|
|
3940
3961
|
const comment = formatReviewComment(result.reviewContent, taskId);
|
|
3941
|
-
postPRComment(prNumber, comment);
|
|
3942
3962
|
const verdict = detectReviewVerdict(result.reviewContent);
|
|
3943
3963
|
if (verdict === "fail") {
|
|
3944
3964
|
submitPRReview(prNumber, comment, "request-changes");
|