@kody-ade/kody-engine-lite 0.1.146 → 0.1.148
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 +538 -246
- package/kody.config.schema.json +5 -0
- package/package.json +1 -1
- package/templates/kody-watch.yml +6 -1
- package/templates/watch-agents/dependency-checker/agent.json +7 -0
- package/templates/watch-agents/dependency-checker/agent.md +14 -0
- package/templates/watch-agents/stale-pr-reviewer/agent.json +7 -0
- package/templates/watch-agents/stale-pr-reviewer/agent.md +13 -0
package/dist/bin/cli.js
CHANGED
|
@@ -254,7 +254,12 @@ function getProjectConfig() {
|
|
|
254
254
|
enabled: raw.mcp.enabled ?? !!raw.mcp.devServer
|
|
255
255
|
} : void 0,
|
|
256
256
|
// Top-level devServer takes precedence; fall back to mcp.devServer for backward compat
|
|
257
|
-
devServer: raw.devServer ?? raw.mcp?.devServer ?? void 0
|
|
257
|
+
devServer: raw.devServer ?? raw.mcp?.devServer ?? void 0,
|
|
258
|
+
watch: raw.watch ? {
|
|
259
|
+
enabled: raw.watch.enabled ?? false,
|
|
260
|
+
digestIssue: raw.watch.digestIssue,
|
|
261
|
+
model: raw.watch.model
|
|
262
|
+
} : void 0
|
|
258
263
|
};
|
|
259
264
|
} catch {
|
|
260
265
|
logger.warn("kody.config.json is invalid JSON \u2014 using defaults");
|
|
@@ -1193,13 +1198,11 @@ async function tryStartLitellm(url, projectDir, generatedConfig) {
|
|
|
1193
1198
|
const child = spawn3(cmd, args2, {
|
|
1194
1199
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1195
1200
|
detached: true,
|
|
1196
|
-
env: {
|
|
1197
|
-
...process.env,
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
DATABASE_URL: ""
|
|
1202
|
-
}
|
|
1201
|
+
env: (() => {
|
|
1202
|
+
const env = { ...process.env, ...dotenvVars };
|
|
1203
|
+
delete env.DATABASE_URL;
|
|
1204
|
+
return env;
|
|
1205
|
+
})()
|
|
1203
1206
|
});
|
|
1204
1207
|
let proxyStderr = "";
|
|
1205
1208
|
child.stderr?.on("data", (chunk) => {
|
|
@@ -3289,9 +3292,92 @@ var init_logger2 = __esm({
|
|
|
3289
3292
|
}
|
|
3290
3293
|
});
|
|
3291
3294
|
|
|
3295
|
+
// src/watch/agents/prompt-builder.ts
|
|
3296
|
+
function buildWatchAgentPrompt(agent, ctx) {
|
|
3297
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3298
|
+
const preamble = `You are a Kody Watch agent monitoring the GitHub repository **${ctx.repo}**.
|
|
3299
|
+
Your role is described below. Use the tools available to you to inspect the repository and take action.
|
|
3300
|
+
|
|
3301
|
+
## Environment
|
|
3302
|
+
- Repository: ${ctx.repo}
|
|
3303
|
+
- Current date: ${now}
|
|
3304
|
+
- Watch cycle: #${ctx.cycleNumber}
|
|
3305
|
+
${ctx.digestIssue ? `- Digest issue: #${ctx.digestIssue}` : ""}
|
|
3306
|
+
|
|
3307
|
+
## Tools
|
|
3308
|
+
Use \`gh\` CLI (via Bash) for all GitHub operations. Examples:
|
|
3309
|
+
- \`gh pr list --repo ${ctx.repo} --state open --json number,title,author,updatedAt,labels,createdAt\`
|
|
3310
|
+
- \`gh pr view 123 --repo ${ctx.repo} --json number,title,body,author,commits,files,comments,reviews,updatedAt\`
|
|
3311
|
+
- \`gh issue list --repo ${ctx.repo} --state open --json number,title,labels,updatedAt\`
|
|
3312
|
+
- \`gh issue create --repo ${ctx.repo} --title "..." --body "..." --label "kody:watch"\`
|
|
3313
|
+
- \`gh api repos/${ctx.repo}/actions/runs --jq '.workflow_runs[:10]'\`
|
|
3314
|
+
|
|
3315
|
+
Use Read/Glob/Grep for inspecting repository files.
|
|
3316
|
+
|
|
3317
|
+
## Guidelines
|
|
3318
|
+
- **Check before creating**: Always search for existing open issues with similar titles before creating new ones to avoid duplicates.
|
|
3319
|
+
- **Label issues**: Prefix all created issue labels with \`kody:watch:\` (e.g. \`kody:watch:stale-pr\`).
|
|
3320
|
+
- **Be concise**: Issue bodies should contain evidence and a suggested action, not lengthy explanations.
|
|
3321
|
+
- **Stay focused**: Only act on what your instructions below describe. Do not perform unrelated analysis.`;
|
|
3322
|
+
const agentInstructions = `## Your Instructions
|
|
3323
|
+
|
|
3324
|
+
${agent.systemPrompt}`;
|
|
3325
|
+
return `${preamble}
|
|
3326
|
+
|
|
3327
|
+
${agentInstructions}`;
|
|
3328
|
+
}
|
|
3329
|
+
var init_prompt_builder = __esm({
|
|
3330
|
+
"src/watch/agents/prompt-builder.ts"() {
|
|
3331
|
+
"use strict";
|
|
3332
|
+
}
|
|
3333
|
+
});
|
|
3334
|
+
|
|
3335
|
+
// src/watch/agents/run-agent.ts
|
|
3336
|
+
async function runWatchAgent(agent, ctx, options) {
|
|
3337
|
+
const { model, provider, projectDir, timeoutMs = AGENT_TIMEOUT_MS } = options;
|
|
3338
|
+
const prompt = buildWatchAgentPrompt(agent, {
|
|
3339
|
+
repo: ctx.repo,
|
|
3340
|
+
cycleNumber: ctx.cycleNumber,
|
|
3341
|
+
digestIssue: ctx.digestIssue
|
|
3342
|
+
});
|
|
3343
|
+
const runner = createClaudeCodeRunner();
|
|
3344
|
+
const extraEnv = {};
|
|
3345
|
+
const stageConfig = { provider: provider ?? "claude", model };
|
|
3346
|
+
if (stageNeedsProxy(stageConfig)) {
|
|
3347
|
+
extraEnv.ANTHROPIC_BASE_URL = getLitellmUrl();
|
|
3348
|
+
}
|
|
3349
|
+
const result2 = await runner.run(
|
|
3350
|
+
`watch:${agent.config.name}`,
|
|
3351
|
+
prompt,
|
|
3352
|
+
model,
|
|
3353
|
+
timeoutMs,
|
|
3354
|
+
projectDir,
|
|
3355
|
+
{
|
|
3356
|
+
cwd: projectDir,
|
|
3357
|
+
env: extraEnv
|
|
3358
|
+
}
|
|
3359
|
+
);
|
|
3360
|
+
return {
|
|
3361
|
+
agentName: agent.config.name,
|
|
3362
|
+
outcome: result2.outcome,
|
|
3363
|
+
output: result2.output,
|
|
3364
|
+
error: result2.error
|
|
3365
|
+
};
|
|
3366
|
+
}
|
|
3367
|
+
var AGENT_TIMEOUT_MS;
|
|
3368
|
+
var init_run_agent = __esm({
|
|
3369
|
+
"src/watch/agents/run-agent.ts"() {
|
|
3370
|
+
"use strict";
|
|
3371
|
+
init_agent_runner();
|
|
3372
|
+
init_config();
|
|
3373
|
+
init_prompt_builder();
|
|
3374
|
+
AGENT_TIMEOUT_MS = 2 * 60 * 1e3;
|
|
3375
|
+
}
|
|
3376
|
+
});
|
|
3377
|
+
|
|
3292
3378
|
// src/watch/core/watch.ts
|
|
3293
3379
|
async function runWatch(config) {
|
|
3294
|
-
const { repo, dryRun, stateFile, plugins } = config;
|
|
3380
|
+
const { repo, dryRun, stateFile, plugins, agents } = config;
|
|
3295
3381
|
const token = process.env.GH_TOKEN || "";
|
|
3296
3382
|
const github = createGitHubClient(repo, token);
|
|
3297
3383
|
const state = createStateStore(stateFile, github, config.digestIssue);
|
|
@@ -3372,6 +3458,45 @@ async function runWatch(config) {
|
|
|
3372
3458
|
} else {
|
|
3373
3459
|
log2.info({ actionCount: dedupedActions.length }, "Dry run \u2014 skipping action execution");
|
|
3374
3460
|
}
|
|
3461
|
+
const scheduledAgents = agents.filter((agent) => {
|
|
3462
|
+
return cycleNumber % agent.config.schedule.every === 0;
|
|
3463
|
+
});
|
|
3464
|
+
const agentResults = [];
|
|
3465
|
+
if (scheduledAgents.length > 0 && !dryRun) {
|
|
3466
|
+
log2.info(
|
|
3467
|
+
{ agentsTotal: agents.length, agentsScheduled: scheduledAgents.length },
|
|
3468
|
+
"Running watch agents"
|
|
3469
|
+
);
|
|
3470
|
+
const provider = config.provider;
|
|
3471
|
+
for (const agent of scheduledAgents) {
|
|
3472
|
+
try {
|
|
3473
|
+
log2.info({ agent: agent.config.name }, "Running watch agent");
|
|
3474
|
+
const result3 = await runWatchAgent(agent, ctx, {
|
|
3475
|
+
model: config.model,
|
|
3476
|
+
provider,
|
|
3477
|
+
projectDir: config.projectDir
|
|
3478
|
+
});
|
|
3479
|
+
agentResults.push(result3);
|
|
3480
|
+
if (result3.outcome === "completed") {
|
|
3481
|
+
log2.info({ agent: agent.config.name }, "Watch agent completed");
|
|
3482
|
+
} else {
|
|
3483
|
+
const errMsg = `Agent ${agent.config.name}: ${result3.outcome}${result3.error ? ` \u2014 ${result3.error}` : ""}`;
|
|
3484
|
+
errors.push(errMsg);
|
|
3485
|
+
log2.warn({ agent: agent.config.name, outcome: result3.outcome }, "Watch agent did not complete");
|
|
3486
|
+
}
|
|
3487
|
+
} catch (error) {
|
|
3488
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3489
|
+
errors.push(`Agent ${agent.config.name}: ${message}`);
|
|
3490
|
+
log2.error({ agent: agent.config.name, error: message }, "Watch agent failed");
|
|
3491
|
+
agentResults.push({ agentName: agent.config.name, outcome: "failed", error: message });
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
} else if (scheduledAgents.length > 0 && dryRun) {
|
|
3495
|
+
log2.info(
|
|
3496
|
+
{ agentCount: scheduledAgents.length },
|
|
3497
|
+
"Dry run \u2014 skipping watch agent execution"
|
|
3498
|
+
);
|
|
3499
|
+
}
|
|
3375
3500
|
state.save();
|
|
3376
3501
|
const result2 = {
|
|
3377
3502
|
cycleNumber,
|
|
@@ -3379,6 +3504,8 @@ async function runWatch(config) {
|
|
|
3379
3504
|
actionsProduced: allActions.length,
|
|
3380
3505
|
actionsExecuted,
|
|
3381
3506
|
actionsDeduplicated,
|
|
3507
|
+
agentsRun: scheduledAgents.length,
|
|
3508
|
+
agentResults,
|
|
3382
3509
|
errors
|
|
3383
3510
|
};
|
|
3384
3511
|
log2.info(
|
|
@@ -3388,6 +3515,7 @@ async function runWatch(config) {
|
|
|
3388
3515
|
actionsProduced: result2.actionsProduced,
|
|
3389
3516
|
actionsExecuted: result2.actionsExecuted,
|
|
3390
3517
|
actionsDeduplicated: result2.actionsDeduplicated,
|
|
3518
|
+
agentsRun: result2.agentsRun,
|
|
3391
3519
|
errors: result2.errors.length
|
|
3392
3520
|
},
|
|
3393
3521
|
"Watch cycle completed"
|
|
@@ -3401,6 +3529,7 @@ var init_watch = __esm({
|
|
|
3401
3529
|
init_dedup();
|
|
3402
3530
|
init_github();
|
|
3403
3531
|
init_logger2();
|
|
3532
|
+
init_run_agent();
|
|
3404
3533
|
}
|
|
3405
3534
|
});
|
|
3406
3535
|
|
|
@@ -4111,27 +4240,134 @@ var init_config_health = __esm({
|
|
|
4111
4240
|
}
|
|
4112
4241
|
});
|
|
4113
4242
|
|
|
4243
|
+
// src/watch/agents/loader.ts
|
|
4244
|
+
import * as fs19 from "fs";
|
|
4245
|
+
import * as path17 from "path";
|
|
4246
|
+
function validateAgentConfig(raw, dirName) {
|
|
4247
|
+
if (!raw || typeof raw !== "object") {
|
|
4248
|
+
return `${dirName}: agent.json must be a JSON object`;
|
|
4249
|
+
}
|
|
4250
|
+
const obj = raw;
|
|
4251
|
+
if (typeof obj.name !== "string" || !obj.name.trim()) {
|
|
4252
|
+
return `${dirName}: agent.json missing required "name" (string)`;
|
|
4253
|
+
}
|
|
4254
|
+
if (typeof obj.description !== "string" || !obj.description.trim()) {
|
|
4255
|
+
return `${dirName}: agent.json missing required "description" (string)`;
|
|
4256
|
+
}
|
|
4257
|
+
let every = 1;
|
|
4258
|
+
if (obj.schedule && typeof obj.schedule === "object") {
|
|
4259
|
+
const sched = obj.schedule;
|
|
4260
|
+
if (sched.every !== void 0) {
|
|
4261
|
+
if (typeof sched.every !== "number" || sched.every < 1 || !Number.isInteger(sched.every)) {
|
|
4262
|
+
return `${dirName}: schedule.every must be an integer >= 1`;
|
|
4263
|
+
}
|
|
4264
|
+
every = sched.every;
|
|
4265
|
+
}
|
|
4266
|
+
}
|
|
4267
|
+
return {
|
|
4268
|
+
name: obj.name.trim(),
|
|
4269
|
+
description: obj.description.trim(),
|
|
4270
|
+
schedule: { every }
|
|
4271
|
+
};
|
|
4272
|
+
}
|
|
4273
|
+
function loadWatchAgents(projectDir) {
|
|
4274
|
+
const agentsDir = path17.join(projectDir, AGENTS_DIR);
|
|
4275
|
+
const warnings = [];
|
|
4276
|
+
const agents = [];
|
|
4277
|
+
if (!fs19.existsSync(agentsDir)) {
|
|
4278
|
+
return { agents, warnings };
|
|
4279
|
+
}
|
|
4280
|
+
let entries;
|
|
4281
|
+
try {
|
|
4282
|
+
entries = fs19.readdirSync(agentsDir, { withFileTypes: true });
|
|
4283
|
+
} catch {
|
|
4284
|
+
warnings.push(`Cannot read ${AGENTS_DIR}`);
|
|
4285
|
+
return { agents, warnings };
|
|
4286
|
+
}
|
|
4287
|
+
for (const entry of entries) {
|
|
4288
|
+
if (!entry.isDirectory()) continue;
|
|
4289
|
+
const dirPath = path17.join(agentsDir, entry.name);
|
|
4290
|
+
const jsonPath = path17.join(dirPath, "agent.json");
|
|
4291
|
+
const mdPath = path17.join(dirPath, "agent.md");
|
|
4292
|
+
if (!fs19.existsSync(jsonPath)) {
|
|
4293
|
+
warnings.push(`${entry.name}: missing agent.json \u2014 skipped`);
|
|
4294
|
+
continue;
|
|
4295
|
+
}
|
|
4296
|
+
if (!fs19.existsSync(mdPath)) {
|
|
4297
|
+
warnings.push(`${entry.name}: missing agent.md \u2014 skipped`);
|
|
4298
|
+
continue;
|
|
4299
|
+
}
|
|
4300
|
+
let rawJson;
|
|
4301
|
+
try {
|
|
4302
|
+
rawJson = JSON.parse(fs19.readFileSync(jsonPath, "utf-8"));
|
|
4303
|
+
} catch {
|
|
4304
|
+
warnings.push(`${entry.name}: agent.json is invalid JSON \u2014 skipped`);
|
|
4305
|
+
continue;
|
|
4306
|
+
}
|
|
4307
|
+
const configOrError = validateAgentConfig(rawJson, entry.name);
|
|
4308
|
+
if (typeof configOrError === "string") {
|
|
4309
|
+
warnings.push(`${configOrError} \u2014 skipped`);
|
|
4310
|
+
continue;
|
|
4311
|
+
}
|
|
4312
|
+
const systemPrompt = fs19.readFileSync(mdPath, "utf-8").trim();
|
|
4313
|
+
if (!systemPrompt) {
|
|
4314
|
+
warnings.push(`${entry.name}: agent.md is empty \u2014 skipped`);
|
|
4315
|
+
continue;
|
|
4316
|
+
}
|
|
4317
|
+
agents.push({
|
|
4318
|
+
config: configOrError,
|
|
4319
|
+
systemPrompt,
|
|
4320
|
+
dirPath
|
|
4321
|
+
});
|
|
4322
|
+
}
|
|
4323
|
+
return { agents, warnings };
|
|
4324
|
+
}
|
|
4325
|
+
var AGENTS_DIR;
|
|
4326
|
+
var init_loader = __esm({
|
|
4327
|
+
"src/watch/agents/loader.ts"() {
|
|
4328
|
+
"use strict";
|
|
4329
|
+
AGENTS_DIR = ".kody/watch/agents";
|
|
4330
|
+
}
|
|
4331
|
+
});
|
|
4332
|
+
|
|
4114
4333
|
// src/watch/index.ts
|
|
4115
4334
|
var watch_exports = {};
|
|
4116
4335
|
__export(watch_exports, {
|
|
4117
4336
|
runWatchCommand: () => runWatchCommand
|
|
4118
4337
|
});
|
|
4119
|
-
import * as
|
|
4120
|
-
import * as
|
|
4338
|
+
import * as fs20 from "fs";
|
|
4339
|
+
import * as path18 from "path";
|
|
4121
4340
|
async function runWatchCommand(opts) {
|
|
4122
4341
|
const cwd = process.cwd();
|
|
4123
|
-
|
|
4342
|
+
let litellmProcess = null;
|
|
4343
|
+
const configPath = path18.join(cwd, "kody.config.json");
|
|
4124
4344
|
let repo = process.env.REPO || "";
|
|
4125
4345
|
let digestIssue;
|
|
4126
|
-
|
|
4346
|
+
let watchModel;
|
|
4347
|
+
let agentProvider;
|
|
4348
|
+
let agentModelMap;
|
|
4349
|
+
let defaultCheapModel = "claude-haiku-4-5-20251001";
|
|
4350
|
+
if (!repo && fs20.existsSync(configPath)) {
|
|
4127
4351
|
try {
|
|
4128
|
-
const config2 = JSON.parse(
|
|
4352
|
+
const config2 = JSON.parse(fs20.readFileSync(configPath, "utf-8"));
|
|
4129
4353
|
if (config2.github?.owner && config2.github?.repo) {
|
|
4130
4354
|
repo = `${config2.github.owner}/${config2.github.repo}`;
|
|
4131
4355
|
}
|
|
4132
4356
|
if (config2.watch?.digestIssue) {
|
|
4133
4357
|
digestIssue = config2.watch.digestIssue;
|
|
4134
4358
|
}
|
|
4359
|
+
if (config2.watch?.model) {
|
|
4360
|
+
watchModel = config2.watch.model;
|
|
4361
|
+
}
|
|
4362
|
+
if (config2.agent?.provider) {
|
|
4363
|
+
agentProvider = config2.agent.provider;
|
|
4364
|
+
}
|
|
4365
|
+
if (config2.agent?.modelMap) {
|
|
4366
|
+
agentModelMap = config2.agent.modelMap;
|
|
4367
|
+
}
|
|
4368
|
+
if (config2.agent?.modelMap?.cheap) {
|
|
4369
|
+
defaultCheapModel = config2.agent.modelMap.cheap;
|
|
4370
|
+
}
|
|
4135
4371
|
} catch {
|
|
4136
4372
|
}
|
|
4137
4373
|
}
|
|
@@ -4150,12 +4386,43 @@ async function runWatchCommand(opts) {
|
|
|
4150
4386
|
registry.register(pipelineHealthPlugin);
|
|
4151
4387
|
registry.register(securityScanPlugin);
|
|
4152
4388
|
registry.register(configHealthPlugin);
|
|
4389
|
+
const { agents, warnings } = loadWatchAgents(cwd);
|
|
4390
|
+
for (const w of warnings) {
|
|
4391
|
+
console.warn(` Agent warning: ${w}`);
|
|
4392
|
+
}
|
|
4393
|
+
const model = watchModel ?? defaultCheapModel;
|
|
4394
|
+
const needsProxy = agentProvider && agentProvider !== "claude" && agentProvider !== "anthropic";
|
|
4395
|
+
if (agents.length > 0) {
|
|
4396
|
+
console.log(` Found ${agents.length} watch agent(s): ${agents.map((a) => a.config.name).join(", ")}`);
|
|
4397
|
+
console.log(` Model: ${model}${agentProvider ? ` (provider: ${agentProvider})` : ""}`);
|
|
4398
|
+
if (needsProxy && !opts.dryRun) {
|
|
4399
|
+
const litellmUrl = LITELLM_DEFAULT_URL;
|
|
4400
|
+
const isHealthy = await checkLitellmHealth(litellmUrl);
|
|
4401
|
+
if (!isHealthy) {
|
|
4402
|
+
const proxyModelMap = { ...agentModelMap, watch: model };
|
|
4403
|
+
const generatedConfig = generateLitellmConfig(agentProvider, proxyModelMap);
|
|
4404
|
+
console.log(` Starting LiteLLM proxy for ${agentProvider}...`);
|
|
4405
|
+
litellmProcess = await tryStartLitellm(litellmUrl, cwd, generatedConfig);
|
|
4406
|
+
if (litellmProcess) {
|
|
4407
|
+
console.log(` LiteLLM proxy started`);
|
|
4408
|
+
} else {
|
|
4409
|
+
console.warn(` LiteLLM proxy failed to start \u2014 agents using ${agentProvider} may fail`);
|
|
4410
|
+
}
|
|
4411
|
+
} else {
|
|
4412
|
+
console.log(` LiteLLM proxy already running`);
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4415
|
+
}
|
|
4153
4416
|
const config = {
|
|
4154
4417
|
repo,
|
|
4155
4418
|
dryRun: opts.dryRun,
|
|
4156
|
-
stateFile:
|
|
4419
|
+
stateFile: path18.join(cwd, ".kody", "watch-state.json"),
|
|
4157
4420
|
plugins: registry.getAll(),
|
|
4158
|
-
digestIssue
|
|
4421
|
+
digestIssue,
|
|
4422
|
+
agents,
|
|
4423
|
+
model,
|
|
4424
|
+
provider: agentProvider,
|
|
4425
|
+
projectDir: cwd
|
|
4159
4426
|
};
|
|
4160
4427
|
console.log(`
|
|
4161
4428
|
Kody Watch \u2014 repo: ${repo}, dry-run: ${opts.dryRun}
|
|
@@ -4167,13 +4434,17 @@ Kody Watch \u2014 repo: ${repo}, dry-run: ${opts.dryRun}
|
|
|
4167
4434
|
console.warn(` Warning: ${error}`);
|
|
4168
4435
|
}
|
|
4169
4436
|
}
|
|
4437
|
+
const agentSummary = result2.agentsRun > 0 ? `, ${result2.agentsRun} agents` : "";
|
|
4170
4438
|
console.log(`
|
|
4171
|
-
Cycle #${result2.cycleNumber} complete: ${result2.pluginsRun} plugins, ${result2.actionsExecuted} actions, ${result2.actionsDeduplicated} deduplicated`);
|
|
4172
|
-
process.exit(0);
|
|
4439
|
+
Cycle #${result2.cycleNumber} complete: ${result2.pluginsRun} plugins${agentSummary}, ${result2.actionsExecuted} actions, ${result2.actionsDeduplicated} deduplicated`);
|
|
4173
4440
|
} catch (error) {
|
|
4174
4441
|
const message = error instanceof Error ? error.message : String(error);
|
|
4175
4442
|
console.error(`Watch failed: ${message}`);
|
|
4176
|
-
|
|
4443
|
+
} finally {
|
|
4444
|
+
if (litellmProcess) {
|
|
4445
|
+
litellmProcess.kill("SIGTERM");
|
|
4446
|
+
}
|
|
4447
|
+
process.exit(0);
|
|
4177
4448
|
}
|
|
4178
4449
|
}
|
|
4179
4450
|
var init_watch2 = __esm({
|
|
@@ -4184,6 +4455,9 @@ var init_watch2 = __esm({
|
|
|
4184
4455
|
init_pipeline_health();
|
|
4185
4456
|
init_security_scan();
|
|
4186
4457
|
init_config_health();
|
|
4458
|
+
init_loader();
|
|
4459
|
+
init_litellm();
|
|
4460
|
+
init_config();
|
|
4187
4461
|
}
|
|
4188
4462
|
});
|
|
4189
4463
|
|
|
@@ -4462,14 +4736,14 @@ var init_git_utils = __esm({
|
|
|
4462
4736
|
});
|
|
4463
4737
|
|
|
4464
4738
|
// src/pipeline/state.ts
|
|
4465
|
-
import * as
|
|
4466
|
-
import * as
|
|
4739
|
+
import * as fs21 from "fs";
|
|
4740
|
+
import * as path19 from "path";
|
|
4467
4741
|
function loadState(taskId, taskDir) {
|
|
4468
|
-
const p =
|
|
4469
|
-
if (!
|
|
4742
|
+
const p = path19.join(taskDir, "status.json");
|
|
4743
|
+
if (!fs21.existsSync(p)) return null;
|
|
4470
4744
|
try {
|
|
4471
4745
|
const result2 = parseJsonSafe(
|
|
4472
|
-
|
|
4746
|
+
fs21.readFileSync(p, "utf-8"),
|
|
4473
4747
|
["taskId", "state", "stages", "createdAt", "updatedAt"]
|
|
4474
4748
|
);
|
|
4475
4749
|
if (!result2.ok) {
|
|
@@ -4487,10 +4761,10 @@ function writeState(state, taskDir) {
|
|
|
4487
4761
|
...state,
|
|
4488
4762
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4489
4763
|
};
|
|
4490
|
-
const target =
|
|
4764
|
+
const target = path19.join(taskDir, "status.json");
|
|
4491
4765
|
const tmp = target + ".tmp";
|
|
4492
|
-
|
|
4493
|
-
|
|
4766
|
+
fs21.writeFileSync(tmp, JSON.stringify(updated, null, 2));
|
|
4767
|
+
fs21.renameSync(tmp, target);
|
|
4494
4768
|
return updated;
|
|
4495
4769
|
}
|
|
4496
4770
|
function initState(taskId) {
|
|
@@ -4531,16 +4805,16 @@ var init_complexity = __esm({
|
|
|
4531
4805
|
});
|
|
4532
4806
|
|
|
4533
4807
|
// src/memory.ts
|
|
4534
|
-
import * as
|
|
4535
|
-
import * as
|
|
4808
|
+
import * as fs22 from "fs";
|
|
4809
|
+
import * as path20 from "path";
|
|
4536
4810
|
function readProjectMemory(projectDir) {
|
|
4537
|
-
const memoryDir =
|
|
4538
|
-
if (!
|
|
4539
|
-
const files =
|
|
4811
|
+
const memoryDir = path20.join(projectDir, ".kody", "memory");
|
|
4812
|
+
if (!fs22.existsSync(memoryDir)) return "";
|
|
4813
|
+
const files = fs22.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
|
|
4540
4814
|
if (files.length === 0) return "";
|
|
4541
4815
|
const sections = [];
|
|
4542
4816
|
for (const file of files) {
|
|
4543
|
-
const content =
|
|
4817
|
+
const content = fs22.readFileSync(path20.join(memoryDir, file), "utf-8").trim();
|
|
4544
4818
|
if (content) {
|
|
4545
4819
|
sections.push(`## ${file.replace(".md", "")}
|
|
4546
4820
|
${content}`);
|
|
@@ -4559,8 +4833,8 @@ var init_memory = __esm({
|
|
|
4559
4833
|
});
|
|
4560
4834
|
|
|
4561
4835
|
// src/context-tiers.ts
|
|
4562
|
-
import * as
|
|
4563
|
-
import * as
|
|
4836
|
+
import * as fs23 from "fs";
|
|
4837
|
+
import * as path21 from "path";
|
|
4564
4838
|
function estimateTokens(text) {
|
|
4565
4839
|
return Math.ceil(text.length / 4);
|
|
4566
4840
|
}
|
|
@@ -4651,7 +4925,7 @@ function generateL1Json(content) {
|
|
|
4651
4925
|
}
|
|
4652
4926
|
}
|
|
4653
4927
|
function getTieredContent(filePath, content) {
|
|
4654
|
-
const key =
|
|
4928
|
+
const key = path21.basename(filePath);
|
|
4655
4929
|
return {
|
|
4656
4930
|
source: filePath,
|
|
4657
4931
|
L0: generateL0(content, key),
|
|
@@ -4663,15 +4937,15 @@ function selectTier(tiered, tier) {
|
|
|
4663
4937
|
return tiered[tier];
|
|
4664
4938
|
}
|
|
4665
4939
|
function readProjectMemoryTiered(projectDir, tier) {
|
|
4666
|
-
const memoryDir =
|
|
4667
|
-
if (!
|
|
4668
|
-
const files =
|
|
4940
|
+
const memoryDir = path21.join(projectDir, ".kody", "memory");
|
|
4941
|
+
if (!fs23.existsSync(memoryDir)) return "";
|
|
4942
|
+
const files = fs23.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
|
|
4669
4943
|
if (files.length === 0) return "";
|
|
4670
4944
|
const tierLabel2 = tier === "L2" ? "full" : tier === "L1" ? "overview" : "abstract";
|
|
4671
4945
|
const sections = [];
|
|
4672
4946
|
for (const file of files) {
|
|
4673
|
-
const filePath =
|
|
4674
|
-
const content =
|
|
4947
|
+
const filePath = path21.join(memoryDir, file);
|
|
4948
|
+
const content = fs23.readFileSync(filePath, "utf-8").trim();
|
|
4675
4949
|
if (!content) continue;
|
|
4676
4950
|
const tiered = getTieredContent(filePath, content);
|
|
4677
4951
|
const selected = selectTier(tiered, tier);
|
|
@@ -4694,9 +4968,9 @@ function injectTaskContextTiered(prompt, taskId, taskDir, policy, feedback) {
|
|
|
4694
4968
|
`;
|
|
4695
4969
|
context += `Task Directory: ${taskDir}
|
|
4696
4970
|
`;
|
|
4697
|
-
const taskMdPath =
|
|
4698
|
-
if (
|
|
4699
|
-
const content =
|
|
4971
|
+
const taskMdPath = path21.join(taskDir, "task.md");
|
|
4972
|
+
if (fs23.existsSync(taskMdPath)) {
|
|
4973
|
+
const content = fs23.readFileSync(taskMdPath, "utf-8");
|
|
4700
4974
|
const selected = selectContent(taskMdPath, content, policy.taskDescription);
|
|
4701
4975
|
const label = tierLabel("Task Description", policy.taskDescription);
|
|
4702
4976
|
context += `
|
|
@@ -4704,9 +4978,9 @@ function injectTaskContextTiered(prompt, taskId, taskDir, policy, feedback) {
|
|
|
4704
4978
|
${selected}
|
|
4705
4979
|
`;
|
|
4706
4980
|
}
|
|
4707
|
-
const taskJsonPath =
|
|
4708
|
-
if (
|
|
4709
|
-
const content =
|
|
4981
|
+
const taskJsonPath = path21.join(taskDir, "task.json");
|
|
4982
|
+
if (fs23.existsSync(taskJsonPath)) {
|
|
4983
|
+
const content = fs23.readFileSync(taskJsonPath, "utf-8");
|
|
4710
4984
|
if (policy.taskClassification === "L2") {
|
|
4711
4985
|
try {
|
|
4712
4986
|
const taskDef = JSON.parse(content.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, ""));
|
|
@@ -4732,9 +5006,9 @@ ${selected}
|
|
|
4732
5006
|
}
|
|
4733
5007
|
}
|
|
4734
5008
|
}
|
|
4735
|
-
const specPath =
|
|
4736
|
-
if (
|
|
4737
|
-
const content =
|
|
5009
|
+
const specPath = path21.join(taskDir, "spec.md");
|
|
5010
|
+
if (fs23.existsSync(specPath)) {
|
|
5011
|
+
const content = fs23.readFileSync(specPath, "utf-8");
|
|
4738
5012
|
const selected = selectContent(specPath, content, policy.spec);
|
|
4739
5013
|
const label = tierLabel("Spec", policy.spec);
|
|
4740
5014
|
context += `
|
|
@@ -4742,9 +5016,9 @@ ${selected}
|
|
|
4742
5016
|
${selected}
|
|
4743
5017
|
`;
|
|
4744
5018
|
}
|
|
4745
|
-
const planPath =
|
|
4746
|
-
if (
|
|
4747
|
-
const content =
|
|
5019
|
+
const planPath = path21.join(taskDir, "plan.md");
|
|
5020
|
+
if (fs23.existsSync(planPath)) {
|
|
5021
|
+
const content = fs23.readFileSync(planPath, "utf-8");
|
|
4748
5022
|
const selected = selectContent(planPath, content, policy.plan);
|
|
4749
5023
|
const label = tierLabel("Plan", policy.plan);
|
|
4750
5024
|
context += `
|
|
@@ -4752,9 +5026,9 @@ ${selected}
|
|
|
4752
5026
|
${selected}
|
|
4753
5027
|
`;
|
|
4754
5028
|
}
|
|
4755
|
-
const contextMdPath =
|
|
4756
|
-
if (
|
|
4757
|
-
const content =
|
|
5029
|
+
const contextMdPath = path21.join(taskDir, "context.md");
|
|
5030
|
+
if (fs23.existsSync(contextMdPath)) {
|
|
5031
|
+
const content = fs23.readFileSync(contextMdPath, "utf-8");
|
|
4758
5032
|
const selected = selectContent(contextMdPath, content, policy.accumulatedContext);
|
|
4759
5033
|
const label = tierLabel("Previous Stage Context", policy.accumulatedContext);
|
|
4760
5034
|
context += `
|
|
@@ -4840,24 +5114,24 @@ var init_context_tiers = __esm({
|
|
|
4840
5114
|
});
|
|
4841
5115
|
|
|
4842
5116
|
// src/context.ts
|
|
4843
|
-
import * as
|
|
4844
|
-
import * as
|
|
5117
|
+
import * as fs24 from "fs";
|
|
5118
|
+
import * as path22 from "path";
|
|
4845
5119
|
function readPromptFile(stageName, projectDir) {
|
|
4846
5120
|
if (projectDir) {
|
|
4847
|
-
const stepFile =
|
|
4848
|
-
if (
|
|
4849
|
-
return
|
|
5121
|
+
const stepFile = path22.join(projectDir, ".kody", "steps", `${stageName}.md`);
|
|
5122
|
+
if (fs24.existsSync(stepFile)) {
|
|
5123
|
+
return fs24.readFileSync(stepFile, "utf-8");
|
|
4850
5124
|
}
|
|
4851
5125
|
console.warn(` \u26A0 No step file at ${stepFile}, falling back to engine defaults. Run 'kody-engine-lite init --force' to generate step files.`);
|
|
4852
5126
|
}
|
|
4853
5127
|
const scriptDir = new URL(".", import.meta.url).pathname;
|
|
4854
5128
|
const candidates = [
|
|
4855
|
-
|
|
4856
|
-
|
|
5129
|
+
path22.resolve(scriptDir, "..", "prompts", `${stageName}.md`),
|
|
5130
|
+
path22.resolve(scriptDir, "..", "..", "prompts", `${stageName}.md`)
|
|
4857
5131
|
];
|
|
4858
5132
|
for (const candidate of candidates) {
|
|
4859
|
-
if (
|
|
4860
|
-
return
|
|
5133
|
+
if (fs24.existsSync(candidate)) {
|
|
5134
|
+
return fs24.readFileSync(candidate, "utf-8");
|
|
4861
5135
|
}
|
|
4862
5136
|
}
|
|
4863
5137
|
throw new Error(`Prompt file not found: tried ${candidates.join(", ")}`);
|
|
@@ -4869,18 +5143,18 @@ function injectTaskContext(prompt, taskId, taskDir, feedback) {
|
|
|
4869
5143
|
`;
|
|
4870
5144
|
context += `Task Directory: ${taskDir}
|
|
4871
5145
|
`;
|
|
4872
|
-
const taskMdPath =
|
|
4873
|
-
if (
|
|
4874
|
-
const taskMd =
|
|
5146
|
+
const taskMdPath = path22.join(taskDir, "task.md");
|
|
5147
|
+
if (fs24.existsSync(taskMdPath)) {
|
|
5148
|
+
const taskMd = fs24.readFileSync(taskMdPath, "utf-8");
|
|
4875
5149
|
context += `
|
|
4876
5150
|
## Task Description
|
|
4877
5151
|
${taskMd}
|
|
4878
5152
|
`;
|
|
4879
5153
|
}
|
|
4880
|
-
const taskJsonPath =
|
|
4881
|
-
if (
|
|
5154
|
+
const taskJsonPath = path22.join(taskDir, "task.json");
|
|
5155
|
+
if (fs24.existsSync(taskJsonPath)) {
|
|
4882
5156
|
try {
|
|
4883
|
-
const taskDef = JSON.parse(
|
|
5157
|
+
const taskDef = JSON.parse(fs24.readFileSync(taskJsonPath, "utf-8"));
|
|
4884
5158
|
context += `
|
|
4885
5159
|
## Task Classification
|
|
4886
5160
|
`;
|
|
@@ -4893,27 +5167,27 @@ ${taskMd}
|
|
|
4893
5167
|
} catch {
|
|
4894
5168
|
}
|
|
4895
5169
|
}
|
|
4896
|
-
const specPath =
|
|
4897
|
-
if (
|
|
4898
|
-
const spec =
|
|
5170
|
+
const specPath = path22.join(taskDir, "spec.md");
|
|
5171
|
+
if (fs24.existsSync(specPath)) {
|
|
5172
|
+
const spec = fs24.readFileSync(specPath, "utf-8");
|
|
4899
5173
|
const truncated = spec.slice(0, MAX_TASK_CONTEXT_SPEC);
|
|
4900
5174
|
context += `
|
|
4901
5175
|
## Spec Summary
|
|
4902
5176
|
${truncated}${spec.length > MAX_TASK_CONTEXT_SPEC ? "\n..." : ""}
|
|
4903
5177
|
`;
|
|
4904
5178
|
}
|
|
4905
|
-
const planPath =
|
|
4906
|
-
if (
|
|
4907
|
-
const plan =
|
|
5179
|
+
const planPath = path22.join(taskDir, "plan.md");
|
|
5180
|
+
if (fs24.existsSync(planPath)) {
|
|
5181
|
+
const plan = fs24.readFileSync(planPath, "utf-8");
|
|
4908
5182
|
const truncated = plan.slice(0, MAX_TASK_CONTEXT_PLAN);
|
|
4909
5183
|
context += `
|
|
4910
5184
|
## Plan Summary
|
|
4911
5185
|
${truncated}${plan.length > MAX_TASK_CONTEXT_PLAN ? "\n..." : ""}
|
|
4912
5186
|
`;
|
|
4913
5187
|
}
|
|
4914
|
-
const contextMdPath =
|
|
4915
|
-
if (
|
|
4916
|
-
const accumulated =
|
|
5188
|
+
const contextMdPath = path22.join(taskDir, "context.md");
|
|
5189
|
+
if (fs24.existsSync(contextMdPath)) {
|
|
5190
|
+
const accumulated = fs24.readFileSync(contextMdPath, "utf-8");
|
|
4917
5191
|
const truncated = accumulated.slice(-MAX_ACCUMULATED_CONTEXT);
|
|
4918
5192
|
const prefix = accumulated.length > MAX_ACCUMULATED_CONTEXT ? "...(earlier context truncated)\n" : "";
|
|
4919
5193
|
context += `
|
|
@@ -4931,17 +5205,17 @@ ${feedback}
|
|
|
4931
5205
|
}
|
|
4932
5206
|
function inferHasUIFromScope(scope) {
|
|
4933
5207
|
return scope.some((filePath) => {
|
|
4934
|
-
const ext =
|
|
5208
|
+
const ext = path22.extname(filePath).toLowerCase();
|
|
4935
5209
|
if (UI_EXTENSIONS.has(ext)) return true;
|
|
4936
5210
|
const normalized = filePath.replace(/\\/g, "/");
|
|
4937
5211
|
return UI_PATH_SEGMENTS.some((seg) => normalized.includes(seg));
|
|
4938
5212
|
});
|
|
4939
5213
|
}
|
|
4940
5214
|
function taskHasUI(taskDir) {
|
|
4941
|
-
const taskJsonPath =
|
|
4942
|
-
if (!
|
|
5215
|
+
const taskJsonPath = path22.join(taskDir, "task.json");
|
|
5216
|
+
if (!fs24.existsSync(taskJsonPath)) return true;
|
|
4943
5217
|
try {
|
|
4944
|
-
const taskDef = JSON.parse(
|
|
5218
|
+
const taskDef = JSON.parse(fs24.readFileSync(taskJsonPath, "utf-8"));
|
|
4945
5219
|
const scope = Array.isArray(taskDef.scope) ? taskDef.scope : [];
|
|
4946
5220
|
if (scope.length === 0) return true;
|
|
4947
5221
|
return inferHasUIFromScope(scope);
|
|
@@ -5127,9 +5401,9 @@ ${prompt}` : prompt;
|
|
|
5127
5401
|
const hasBrowserTools = isMcpEnabledForStage(stageName, config.mcp) || config.devServer && browserStages.includes(stageName);
|
|
5128
5402
|
if (hasBrowserTools && taskHasUI(taskDir)) {
|
|
5129
5403
|
assembled = assembled + "\n\n" + getBrowserToolGuidance(stageName, taskDir);
|
|
5130
|
-
const qaGuidePath =
|
|
5131
|
-
if (
|
|
5132
|
-
const qaGuide =
|
|
5404
|
+
const qaGuidePath = path22.join(projectDir, ".kody", "qa-guide.md");
|
|
5405
|
+
if (fs24.existsSync(qaGuidePath)) {
|
|
5406
|
+
const qaGuide = fs24.readFileSync(qaGuidePath, "utf-8").trim();
|
|
5133
5407
|
assembled = assembled + "\n\n" + qaGuide;
|
|
5134
5408
|
}
|
|
5135
5409
|
}
|
|
@@ -5239,7 +5513,7 @@ async function waitForReady(url, timeoutSec, stdoutMatch) {
|
|
|
5239
5513
|
while (Date.now() < deadline) {
|
|
5240
5514
|
if (stdoutMatch()) {
|
|
5241
5515
|
logger.info(" Dev server stdout matched ready pattern");
|
|
5242
|
-
const httpTimeout = Math.
|
|
5516
|
+
const httpTimeout = Math.max(1, Math.floor((deadline - Date.now()) / 1e3));
|
|
5243
5517
|
return pollReady(url, httpTimeout);
|
|
5244
5518
|
}
|
|
5245
5519
|
await new Promise((r) => setTimeout(r, 500));
|
|
@@ -5317,8 +5591,8 @@ var init_dev_server = __esm({
|
|
|
5317
5591
|
});
|
|
5318
5592
|
|
|
5319
5593
|
// src/stages/agent.ts
|
|
5320
|
-
import * as
|
|
5321
|
-
import * as
|
|
5594
|
+
import * as fs25 from "fs";
|
|
5595
|
+
import * as path23 from "path";
|
|
5322
5596
|
function getSessionInfo(stageName, sessions) {
|
|
5323
5597
|
const group = SESSION_GROUP[stageName];
|
|
5324
5598
|
if (!group) return void 0;
|
|
@@ -5440,27 +5714,27 @@ async function executeAgentStage(ctx, def) {
|
|
|
5440
5714
|
}
|
|
5441
5715
|
const result2 = lastResult;
|
|
5442
5716
|
if (def.outputFile && result2.output) {
|
|
5443
|
-
|
|
5717
|
+
fs25.writeFileSync(path23.join(ctx.taskDir, def.outputFile), result2.output);
|
|
5444
5718
|
}
|
|
5445
5719
|
if (def.outputFile) {
|
|
5446
|
-
const outputPath =
|
|
5447
|
-
if (!
|
|
5448
|
-
const ext =
|
|
5449
|
-
const base =
|
|
5450
|
-
const files =
|
|
5720
|
+
const outputPath = path23.join(ctx.taskDir, def.outputFile);
|
|
5721
|
+
if (!fs25.existsSync(outputPath)) {
|
|
5722
|
+
const ext = path23.extname(def.outputFile);
|
|
5723
|
+
const base = path23.basename(def.outputFile, ext);
|
|
5724
|
+
const files = fs25.readdirSync(ctx.taskDir);
|
|
5451
5725
|
const variant = files.find(
|
|
5452
5726
|
(f) => f.startsWith(base + "-") && f.endsWith(ext)
|
|
5453
5727
|
);
|
|
5454
5728
|
if (variant) {
|
|
5455
|
-
|
|
5729
|
+
fs25.renameSync(path23.join(ctx.taskDir, variant), outputPath);
|
|
5456
5730
|
logger.info(` Renamed variant ${variant} \u2192 ${def.outputFile}`);
|
|
5457
5731
|
}
|
|
5458
5732
|
}
|
|
5459
5733
|
}
|
|
5460
5734
|
if (def.outputFile) {
|
|
5461
|
-
const outputPath =
|
|
5462
|
-
if (
|
|
5463
|
-
const content =
|
|
5735
|
+
const outputPath = path23.join(ctx.taskDir, def.outputFile);
|
|
5736
|
+
if (fs25.existsSync(outputPath)) {
|
|
5737
|
+
const content = fs25.readFileSync(outputPath, "utf-8");
|
|
5464
5738
|
const validation = validateStageOutput(def.name, content);
|
|
5465
5739
|
if (!validation.valid) {
|
|
5466
5740
|
if (def.name === "taskify") {
|
|
@@ -5474,7 +5748,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
5474
5748
|
const stripped = stripFences(retryResult.output);
|
|
5475
5749
|
const retryValidation = validateTaskJson(stripped);
|
|
5476
5750
|
if (retryValidation.valid) {
|
|
5477
|
-
|
|
5751
|
+
fs25.writeFileSync(outputPath, retryResult.output);
|
|
5478
5752
|
logger.info(` taskify retry produced valid JSON`);
|
|
5479
5753
|
} else {
|
|
5480
5754
|
logger.warn(` taskify retry still invalid: ${retryValidation.error}`);
|
|
@@ -5487,7 +5761,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
5487
5761
|
risk_level: "low",
|
|
5488
5762
|
questions: []
|
|
5489
5763
|
}, null, 2);
|
|
5490
|
-
|
|
5764
|
+
fs25.writeFileSync(outputPath, fallback);
|
|
5491
5765
|
logger.info(` taskify fallback: generated minimal task.json (risk_level=low)`);
|
|
5492
5766
|
}
|
|
5493
5767
|
}
|
|
@@ -5501,7 +5775,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
5501
5775
|
return { outcome: "completed", outputFile: def.outputFile, retries };
|
|
5502
5776
|
}
|
|
5503
5777
|
function appendStageContext(taskDir, stageName, output) {
|
|
5504
|
-
const contextPath =
|
|
5778
|
+
const contextPath = path23.join(taskDir, "context.md");
|
|
5505
5779
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19);
|
|
5506
5780
|
let summary;
|
|
5507
5781
|
if (output && output.trim()) {
|
|
@@ -5514,7 +5788,7 @@ function appendStageContext(taskDir, stageName, output) {
|
|
|
5514
5788
|
### ${stageName} (${timestamp2})
|
|
5515
5789
|
${summary}
|
|
5516
5790
|
`;
|
|
5517
|
-
|
|
5791
|
+
fs25.appendFileSync(contextPath, entry);
|
|
5518
5792
|
}
|
|
5519
5793
|
var SESSION_GROUP;
|
|
5520
5794
|
var init_agent = __esm({
|
|
@@ -5795,8 +6069,8 @@ Error context:
|
|
|
5795
6069
|
});
|
|
5796
6070
|
|
|
5797
6071
|
// src/stages/gate.ts
|
|
5798
|
-
import * as
|
|
5799
|
-
import * as
|
|
6072
|
+
import * as fs26 from "fs";
|
|
6073
|
+
import * as path24 from "path";
|
|
5800
6074
|
function executeGateStage(ctx, def) {
|
|
5801
6075
|
if (ctx.input.dryRun) {
|
|
5802
6076
|
logger.info(` [dry-run] skipping ${def.name}`);
|
|
@@ -5839,7 +6113,7 @@ ${output}
|
|
|
5839
6113
|
`);
|
|
5840
6114
|
}
|
|
5841
6115
|
}
|
|
5842
|
-
|
|
6116
|
+
fs26.writeFileSync(path24.join(ctx.taskDir, "verify.md"), lines.join(""));
|
|
5843
6117
|
return {
|
|
5844
6118
|
outcome: verifyResult.pass ? "completed" : "failed",
|
|
5845
6119
|
retries: 0
|
|
@@ -5854,8 +6128,8 @@ var init_gate = __esm({
|
|
|
5854
6128
|
});
|
|
5855
6129
|
|
|
5856
6130
|
// src/stages/verify.ts
|
|
5857
|
-
import * as
|
|
5858
|
-
import * as
|
|
6131
|
+
import * as fs27 from "fs";
|
|
6132
|
+
import * as path25 from "path";
|
|
5859
6133
|
import { execFileSync as execFileSync16 } from "child_process";
|
|
5860
6134
|
async function executeVerifyWithAutofix(ctx, def) {
|
|
5861
6135
|
const maxAttempts = def.maxRetries ?? 2;
|
|
@@ -5866,8 +6140,8 @@ async function executeVerifyWithAutofix(ctx, def) {
|
|
|
5866
6140
|
return { ...gateResult, retries: attempt };
|
|
5867
6141
|
}
|
|
5868
6142
|
if (attempt < maxAttempts) {
|
|
5869
|
-
const verifyPath =
|
|
5870
|
-
const errorOutput =
|
|
6143
|
+
const verifyPath = path25.join(ctx.taskDir, "verify.md");
|
|
6144
|
+
const errorOutput = fs27.existsSync(verifyPath) ? fs27.readFileSync(verifyPath, "utf-8") : "Unknown error";
|
|
5871
6145
|
const modifiedFiles = getModifiedFiles(ctx.projectDir);
|
|
5872
6146
|
const defaultRunner = getRunnerForStage(ctx, "taskify");
|
|
5873
6147
|
const diagConfig = getProjectConfig();
|
|
@@ -5963,8 +6237,8 @@ var init_verify = __esm({
|
|
|
5963
6237
|
});
|
|
5964
6238
|
|
|
5965
6239
|
// src/review-standalone.ts
|
|
5966
|
-
import * as
|
|
5967
|
-
import * as
|
|
6240
|
+
import * as fs28 from "fs";
|
|
6241
|
+
import * as path26 from "path";
|
|
5968
6242
|
function resolveReviewTarget(input) {
|
|
5969
6243
|
if (input.prs.length === 0) {
|
|
5970
6244
|
return {
|
|
@@ -5988,8 +6262,8 @@ Or comment on the specific PR: \`@kody review\``
|
|
|
5988
6262
|
}
|
|
5989
6263
|
async function runStandaloneReview(input) {
|
|
5990
6264
|
const taskId = input.taskId ?? `review-${generateTaskId()}`;
|
|
5991
|
-
const taskDir =
|
|
5992
|
-
|
|
6265
|
+
const taskDir = path26.join(input.projectDir, ".kody", "tasks", taskId);
|
|
6266
|
+
fs28.mkdirSync(taskDir, { recursive: true });
|
|
5993
6267
|
let diffInstruction = "";
|
|
5994
6268
|
let filesChangedSection = "";
|
|
5995
6269
|
if (input.baseBranch) {
|
|
@@ -6016,7 +6290,7 @@ ${fileList}`;
|
|
|
6016
6290
|
const taskContent = `# ${input.prTitle}
|
|
6017
6291
|
|
|
6018
6292
|
${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
|
|
6019
|
-
|
|
6293
|
+
fs28.writeFileSync(path26.join(taskDir, "task.md"), taskContent);
|
|
6020
6294
|
const reviewDef = STAGES.find((s) => s.name === "review");
|
|
6021
6295
|
const ctx = {
|
|
6022
6296
|
taskId,
|
|
@@ -6038,10 +6312,10 @@ ${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
|
|
|
6038
6312
|
error: result2.error ?? "Review stage failed"
|
|
6039
6313
|
};
|
|
6040
6314
|
}
|
|
6041
|
-
const reviewPath =
|
|
6315
|
+
const reviewPath = path26.join(taskDir, "review.md");
|
|
6042
6316
|
let reviewContent;
|
|
6043
|
-
if (
|
|
6044
|
-
reviewContent =
|
|
6317
|
+
if (fs28.existsSync(reviewPath)) {
|
|
6318
|
+
reviewContent = fs28.readFileSync(reviewPath, "utf-8");
|
|
6045
6319
|
}
|
|
6046
6320
|
return {
|
|
6047
6321
|
outcome: "completed",
|
|
@@ -6081,8 +6355,8 @@ var init_review_standalone = __esm({
|
|
|
6081
6355
|
});
|
|
6082
6356
|
|
|
6083
6357
|
// src/stages/review.ts
|
|
6084
|
-
import * as
|
|
6085
|
-
import * as
|
|
6358
|
+
import * as fs29 from "fs";
|
|
6359
|
+
import * as path27 from "path";
|
|
6086
6360
|
async function executeReviewWithFix(ctx, def) {
|
|
6087
6361
|
if (ctx.input.dryRun) {
|
|
6088
6362
|
return { outcome: "completed", retries: 0 };
|
|
@@ -6096,11 +6370,11 @@ async function executeReviewWithFix(ctx, def) {
|
|
|
6096
6370
|
if (reviewResult.outcome !== "completed") {
|
|
6097
6371
|
return reviewResult;
|
|
6098
6372
|
}
|
|
6099
|
-
const reviewFile =
|
|
6100
|
-
if (!
|
|
6373
|
+
const reviewFile = path27.join(ctx.taskDir, "review.md");
|
|
6374
|
+
if (!fs29.existsSync(reviewFile)) {
|
|
6101
6375
|
return { outcome: "failed", retries: iteration, error: "review.md not found" };
|
|
6102
6376
|
}
|
|
6103
|
-
const content =
|
|
6377
|
+
const content = fs29.readFileSync(reviewFile, "utf-8");
|
|
6104
6378
|
if (detectReviewVerdict(content) !== "fail") {
|
|
6105
6379
|
return { ...reviewResult, retries: iteration };
|
|
6106
6380
|
}
|
|
@@ -6129,15 +6403,15 @@ var init_review = __esm({
|
|
|
6129
6403
|
});
|
|
6130
6404
|
|
|
6131
6405
|
// src/stages/ship.ts
|
|
6132
|
-
import * as
|
|
6133
|
-
import * as
|
|
6406
|
+
import * as fs30 from "fs";
|
|
6407
|
+
import * as path28 from "path";
|
|
6134
6408
|
import { execFileSync as execFileSync17 } from "child_process";
|
|
6135
6409
|
function buildPrBody(ctx) {
|
|
6136
6410
|
const sections = [];
|
|
6137
|
-
const taskJsonPath =
|
|
6138
|
-
if (
|
|
6411
|
+
const taskJsonPath = path28.join(ctx.taskDir, "task.json");
|
|
6412
|
+
if (fs30.existsSync(taskJsonPath)) {
|
|
6139
6413
|
try {
|
|
6140
|
-
const raw =
|
|
6414
|
+
const raw = fs30.readFileSync(taskJsonPath, "utf-8");
|
|
6141
6415
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
6142
6416
|
const task = JSON.parse(cleaned);
|
|
6143
6417
|
if (task.description) {
|
|
@@ -6156,9 +6430,9 @@ ${task.scope.map((s) => `- \`${s}\``).join("\n")}`);
|
|
|
6156
6430
|
} catch {
|
|
6157
6431
|
}
|
|
6158
6432
|
}
|
|
6159
|
-
const reviewPath =
|
|
6160
|
-
if (
|
|
6161
|
-
const review =
|
|
6433
|
+
const reviewPath = path28.join(ctx.taskDir, "review.md");
|
|
6434
|
+
if (fs30.existsSync(reviewPath)) {
|
|
6435
|
+
const review = fs30.readFileSync(reviewPath, "utf-8");
|
|
6162
6436
|
const summaryMatch = review.match(/## Summary\s*\n([\s\S]*?)(?=\n## |\n*$)/);
|
|
6163
6437
|
if (summaryMatch) {
|
|
6164
6438
|
const summary = summaryMatch[1].trim();
|
|
@@ -6175,14 +6449,14 @@ ${summary}`);
|
|
|
6175
6449
|
**Review:** ${verdictMatch[1].toUpperCase() === "PASS" ? "\u2705 PASS" : "\u274C FAIL"}`);
|
|
6176
6450
|
}
|
|
6177
6451
|
}
|
|
6178
|
-
const verifyPath =
|
|
6179
|
-
if (
|
|
6180
|
-
const verify =
|
|
6452
|
+
const verifyPath = path28.join(ctx.taskDir, "verify.md");
|
|
6453
|
+
if (fs30.existsSync(verifyPath)) {
|
|
6454
|
+
const verify = fs30.readFileSync(verifyPath, "utf-8");
|
|
6181
6455
|
if (/PASS/i.test(verify)) sections.push(`**Verify:** \u2705 typecheck + tests + lint passed`);
|
|
6182
6456
|
}
|
|
6183
|
-
const planPath =
|
|
6184
|
-
if (
|
|
6185
|
-
const plan =
|
|
6457
|
+
const planPath = path28.join(ctx.taskDir, "plan.md");
|
|
6458
|
+
if (fs30.existsSync(planPath)) {
|
|
6459
|
+
const plan = fs30.readFileSync(planPath, "utf-8").trim();
|
|
6186
6460
|
if (plan) {
|
|
6187
6461
|
const truncated = plan.length > 800 ? plan.slice(0, 800) + "\n..." : plan;
|
|
6188
6462
|
sections.push(`
|
|
@@ -6202,22 +6476,22 @@ Closes #${ctx.input.issueNumber}`);
|
|
|
6202
6476
|
return sections.join("\n");
|
|
6203
6477
|
}
|
|
6204
6478
|
function executeShipStage(ctx, _def) {
|
|
6205
|
-
const shipPath =
|
|
6479
|
+
const shipPath = path28.join(ctx.taskDir, "ship.md");
|
|
6206
6480
|
if (ctx.input.dryRun) {
|
|
6207
|
-
|
|
6481
|
+
fs30.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
|
|
6208
6482
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
6209
6483
|
}
|
|
6210
6484
|
if (ctx.input.local && !ctx.input.issueNumber) {
|
|
6211
|
-
|
|
6485
|
+
fs30.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
|
|
6212
6486
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
6213
6487
|
}
|
|
6214
6488
|
try {
|
|
6215
6489
|
const head = getCurrentBranch(ctx.projectDir);
|
|
6216
6490
|
const base = getDefaultBranch(ctx.projectDir);
|
|
6217
6491
|
try {
|
|
6218
|
-
const memoryDir =
|
|
6492
|
+
const memoryDir = path28.join(ctx.projectDir, ".kody", "memory");
|
|
6219
6493
|
const addPaths = [ctx.taskDir];
|
|
6220
|
-
if (
|
|
6494
|
+
if (fs30.existsSync(memoryDir)) addPaths.push(memoryDir);
|
|
6221
6495
|
execFileSync17("git", ["add", ...addPaths], {
|
|
6222
6496
|
cwd: ctx.projectDir,
|
|
6223
6497
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
@@ -6258,28 +6532,28 @@ function executeShipStage(ctx, _def) {
|
|
|
6258
6532
|
chore: "chore"
|
|
6259
6533
|
};
|
|
6260
6534
|
let prefix = "chore";
|
|
6261
|
-
const taskJsonPath =
|
|
6262
|
-
if (
|
|
6535
|
+
const taskJsonPath = path28.join(ctx.taskDir, "task.json");
|
|
6536
|
+
if (fs30.existsSync(taskJsonPath)) {
|
|
6263
6537
|
try {
|
|
6264
|
-
const raw =
|
|
6538
|
+
const raw = fs30.readFileSync(taskJsonPath, "utf-8");
|
|
6265
6539
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
6266
6540
|
const task = JSON.parse(cleaned);
|
|
6267
6541
|
prefix = TYPE_PREFIX[task.task_type] ?? "chore";
|
|
6268
6542
|
} catch {
|
|
6269
6543
|
}
|
|
6270
6544
|
}
|
|
6271
|
-
const taskMdPath =
|
|
6272
|
-
if (
|
|
6273
|
-
const content =
|
|
6545
|
+
const taskMdPath = path28.join(ctx.taskDir, "task.md");
|
|
6546
|
+
if (fs30.existsSync(taskMdPath)) {
|
|
6547
|
+
const content = fs30.readFileSync(taskMdPath, "utf-8");
|
|
6274
6548
|
const heading = content.split("\n").find((l) => l.startsWith("# "));
|
|
6275
6549
|
if (heading) {
|
|
6276
6550
|
title = `${prefix}: ${heading.replace(/^#\s*/, "").trim()}`.slice(0, 72);
|
|
6277
6551
|
}
|
|
6278
6552
|
}
|
|
6279
6553
|
if (title === "Update") {
|
|
6280
|
-
if (
|
|
6554
|
+
if (fs30.existsSync(taskJsonPath)) {
|
|
6281
6555
|
try {
|
|
6282
|
-
const raw =
|
|
6556
|
+
const raw = fs30.readFileSync(taskJsonPath, "utf-8");
|
|
6283
6557
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
6284
6558
|
const task = JSON.parse(cleaned);
|
|
6285
6559
|
if (task.title) title = `${prefix}: ${task.title}`.slice(0, 72);
|
|
@@ -6302,7 +6576,7 @@ function executeShipStage(ctx, _def) {
|
|
|
6302
6576
|
} catch {
|
|
6303
6577
|
}
|
|
6304
6578
|
}
|
|
6305
|
-
|
|
6579
|
+
fs30.writeFileSync(shipPath, `# Ship
|
|
6306
6580
|
|
|
6307
6581
|
Updated existing PR: ${existingPr.url}
|
|
6308
6582
|
PR #${existingPr.number}
|
|
@@ -6323,20 +6597,20 @@ PR #${existingPr.number}
|
|
|
6323
6597
|
} catch {
|
|
6324
6598
|
}
|
|
6325
6599
|
}
|
|
6326
|
-
|
|
6600
|
+
fs30.writeFileSync(shipPath, `# Ship
|
|
6327
6601
|
|
|
6328
6602
|
PR created: ${pr.url}
|
|
6329
6603
|
PR #${pr.number}
|
|
6330
6604
|
`);
|
|
6331
6605
|
} else {
|
|
6332
|
-
|
|
6606
|
+
fs30.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
|
|
6333
6607
|
}
|
|
6334
6608
|
}
|
|
6335
6609
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
6336
6610
|
} catch (err) {
|
|
6337
6611
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6338
6612
|
try {
|
|
6339
|
-
|
|
6613
|
+
fs30.writeFileSync(shipPath, `# Ship
|
|
6340
6614
|
|
|
6341
6615
|
Failed: ${msg}
|
|
6342
6616
|
`);
|
|
@@ -6385,15 +6659,15 @@ var init_executor_registry = __esm({
|
|
|
6385
6659
|
});
|
|
6386
6660
|
|
|
6387
6661
|
// src/pipeline/questions.ts
|
|
6388
|
-
import * as
|
|
6389
|
-
import * as
|
|
6662
|
+
import * as fs31 from "fs";
|
|
6663
|
+
import * as path29 from "path";
|
|
6390
6664
|
function checkForQuestions(ctx, stageName) {
|
|
6391
6665
|
if (ctx.input.local || !ctx.input.issueNumber) return false;
|
|
6392
6666
|
try {
|
|
6393
6667
|
if (stageName === "taskify") {
|
|
6394
|
-
const taskJsonPath =
|
|
6395
|
-
if (!
|
|
6396
|
-
const raw =
|
|
6668
|
+
const taskJsonPath = path29.join(ctx.taskDir, "task.json");
|
|
6669
|
+
if (!fs31.existsSync(taskJsonPath)) return false;
|
|
6670
|
+
const raw = fs31.readFileSync(taskJsonPath, "utf-8");
|
|
6397
6671
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
6398
6672
|
const taskJson = JSON.parse(cleaned);
|
|
6399
6673
|
if (taskJson.questions && Array.isArray(taskJson.questions) && taskJson.questions.length > 0) {
|
|
@@ -6408,9 +6682,9 @@ Reply with \`@kody approve\` and your answers in the comment body.`;
|
|
|
6408
6682
|
}
|
|
6409
6683
|
}
|
|
6410
6684
|
if (stageName === "plan") {
|
|
6411
|
-
const planPath =
|
|
6412
|
-
if (!
|
|
6413
|
-
const plan =
|
|
6685
|
+
const planPath = path29.join(ctx.taskDir, "plan.md");
|
|
6686
|
+
if (!fs31.existsSync(planPath)) return false;
|
|
6687
|
+
const plan = fs31.readFileSync(planPath, "utf-8");
|
|
6414
6688
|
const questionsMatch = plan.match(/## Questions\s*\n([\s\S]*?)(?=\n## |\n*$)/);
|
|
6415
6689
|
if (questionsMatch) {
|
|
6416
6690
|
const questionsText = questionsMatch[1].trim();
|
|
@@ -6439,8 +6713,8 @@ var init_questions = __esm({
|
|
|
6439
6713
|
});
|
|
6440
6714
|
|
|
6441
6715
|
// src/pipeline/hooks.ts
|
|
6442
|
-
import * as
|
|
6443
|
-
import * as
|
|
6716
|
+
import * as fs32 from "fs";
|
|
6717
|
+
import * as path30 from "path";
|
|
6444
6718
|
function applyPreStageLabel(ctx, def) {
|
|
6445
6719
|
if (!ctx.input.issueNumber || ctx.input.local) return;
|
|
6446
6720
|
if (def.name === "plan") setLifecycleLabel(ctx.input.issueNumber, "planning");
|
|
@@ -6481,9 +6755,9 @@ function autoDetectComplexity(ctx, def) {
|
|
|
6481
6755
|
return { complexity, activeStages };
|
|
6482
6756
|
}
|
|
6483
6757
|
try {
|
|
6484
|
-
const taskJsonPath =
|
|
6485
|
-
if (!
|
|
6486
|
-
const raw =
|
|
6758
|
+
const taskJsonPath = path30.join(ctx.taskDir, "task.json");
|
|
6759
|
+
if (!fs32.existsSync(taskJsonPath)) return null;
|
|
6760
|
+
const raw = fs32.readFileSync(taskJsonPath, "utf-8");
|
|
6487
6761
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
6488
6762
|
const taskJson = JSON.parse(cleaned);
|
|
6489
6763
|
if (!taskJson.risk_level || !isValidComplexity(taskJson.risk_level)) return null;
|
|
@@ -6513,8 +6787,8 @@ function checkRiskGate(ctx, def, state, complexity) {
|
|
|
6513
6787
|
if (ctx.input.dryRun || ctx.input.local) return null;
|
|
6514
6788
|
if (ctx.input.mode === "rerun") return null;
|
|
6515
6789
|
if (!ctx.input.issueNumber) return null;
|
|
6516
|
-
const planPath =
|
|
6517
|
-
const plan =
|
|
6790
|
+
const planPath = path30.join(ctx.taskDir, "plan.md");
|
|
6791
|
+
const plan = fs32.existsSync(planPath) ? fs32.readFileSync(planPath, "utf-8").slice(0, 1500) : "(plan not available)";
|
|
6518
6792
|
try {
|
|
6519
6793
|
postComment(
|
|
6520
6794
|
ctx.input.issueNumber,
|
|
@@ -6581,22 +6855,22 @@ var init_hooks = __esm({
|
|
|
6581
6855
|
});
|
|
6582
6856
|
|
|
6583
6857
|
// src/learning/auto-learn.ts
|
|
6584
|
-
import * as
|
|
6585
|
-
import * as
|
|
6858
|
+
import * as fs33 from "fs";
|
|
6859
|
+
import * as path31 from "path";
|
|
6586
6860
|
function stripAnsi(str) {
|
|
6587
6861
|
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
6588
6862
|
}
|
|
6589
6863
|
function autoLearn(ctx) {
|
|
6590
6864
|
try {
|
|
6591
|
-
const memoryDir =
|
|
6592
|
-
if (!
|
|
6593
|
-
|
|
6865
|
+
const memoryDir = path31.join(ctx.projectDir, ".kody", "memory");
|
|
6866
|
+
if (!fs33.existsSync(memoryDir)) {
|
|
6867
|
+
fs33.mkdirSync(memoryDir, { recursive: true });
|
|
6594
6868
|
}
|
|
6595
6869
|
const learnings = [];
|
|
6596
6870
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6597
|
-
const verifyPath =
|
|
6598
|
-
if (
|
|
6599
|
-
const verify = stripAnsi(
|
|
6871
|
+
const verifyPath = path31.join(ctx.taskDir, "verify.md");
|
|
6872
|
+
if (fs33.existsSync(verifyPath)) {
|
|
6873
|
+
const verify = stripAnsi(fs33.readFileSync(verifyPath, "utf-8"));
|
|
6600
6874
|
if (/vitest/i.test(verify)) learnings.push("- Uses vitest for testing");
|
|
6601
6875
|
if (/jest/i.test(verify)) learnings.push("- Uses jest for testing");
|
|
6602
6876
|
if (/eslint/i.test(verify)) learnings.push("- Uses eslint for linting");
|
|
@@ -6605,18 +6879,18 @@ function autoLearn(ctx) {
|
|
|
6605
6879
|
if (/jsdom/i.test(verify)) learnings.push("- Test environment: jsdom");
|
|
6606
6880
|
if (/node/i.test(verify) && /environment/i.test(verify)) learnings.push("- Test environment: node");
|
|
6607
6881
|
}
|
|
6608
|
-
const reviewPath =
|
|
6609
|
-
if (
|
|
6610
|
-
const review =
|
|
6882
|
+
const reviewPath = path31.join(ctx.taskDir, "review.md");
|
|
6883
|
+
if (fs33.existsSync(reviewPath)) {
|
|
6884
|
+
const review = fs33.readFileSync(reviewPath, "utf-8");
|
|
6611
6885
|
if (/\.js extension/i.test(review)) learnings.push("- Imports use .js extensions (ESM)");
|
|
6612
6886
|
if (/barrel export/i.test(review)) learnings.push("- Uses barrel exports (index.ts)");
|
|
6613
6887
|
if (/timezone/i.test(review)) learnings.push("- Timezone handling is a concern in this codebase");
|
|
6614
6888
|
if (/UTC/i.test(review)) learnings.push("- Date operations should consider UTC vs local time");
|
|
6615
6889
|
}
|
|
6616
|
-
const taskJsonPath =
|
|
6617
|
-
if (
|
|
6890
|
+
const taskJsonPath = path31.join(ctx.taskDir, "task.json");
|
|
6891
|
+
if (fs33.existsSync(taskJsonPath)) {
|
|
6618
6892
|
try {
|
|
6619
|
-
const raw = stripAnsi(
|
|
6893
|
+
const raw = stripAnsi(fs33.readFileSync(taskJsonPath, "utf-8"));
|
|
6620
6894
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
6621
6895
|
const task = JSON.parse(cleaned);
|
|
6622
6896
|
if (task.scope && Array.isArray(task.scope)) {
|
|
@@ -6627,12 +6901,12 @@ function autoLearn(ctx) {
|
|
|
6627
6901
|
}
|
|
6628
6902
|
}
|
|
6629
6903
|
if (learnings.length > 0) {
|
|
6630
|
-
const conventionsPath =
|
|
6904
|
+
const conventionsPath = path31.join(memoryDir, "conventions.md");
|
|
6631
6905
|
const entry = `
|
|
6632
6906
|
## Learned ${timestamp2} (task: ${ctx.taskId})
|
|
6633
6907
|
${learnings.join("\n")}
|
|
6634
6908
|
`;
|
|
6635
|
-
|
|
6909
|
+
fs33.appendFileSync(conventionsPath, entry);
|
|
6636
6910
|
logger.info(`Auto-learned ${learnings.length} convention(s)`);
|
|
6637
6911
|
}
|
|
6638
6912
|
autoLearnArchitecture(ctx.projectDir, memoryDir, timestamp2);
|
|
@@ -6640,8 +6914,8 @@ ${learnings.join("\n")}
|
|
|
6640
6914
|
}
|
|
6641
6915
|
}
|
|
6642
6916
|
function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
6643
|
-
const archPath =
|
|
6644
|
-
if (
|
|
6917
|
+
const archPath = path31.join(memoryDir, "architecture.md");
|
|
6918
|
+
if (fs33.existsSync(archPath)) return;
|
|
6645
6919
|
const detected = detectArchitectureBasic(projectDir);
|
|
6646
6920
|
if (detected.length > 0) {
|
|
6647
6921
|
const content = `# Architecture (auto-detected ${timestamp2})
|
|
@@ -6649,7 +6923,7 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
|
6649
6923
|
## Overview
|
|
6650
6924
|
${detected.join("\n")}
|
|
6651
6925
|
`;
|
|
6652
|
-
|
|
6926
|
+
fs33.writeFileSync(archPath, content);
|
|
6653
6927
|
logger.info(`Auto-detected architecture (${detected.length} items)`);
|
|
6654
6928
|
}
|
|
6655
6929
|
}
|
|
@@ -6662,13 +6936,13 @@ var init_auto_learn = __esm({
|
|
|
6662
6936
|
});
|
|
6663
6937
|
|
|
6664
6938
|
// src/retrospective.ts
|
|
6665
|
-
import * as
|
|
6666
|
-
import * as
|
|
6939
|
+
import * as fs34 from "fs";
|
|
6940
|
+
import * as path32 from "path";
|
|
6667
6941
|
function readArtifact(taskDir, filename, maxChars) {
|
|
6668
|
-
const p =
|
|
6669
|
-
if (!
|
|
6942
|
+
const p = path32.join(taskDir, filename);
|
|
6943
|
+
if (!fs34.existsSync(p)) return null;
|
|
6670
6944
|
try {
|
|
6671
|
-
const content =
|
|
6945
|
+
const content = fs34.readFileSync(p, "utf-8");
|
|
6672
6946
|
return content.length > maxChars ? content.slice(0, maxChars) + "\n...(truncated)" : content;
|
|
6673
6947
|
} catch {
|
|
6674
6948
|
return null;
|
|
@@ -6721,13 +6995,13 @@ function collectRunContext(ctx, state, pipelineStartTime) {
|
|
|
6721
6995
|
return lines.join("\n");
|
|
6722
6996
|
}
|
|
6723
6997
|
function getLogPath(projectDir) {
|
|
6724
|
-
return
|
|
6998
|
+
return path32.join(projectDir, ".kody", "memory", "observer-log.jsonl");
|
|
6725
6999
|
}
|
|
6726
7000
|
function readPreviousRetrospectives(projectDir, limit = 10) {
|
|
6727
7001
|
const logPath = getLogPath(projectDir);
|
|
6728
|
-
if (!
|
|
7002
|
+
if (!fs34.existsSync(logPath)) return [];
|
|
6729
7003
|
try {
|
|
6730
|
-
const content =
|
|
7004
|
+
const content = fs34.readFileSync(logPath, "utf-8");
|
|
6731
7005
|
const lines = content.split("\n").filter(Boolean);
|
|
6732
7006
|
const entries = [];
|
|
6733
7007
|
const start = Math.max(0, lines.length - limit);
|
|
@@ -6754,11 +7028,11 @@ function formatPreviousEntries(entries) {
|
|
|
6754
7028
|
}
|
|
6755
7029
|
function appendRetrospectiveEntry(projectDir, entry) {
|
|
6756
7030
|
const logPath = getLogPath(projectDir);
|
|
6757
|
-
const dir =
|
|
6758
|
-
if (!
|
|
6759
|
-
|
|
7031
|
+
const dir = path32.dirname(logPath);
|
|
7032
|
+
if (!fs34.existsSync(dir)) {
|
|
7033
|
+
fs34.mkdirSync(dir, { recursive: true });
|
|
6760
7034
|
}
|
|
6761
|
-
|
|
7035
|
+
fs34.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
6762
7036
|
}
|
|
6763
7037
|
async function runRetrospective(ctx, state, pipelineStartTime) {
|
|
6764
7038
|
if (ctx.input.dryRun) return;
|
|
@@ -6926,15 +7200,15 @@ var init_summary = __esm({
|
|
|
6926
7200
|
});
|
|
6927
7201
|
|
|
6928
7202
|
// src/tools.ts
|
|
6929
|
-
import * as
|
|
6930
|
-
import * as
|
|
7203
|
+
import * as fs35 from "fs";
|
|
7204
|
+
import * as path33 from "path";
|
|
6931
7205
|
import { execSync as execSync3 } from "child_process";
|
|
6932
7206
|
import { parse as parseYaml } from "yaml";
|
|
6933
7207
|
function loadToolDeclarations(projectDir) {
|
|
6934
|
-
const toolsPath =
|
|
6935
|
-
if (!
|
|
7208
|
+
const toolsPath = path33.join(projectDir, ".kody", "tools.yml");
|
|
7209
|
+
if (!fs35.existsSync(toolsPath)) return [];
|
|
6936
7210
|
try {
|
|
6937
|
-
const raw =
|
|
7211
|
+
const raw = fs35.readFileSync(toolsPath, "utf-8");
|
|
6938
7212
|
const parsed = parseYaml(raw);
|
|
6939
7213
|
if (!parsed || typeof parsed !== "object") return [];
|
|
6940
7214
|
return Object.entries(parsed).map(([name, value]) => {
|
|
@@ -6955,7 +7229,7 @@ function loadToolDeclarations(projectDir) {
|
|
|
6955
7229
|
function detectTools(declarations, projectDir) {
|
|
6956
7230
|
const resolved = [];
|
|
6957
7231
|
for (const decl of declarations) {
|
|
6958
|
-
const detected = decl.detect.some((pattern) =>
|
|
7232
|
+
const detected = decl.detect.some((pattern) => fs35.existsSync(path33.join(projectDir, pattern)));
|
|
6959
7233
|
if (!detected) continue;
|
|
6960
7234
|
resolved.push({
|
|
6961
7235
|
name: decl.name,
|
|
@@ -6996,8 +7270,8 @@ var init_tools = __esm({
|
|
|
6996
7270
|
});
|
|
6997
7271
|
|
|
6998
7272
|
// src/pipeline.ts
|
|
6999
|
-
import * as
|
|
7000
|
-
import * as
|
|
7273
|
+
import * as fs36 from "fs";
|
|
7274
|
+
import * as path34 from "path";
|
|
7001
7275
|
function ensureFeatureBranchIfNeeded(ctx) {
|
|
7002
7276
|
if (ctx.input.dryRun) return;
|
|
7003
7277
|
if (ctx.input.prNumber) {
|
|
@@ -7010,8 +7284,8 @@ function ensureFeatureBranchIfNeeded(ctx) {
|
|
|
7010
7284
|
}
|
|
7011
7285
|
if (!ctx.input.issueNumber) return;
|
|
7012
7286
|
try {
|
|
7013
|
-
const taskMdPath =
|
|
7014
|
-
const title =
|
|
7287
|
+
const taskMdPath = path34.join(ctx.taskDir, "task.md");
|
|
7288
|
+
const title = fs36.existsSync(taskMdPath) ? fs36.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
|
|
7015
7289
|
ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
|
|
7016
7290
|
syncWithDefault(ctx.projectDir);
|
|
7017
7291
|
} catch (err) {
|
|
@@ -7025,10 +7299,10 @@ function ensureFeatureBranchIfNeeded(ctx) {
|
|
|
7025
7299
|
}
|
|
7026
7300
|
}
|
|
7027
7301
|
function acquireLock(taskDir) {
|
|
7028
|
-
const lockPath =
|
|
7029
|
-
if (
|
|
7302
|
+
const lockPath = path34.join(taskDir, ".lock");
|
|
7303
|
+
if (fs36.existsSync(lockPath)) {
|
|
7030
7304
|
try {
|
|
7031
|
-
const pid = parseInt(
|
|
7305
|
+
const pid = parseInt(fs36.readFileSync(lockPath, "utf-8").trim(), 10);
|
|
7032
7306
|
if (!isNaN(pid)) {
|
|
7033
7307
|
try {
|
|
7034
7308
|
process.kill(pid, 0);
|
|
@@ -7045,14 +7319,14 @@ function acquireLock(taskDir) {
|
|
|
7045
7319
|
logger.warn(` Corrupt lock file \u2014 overwriting`);
|
|
7046
7320
|
}
|
|
7047
7321
|
try {
|
|
7048
|
-
|
|
7322
|
+
fs36.unlinkSync(lockPath);
|
|
7049
7323
|
} catch {
|
|
7050
7324
|
}
|
|
7051
7325
|
}
|
|
7052
7326
|
try {
|
|
7053
|
-
const fd =
|
|
7054
|
-
|
|
7055
|
-
|
|
7327
|
+
const fd = fs36.openSync(lockPath, fs36.constants.O_WRONLY | fs36.constants.O_CREAT | fs36.constants.O_EXCL);
|
|
7328
|
+
fs36.writeSync(fd, String(process.pid));
|
|
7329
|
+
fs36.closeSync(fd);
|
|
7056
7330
|
} catch (err) {
|
|
7057
7331
|
if (err.code === "EEXIST") {
|
|
7058
7332
|
throw new Error("Pipeline already running (lock acquired by another process)");
|
|
@@ -7062,7 +7336,7 @@ function acquireLock(taskDir) {
|
|
|
7062
7336
|
}
|
|
7063
7337
|
function releaseLock(taskDir) {
|
|
7064
7338
|
try {
|
|
7065
|
-
|
|
7339
|
+
fs36.unlinkSync(path34.join(taskDir, ".lock"));
|
|
7066
7340
|
} catch {
|
|
7067
7341
|
}
|
|
7068
7342
|
}
|
|
@@ -7279,7 +7553,7 @@ var init_pipeline = __esm({
|
|
|
7279
7553
|
|
|
7280
7554
|
// src/preflight.ts
|
|
7281
7555
|
import { execFileSync as execFileSync18 } from "child_process";
|
|
7282
|
-
import * as
|
|
7556
|
+
import * as fs37 from "fs";
|
|
7283
7557
|
function check(name, fn) {
|
|
7284
7558
|
try {
|
|
7285
7559
|
const detail = fn() ?? void 0;
|
|
@@ -7332,7 +7606,7 @@ function runPreflight() {
|
|
|
7332
7606
|
return v;
|
|
7333
7607
|
}),
|
|
7334
7608
|
check("package.json", () => {
|
|
7335
|
-
if (!
|
|
7609
|
+
if (!fs37.existsSync("package.json")) throw new Error("not found");
|
|
7336
7610
|
})
|
|
7337
7611
|
];
|
|
7338
7612
|
const failed = checks.filter((c) => !c.ok);
|
|
@@ -7409,8 +7683,8 @@ var init_args = __esm({
|
|
|
7409
7683
|
});
|
|
7410
7684
|
|
|
7411
7685
|
// src/cli/task-state.ts
|
|
7412
|
-
import * as
|
|
7413
|
-
import * as
|
|
7686
|
+
import * as fs38 from "fs";
|
|
7687
|
+
import * as path35 from "path";
|
|
7414
7688
|
function resolveTaskAction(issueNumber, existingTaskId, existingState) {
|
|
7415
7689
|
if (!existingTaskId || !existingState) {
|
|
7416
7690
|
return { action: "start-fresh", taskId: `${issueNumber}-${generateTaskId()}` };
|
|
@@ -7442,11 +7716,11 @@ function resolveTaskAction(issueNumber, existingTaskId, existingState) {
|
|
|
7442
7716
|
function resolveForIssue(issueNumber, projectDir) {
|
|
7443
7717
|
const existingTaskId = findLatestTaskForIssue(issueNumber, projectDir);
|
|
7444
7718
|
if (existingTaskId) {
|
|
7445
|
-
const statusPath =
|
|
7719
|
+
const statusPath = path35.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
|
|
7446
7720
|
let existingState = null;
|
|
7447
|
-
if (
|
|
7721
|
+
if (fs38.existsSync(statusPath)) {
|
|
7448
7722
|
try {
|
|
7449
|
-
existingState = JSON.parse(
|
|
7723
|
+
existingState = JSON.parse(fs38.readFileSync(statusPath, "utf-8"));
|
|
7450
7724
|
} catch {
|
|
7451
7725
|
}
|
|
7452
7726
|
}
|
|
@@ -7642,8 +7916,8 @@ var init_resolve = __esm({
|
|
|
7642
7916
|
|
|
7643
7917
|
// src/entry.ts
|
|
7644
7918
|
var entry_exports = {};
|
|
7645
|
-
import * as
|
|
7646
|
-
import * as
|
|
7919
|
+
import * as fs39 from "fs";
|
|
7920
|
+
import * as path36 from "path";
|
|
7647
7921
|
async function ensureLitellmProxy(config, projectDir) {
|
|
7648
7922
|
if (!anyStageNeedsProxy(config)) return null;
|
|
7649
7923
|
const litellmUrl = getLitellmUrl();
|
|
@@ -7698,9 +7972,9 @@ async function runModelHealthCheck(config) {
|
|
|
7698
7972
|
}
|
|
7699
7973
|
async function main() {
|
|
7700
7974
|
const input = parseArgs();
|
|
7701
|
-
const projectDir = input.cwd ?
|
|
7975
|
+
const projectDir = input.cwd ? path36.resolve(input.cwd) : process.cwd();
|
|
7702
7976
|
if (input.cwd) {
|
|
7703
|
-
if (!
|
|
7977
|
+
if (!fs39.existsSync(projectDir)) {
|
|
7704
7978
|
console.error(`--cwd path does not exist: ${projectDir}`);
|
|
7705
7979
|
process.exit(1);
|
|
7706
7980
|
}
|
|
@@ -7760,8 +8034,8 @@ async function main() {
|
|
|
7760
8034
|
process.exit(1);
|
|
7761
8035
|
}
|
|
7762
8036
|
}
|
|
7763
|
-
const taskDir =
|
|
7764
|
-
|
|
8037
|
+
const taskDir = path36.join(projectDir, ".kody", "tasks", taskId);
|
|
8038
|
+
fs39.mkdirSync(taskDir, { recursive: true });
|
|
7765
8039
|
if (input.command === "rerun" && isTaskifyRun(taskDir)) {
|
|
7766
8040
|
const marker = readTaskifyMarker(taskDir);
|
|
7767
8041
|
if (marker) {
|
|
@@ -7893,31 +8167,31 @@ async function main() {
|
|
|
7893
8167
|
logger.info("Preflight checks:");
|
|
7894
8168
|
runPreflight();
|
|
7895
8169
|
if (input.task) {
|
|
7896
|
-
|
|
8170
|
+
fs39.writeFileSync(path36.join(taskDir, "task.md"), input.task);
|
|
7897
8171
|
}
|
|
7898
|
-
const taskMdPath =
|
|
7899
|
-
if (!
|
|
8172
|
+
const taskMdPath = path36.join(taskDir, "task.md");
|
|
8173
|
+
if (!fs39.existsSync(taskMdPath) && isPRFix && input.prNumber) {
|
|
7900
8174
|
logger.info(`Fetching PR #${input.prNumber} details as task context...`);
|
|
7901
8175
|
const prDetails = getPRDetails(input.prNumber);
|
|
7902
8176
|
if (prDetails) {
|
|
7903
8177
|
const taskContent = `# ${prDetails.title}
|
|
7904
8178
|
|
|
7905
8179
|
${prDetails.body ?? ""}`;
|
|
7906
|
-
|
|
8180
|
+
fs39.writeFileSync(taskMdPath, taskContent);
|
|
7907
8181
|
logger.info(` Task loaded from PR #${input.prNumber}: ${prDetails.title}`);
|
|
7908
8182
|
}
|
|
7909
|
-
} else if (!
|
|
8183
|
+
} else if (!fs39.existsSync(taskMdPath) && input.issueNumber) {
|
|
7910
8184
|
logger.info(`Fetching issue #${input.issueNumber} body as task...`);
|
|
7911
8185
|
const issue = getIssue(input.issueNumber);
|
|
7912
8186
|
if (issue) {
|
|
7913
8187
|
const taskContent = `# ${issue.title}
|
|
7914
8188
|
|
|
7915
8189
|
${issue.body ?? ""}`;
|
|
7916
|
-
|
|
8190
|
+
fs39.writeFileSync(taskMdPath, taskContent);
|
|
7917
8191
|
logger.info(` Task loaded from issue #${input.issueNumber}: ${issue.title}`);
|
|
7918
8192
|
}
|
|
7919
8193
|
}
|
|
7920
|
-
if (!
|
|
8194
|
+
if (!fs39.existsSync(taskMdPath)) {
|
|
7921
8195
|
console.error("No task.md found. Provide --task, --issue-number, or ensure .kody/tasks/<id>/task.md exists.");
|
|
7922
8196
|
process.exit(1);
|
|
7923
8197
|
}
|
|
@@ -8061,7 +8335,7 @@ To rerun: \`@kody rerun ${taskId} --from <stage>\``
|
|
|
8061
8335
|
}
|
|
8062
8336
|
}
|
|
8063
8337
|
const state = await runPipeline(ctx);
|
|
8064
|
-
const files =
|
|
8338
|
+
const files = fs39.readdirSync(taskDir);
|
|
8065
8339
|
console.log(`
|
|
8066
8340
|
Artifacts in ${taskDir}:`);
|
|
8067
8341
|
for (const f of files) {
|
|
@@ -8143,8 +8417,8 @@ var init_entry = __esm({
|
|
|
8143
8417
|
});
|
|
8144
8418
|
|
|
8145
8419
|
// src/bin/cli.ts
|
|
8146
|
-
import * as
|
|
8147
|
-
import * as
|
|
8420
|
+
import * as fs40 from "fs";
|
|
8421
|
+
import * as path37 from "path";
|
|
8148
8422
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8149
8423
|
|
|
8150
8424
|
// src/bin/commands/init.ts
|
|
@@ -8475,6 +8749,24 @@ function initCommand(opts, pkgRoot) {
|
|
|
8475
8749
|
console.log(" \u2139 Kody Watch will monitor pipeline health every 30 minutes");
|
|
8476
8750
|
console.log(" \u2139 Digest issue will be created during bootstrap");
|
|
8477
8751
|
}
|
|
8752
|
+
const agentTemplatesDir = path2.join(templatesDir, "watch-agents");
|
|
8753
|
+
const watchAgentsDir = path2.join(cwd, ".kody", "watch", "agents");
|
|
8754
|
+
if (fs3.existsSync(agentTemplatesDir)) {
|
|
8755
|
+
const agentDirs = fs3.readdirSync(agentTemplatesDir, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
8756
|
+
for (const agentDir of agentDirs) {
|
|
8757
|
+
const destDir = path2.join(watchAgentsDir, agentDir.name);
|
|
8758
|
+
if (fs3.existsSync(destDir) && !opts.force) {
|
|
8759
|
+
console.log(` \u25CB .kody/watch/agents/${agentDir.name} (exists)`);
|
|
8760
|
+
continue;
|
|
8761
|
+
}
|
|
8762
|
+
fs3.mkdirSync(destDir, { recursive: true });
|
|
8763
|
+
const srcDir = path2.join(agentTemplatesDir, agentDir.name);
|
|
8764
|
+
for (const file of fs3.readdirSync(srcDir)) {
|
|
8765
|
+
fs3.copyFileSync(path2.join(srcDir, file), path2.join(destDir, file));
|
|
8766
|
+
}
|
|
8767
|
+
console.log(` \u2713 .kody/watch/agents/${agentDir.name}`);
|
|
8768
|
+
}
|
|
8769
|
+
}
|
|
8478
8770
|
} else {
|
|
8479
8771
|
console.log(" \u25CB kody-watch.yml template not found \u2014 skipping");
|
|
8480
8772
|
}
|
|
@@ -9984,11 +10276,11 @@ Create it manually.`, cwd);
|
|
|
9984
10276
|
|
|
9985
10277
|
// src/bin/cli.ts
|
|
9986
10278
|
init_architecture_detection();
|
|
9987
|
-
var __dirname2 =
|
|
9988
|
-
var PKG_ROOT =
|
|
10279
|
+
var __dirname2 = path37.dirname(fileURLToPath2(import.meta.url));
|
|
10280
|
+
var PKG_ROOT = path37.resolve(__dirname2, "..", "..");
|
|
9989
10281
|
function getVersion() {
|
|
9990
|
-
const pkgPath =
|
|
9991
|
-
const pkg = JSON.parse(
|
|
10282
|
+
const pkgPath = path37.join(PKG_ROOT, "package.json");
|
|
10283
|
+
const pkg = JSON.parse(fs40.readFileSync(pkgPath, "utf-8"));
|
|
9992
10284
|
return pkg.version;
|
|
9993
10285
|
}
|
|
9994
10286
|
var args = process.argv.slice(2);
|