@kody-ade/kody-engine-lite 0.1.147 → 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 +533 -239
- 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");
|
|
@@ -3287,9 +3292,92 @@ var init_logger2 = __esm({
|
|
|
3287
3292
|
}
|
|
3288
3293
|
});
|
|
3289
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
|
+
|
|
3290
3378
|
// src/watch/core/watch.ts
|
|
3291
3379
|
async function runWatch(config) {
|
|
3292
|
-
const { repo, dryRun, stateFile, plugins } = config;
|
|
3380
|
+
const { repo, dryRun, stateFile, plugins, agents } = config;
|
|
3293
3381
|
const token = process.env.GH_TOKEN || "";
|
|
3294
3382
|
const github = createGitHubClient(repo, token);
|
|
3295
3383
|
const state = createStateStore(stateFile, github, config.digestIssue);
|
|
@@ -3370,6 +3458,45 @@ async function runWatch(config) {
|
|
|
3370
3458
|
} else {
|
|
3371
3459
|
log2.info({ actionCount: dedupedActions.length }, "Dry run \u2014 skipping action execution");
|
|
3372
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
|
+
}
|
|
3373
3500
|
state.save();
|
|
3374
3501
|
const result2 = {
|
|
3375
3502
|
cycleNumber,
|
|
@@ -3377,6 +3504,8 @@ async function runWatch(config) {
|
|
|
3377
3504
|
actionsProduced: allActions.length,
|
|
3378
3505
|
actionsExecuted,
|
|
3379
3506
|
actionsDeduplicated,
|
|
3507
|
+
agentsRun: scheduledAgents.length,
|
|
3508
|
+
agentResults,
|
|
3380
3509
|
errors
|
|
3381
3510
|
};
|
|
3382
3511
|
log2.info(
|
|
@@ -3386,6 +3515,7 @@ async function runWatch(config) {
|
|
|
3386
3515
|
actionsProduced: result2.actionsProduced,
|
|
3387
3516
|
actionsExecuted: result2.actionsExecuted,
|
|
3388
3517
|
actionsDeduplicated: result2.actionsDeduplicated,
|
|
3518
|
+
agentsRun: result2.agentsRun,
|
|
3389
3519
|
errors: result2.errors.length
|
|
3390
3520
|
},
|
|
3391
3521
|
"Watch cycle completed"
|
|
@@ -3399,6 +3529,7 @@ var init_watch = __esm({
|
|
|
3399
3529
|
init_dedup();
|
|
3400
3530
|
init_github();
|
|
3401
3531
|
init_logger2();
|
|
3532
|
+
init_run_agent();
|
|
3402
3533
|
}
|
|
3403
3534
|
});
|
|
3404
3535
|
|
|
@@ -4109,27 +4240,134 @@ var init_config_health = __esm({
|
|
|
4109
4240
|
}
|
|
4110
4241
|
});
|
|
4111
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
|
+
|
|
4112
4333
|
// src/watch/index.ts
|
|
4113
4334
|
var watch_exports = {};
|
|
4114
4335
|
__export(watch_exports, {
|
|
4115
4336
|
runWatchCommand: () => runWatchCommand
|
|
4116
4337
|
});
|
|
4117
|
-
import * as
|
|
4118
|
-
import * as
|
|
4338
|
+
import * as fs20 from "fs";
|
|
4339
|
+
import * as path18 from "path";
|
|
4119
4340
|
async function runWatchCommand(opts) {
|
|
4120
4341
|
const cwd = process.cwd();
|
|
4121
|
-
|
|
4342
|
+
let litellmProcess = null;
|
|
4343
|
+
const configPath = path18.join(cwd, "kody.config.json");
|
|
4122
4344
|
let repo = process.env.REPO || "";
|
|
4123
4345
|
let digestIssue;
|
|
4124
|
-
|
|
4346
|
+
let watchModel;
|
|
4347
|
+
let agentProvider;
|
|
4348
|
+
let agentModelMap;
|
|
4349
|
+
let defaultCheapModel = "claude-haiku-4-5-20251001";
|
|
4350
|
+
if (!repo && fs20.existsSync(configPath)) {
|
|
4125
4351
|
try {
|
|
4126
|
-
const config2 = JSON.parse(
|
|
4352
|
+
const config2 = JSON.parse(fs20.readFileSync(configPath, "utf-8"));
|
|
4127
4353
|
if (config2.github?.owner && config2.github?.repo) {
|
|
4128
4354
|
repo = `${config2.github.owner}/${config2.github.repo}`;
|
|
4129
4355
|
}
|
|
4130
4356
|
if (config2.watch?.digestIssue) {
|
|
4131
4357
|
digestIssue = config2.watch.digestIssue;
|
|
4132
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
|
+
}
|
|
4133
4371
|
} catch {
|
|
4134
4372
|
}
|
|
4135
4373
|
}
|
|
@@ -4148,12 +4386,43 @@ async function runWatchCommand(opts) {
|
|
|
4148
4386
|
registry.register(pipelineHealthPlugin);
|
|
4149
4387
|
registry.register(securityScanPlugin);
|
|
4150
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
|
+
}
|
|
4151
4416
|
const config = {
|
|
4152
4417
|
repo,
|
|
4153
4418
|
dryRun: opts.dryRun,
|
|
4154
|
-
stateFile:
|
|
4419
|
+
stateFile: path18.join(cwd, ".kody", "watch-state.json"),
|
|
4155
4420
|
plugins: registry.getAll(),
|
|
4156
|
-
digestIssue
|
|
4421
|
+
digestIssue,
|
|
4422
|
+
agents,
|
|
4423
|
+
model,
|
|
4424
|
+
provider: agentProvider,
|
|
4425
|
+
projectDir: cwd
|
|
4157
4426
|
};
|
|
4158
4427
|
console.log(`
|
|
4159
4428
|
Kody Watch \u2014 repo: ${repo}, dry-run: ${opts.dryRun}
|
|
@@ -4165,13 +4434,17 @@ Kody Watch \u2014 repo: ${repo}, dry-run: ${opts.dryRun}
|
|
|
4165
4434
|
console.warn(` Warning: ${error}`);
|
|
4166
4435
|
}
|
|
4167
4436
|
}
|
|
4437
|
+
const agentSummary = result2.agentsRun > 0 ? `, ${result2.agentsRun} agents` : "";
|
|
4168
4438
|
console.log(`
|
|
4169
|
-
Cycle #${result2.cycleNumber} complete: ${result2.pluginsRun} plugins, ${result2.actionsExecuted} actions, ${result2.actionsDeduplicated} deduplicated`);
|
|
4170
|
-
process.exit(0);
|
|
4439
|
+
Cycle #${result2.cycleNumber} complete: ${result2.pluginsRun} plugins${agentSummary}, ${result2.actionsExecuted} actions, ${result2.actionsDeduplicated} deduplicated`);
|
|
4171
4440
|
} catch (error) {
|
|
4172
4441
|
const message = error instanceof Error ? error.message : String(error);
|
|
4173
4442
|
console.error(`Watch failed: ${message}`);
|
|
4174
|
-
|
|
4443
|
+
} finally {
|
|
4444
|
+
if (litellmProcess) {
|
|
4445
|
+
litellmProcess.kill("SIGTERM");
|
|
4446
|
+
}
|
|
4447
|
+
process.exit(0);
|
|
4175
4448
|
}
|
|
4176
4449
|
}
|
|
4177
4450
|
var init_watch2 = __esm({
|
|
@@ -4182,6 +4455,9 @@ var init_watch2 = __esm({
|
|
|
4182
4455
|
init_pipeline_health();
|
|
4183
4456
|
init_security_scan();
|
|
4184
4457
|
init_config_health();
|
|
4458
|
+
init_loader();
|
|
4459
|
+
init_litellm();
|
|
4460
|
+
init_config();
|
|
4185
4461
|
}
|
|
4186
4462
|
});
|
|
4187
4463
|
|
|
@@ -4460,14 +4736,14 @@ var init_git_utils = __esm({
|
|
|
4460
4736
|
});
|
|
4461
4737
|
|
|
4462
4738
|
// src/pipeline/state.ts
|
|
4463
|
-
import * as
|
|
4464
|
-
import * as
|
|
4739
|
+
import * as fs21 from "fs";
|
|
4740
|
+
import * as path19 from "path";
|
|
4465
4741
|
function loadState(taskId, taskDir) {
|
|
4466
|
-
const p =
|
|
4467
|
-
if (!
|
|
4742
|
+
const p = path19.join(taskDir, "status.json");
|
|
4743
|
+
if (!fs21.existsSync(p)) return null;
|
|
4468
4744
|
try {
|
|
4469
4745
|
const result2 = parseJsonSafe(
|
|
4470
|
-
|
|
4746
|
+
fs21.readFileSync(p, "utf-8"),
|
|
4471
4747
|
["taskId", "state", "stages", "createdAt", "updatedAt"]
|
|
4472
4748
|
);
|
|
4473
4749
|
if (!result2.ok) {
|
|
@@ -4485,10 +4761,10 @@ function writeState(state, taskDir) {
|
|
|
4485
4761
|
...state,
|
|
4486
4762
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4487
4763
|
};
|
|
4488
|
-
const target =
|
|
4764
|
+
const target = path19.join(taskDir, "status.json");
|
|
4489
4765
|
const tmp = target + ".tmp";
|
|
4490
|
-
|
|
4491
|
-
|
|
4766
|
+
fs21.writeFileSync(tmp, JSON.stringify(updated, null, 2));
|
|
4767
|
+
fs21.renameSync(tmp, target);
|
|
4492
4768
|
return updated;
|
|
4493
4769
|
}
|
|
4494
4770
|
function initState(taskId) {
|
|
@@ -4529,16 +4805,16 @@ var init_complexity = __esm({
|
|
|
4529
4805
|
});
|
|
4530
4806
|
|
|
4531
4807
|
// src/memory.ts
|
|
4532
|
-
import * as
|
|
4533
|
-
import * as
|
|
4808
|
+
import * as fs22 from "fs";
|
|
4809
|
+
import * as path20 from "path";
|
|
4534
4810
|
function readProjectMemory(projectDir) {
|
|
4535
|
-
const memoryDir =
|
|
4536
|
-
if (!
|
|
4537
|
-
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();
|
|
4538
4814
|
if (files.length === 0) return "";
|
|
4539
4815
|
const sections = [];
|
|
4540
4816
|
for (const file of files) {
|
|
4541
|
-
const content =
|
|
4817
|
+
const content = fs22.readFileSync(path20.join(memoryDir, file), "utf-8").trim();
|
|
4542
4818
|
if (content) {
|
|
4543
4819
|
sections.push(`## ${file.replace(".md", "")}
|
|
4544
4820
|
${content}`);
|
|
@@ -4557,8 +4833,8 @@ var init_memory = __esm({
|
|
|
4557
4833
|
});
|
|
4558
4834
|
|
|
4559
4835
|
// src/context-tiers.ts
|
|
4560
|
-
import * as
|
|
4561
|
-
import * as
|
|
4836
|
+
import * as fs23 from "fs";
|
|
4837
|
+
import * as path21 from "path";
|
|
4562
4838
|
function estimateTokens(text) {
|
|
4563
4839
|
return Math.ceil(text.length / 4);
|
|
4564
4840
|
}
|
|
@@ -4649,7 +4925,7 @@ function generateL1Json(content) {
|
|
|
4649
4925
|
}
|
|
4650
4926
|
}
|
|
4651
4927
|
function getTieredContent(filePath, content) {
|
|
4652
|
-
const key =
|
|
4928
|
+
const key = path21.basename(filePath);
|
|
4653
4929
|
return {
|
|
4654
4930
|
source: filePath,
|
|
4655
4931
|
L0: generateL0(content, key),
|
|
@@ -4661,15 +4937,15 @@ function selectTier(tiered, tier) {
|
|
|
4661
4937
|
return tiered[tier];
|
|
4662
4938
|
}
|
|
4663
4939
|
function readProjectMemoryTiered(projectDir, tier) {
|
|
4664
|
-
const memoryDir =
|
|
4665
|
-
if (!
|
|
4666
|
-
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();
|
|
4667
4943
|
if (files.length === 0) return "";
|
|
4668
4944
|
const tierLabel2 = tier === "L2" ? "full" : tier === "L1" ? "overview" : "abstract";
|
|
4669
4945
|
const sections = [];
|
|
4670
4946
|
for (const file of files) {
|
|
4671
|
-
const filePath =
|
|
4672
|
-
const content =
|
|
4947
|
+
const filePath = path21.join(memoryDir, file);
|
|
4948
|
+
const content = fs23.readFileSync(filePath, "utf-8").trim();
|
|
4673
4949
|
if (!content) continue;
|
|
4674
4950
|
const tiered = getTieredContent(filePath, content);
|
|
4675
4951
|
const selected = selectTier(tiered, tier);
|
|
@@ -4692,9 +4968,9 @@ function injectTaskContextTiered(prompt, taskId, taskDir, policy, feedback) {
|
|
|
4692
4968
|
`;
|
|
4693
4969
|
context += `Task Directory: ${taskDir}
|
|
4694
4970
|
`;
|
|
4695
|
-
const taskMdPath =
|
|
4696
|
-
if (
|
|
4697
|
-
const content =
|
|
4971
|
+
const taskMdPath = path21.join(taskDir, "task.md");
|
|
4972
|
+
if (fs23.existsSync(taskMdPath)) {
|
|
4973
|
+
const content = fs23.readFileSync(taskMdPath, "utf-8");
|
|
4698
4974
|
const selected = selectContent(taskMdPath, content, policy.taskDescription);
|
|
4699
4975
|
const label = tierLabel("Task Description", policy.taskDescription);
|
|
4700
4976
|
context += `
|
|
@@ -4702,9 +4978,9 @@ function injectTaskContextTiered(prompt, taskId, taskDir, policy, feedback) {
|
|
|
4702
4978
|
${selected}
|
|
4703
4979
|
`;
|
|
4704
4980
|
}
|
|
4705
|
-
const taskJsonPath =
|
|
4706
|
-
if (
|
|
4707
|
-
const content =
|
|
4981
|
+
const taskJsonPath = path21.join(taskDir, "task.json");
|
|
4982
|
+
if (fs23.existsSync(taskJsonPath)) {
|
|
4983
|
+
const content = fs23.readFileSync(taskJsonPath, "utf-8");
|
|
4708
4984
|
if (policy.taskClassification === "L2") {
|
|
4709
4985
|
try {
|
|
4710
4986
|
const taskDef = JSON.parse(content.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, ""));
|
|
@@ -4730,9 +5006,9 @@ ${selected}
|
|
|
4730
5006
|
}
|
|
4731
5007
|
}
|
|
4732
5008
|
}
|
|
4733
|
-
const specPath =
|
|
4734
|
-
if (
|
|
4735
|
-
const content =
|
|
5009
|
+
const specPath = path21.join(taskDir, "spec.md");
|
|
5010
|
+
if (fs23.existsSync(specPath)) {
|
|
5011
|
+
const content = fs23.readFileSync(specPath, "utf-8");
|
|
4736
5012
|
const selected = selectContent(specPath, content, policy.spec);
|
|
4737
5013
|
const label = tierLabel("Spec", policy.spec);
|
|
4738
5014
|
context += `
|
|
@@ -4740,9 +5016,9 @@ ${selected}
|
|
|
4740
5016
|
${selected}
|
|
4741
5017
|
`;
|
|
4742
5018
|
}
|
|
4743
|
-
const planPath =
|
|
4744
|
-
if (
|
|
4745
|
-
const content =
|
|
5019
|
+
const planPath = path21.join(taskDir, "plan.md");
|
|
5020
|
+
if (fs23.existsSync(planPath)) {
|
|
5021
|
+
const content = fs23.readFileSync(planPath, "utf-8");
|
|
4746
5022
|
const selected = selectContent(planPath, content, policy.plan);
|
|
4747
5023
|
const label = tierLabel("Plan", policy.plan);
|
|
4748
5024
|
context += `
|
|
@@ -4750,9 +5026,9 @@ ${selected}
|
|
|
4750
5026
|
${selected}
|
|
4751
5027
|
`;
|
|
4752
5028
|
}
|
|
4753
|
-
const contextMdPath =
|
|
4754
|
-
if (
|
|
4755
|
-
const content =
|
|
5029
|
+
const contextMdPath = path21.join(taskDir, "context.md");
|
|
5030
|
+
if (fs23.existsSync(contextMdPath)) {
|
|
5031
|
+
const content = fs23.readFileSync(contextMdPath, "utf-8");
|
|
4756
5032
|
const selected = selectContent(contextMdPath, content, policy.accumulatedContext);
|
|
4757
5033
|
const label = tierLabel("Previous Stage Context", policy.accumulatedContext);
|
|
4758
5034
|
context += `
|
|
@@ -4838,24 +5114,24 @@ var init_context_tiers = __esm({
|
|
|
4838
5114
|
});
|
|
4839
5115
|
|
|
4840
5116
|
// src/context.ts
|
|
4841
|
-
import * as
|
|
4842
|
-
import * as
|
|
5117
|
+
import * as fs24 from "fs";
|
|
5118
|
+
import * as path22 from "path";
|
|
4843
5119
|
function readPromptFile(stageName, projectDir) {
|
|
4844
5120
|
if (projectDir) {
|
|
4845
|
-
const stepFile =
|
|
4846
|
-
if (
|
|
4847
|
-
return
|
|
5121
|
+
const stepFile = path22.join(projectDir, ".kody", "steps", `${stageName}.md`);
|
|
5122
|
+
if (fs24.existsSync(stepFile)) {
|
|
5123
|
+
return fs24.readFileSync(stepFile, "utf-8");
|
|
4848
5124
|
}
|
|
4849
5125
|
console.warn(` \u26A0 No step file at ${stepFile}, falling back to engine defaults. Run 'kody-engine-lite init --force' to generate step files.`);
|
|
4850
5126
|
}
|
|
4851
5127
|
const scriptDir = new URL(".", import.meta.url).pathname;
|
|
4852
5128
|
const candidates = [
|
|
4853
|
-
|
|
4854
|
-
|
|
5129
|
+
path22.resolve(scriptDir, "..", "prompts", `${stageName}.md`),
|
|
5130
|
+
path22.resolve(scriptDir, "..", "..", "prompts", `${stageName}.md`)
|
|
4855
5131
|
];
|
|
4856
5132
|
for (const candidate of candidates) {
|
|
4857
|
-
if (
|
|
4858
|
-
return
|
|
5133
|
+
if (fs24.existsSync(candidate)) {
|
|
5134
|
+
return fs24.readFileSync(candidate, "utf-8");
|
|
4859
5135
|
}
|
|
4860
5136
|
}
|
|
4861
5137
|
throw new Error(`Prompt file not found: tried ${candidates.join(", ")}`);
|
|
@@ -4867,18 +5143,18 @@ function injectTaskContext(prompt, taskId, taskDir, feedback) {
|
|
|
4867
5143
|
`;
|
|
4868
5144
|
context += `Task Directory: ${taskDir}
|
|
4869
5145
|
`;
|
|
4870
|
-
const taskMdPath =
|
|
4871
|
-
if (
|
|
4872
|
-
const taskMd =
|
|
5146
|
+
const taskMdPath = path22.join(taskDir, "task.md");
|
|
5147
|
+
if (fs24.existsSync(taskMdPath)) {
|
|
5148
|
+
const taskMd = fs24.readFileSync(taskMdPath, "utf-8");
|
|
4873
5149
|
context += `
|
|
4874
5150
|
## Task Description
|
|
4875
5151
|
${taskMd}
|
|
4876
5152
|
`;
|
|
4877
5153
|
}
|
|
4878
|
-
const taskJsonPath =
|
|
4879
|
-
if (
|
|
5154
|
+
const taskJsonPath = path22.join(taskDir, "task.json");
|
|
5155
|
+
if (fs24.existsSync(taskJsonPath)) {
|
|
4880
5156
|
try {
|
|
4881
|
-
const taskDef = JSON.parse(
|
|
5157
|
+
const taskDef = JSON.parse(fs24.readFileSync(taskJsonPath, "utf-8"));
|
|
4882
5158
|
context += `
|
|
4883
5159
|
## Task Classification
|
|
4884
5160
|
`;
|
|
@@ -4891,27 +5167,27 @@ ${taskMd}
|
|
|
4891
5167
|
} catch {
|
|
4892
5168
|
}
|
|
4893
5169
|
}
|
|
4894
|
-
const specPath =
|
|
4895
|
-
if (
|
|
4896
|
-
const spec =
|
|
5170
|
+
const specPath = path22.join(taskDir, "spec.md");
|
|
5171
|
+
if (fs24.existsSync(specPath)) {
|
|
5172
|
+
const spec = fs24.readFileSync(specPath, "utf-8");
|
|
4897
5173
|
const truncated = spec.slice(0, MAX_TASK_CONTEXT_SPEC);
|
|
4898
5174
|
context += `
|
|
4899
5175
|
## Spec Summary
|
|
4900
5176
|
${truncated}${spec.length > MAX_TASK_CONTEXT_SPEC ? "\n..." : ""}
|
|
4901
5177
|
`;
|
|
4902
5178
|
}
|
|
4903
|
-
const planPath =
|
|
4904
|
-
if (
|
|
4905
|
-
const plan =
|
|
5179
|
+
const planPath = path22.join(taskDir, "plan.md");
|
|
5180
|
+
if (fs24.existsSync(planPath)) {
|
|
5181
|
+
const plan = fs24.readFileSync(planPath, "utf-8");
|
|
4906
5182
|
const truncated = plan.slice(0, MAX_TASK_CONTEXT_PLAN);
|
|
4907
5183
|
context += `
|
|
4908
5184
|
## Plan Summary
|
|
4909
5185
|
${truncated}${plan.length > MAX_TASK_CONTEXT_PLAN ? "\n..." : ""}
|
|
4910
5186
|
`;
|
|
4911
5187
|
}
|
|
4912
|
-
const contextMdPath =
|
|
4913
|
-
if (
|
|
4914
|
-
const accumulated =
|
|
5188
|
+
const contextMdPath = path22.join(taskDir, "context.md");
|
|
5189
|
+
if (fs24.existsSync(contextMdPath)) {
|
|
5190
|
+
const accumulated = fs24.readFileSync(contextMdPath, "utf-8");
|
|
4915
5191
|
const truncated = accumulated.slice(-MAX_ACCUMULATED_CONTEXT);
|
|
4916
5192
|
const prefix = accumulated.length > MAX_ACCUMULATED_CONTEXT ? "...(earlier context truncated)\n" : "";
|
|
4917
5193
|
context += `
|
|
@@ -4929,17 +5205,17 @@ ${feedback}
|
|
|
4929
5205
|
}
|
|
4930
5206
|
function inferHasUIFromScope(scope) {
|
|
4931
5207
|
return scope.some((filePath) => {
|
|
4932
|
-
const ext =
|
|
5208
|
+
const ext = path22.extname(filePath).toLowerCase();
|
|
4933
5209
|
if (UI_EXTENSIONS.has(ext)) return true;
|
|
4934
5210
|
const normalized = filePath.replace(/\\/g, "/");
|
|
4935
5211
|
return UI_PATH_SEGMENTS.some((seg) => normalized.includes(seg));
|
|
4936
5212
|
});
|
|
4937
5213
|
}
|
|
4938
5214
|
function taskHasUI(taskDir) {
|
|
4939
|
-
const taskJsonPath =
|
|
4940
|
-
if (!
|
|
5215
|
+
const taskJsonPath = path22.join(taskDir, "task.json");
|
|
5216
|
+
if (!fs24.existsSync(taskJsonPath)) return true;
|
|
4941
5217
|
try {
|
|
4942
|
-
const taskDef = JSON.parse(
|
|
5218
|
+
const taskDef = JSON.parse(fs24.readFileSync(taskJsonPath, "utf-8"));
|
|
4943
5219
|
const scope = Array.isArray(taskDef.scope) ? taskDef.scope : [];
|
|
4944
5220
|
if (scope.length === 0) return true;
|
|
4945
5221
|
return inferHasUIFromScope(scope);
|
|
@@ -5125,9 +5401,9 @@ ${prompt}` : prompt;
|
|
|
5125
5401
|
const hasBrowserTools = isMcpEnabledForStage(stageName, config.mcp) || config.devServer && browserStages.includes(stageName);
|
|
5126
5402
|
if (hasBrowserTools && taskHasUI(taskDir)) {
|
|
5127
5403
|
assembled = assembled + "\n\n" + getBrowserToolGuidance(stageName, taskDir);
|
|
5128
|
-
const qaGuidePath =
|
|
5129
|
-
if (
|
|
5130
|
-
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();
|
|
5131
5407
|
assembled = assembled + "\n\n" + qaGuide;
|
|
5132
5408
|
}
|
|
5133
5409
|
}
|
|
@@ -5237,7 +5513,7 @@ async function waitForReady(url, timeoutSec, stdoutMatch) {
|
|
|
5237
5513
|
while (Date.now() < deadline) {
|
|
5238
5514
|
if (stdoutMatch()) {
|
|
5239
5515
|
logger.info(" Dev server stdout matched ready pattern");
|
|
5240
|
-
const httpTimeout = Math.
|
|
5516
|
+
const httpTimeout = Math.max(1, Math.floor((deadline - Date.now()) / 1e3));
|
|
5241
5517
|
return pollReady(url, httpTimeout);
|
|
5242
5518
|
}
|
|
5243
5519
|
await new Promise((r) => setTimeout(r, 500));
|
|
@@ -5315,8 +5591,8 @@ var init_dev_server = __esm({
|
|
|
5315
5591
|
});
|
|
5316
5592
|
|
|
5317
5593
|
// src/stages/agent.ts
|
|
5318
|
-
import * as
|
|
5319
|
-
import * as
|
|
5594
|
+
import * as fs25 from "fs";
|
|
5595
|
+
import * as path23 from "path";
|
|
5320
5596
|
function getSessionInfo(stageName, sessions) {
|
|
5321
5597
|
const group = SESSION_GROUP[stageName];
|
|
5322
5598
|
if (!group) return void 0;
|
|
@@ -5438,27 +5714,27 @@ async function executeAgentStage(ctx, def) {
|
|
|
5438
5714
|
}
|
|
5439
5715
|
const result2 = lastResult;
|
|
5440
5716
|
if (def.outputFile && result2.output) {
|
|
5441
|
-
|
|
5717
|
+
fs25.writeFileSync(path23.join(ctx.taskDir, def.outputFile), result2.output);
|
|
5442
5718
|
}
|
|
5443
5719
|
if (def.outputFile) {
|
|
5444
|
-
const outputPath =
|
|
5445
|
-
if (!
|
|
5446
|
-
const ext =
|
|
5447
|
-
const base =
|
|
5448
|
-
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);
|
|
5449
5725
|
const variant = files.find(
|
|
5450
5726
|
(f) => f.startsWith(base + "-") && f.endsWith(ext)
|
|
5451
5727
|
);
|
|
5452
5728
|
if (variant) {
|
|
5453
|
-
|
|
5729
|
+
fs25.renameSync(path23.join(ctx.taskDir, variant), outputPath);
|
|
5454
5730
|
logger.info(` Renamed variant ${variant} \u2192 ${def.outputFile}`);
|
|
5455
5731
|
}
|
|
5456
5732
|
}
|
|
5457
5733
|
}
|
|
5458
5734
|
if (def.outputFile) {
|
|
5459
|
-
const outputPath =
|
|
5460
|
-
if (
|
|
5461
|
-
const content =
|
|
5735
|
+
const outputPath = path23.join(ctx.taskDir, def.outputFile);
|
|
5736
|
+
if (fs25.existsSync(outputPath)) {
|
|
5737
|
+
const content = fs25.readFileSync(outputPath, "utf-8");
|
|
5462
5738
|
const validation = validateStageOutput(def.name, content);
|
|
5463
5739
|
if (!validation.valid) {
|
|
5464
5740
|
if (def.name === "taskify") {
|
|
@@ -5472,7 +5748,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
5472
5748
|
const stripped = stripFences(retryResult.output);
|
|
5473
5749
|
const retryValidation = validateTaskJson(stripped);
|
|
5474
5750
|
if (retryValidation.valid) {
|
|
5475
|
-
|
|
5751
|
+
fs25.writeFileSync(outputPath, retryResult.output);
|
|
5476
5752
|
logger.info(` taskify retry produced valid JSON`);
|
|
5477
5753
|
} else {
|
|
5478
5754
|
logger.warn(` taskify retry still invalid: ${retryValidation.error}`);
|
|
@@ -5485,7 +5761,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
5485
5761
|
risk_level: "low",
|
|
5486
5762
|
questions: []
|
|
5487
5763
|
}, null, 2);
|
|
5488
|
-
|
|
5764
|
+
fs25.writeFileSync(outputPath, fallback);
|
|
5489
5765
|
logger.info(` taskify fallback: generated minimal task.json (risk_level=low)`);
|
|
5490
5766
|
}
|
|
5491
5767
|
}
|
|
@@ -5499,7 +5775,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
5499
5775
|
return { outcome: "completed", outputFile: def.outputFile, retries };
|
|
5500
5776
|
}
|
|
5501
5777
|
function appendStageContext(taskDir, stageName, output) {
|
|
5502
|
-
const contextPath =
|
|
5778
|
+
const contextPath = path23.join(taskDir, "context.md");
|
|
5503
5779
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19);
|
|
5504
5780
|
let summary;
|
|
5505
5781
|
if (output && output.trim()) {
|
|
@@ -5512,7 +5788,7 @@ function appendStageContext(taskDir, stageName, output) {
|
|
|
5512
5788
|
### ${stageName} (${timestamp2})
|
|
5513
5789
|
${summary}
|
|
5514
5790
|
`;
|
|
5515
|
-
|
|
5791
|
+
fs25.appendFileSync(contextPath, entry);
|
|
5516
5792
|
}
|
|
5517
5793
|
var SESSION_GROUP;
|
|
5518
5794
|
var init_agent = __esm({
|
|
@@ -5793,8 +6069,8 @@ Error context:
|
|
|
5793
6069
|
});
|
|
5794
6070
|
|
|
5795
6071
|
// src/stages/gate.ts
|
|
5796
|
-
import * as
|
|
5797
|
-
import * as
|
|
6072
|
+
import * as fs26 from "fs";
|
|
6073
|
+
import * as path24 from "path";
|
|
5798
6074
|
function executeGateStage(ctx, def) {
|
|
5799
6075
|
if (ctx.input.dryRun) {
|
|
5800
6076
|
logger.info(` [dry-run] skipping ${def.name}`);
|
|
@@ -5837,7 +6113,7 @@ ${output}
|
|
|
5837
6113
|
`);
|
|
5838
6114
|
}
|
|
5839
6115
|
}
|
|
5840
|
-
|
|
6116
|
+
fs26.writeFileSync(path24.join(ctx.taskDir, "verify.md"), lines.join(""));
|
|
5841
6117
|
return {
|
|
5842
6118
|
outcome: verifyResult.pass ? "completed" : "failed",
|
|
5843
6119
|
retries: 0
|
|
@@ -5852,8 +6128,8 @@ var init_gate = __esm({
|
|
|
5852
6128
|
});
|
|
5853
6129
|
|
|
5854
6130
|
// src/stages/verify.ts
|
|
5855
|
-
import * as
|
|
5856
|
-
import * as
|
|
6131
|
+
import * as fs27 from "fs";
|
|
6132
|
+
import * as path25 from "path";
|
|
5857
6133
|
import { execFileSync as execFileSync16 } from "child_process";
|
|
5858
6134
|
async function executeVerifyWithAutofix(ctx, def) {
|
|
5859
6135
|
const maxAttempts = def.maxRetries ?? 2;
|
|
@@ -5864,8 +6140,8 @@ async function executeVerifyWithAutofix(ctx, def) {
|
|
|
5864
6140
|
return { ...gateResult, retries: attempt };
|
|
5865
6141
|
}
|
|
5866
6142
|
if (attempt < maxAttempts) {
|
|
5867
|
-
const verifyPath =
|
|
5868
|
-
const errorOutput =
|
|
6143
|
+
const verifyPath = path25.join(ctx.taskDir, "verify.md");
|
|
6144
|
+
const errorOutput = fs27.existsSync(verifyPath) ? fs27.readFileSync(verifyPath, "utf-8") : "Unknown error";
|
|
5869
6145
|
const modifiedFiles = getModifiedFiles(ctx.projectDir);
|
|
5870
6146
|
const defaultRunner = getRunnerForStage(ctx, "taskify");
|
|
5871
6147
|
const diagConfig = getProjectConfig();
|
|
@@ -5961,8 +6237,8 @@ var init_verify = __esm({
|
|
|
5961
6237
|
});
|
|
5962
6238
|
|
|
5963
6239
|
// src/review-standalone.ts
|
|
5964
|
-
import * as
|
|
5965
|
-
import * as
|
|
6240
|
+
import * as fs28 from "fs";
|
|
6241
|
+
import * as path26 from "path";
|
|
5966
6242
|
function resolveReviewTarget(input) {
|
|
5967
6243
|
if (input.prs.length === 0) {
|
|
5968
6244
|
return {
|
|
@@ -5986,8 +6262,8 @@ Or comment on the specific PR: \`@kody review\``
|
|
|
5986
6262
|
}
|
|
5987
6263
|
async function runStandaloneReview(input) {
|
|
5988
6264
|
const taskId = input.taskId ?? `review-${generateTaskId()}`;
|
|
5989
|
-
const taskDir =
|
|
5990
|
-
|
|
6265
|
+
const taskDir = path26.join(input.projectDir, ".kody", "tasks", taskId);
|
|
6266
|
+
fs28.mkdirSync(taskDir, { recursive: true });
|
|
5991
6267
|
let diffInstruction = "";
|
|
5992
6268
|
let filesChangedSection = "";
|
|
5993
6269
|
if (input.baseBranch) {
|
|
@@ -6014,7 +6290,7 @@ ${fileList}`;
|
|
|
6014
6290
|
const taskContent = `# ${input.prTitle}
|
|
6015
6291
|
|
|
6016
6292
|
${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
|
|
6017
|
-
|
|
6293
|
+
fs28.writeFileSync(path26.join(taskDir, "task.md"), taskContent);
|
|
6018
6294
|
const reviewDef = STAGES.find((s) => s.name === "review");
|
|
6019
6295
|
const ctx = {
|
|
6020
6296
|
taskId,
|
|
@@ -6036,10 +6312,10 @@ ${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
|
|
|
6036
6312
|
error: result2.error ?? "Review stage failed"
|
|
6037
6313
|
};
|
|
6038
6314
|
}
|
|
6039
|
-
const reviewPath =
|
|
6315
|
+
const reviewPath = path26.join(taskDir, "review.md");
|
|
6040
6316
|
let reviewContent;
|
|
6041
|
-
if (
|
|
6042
|
-
reviewContent =
|
|
6317
|
+
if (fs28.existsSync(reviewPath)) {
|
|
6318
|
+
reviewContent = fs28.readFileSync(reviewPath, "utf-8");
|
|
6043
6319
|
}
|
|
6044
6320
|
return {
|
|
6045
6321
|
outcome: "completed",
|
|
@@ -6079,8 +6355,8 @@ var init_review_standalone = __esm({
|
|
|
6079
6355
|
});
|
|
6080
6356
|
|
|
6081
6357
|
// src/stages/review.ts
|
|
6082
|
-
import * as
|
|
6083
|
-
import * as
|
|
6358
|
+
import * as fs29 from "fs";
|
|
6359
|
+
import * as path27 from "path";
|
|
6084
6360
|
async function executeReviewWithFix(ctx, def) {
|
|
6085
6361
|
if (ctx.input.dryRun) {
|
|
6086
6362
|
return { outcome: "completed", retries: 0 };
|
|
@@ -6094,11 +6370,11 @@ async function executeReviewWithFix(ctx, def) {
|
|
|
6094
6370
|
if (reviewResult.outcome !== "completed") {
|
|
6095
6371
|
return reviewResult;
|
|
6096
6372
|
}
|
|
6097
|
-
const reviewFile =
|
|
6098
|
-
if (!
|
|
6373
|
+
const reviewFile = path27.join(ctx.taskDir, "review.md");
|
|
6374
|
+
if (!fs29.existsSync(reviewFile)) {
|
|
6099
6375
|
return { outcome: "failed", retries: iteration, error: "review.md not found" };
|
|
6100
6376
|
}
|
|
6101
|
-
const content =
|
|
6377
|
+
const content = fs29.readFileSync(reviewFile, "utf-8");
|
|
6102
6378
|
if (detectReviewVerdict(content) !== "fail") {
|
|
6103
6379
|
return { ...reviewResult, retries: iteration };
|
|
6104
6380
|
}
|
|
@@ -6127,15 +6403,15 @@ var init_review = __esm({
|
|
|
6127
6403
|
});
|
|
6128
6404
|
|
|
6129
6405
|
// src/stages/ship.ts
|
|
6130
|
-
import * as
|
|
6131
|
-
import * as
|
|
6406
|
+
import * as fs30 from "fs";
|
|
6407
|
+
import * as path28 from "path";
|
|
6132
6408
|
import { execFileSync as execFileSync17 } from "child_process";
|
|
6133
6409
|
function buildPrBody(ctx) {
|
|
6134
6410
|
const sections = [];
|
|
6135
|
-
const taskJsonPath =
|
|
6136
|
-
if (
|
|
6411
|
+
const taskJsonPath = path28.join(ctx.taskDir, "task.json");
|
|
6412
|
+
if (fs30.existsSync(taskJsonPath)) {
|
|
6137
6413
|
try {
|
|
6138
|
-
const raw =
|
|
6414
|
+
const raw = fs30.readFileSync(taskJsonPath, "utf-8");
|
|
6139
6415
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
6140
6416
|
const task = JSON.parse(cleaned);
|
|
6141
6417
|
if (task.description) {
|
|
@@ -6154,9 +6430,9 @@ ${task.scope.map((s) => `- \`${s}\``).join("\n")}`);
|
|
|
6154
6430
|
} catch {
|
|
6155
6431
|
}
|
|
6156
6432
|
}
|
|
6157
|
-
const reviewPath =
|
|
6158
|
-
if (
|
|
6159
|
-
const review =
|
|
6433
|
+
const reviewPath = path28.join(ctx.taskDir, "review.md");
|
|
6434
|
+
if (fs30.existsSync(reviewPath)) {
|
|
6435
|
+
const review = fs30.readFileSync(reviewPath, "utf-8");
|
|
6160
6436
|
const summaryMatch = review.match(/## Summary\s*\n([\s\S]*?)(?=\n## |\n*$)/);
|
|
6161
6437
|
if (summaryMatch) {
|
|
6162
6438
|
const summary = summaryMatch[1].trim();
|
|
@@ -6173,14 +6449,14 @@ ${summary}`);
|
|
|
6173
6449
|
**Review:** ${verdictMatch[1].toUpperCase() === "PASS" ? "\u2705 PASS" : "\u274C FAIL"}`);
|
|
6174
6450
|
}
|
|
6175
6451
|
}
|
|
6176
|
-
const verifyPath =
|
|
6177
|
-
if (
|
|
6178
|
-
const verify =
|
|
6452
|
+
const verifyPath = path28.join(ctx.taskDir, "verify.md");
|
|
6453
|
+
if (fs30.existsSync(verifyPath)) {
|
|
6454
|
+
const verify = fs30.readFileSync(verifyPath, "utf-8");
|
|
6179
6455
|
if (/PASS/i.test(verify)) sections.push(`**Verify:** \u2705 typecheck + tests + lint passed`);
|
|
6180
6456
|
}
|
|
6181
|
-
const planPath =
|
|
6182
|
-
if (
|
|
6183
|
-
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();
|
|
6184
6460
|
if (plan) {
|
|
6185
6461
|
const truncated = plan.length > 800 ? plan.slice(0, 800) + "\n..." : plan;
|
|
6186
6462
|
sections.push(`
|
|
@@ -6200,22 +6476,22 @@ Closes #${ctx.input.issueNumber}`);
|
|
|
6200
6476
|
return sections.join("\n");
|
|
6201
6477
|
}
|
|
6202
6478
|
function executeShipStage(ctx, _def) {
|
|
6203
|
-
const shipPath =
|
|
6479
|
+
const shipPath = path28.join(ctx.taskDir, "ship.md");
|
|
6204
6480
|
if (ctx.input.dryRun) {
|
|
6205
|
-
|
|
6481
|
+
fs30.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
|
|
6206
6482
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
6207
6483
|
}
|
|
6208
6484
|
if (ctx.input.local && !ctx.input.issueNumber) {
|
|
6209
|
-
|
|
6485
|
+
fs30.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
|
|
6210
6486
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
6211
6487
|
}
|
|
6212
6488
|
try {
|
|
6213
6489
|
const head = getCurrentBranch(ctx.projectDir);
|
|
6214
6490
|
const base = getDefaultBranch(ctx.projectDir);
|
|
6215
6491
|
try {
|
|
6216
|
-
const memoryDir =
|
|
6492
|
+
const memoryDir = path28.join(ctx.projectDir, ".kody", "memory");
|
|
6217
6493
|
const addPaths = [ctx.taskDir];
|
|
6218
|
-
if (
|
|
6494
|
+
if (fs30.existsSync(memoryDir)) addPaths.push(memoryDir);
|
|
6219
6495
|
execFileSync17("git", ["add", ...addPaths], {
|
|
6220
6496
|
cwd: ctx.projectDir,
|
|
6221
6497
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
@@ -6256,28 +6532,28 @@ function executeShipStage(ctx, _def) {
|
|
|
6256
6532
|
chore: "chore"
|
|
6257
6533
|
};
|
|
6258
6534
|
let prefix = "chore";
|
|
6259
|
-
const taskJsonPath =
|
|
6260
|
-
if (
|
|
6535
|
+
const taskJsonPath = path28.join(ctx.taskDir, "task.json");
|
|
6536
|
+
if (fs30.existsSync(taskJsonPath)) {
|
|
6261
6537
|
try {
|
|
6262
|
-
const raw =
|
|
6538
|
+
const raw = fs30.readFileSync(taskJsonPath, "utf-8");
|
|
6263
6539
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
6264
6540
|
const task = JSON.parse(cleaned);
|
|
6265
6541
|
prefix = TYPE_PREFIX[task.task_type] ?? "chore";
|
|
6266
6542
|
} catch {
|
|
6267
6543
|
}
|
|
6268
6544
|
}
|
|
6269
|
-
const taskMdPath =
|
|
6270
|
-
if (
|
|
6271
|
-
const content =
|
|
6545
|
+
const taskMdPath = path28.join(ctx.taskDir, "task.md");
|
|
6546
|
+
if (fs30.existsSync(taskMdPath)) {
|
|
6547
|
+
const content = fs30.readFileSync(taskMdPath, "utf-8");
|
|
6272
6548
|
const heading = content.split("\n").find((l) => l.startsWith("# "));
|
|
6273
6549
|
if (heading) {
|
|
6274
6550
|
title = `${prefix}: ${heading.replace(/^#\s*/, "").trim()}`.slice(0, 72);
|
|
6275
6551
|
}
|
|
6276
6552
|
}
|
|
6277
6553
|
if (title === "Update") {
|
|
6278
|
-
if (
|
|
6554
|
+
if (fs30.existsSync(taskJsonPath)) {
|
|
6279
6555
|
try {
|
|
6280
|
-
const raw =
|
|
6556
|
+
const raw = fs30.readFileSync(taskJsonPath, "utf-8");
|
|
6281
6557
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
6282
6558
|
const task = JSON.parse(cleaned);
|
|
6283
6559
|
if (task.title) title = `${prefix}: ${task.title}`.slice(0, 72);
|
|
@@ -6300,7 +6576,7 @@ function executeShipStage(ctx, _def) {
|
|
|
6300
6576
|
} catch {
|
|
6301
6577
|
}
|
|
6302
6578
|
}
|
|
6303
|
-
|
|
6579
|
+
fs30.writeFileSync(shipPath, `# Ship
|
|
6304
6580
|
|
|
6305
6581
|
Updated existing PR: ${existingPr.url}
|
|
6306
6582
|
PR #${existingPr.number}
|
|
@@ -6321,20 +6597,20 @@ PR #${existingPr.number}
|
|
|
6321
6597
|
} catch {
|
|
6322
6598
|
}
|
|
6323
6599
|
}
|
|
6324
|
-
|
|
6600
|
+
fs30.writeFileSync(shipPath, `# Ship
|
|
6325
6601
|
|
|
6326
6602
|
PR created: ${pr.url}
|
|
6327
6603
|
PR #${pr.number}
|
|
6328
6604
|
`);
|
|
6329
6605
|
} else {
|
|
6330
|
-
|
|
6606
|
+
fs30.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
|
|
6331
6607
|
}
|
|
6332
6608
|
}
|
|
6333
6609
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
6334
6610
|
} catch (err) {
|
|
6335
6611
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6336
6612
|
try {
|
|
6337
|
-
|
|
6613
|
+
fs30.writeFileSync(shipPath, `# Ship
|
|
6338
6614
|
|
|
6339
6615
|
Failed: ${msg}
|
|
6340
6616
|
`);
|
|
@@ -6383,15 +6659,15 @@ var init_executor_registry = __esm({
|
|
|
6383
6659
|
});
|
|
6384
6660
|
|
|
6385
6661
|
// src/pipeline/questions.ts
|
|
6386
|
-
import * as
|
|
6387
|
-
import * as
|
|
6662
|
+
import * as fs31 from "fs";
|
|
6663
|
+
import * as path29 from "path";
|
|
6388
6664
|
function checkForQuestions(ctx, stageName) {
|
|
6389
6665
|
if (ctx.input.local || !ctx.input.issueNumber) return false;
|
|
6390
6666
|
try {
|
|
6391
6667
|
if (stageName === "taskify") {
|
|
6392
|
-
const taskJsonPath =
|
|
6393
|
-
if (!
|
|
6394
|
-
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");
|
|
6395
6671
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
6396
6672
|
const taskJson = JSON.parse(cleaned);
|
|
6397
6673
|
if (taskJson.questions && Array.isArray(taskJson.questions) && taskJson.questions.length > 0) {
|
|
@@ -6406,9 +6682,9 @@ Reply with \`@kody approve\` and your answers in the comment body.`;
|
|
|
6406
6682
|
}
|
|
6407
6683
|
}
|
|
6408
6684
|
if (stageName === "plan") {
|
|
6409
|
-
const planPath =
|
|
6410
|
-
if (!
|
|
6411
|
-
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");
|
|
6412
6688
|
const questionsMatch = plan.match(/## Questions\s*\n([\s\S]*?)(?=\n## |\n*$)/);
|
|
6413
6689
|
if (questionsMatch) {
|
|
6414
6690
|
const questionsText = questionsMatch[1].trim();
|
|
@@ -6437,8 +6713,8 @@ var init_questions = __esm({
|
|
|
6437
6713
|
});
|
|
6438
6714
|
|
|
6439
6715
|
// src/pipeline/hooks.ts
|
|
6440
|
-
import * as
|
|
6441
|
-
import * as
|
|
6716
|
+
import * as fs32 from "fs";
|
|
6717
|
+
import * as path30 from "path";
|
|
6442
6718
|
function applyPreStageLabel(ctx, def) {
|
|
6443
6719
|
if (!ctx.input.issueNumber || ctx.input.local) return;
|
|
6444
6720
|
if (def.name === "plan") setLifecycleLabel(ctx.input.issueNumber, "planning");
|
|
@@ -6479,9 +6755,9 @@ function autoDetectComplexity(ctx, def) {
|
|
|
6479
6755
|
return { complexity, activeStages };
|
|
6480
6756
|
}
|
|
6481
6757
|
try {
|
|
6482
|
-
const taskJsonPath =
|
|
6483
|
-
if (!
|
|
6484
|
-
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");
|
|
6485
6761
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
6486
6762
|
const taskJson = JSON.parse(cleaned);
|
|
6487
6763
|
if (!taskJson.risk_level || !isValidComplexity(taskJson.risk_level)) return null;
|
|
@@ -6511,8 +6787,8 @@ function checkRiskGate(ctx, def, state, complexity) {
|
|
|
6511
6787
|
if (ctx.input.dryRun || ctx.input.local) return null;
|
|
6512
6788
|
if (ctx.input.mode === "rerun") return null;
|
|
6513
6789
|
if (!ctx.input.issueNumber) return null;
|
|
6514
|
-
const planPath =
|
|
6515
|
-
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)";
|
|
6516
6792
|
try {
|
|
6517
6793
|
postComment(
|
|
6518
6794
|
ctx.input.issueNumber,
|
|
@@ -6579,22 +6855,22 @@ var init_hooks = __esm({
|
|
|
6579
6855
|
});
|
|
6580
6856
|
|
|
6581
6857
|
// src/learning/auto-learn.ts
|
|
6582
|
-
import * as
|
|
6583
|
-
import * as
|
|
6858
|
+
import * as fs33 from "fs";
|
|
6859
|
+
import * as path31 from "path";
|
|
6584
6860
|
function stripAnsi(str) {
|
|
6585
6861
|
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
6586
6862
|
}
|
|
6587
6863
|
function autoLearn(ctx) {
|
|
6588
6864
|
try {
|
|
6589
|
-
const memoryDir =
|
|
6590
|
-
if (!
|
|
6591
|
-
|
|
6865
|
+
const memoryDir = path31.join(ctx.projectDir, ".kody", "memory");
|
|
6866
|
+
if (!fs33.existsSync(memoryDir)) {
|
|
6867
|
+
fs33.mkdirSync(memoryDir, { recursive: true });
|
|
6592
6868
|
}
|
|
6593
6869
|
const learnings = [];
|
|
6594
6870
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6595
|
-
const verifyPath =
|
|
6596
|
-
if (
|
|
6597
|
-
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"));
|
|
6598
6874
|
if (/vitest/i.test(verify)) learnings.push("- Uses vitest for testing");
|
|
6599
6875
|
if (/jest/i.test(verify)) learnings.push("- Uses jest for testing");
|
|
6600
6876
|
if (/eslint/i.test(verify)) learnings.push("- Uses eslint for linting");
|
|
@@ -6603,18 +6879,18 @@ function autoLearn(ctx) {
|
|
|
6603
6879
|
if (/jsdom/i.test(verify)) learnings.push("- Test environment: jsdom");
|
|
6604
6880
|
if (/node/i.test(verify) && /environment/i.test(verify)) learnings.push("- Test environment: node");
|
|
6605
6881
|
}
|
|
6606
|
-
const reviewPath =
|
|
6607
|
-
if (
|
|
6608
|
-
const review =
|
|
6882
|
+
const reviewPath = path31.join(ctx.taskDir, "review.md");
|
|
6883
|
+
if (fs33.existsSync(reviewPath)) {
|
|
6884
|
+
const review = fs33.readFileSync(reviewPath, "utf-8");
|
|
6609
6885
|
if (/\.js extension/i.test(review)) learnings.push("- Imports use .js extensions (ESM)");
|
|
6610
6886
|
if (/barrel export/i.test(review)) learnings.push("- Uses barrel exports (index.ts)");
|
|
6611
6887
|
if (/timezone/i.test(review)) learnings.push("- Timezone handling is a concern in this codebase");
|
|
6612
6888
|
if (/UTC/i.test(review)) learnings.push("- Date operations should consider UTC vs local time");
|
|
6613
6889
|
}
|
|
6614
|
-
const taskJsonPath =
|
|
6615
|
-
if (
|
|
6890
|
+
const taskJsonPath = path31.join(ctx.taskDir, "task.json");
|
|
6891
|
+
if (fs33.existsSync(taskJsonPath)) {
|
|
6616
6892
|
try {
|
|
6617
|
-
const raw = stripAnsi(
|
|
6893
|
+
const raw = stripAnsi(fs33.readFileSync(taskJsonPath, "utf-8"));
|
|
6618
6894
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
6619
6895
|
const task = JSON.parse(cleaned);
|
|
6620
6896
|
if (task.scope && Array.isArray(task.scope)) {
|
|
@@ -6625,12 +6901,12 @@ function autoLearn(ctx) {
|
|
|
6625
6901
|
}
|
|
6626
6902
|
}
|
|
6627
6903
|
if (learnings.length > 0) {
|
|
6628
|
-
const conventionsPath =
|
|
6904
|
+
const conventionsPath = path31.join(memoryDir, "conventions.md");
|
|
6629
6905
|
const entry = `
|
|
6630
6906
|
## Learned ${timestamp2} (task: ${ctx.taskId})
|
|
6631
6907
|
${learnings.join("\n")}
|
|
6632
6908
|
`;
|
|
6633
|
-
|
|
6909
|
+
fs33.appendFileSync(conventionsPath, entry);
|
|
6634
6910
|
logger.info(`Auto-learned ${learnings.length} convention(s)`);
|
|
6635
6911
|
}
|
|
6636
6912
|
autoLearnArchitecture(ctx.projectDir, memoryDir, timestamp2);
|
|
@@ -6638,8 +6914,8 @@ ${learnings.join("\n")}
|
|
|
6638
6914
|
}
|
|
6639
6915
|
}
|
|
6640
6916
|
function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
6641
|
-
const archPath =
|
|
6642
|
-
if (
|
|
6917
|
+
const archPath = path31.join(memoryDir, "architecture.md");
|
|
6918
|
+
if (fs33.existsSync(archPath)) return;
|
|
6643
6919
|
const detected = detectArchitectureBasic(projectDir);
|
|
6644
6920
|
if (detected.length > 0) {
|
|
6645
6921
|
const content = `# Architecture (auto-detected ${timestamp2})
|
|
@@ -6647,7 +6923,7 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
|
6647
6923
|
## Overview
|
|
6648
6924
|
${detected.join("\n")}
|
|
6649
6925
|
`;
|
|
6650
|
-
|
|
6926
|
+
fs33.writeFileSync(archPath, content);
|
|
6651
6927
|
logger.info(`Auto-detected architecture (${detected.length} items)`);
|
|
6652
6928
|
}
|
|
6653
6929
|
}
|
|
@@ -6660,13 +6936,13 @@ var init_auto_learn = __esm({
|
|
|
6660
6936
|
});
|
|
6661
6937
|
|
|
6662
6938
|
// src/retrospective.ts
|
|
6663
|
-
import * as
|
|
6664
|
-
import * as
|
|
6939
|
+
import * as fs34 from "fs";
|
|
6940
|
+
import * as path32 from "path";
|
|
6665
6941
|
function readArtifact(taskDir, filename, maxChars) {
|
|
6666
|
-
const p =
|
|
6667
|
-
if (!
|
|
6942
|
+
const p = path32.join(taskDir, filename);
|
|
6943
|
+
if (!fs34.existsSync(p)) return null;
|
|
6668
6944
|
try {
|
|
6669
|
-
const content =
|
|
6945
|
+
const content = fs34.readFileSync(p, "utf-8");
|
|
6670
6946
|
return content.length > maxChars ? content.slice(0, maxChars) + "\n...(truncated)" : content;
|
|
6671
6947
|
} catch {
|
|
6672
6948
|
return null;
|
|
@@ -6719,13 +6995,13 @@ function collectRunContext(ctx, state, pipelineStartTime) {
|
|
|
6719
6995
|
return lines.join("\n");
|
|
6720
6996
|
}
|
|
6721
6997
|
function getLogPath(projectDir) {
|
|
6722
|
-
return
|
|
6998
|
+
return path32.join(projectDir, ".kody", "memory", "observer-log.jsonl");
|
|
6723
6999
|
}
|
|
6724
7000
|
function readPreviousRetrospectives(projectDir, limit = 10) {
|
|
6725
7001
|
const logPath = getLogPath(projectDir);
|
|
6726
|
-
if (!
|
|
7002
|
+
if (!fs34.existsSync(logPath)) return [];
|
|
6727
7003
|
try {
|
|
6728
|
-
const content =
|
|
7004
|
+
const content = fs34.readFileSync(logPath, "utf-8");
|
|
6729
7005
|
const lines = content.split("\n").filter(Boolean);
|
|
6730
7006
|
const entries = [];
|
|
6731
7007
|
const start = Math.max(0, lines.length - limit);
|
|
@@ -6752,11 +7028,11 @@ function formatPreviousEntries(entries) {
|
|
|
6752
7028
|
}
|
|
6753
7029
|
function appendRetrospectiveEntry(projectDir, entry) {
|
|
6754
7030
|
const logPath = getLogPath(projectDir);
|
|
6755
|
-
const dir =
|
|
6756
|
-
if (!
|
|
6757
|
-
|
|
7031
|
+
const dir = path32.dirname(logPath);
|
|
7032
|
+
if (!fs34.existsSync(dir)) {
|
|
7033
|
+
fs34.mkdirSync(dir, { recursive: true });
|
|
6758
7034
|
}
|
|
6759
|
-
|
|
7035
|
+
fs34.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
6760
7036
|
}
|
|
6761
7037
|
async function runRetrospective(ctx, state, pipelineStartTime) {
|
|
6762
7038
|
if (ctx.input.dryRun) return;
|
|
@@ -6924,15 +7200,15 @@ var init_summary = __esm({
|
|
|
6924
7200
|
});
|
|
6925
7201
|
|
|
6926
7202
|
// src/tools.ts
|
|
6927
|
-
import * as
|
|
6928
|
-
import * as
|
|
7203
|
+
import * as fs35 from "fs";
|
|
7204
|
+
import * as path33 from "path";
|
|
6929
7205
|
import { execSync as execSync3 } from "child_process";
|
|
6930
7206
|
import { parse as parseYaml } from "yaml";
|
|
6931
7207
|
function loadToolDeclarations(projectDir) {
|
|
6932
|
-
const toolsPath =
|
|
6933
|
-
if (!
|
|
7208
|
+
const toolsPath = path33.join(projectDir, ".kody", "tools.yml");
|
|
7209
|
+
if (!fs35.existsSync(toolsPath)) return [];
|
|
6934
7210
|
try {
|
|
6935
|
-
const raw =
|
|
7211
|
+
const raw = fs35.readFileSync(toolsPath, "utf-8");
|
|
6936
7212
|
const parsed = parseYaml(raw);
|
|
6937
7213
|
if (!parsed || typeof parsed !== "object") return [];
|
|
6938
7214
|
return Object.entries(parsed).map(([name, value]) => {
|
|
@@ -6953,7 +7229,7 @@ function loadToolDeclarations(projectDir) {
|
|
|
6953
7229
|
function detectTools(declarations, projectDir) {
|
|
6954
7230
|
const resolved = [];
|
|
6955
7231
|
for (const decl of declarations) {
|
|
6956
|
-
const detected = decl.detect.some((pattern) =>
|
|
7232
|
+
const detected = decl.detect.some((pattern) => fs35.existsSync(path33.join(projectDir, pattern)));
|
|
6957
7233
|
if (!detected) continue;
|
|
6958
7234
|
resolved.push({
|
|
6959
7235
|
name: decl.name,
|
|
@@ -6994,8 +7270,8 @@ var init_tools = __esm({
|
|
|
6994
7270
|
});
|
|
6995
7271
|
|
|
6996
7272
|
// src/pipeline.ts
|
|
6997
|
-
import * as
|
|
6998
|
-
import * as
|
|
7273
|
+
import * as fs36 from "fs";
|
|
7274
|
+
import * as path34 from "path";
|
|
6999
7275
|
function ensureFeatureBranchIfNeeded(ctx) {
|
|
7000
7276
|
if (ctx.input.dryRun) return;
|
|
7001
7277
|
if (ctx.input.prNumber) {
|
|
@@ -7008,8 +7284,8 @@ function ensureFeatureBranchIfNeeded(ctx) {
|
|
|
7008
7284
|
}
|
|
7009
7285
|
if (!ctx.input.issueNumber) return;
|
|
7010
7286
|
try {
|
|
7011
|
-
const taskMdPath =
|
|
7012
|
-
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;
|
|
7013
7289
|
ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
|
|
7014
7290
|
syncWithDefault(ctx.projectDir);
|
|
7015
7291
|
} catch (err) {
|
|
@@ -7023,10 +7299,10 @@ function ensureFeatureBranchIfNeeded(ctx) {
|
|
|
7023
7299
|
}
|
|
7024
7300
|
}
|
|
7025
7301
|
function acquireLock(taskDir) {
|
|
7026
|
-
const lockPath =
|
|
7027
|
-
if (
|
|
7302
|
+
const lockPath = path34.join(taskDir, ".lock");
|
|
7303
|
+
if (fs36.existsSync(lockPath)) {
|
|
7028
7304
|
try {
|
|
7029
|
-
const pid = parseInt(
|
|
7305
|
+
const pid = parseInt(fs36.readFileSync(lockPath, "utf-8").trim(), 10);
|
|
7030
7306
|
if (!isNaN(pid)) {
|
|
7031
7307
|
try {
|
|
7032
7308
|
process.kill(pid, 0);
|
|
@@ -7043,14 +7319,14 @@ function acquireLock(taskDir) {
|
|
|
7043
7319
|
logger.warn(` Corrupt lock file \u2014 overwriting`);
|
|
7044
7320
|
}
|
|
7045
7321
|
try {
|
|
7046
|
-
|
|
7322
|
+
fs36.unlinkSync(lockPath);
|
|
7047
7323
|
} catch {
|
|
7048
7324
|
}
|
|
7049
7325
|
}
|
|
7050
7326
|
try {
|
|
7051
|
-
const fd =
|
|
7052
|
-
|
|
7053
|
-
|
|
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);
|
|
7054
7330
|
} catch (err) {
|
|
7055
7331
|
if (err.code === "EEXIST") {
|
|
7056
7332
|
throw new Error("Pipeline already running (lock acquired by another process)");
|
|
@@ -7060,7 +7336,7 @@ function acquireLock(taskDir) {
|
|
|
7060
7336
|
}
|
|
7061
7337
|
function releaseLock(taskDir) {
|
|
7062
7338
|
try {
|
|
7063
|
-
|
|
7339
|
+
fs36.unlinkSync(path34.join(taskDir, ".lock"));
|
|
7064
7340
|
} catch {
|
|
7065
7341
|
}
|
|
7066
7342
|
}
|
|
@@ -7277,7 +7553,7 @@ var init_pipeline = __esm({
|
|
|
7277
7553
|
|
|
7278
7554
|
// src/preflight.ts
|
|
7279
7555
|
import { execFileSync as execFileSync18 } from "child_process";
|
|
7280
|
-
import * as
|
|
7556
|
+
import * as fs37 from "fs";
|
|
7281
7557
|
function check(name, fn) {
|
|
7282
7558
|
try {
|
|
7283
7559
|
const detail = fn() ?? void 0;
|
|
@@ -7330,7 +7606,7 @@ function runPreflight() {
|
|
|
7330
7606
|
return v;
|
|
7331
7607
|
}),
|
|
7332
7608
|
check("package.json", () => {
|
|
7333
|
-
if (!
|
|
7609
|
+
if (!fs37.existsSync("package.json")) throw new Error("not found");
|
|
7334
7610
|
})
|
|
7335
7611
|
];
|
|
7336
7612
|
const failed = checks.filter((c) => !c.ok);
|
|
@@ -7407,8 +7683,8 @@ var init_args = __esm({
|
|
|
7407
7683
|
});
|
|
7408
7684
|
|
|
7409
7685
|
// src/cli/task-state.ts
|
|
7410
|
-
import * as
|
|
7411
|
-
import * as
|
|
7686
|
+
import * as fs38 from "fs";
|
|
7687
|
+
import * as path35 from "path";
|
|
7412
7688
|
function resolveTaskAction(issueNumber, existingTaskId, existingState) {
|
|
7413
7689
|
if (!existingTaskId || !existingState) {
|
|
7414
7690
|
return { action: "start-fresh", taskId: `${issueNumber}-${generateTaskId()}` };
|
|
@@ -7440,11 +7716,11 @@ function resolveTaskAction(issueNumber, existingTaskId, existingState) {
|
|
|
7440
7716
|
function resolveForIssue(issueNumber, projectDir) {
|
|
7441
7717
|
const existingTaskId = findLatestTaskForIssue(issueNumber, projectDir);
|
|
7442
7718
|
if (existingTaskId) {
|
|
7443
|
-
const statusPath =
|
|
7719
|
+
const statusPath = path35.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
|
|
7444
7720
|
let existingState = null;
|
|
7445
|
-
if (
|
|
7721
|
+
if (fs38.existsSync(statusPath)) {
|
|
7446
7722
|
try {
|
|
7447
|
-
existingState = JSON.parse(
|
|
7723
|
+
existingState = JSON.parse(fs38.readFileSync(statusPath, "utf-8"));
|
|
7448
7724
|
} catch {
|
|
7449
7725
|
}
|
|
7450
7726
|
}
|
|
@@ -7640,8 +7916,8 @@ var init_resolve = __esm({
|
|
|
7640
7916
|
|
|
7641
7917
|
// src/entry.ts
|
|
7642
7918
|
var entry_exports = {};
|
|
7643
|
-
import * as
|
|
7644
|
-
import * as
|
|
7919
|
+
import * as fs39 from "fs";
|
|
7920
|
+
import * as path36 from "path";
|
|
7645
7921
|
async function ensureLitellmProxy(config, projectDir) {
|
|
7646
7922
|
if (!anyStageNeedsProxy(config)) return null;
|
|
7647
7923
|
const litellmUrl = getLitellmUrl();
|
|
@@ -7696,9 +7972,9 @@ async function runModelHealthCheck(config) {
|
|
|
7696
7972
|
}
|
|
7697
7973
|
async function main() {
|
|
7698
7974
|
const input = parseArgs();
|
|
7699
|
-
const projectDir = input.cwd ?
|
|
7975
|
+
const projectDir = input.cwd ? path36.resolve(input.cwd) : process.cwd();
|
|
7700
7976
|
if (input.cwd) {
|
|
7701
|
-
if (!
|
|
7977
|
+
if (!fs39.existsSync(projectDir)) {
|
|
7702
7978
|
console.error(`--cwd path does not exist: ${projectDir}`);
|
|
7703
7979
|
process.exit(1);
|
|
7704
7980
|
}
|
|
@@ -7758,8 +8034,8 @@ async function main() {
|
|
|
7758
8034
|
process.exit(1);
|
|
7759
8035
|
}
|
|
7760
8036
|
}
|
|
7761
|
-
const taskDir =
|
|
7762
|
-
|
|
8037
|
+
const taskDir = path36.join(projectDir, ".kody", "tasks", taskId);
|
|
8038
|
+
fs39.mkdirSync(taskDir, { recursive: true });
|
|
7763
8039
|
if (input.command === "rerun" && isTaskifyRun(taskDir)) {
|
|
7764
8040
|
const marker = readTaskifyMarker(taskDir);
|
|
7765
8041
|
if (marker) {
|
|
@@ -7891,31 +8167,31 @@ async function main() {
|
|
|
7891
8167
|
logger.info("Preflight checks:");
|
|
7892
8168
|
runPreflight();
|
|
7893
8169
|
if (input.task) {
|
|
7894
|
-
|
|
8170
|
+
fs39.writeFileSync(path36.join(taskDir, "task.md"), input.task);
|
|
7895
8171
|
}
|
|
7896
|
-
const taskMdPath =
|
|
7897
|
-
if (!
|
|
8172
|
+
const taskMdPath = path36.join(taskDir, "task.md");
|
|
8173
|
+
if (!fs39.existsSync(taskMdPath) && isPRFix && input.prNumber) {
|
|
7898
8174
|
logger.info(`Fetching PR #${input.prNumber} details as task context...`);
|
|
7899
8175
|
const prDetails = getPRDetails(input.prNumber);
|
|
7900
8176
|
if (prDetails) {
|
|
7901
8177
|
const taskContent = `# ${prDetails.title}
|
|
7902
8178
|
|
|
7903
8179
|
${prDetails.body ?? ""}`;
|
|
7904
|
-
|
|
8180
|
+
fs39.writeFileSync(taskMdPath, taskContent);
|
|
7905
8181
|
logger.info(` Task loaded from PR #${input.prNumber}: ${prDetails.title}`);
|
|
7906
8182
|
}
|
|
7907
|
-
} else if (!
|
|
8183
|
+
} else if (!fs39.existsSync(taskMdPath) && input.issueNumber) {
|
|
7908
8184
|
logger.info(`Fetching issue #${input.issueNumber} body as task...`);
|
|
7909
8185
|
const issue = getIssue(input.issueNumber);
|
|
7910
8186
|
if (issue) {
|
|
7911
8187
|
const taskContent = `# ${issue.title}
|
|
7912
8188
|
|
|
7913
8189
|
${issue.body ?? ""}`;
|
|
7914
|
-
|
|
8190
|
+
fs39.writeFileSync(taskMdPath, taskContent);
|
|
7915
8191
|
logger.info(` Task loaded from issue #${input.issueNumber}: ${issue.title}`);
|
|
7916
8192
|
}
|
|
7917
8193
|
}
|
|
7918
|
-
if (!
|
|
8194
|
+
if (!fs39.existsSync(taskMdPath)) {
|
|
7919
8195
|
console.error("No task.md found. Provide --task, --issue-number, or ensure .kody/tasks/<id>/task.md exists.");
|
|
7920
8196
|
process.exit(1);
|
|
7921
8197
|
}
|
|
@@ -8059,7 +8335,7 @@ To rerun: \`@kody rerun ${taskId} --from <stage>\``
|
|
|
8059
8335
|
}
|
|
8060
8336
|
}
|
|
8061
8337
|
const state = await runPipeline(ctx);
|
|
8062
|
-
const files =
|
|
8338
|
+
const files = fs39.readdirSync(taskDir);
|
|
8063
8339
|
console.log(`
|
|
8064
8340
|
Artifacts in ${taskDir}:`);
|
|
8065
8341
|
for (const f of files) {
|
|
@@ -8141,8 +8417,8 @@ var init_entry = __esm({
|
|
|
8141
8417
|
});
|
|
8142
8418
|
|
|
8143
8419
|
// src/bin/cli.ts
|
|
8144
|
-
import * as
|
|
8145
|
-
import * as
|
|
8420
|
+
import * as fs40 from "fs";
|
|
8421
|
+
import * as path37 from "path";
|
|
8146
8422
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8147
8423
|
|
|
8148
8424
|
// src/bin/commands/init.ts
|
|
@@ -8473,6 +8749,24 @@ function initCommand(opts, pkgRoot) {
|
|
|
8473
8749
|
console.log(" \u2139 Kody Watch will monitor pipeline health every 30 minutes");
|
|
8474
8750
|
console.log(" \u2139 Digest issue will be created during bootstrap");
|
|
8475
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
|
+
}
|
|
8476
8770
|
} else {
|
|
8477
8771
|
console.log(" \u25CB kody-watch.yml template not found \u2014 skipping");
|
|
8478
8772
|
}
|
|
@@ -9982,11 +10276,11 @@ Create it manually.`, cwd);
|
|
|
9982
10276
|
|
|
9983
10277
|
// src/bin/cli.ts
|
|
9984
10278
|
init_architecture_detection();
|
|
9985
|
-
var __dirname2 =
|
|
9986
|
-
var PKG_ROOT =
|
|
10279
|
+
var __dirname2 = path37.dirname(fileURLToPath2(import.meta.url));
|
|
10280
|
+
var PKG_ROOT = path37.resolve(__dirname2, "..", "..");
|
|
9987
10281
|
function getVersion() {
|
|
9988
|
-
const pkgPath =
|
|
9989
|
-
const pkg = JSON.parse(
|
|
10282
|
+
const pkgPath = path37.join(PKG_ROOT, "package.json");
|
|
10283
|
+
const pkg = JSON.parse(fs40.readFileSync(pkgPath, "utf-8"));
|
|
9990
10284
|
return pkg.version;
|
|
9991
10285
|
}
|
|
9992
10286
|
var args = process.argv.slice(2);
|