@kody-ade/kody-engine 0.2.21 → 0.2.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/bin/kody2.js +717 -302
- package/dist/executables/fix/profile.json +3 -0
- package/dist/executables/fix/prompt.md +14 -7
- package/dist/executables/plan/profile.json +3 -0
- package/dist/executables/types.ts +2 -0
- package/package.json +15 -14
- package/templates/kody2.yml +48 -15
package/dist/bin/kody2.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@kody-ade/kody-engine",
|
|
6
|
-
version: "0.2.
|
|
6
|
+
version: "0.2.26",
|
|
7
7
|
description: "kody2 \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
8
8
|
license: "MIT",
|
|
9
9
|
type: "module",
|
|
@@ -49,18 +49,82 @@ var package_default = {
|
|
|
49
49
|
bugs: "https://github.com/aharonyaircohen/kody-engine/issues"
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
-
// src/
|
|
53
|
-
import
|
|
54
|
-
import * as
|
|
52
|
+
// src/chat-cli.ts
|
|
53
|
+
import { execFileSync as execFileSync16 } from "child_process";
|
|
54
|
+
import * as fs19 from "fs";
|
|
55
|
+
import * as path16 from "path";
|
|
56
|
+
|
|
57
|
+
// src/chat/events.ts
|
|
58
|
+
import * as fs from "fs";
|
|
59
|
+
import * as path from "path";
|
|
60
|
+
function eventsFilePath(cwd, sessionId) {
|
|
61
|
+
return path.join(cwd, ".kody", "events", `${sessionId}.jsonl`);
|
|
62
|
+
}
|
|
63
|
+
var FileSink = class {
|
|
64
|
+
constructor(file) {
|
|
65
|
+
this.file = file;
|
|
66
|
+
}
|
|
67
|
+
file;
|
|
68
|
+
async emit(event) {
|
|
69
|
+
fs.mkdirSync(path.dirname(this.file), { recursive: true });
|
|
70
|
+
fs.appendFileSync(this.file, `${JSON.stringify(event)}
|
|
71
|
+
`);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var HttpSink = class {
|
|
75
|
+
constructor(baseUrl, sessionId, logger = {
|
|
76
|
+
warn: (m) => process.stderr.write(`[kody2:chat] ${m}
|
|
77
|
+
`)
|
|
78
|
+
}) {
|
|
79
|
+
this.baseUrl = baseUrl;
|
|
80
|
+
this.sessionId = sessionId;
|
|
81
|
+
this.logger = logger;
|
|
82
|
+
}
|
|
83
|
+
baseUrl;
|
|
84
|
+
sessionId;
|
|
85
|
+
logger;
|
|
86
|
+
async emit(event) {
|
|
87
|
+
const url = withSessionParam(this.baseUrl, this.sessionId);
|
|
88
|
+
try {
|
|
89
|
+
const res = await fetch(url, {
|
|
90
|
+
method: "POST",
|
|
91
|
+
headers: { "content-type": "application/json" },
|
|
92
|
+
body: JSON.stringify(event),
|
|
93
|
+
signal: AbortSignal.timeout(5e3)
|
|
94
|
+
});
|
|
95
|
+
if (!res.ok) {
|
|
96
|
+
this.logger.warn(`HttpSink POST ${url} \u2192 ${res.status}`);
|
|
97
|
+
}
|
|
98
|
+
} catch (err) {
|
|
99
|
+
this.logger.warn(`HttpSink POST ${url} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var TeeSink = class {
|
|
104
|
+
constructor(sinks) {
|
|
105
|
+
this.sinks = sinks;
|
|
106
|
+
}
|
|
107
|
+
sinks;
|
|
108
|
+
async emit(event) {
|
|
109
|
+
await Promise.all(this.sinks.map((s) => s.emit(event)));
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
function withSessionParam(baseUrl, sessionId) {
|
|
113
|
+
const joiner = baseUrl.includes("?") ? "&" : "?";
|
|
114
|
+
return `${baseUrl}${joiner}sessionId=${encodeURIComponent(sessionId)}`;
|
|
115
|
+
}
|
|
116
|
+
function makeRunId(sessionId, suffix) {
|
|
117
|
+
return `chat-${sessionId}-${suffix}`;
|
|
118
|
+
}
|
|
55
119
|
|
|
56
120
|
// src/agent.ts
|
|
57
|
-
import * as
|
|
58
|
-
import * as
|
|
121
|
+
import * as fs3 from "fs";
|
|
122
|
+
import * as path3 from "path";
|
|
59
123
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
60
124
|
|
|
61
125
|
// src/config.ts
|
|
62
|
-
import * as
|
|
63
|
-
import * as
|
|
126
|
+
import * as fs2 from "fs";
|
|
127
|
+
import * as path2 from "path";
|
|
64
128
|
var LITELLM_DEFAULT_PORT = 4e3;
|
|
65
129
|
var LITELLM_DEFAULT_URL = `http://localhost:${LITELLM_DEFAULT_PORT}`;
|
|
66
130
|
function parseProviderModel(s) {
|
|
@@ -78,13 +142,13 @@ function needsLitellmProxy(model) {
|
|
|
78
142
|
return model.provider !== "claude" && model.provider !== "anthropic";
|
|
79
143
|
}
|
|
80
144
|
function loadConfig(projectDir = process.cwd()) {
|
|
81
|
-
const configPath =
|
|
82
|
-
if (!
|
|
145
|
+
const configPath = path2.join(projectDir, "kody.config.json");
|
|
146
|
+
if (!fs2.existsSync(configPath)) {
|
|
83
147
|
throw new Error(`kody.config.json not found at ${configPath}`);
|
|
84
148
|
}
|
|
85
149
|
let raw;
|
|
86
150
|
try {
|
|
87
|
-
raw = JSON.parse(
|
|
151
|
+
raw = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
|
|
88
152
|
} catch (err) {
|
|
89
153
|
const msg = err instanceof Error ? err.message : String(err);
|
|
90
154
|
throw new Error(`kody.config.json is invalid JSON: ${msg}`);
|
|
@@ -261,10 +325,10 @@ function formatBytes(bytes) {
|
|
|
261
325
|
// src/agent.ts
|
|
262
326
|
var DEFAULT_ALLOWED_TOOLS = ["Bash", "Edit", "Read", "Write", "Glob", "Grep"];
|
|
263
327
|
async function runAgent(opts) {
|
|
264
|
-
const ndjsonDir = opts.ndjsonDir ??
|
|
265
|
-
|
|
266
|
-
const ndjsonPath =
|
|
267
|
-
const fullLog =
|
|
328
|
+
const ndjsonDir = opts.ndjsonDir ?? path3.join(opts.cwd, ".kody2");
|
|
329
|
+
fs3.mkdirSync(ndjsonDir, { recursive: true });
|
|
330
|
+
const ndjsonPath = path3.join(ndjsonDir, "last-run.jsonl");
|
|
331
|
+
const fullLog = fs3.createWriteStream(ndjsonPath, { flags: "w" });
|
|
268
332
|
const env = {
|
|
269
333
|
...process.env,
|
|
270
334
|
SKIP_HOOKS: "1",
|
|
@@ -295,6 +359,9 @@ async function runAgent(opts) {
|
|
|
295
359
|
if (typeof opts.maxTurns === "number" && opts.maxTurns > 0) {
|
|
296
360
|
queryOptions.maxTurns = opts.maxTurns;
|
|
297
361
|
}
|
|
362
|
+
if (typeof opts.maxThinkingTokens === "number" && opts.maxThinkingTokens > 0) {
|
|
363
|
+
queryOptions.maxThinkingTokens = opts.maxThinkingTokens;
|
|
364
|
+
}
|
|
298
365
|
if (typeof opts.systemPromptAppend === "string" && opts.systemPromptAppend.length > 0) {
|
|
299
366
|
queryOptions.systemPrompt = { type: "preset", preset: "claude_code", append: opts.systemPromptAppend };
|
|
300
367
|
}
|
|
@@ -336,11 +403,228 @@ async function runAgent(opts) {
|
|
|
336
403
|
return { outcome, finalText, error: errorMessage, ndjsonPath };
|
|
337
404
|
}
|
|
338
405
|
|
|
406
|
+
// src/chat/session.ts
|
|
407
|
+
import * as fs4 from "fs";
|
|
408
|
+
import * as path4 from "path";
|
|
409
|
+
function sessionFilePath(cwd, sessionId) {
|
|
410
|
+
return path4.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
|
|
411
|
+
}
|
|
412
|
+
function readSession(file) {
|
|
413
|
+
if (!fs4.existsSync(file)) return [];
|
|
414
|
+
const raw = fs4.readFileSync(file, "utf-8").trim();
|
|
415
|
+
if (!raw) return [];
|
|
416
|
+
const turns = [];
|
|
417
|
+
for (const line of raw.split("\n")) {
|
|
418
|
+
if (!line.trim()) continue;
|
|
419
|
+
try {
|
|
420
|
+
const parsed = JSON.parse(line);
|
|
421
|
+
if (parsed.role !== "user" && parsed.role !== "assistant") continue;
|
|
422
|
+
if (typeof parsed.content !== "string") continue;
|
|
423
|
+
turns.push(parsed);
|
|
424
|
+
} catch {
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return turns;
|
|
428
|
+
}
|
|
429
|
+
function appendTurn(file, turn) {
|
|
430
|
+
fs4.mkdirSync(path4.dirname(file), { recursive: true });
|
|
431
|
+
const line = JSON.stringify({
|
|
432
|
+
role: turn.role,
|
|
433
|
+
content: turn.content,
|
|
434
|
+
timestamp: turn.timestamp,
|
|
435
|
+
toolCalls: turn.toolCalls ?? []
|
|
436
|
+
});
|
|
437
|
+
fs4.appendFileSync(file, `${line}
|
|
438
|
+
`);
|
|
439
|
+
}
|
|
440
|
+
function seedInitialMessage(file, message) {
|
|
441
|
+
if (!message.trim()) return false;
|
|
442
|
+
const turns = readSession(file);
|
|
443
|
+
const lastUser = [...turns].reverse().find((t) => t.role === "user");
|
|
444
|
+
if (lastUser && lastUser.content === message) return false;
|
|
445
|
+
appendTurn(file, {
|
|
446
|
+
role: "user",
|
|
447
|
+
content: message,
|
|
448
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
449
|
+
});
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// src/chat/loop.ts
|
|
454
|
+
var CHAT_SYSTEM_PROMPT = [
|
|
455
|
+
"You are Kody, an AI assistant for the Kody Operations Dashboard. Reply to the user's",
|
|
456
|
+
"latest message using the full conversation below as context. Keep replies focused,",
|
|
457
|
+
"technical when appropriate, and formatted in Markdown. Use the available tools to",
|
|
458
|
+
"read repository code or execute small checks when it helps you answer \u2014 otherwise",
|
|
459
|
+
"reply directly. Do not invent file paths, commit SHAs, or command output."
|
|
460
|
+
].join("\n");
|
|
461
|
+
async function runChatTurn(opts) {
|
|
462
|
+
const turns = readSession(opts.sessionFile);
|
|
463
|
+
if (turns.length === 0) {
|
|
464
|
+
const error = "session file is empty \u2014 nothing to reply to";
|
|
465
|
+
await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
|
|
466
|
+
return { exitCode: 64, error };
|
|
467
|
+
}
|
|
468
|
+
const lastTurn = turns[turns.length - 1];
|
|
469
|
+
if (lastTurn.role !== "user") {
|
|
470
|
+
const error = "last turn is not a user message \u2014 assistant already replied";
|
|
471
|
+
await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
|
|
472
|
+
return { exitCode: 64, error };
|
|
473
|
+
}
|
|
474
|
+
const prompt = buildPrompt(turns, opts.systemPrompt ?? CHAT_SYSTEM_PROMPT);
|
|
475
|
+
const invoke = opts.invokeAgent ?? ((p) => runAgent({
|
|
476
|
+
prompt: p,
|
|
477
|
+
model: opts.model,
|
|
478
|
+
cwd: opts.cwd,
|
|
479
|
+
litellmUrl: opts.litellmUrl,
|
|
480
|
+
verbose: opts.verbose,
|
|
481
|
+
quiet: opts.quiet
|
|
482
|
+
}));
|
|
483
|
+
let result;
|
|
484
|
+
try {
|
|
485
|
+
result = await invoke(prompt);
|
|
486
|
+
} catch (err) {
|
|
487
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
488
|
+
await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
|
|
489
|
+
return { exitCode: 99, error };
|
|
490
|
+
}
|
|
491
|
+
if (result.outcome !== "completed") {
|
|
492
|
+
const error = result.error ?? "agent did not complete";
|
|
493
|
+
await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
|
|
494
|
+
return { exitCode: 99, error };
|
|
495
|
+
}
|
|
496
|
+
const reply = result.finalText.trim();
|
|
497
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
498
|
+
appendTurn(opts.sessionFile, {
|
|
499
|
+
role: "assistant",
|
|
500
|
+
content: reply,
|
|
501
|
+
timestamp: now
|
|
502
|
+
});
|
|
503
|
+
await emit(opts.sink, "chat.message", opts.sessionId, "message", {
|
|
504
|
+
sessionId: opts.sessionId,
|
|
505
|
+
role: "assistant",
|
|
506
|
+
content: reply,
|
|
507
|
+
timestamp: now
|
|
508
|
+
});
|
|
509
|
+
await emit(opts.sink, "chat.done", opts.sessionId, "done", { sessionId: opts.sessionId });
|
|
510
|
+
return { exitCode: 0, reply };
|
|
511
|
+
}
|
|
512
|
+
function buildPrompt(turns, systemPrompt) {
|
|
513
|
+
const header = `System: ${systemPrompt}`;
|
|
514
|
+
const body = turns.map((t) => `${t.role === "user" ? "User" : "Assistant"}: ${t.content}`).join("\n\n");
|
|
515
|
+
return `${header}
|
|
516
|
+
|
|
517
|
+
${body}
|
|
518
|
+
|
|
519
|
+
Assistant:`;
|
|
520
|
+
}
|
|
521
|
+
async function emit(sink, type, sessionId, suffix, payload) {
|
|
522
|
+
await sink.emit({
|
|
523
|
+
event: type,
|
|
524
|
+
payload,
|
|
525
|
+
runId: makeRunId(sessionId, suffix),
|
|
526
|
+
emittedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// src/kody2-cli.ts
|
|
531
|
+
import { execFileSync as execFileSync15 } from "child_process";
|
|
532
|
+
import * as fs18 from "fs";
|
|
533
|
+
import * as path15 from "path";
|
|
534
|
+
|
|
535
|
+
// src/dispatch.ts
|
|
536
|
+
import * as fs5 from "fs";
|
|
537
|
+
function autoDispatch(opts) {
|
|
538
|
+
const explicit = opts?.explicit;
|
|
539
|
+
if (explicit?.issueNumber && explicit.issueNumber > 0) {
|
|
540
|
+
return {
|
|
541
|
+
executable: "run",
|
|
542
|
+
cliArgs: { issue: explicit.issueNumber },
|
|
543
|
+
target: explicit.issueNumber
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
547
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
548
|
+
if (!eventName || !eventPath || !fs5.existsSync(eventPath)) return null;
|
|
549
|
+
let event = {};
|
|
550
|
+
try {
|
|
551
|
+
event = JSON.parse(fs5.readFileSync(eventPath, "utf-8"));
|
|
552
|
+
} catch {
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
if (eventName === "workflow_dispatch") {
|
|
556
|
+
const n = parseInt(String(event.inputs?.issue_number ?? ""), 10);
|
|
557
|
+
if (!Number.isNaN(n) && n > 0) {
|
|
558
|
+
return { executable: "run", cliArgs: { issue: n }, target: n };
|
|
559
|
+
}
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
if (eventName !== "issue_comment") return null;
|
|
563
|
+
const body = String(event.comment?.body ?? "").toLowerCase();
|
|
564
|
+
const targetNum = Number(event.issue?.number ?? 0);
|
|
565
|
+
const isPr = !!event.issue?.pull_request;
|
|
566
|
+
if (!targetNum) return null;
|
|
567
|
+
const afterTag = extractAfterTag(body);
|
|
568
|
+
if (isPr) {
|
|
569
|
+
if (/\bfix-ci\b/.test(afterTag)) {
|
|
570
|
+
return { executable: "fix-ci", cliArgs: { pr: targetNum }, target: targetNum };
|
|
571
|
+
}
|
|
572
|
+
if (/\bresolve\b/.test(afterTag)) {
|
|
573
|
+
return { executable: "resolve", cliArgs: { pr: targetNum }, target: targetNum };
|
|
574
|
+
}
|
|
575
|
+
if (/\breview\b/.test(afterTag)) {
|
|
576
|
+
return { executable: "review", cliArgs: { pr: targetNum }, target: targetNum };
|
|
577
|
+
}
|
|
578
|
+
if (/\bsync\b/.test(afterTag)) {
|
|
579
|
+
return { executable: "sync", cliArgs: { pr: targetNum }, target: targetNum };
|
|
580
|
+
}
|
|
581
|
+
const feedback = extractFeedback(afterTag);
|
|
582
|
+
return {
|
|
583
|
+
executable: "fix",
|
|
584
|
+
cliArgs: { pr: targetNum, ...feedback ? { feedback } : {} },
|
|
585
|
+
target: targetNum
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
const sub = extractSubcommand(afterTag);
|
|
589
|
+
const defaultExec = opts?.config?.defaultExecutable ?? "run";
|
|
590
|
+
if (!sub) {
|
|
591
|
+
return asDispatch(defaultExec, targetNum);
|
|
592
|
+
}
|
|
593
|
+
if (sub === "orchestrate" || sub === "orchestrator") {
|
|
594
|
+
return { executable: "orchestrator", cliArgs: { issue: targetNum }, target: targetNum };
|
|
595
|
+
}
|
|
596
|
+
if (sub === "build") {
|
|
597
|
+
return { executable: "run", cliArgs: { issue: targetNum }, target: targetNum };
|
|
598
|
+
}
|
|
599
|
+
return asDispatch(sub, targetNum);
|
|
600
|
+
}
|
|
601
|
+
function asDispatch(executable, target) {
|
|
602
|
+
return { executable, cliArgs: { issue: target }, target };
|
|
603
|
+
}
|
|
604
|
+
function extractAfterTag(body) {
|
|
605
|
+
const idx = body.indexOf("@kody2");
|
|
606
|
+
if (idx === -1) return body;
|
|
607
|
+
return body.slice(idx + "@kody2".length).trim();
|
|
608
|
+
}
|
|
609
|
+
function extractSubcommand(afterTag) {
|
|
610
|
+
const match = afterTag.match(/^([a-z][a-z0-9-]{1,40})\b/);
|
|
611
|
+
if (!match) return null;
|
|
612
|
+
return match[1];
|
|
613
|
+
}
|
|
614
|
+
function extractFeedback(afterTag) {
|
|
615
|
+
const cleaned = afterTag.replace(/^(fix|please|kindly)[\s:,.-]+/i, "").trim();
|
|
616
|
+
return cleaned.length > 0 ? cleaned : void 0;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// src/executor.ts
|
|
620
|
+
import * as fs17 from "fs";
|
|
621
|
+
import * as path14 from "path";
|
|
622
|
+
|
|
339
623
|
// src/litellm.ts
|
|
340
624
|
import { execFileSync, spawn } from "child_process";
|
|
341
|
-
import * as
|
|
625
|
+
import * as fs6 from "fs";
|
|
342
626
|
import * as os from "os";
|
|
343
|
-
import * as
|
|
627
|
+
import * as path5 from "path";
|
|
344
628
|
async function checkLitellmHealth(url) {
|
|
345
629
|
try {
|
|
346
630
|
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
@@ -380,20 +664,20 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
380
664
|
throw new Error("litellm not installed \u2014 run: pip install 'litellm[proxy]'");
|
|
381
665
|
}
|
|
382
666
|
}
|
|
383
|
-
const configPath =
|
|
384
|
-
|
|
667
|
+
const configPath = path5.join(os.tmpdir(), `kody2-litellm-${Date.now()}.yaml`);
|
|
668
|
+
fs6.writeFileSync(configPath, generateLitellmConfigYaml(model));
|
|
385
669
|
const portMatch = url.match(/:(\d+)/);
|
|
386
670
|
const port = portMatch ? portMatch[1] : "4000";
|
|
387
671
|
const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
|
|
388
672
|
const dotenvVars = readDotenvApiKeys(projectDir);
|
|
389
|
-
const logPath =
|
|
390
|
-
const outFd =
|
|
673
|
+
const logPath = path5.join(os.tmpdir(), `kody2-litellm-${Date.now()}.log`);
|
|
674
|
+
const outFd = fs6.openSync(logPath, "w");
|
|
391
675
|
const child = spawn(cmd, args, {
|
|
392
676
|
stdio: ["ignore", outFd, outFd],
|
|
393
677
|
detached: true,
|
|
394
678
|
env: stripBlockingEnv({ ...process.env, ...dotenvVars })
|
|
395
679
|
});
|
|
396
|
-
|
|
680
|
+
fs6.closeSync(outFd);
|
|
397
681
|
for (let i = 0; i < 30; i++) {
|
|
398
682
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
399
683
|
if (await checkLitellmHealth(url)) {
|
|
@@ -410,7 +694,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
410
694
|
}
|
|
411
695
|
let logTail = "";
|
|
412
696
|
try {
|
|
413
|
-
logTail =
|
|
697
|
+
logTail = fs6.readFileSync(logPath, "utf-8").slice(-2e3);
|
|
414
698
|
} catch {
|
|
415
699
|
}
|
|
416
700
|
try {
|
|
@@ -421,10 +705,10 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
421
705
|
${logTail}`);
|
|
422
706
|
}
|
|
423
707
|
function readDotenvApiKeys(projectDir) {
|
|
424
|
-
const dotenvPath =
|
|
425
|
-
if (!
|
|
708
|
+
const dotenvPath = path5.join(projectDir, ".env");
|
|
709
|
+
if (!fs6.existsSync(dotenvPath)) return {};
|
|
426
710
|
const result = {};
|
|
427
|
-
for (const rawLine of
|
|
711
|
+
for (const rawLine of fs6.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
428
712
|
const line = rawLine.trim();
|
|
429
713
|
if (!line || line.startsWith("#")) continue;
|
|
430
714
|
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
@@ -447,8 +731,8 @@ function stripBlockingEnv(env) {
|
|
|
447
731
|
}
|
|
448
732
|
|
|
449
733
|
// src/profile.ts
|
|
450
|
-
import * as
|
|
451
|
-
import * as
|
|
734
|
+
import * as fs7 from "fs";
|
|
735
|
+
import * as path6 from "path";
|
|
452
736
|
var VALID_INPUT_TYPES = /* @__PURE__ */ new Set(["int", "string", "bool", "enum"]);
|
|
453
737
|
var VALID_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
|
|
454
738
|
var ProfileError = class extends Error {
|
|
@@ -461,12 +745,12 @@ var ProfileError = class extends Error {
|
|
|
461
745
|
profilePath;
|
|
462
746
|
};
|
|
463
747
|
function loadProfile(profilePath) {
|
|
464
|
-
if (!
|
|
748
|
+
if (!fs7.existsSync(profilePath)) {
|
|
465
749
|
throw new ProfileError(profilePath, "file not found");
|
|
466
750
|
}
|
|
467
751
|
let raw;
|
|
468
752
|
try {
|
|
469
|
-
raw = JSON.parse(
|
|
753
|
+
raw = JSON.parse(fs7.readFileSync(profilePath, "utf-8"));
|
|
470
754
|
} catch (err) {
|
|
471
755
|
throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
472
756
|
}
|
|
@@ -490,7 +774,7 @@ function loadProfile(profilePath) {
|
|
|
490
774
|
outputContract: r.outputContract,
|
|
491
775
|
inputArtifacts: parseInputArtifacts(profilePath, r.input),
|
|
492
776
|
outputArtifacts: parseOutputArtifacts(profilePath, r.output),
|
|
493
|
-
dir:
|
|
777
|
+
dir: path6.dirname(profilePath)
|
|
494
778
|
};
|
|
495
779
|
return profile;
|
|
496
780
|
}
|
|
@@ -556,6 +840,7 @@ function parseClaudeCode(p, raw) {
|
|
|
556
840
|
model: typeof r.model === "string" ? r.model : "inherit",
|
|
557
841
|
permissionMode,
|
|
558
842
|
maxTurns: typeof r.maxTurns === "number" ? r.maxTurns : null,
|
|
843
|
+
maxThinkingTokens: typeof r.maxThinkingTokens === "number" ? r.maxThinkingTokens : null,
|
|
559
844
|
systemPromptAppend: typeof r.systemPromptAppend === "string" ? r.systemPromptAppend : null,
|
|
560
845
|
tools,
|
|
561
846
|
hooks: Array.isArray(r.hooks) ? r.hooks : [],
|
|
@@ -669,21 +954,21 @@ function parseScriptList(p, key, raw) {
|
|
|
669
954
|
}
|
|
670
955
|
|
|
671
956
|
// src/scripts/buildSyntheticPlugin.ts
|
|
672
|
-
import * as
|
|
957
|
+
import * as fs8 from "fs";
|
|
673
958
|
import * as os2 from "os";
|
|
674
|
-
import * as
|
|
959
|
+
import * as path7 from "path";
|
|
675
960
|
function getPluginsCatalogRoot() {
|
|
676
|
-
const here =
|
|
961
|
+
const here = path7.dirname(new URL(import.meta.url).pathname);
|
|
677
962
|
const candidates = [
|
|
678
|
-
|
|
963
|
+
path7.join(here, "..", "plugins"),
|
|
679
964
|
// dev: src/scripts → src/plugins
|
|
680
|
-
|
|
965
|
+
path7.join(here, "..", "..", "plugins"),
|
|
681
966
|
// built: dist/scripts → dist/plugins
|
|
682
|
-
|
|
967
|
+
path7.join(here, "..", "..", "src", "plugins")
|
|
683
968
|
// fallback
|
|
684
969
|
];
|
|
685
970
|
for (const c of candidates) {
|
|
686
|
-
if (
|
|
971
|
+
if (fs8.existsSync(c) && fs8.statSync(c).isDirectory()) return c;
|
|
687
972
|
}
|
|
688
973
|
return candidates[0];
|
|
689
974
|
}
|
|
@@ -693,50 +978,50 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
693
978
|
if (!needsSynthetic) return;
|
|
694
979
|
const catalog = getPluginsCatalogRoot();
|
|
695
980
|
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
696
|
-
const root =
|
|
697
|
-
|
|
981
|
+
const root = path7.join(os2.tmpdir(), `kody2-synth-${runId}`);
|
|
982
|
+
fs8.mkdirSync(path7.join(root, ".claude-plugin"), { recursive: true });
|
|
698
983
|
if (cc.skills.length > 0) {
|
|
699
|
-
const dst =
|
|
700
|
-
|
|
984
|
+
const dst = path7.join(root, "skills");
|
|
985
|
+
fs8.mkdirSync(dst, { recursive: true });
|
|
701
986
|
for (const name of cc.skills) {
|
|
702
|
-
const src =
|
|
703
|
-
if (!
|
|
704
|
-
copyDir(src,
|
|
987
|
+
const src = path7.join(catalog, "skills", name);
|
|
988
|
+
if (!fs8.existsSync(src)) throw new Error(`buildSyntheticPlugin: skill not found in catalog: ${name}`);
|
|
989
|
+
copyDir(src, path7.join(dst, name));
|
|
705
990
|
}
|
|
706
991
|
}
|
|
707
992
|
if (cc.commands.length > 0) {
|
|
708
|
-
const dst =
|
|
709
|
-
|
|
993
|
+
const dst = path7.join(root, "commands");
|
|
994
|
+
fs8.mkdirSync(dst, { recursive: true });
|
|
710
995
|
for (const name of cc.commands) {
|
|
711
|
-
const src =
|
|
712
|
-
if (!
|
|
713
|
-
|
|
996
|
+
const src = path7.join(catalog, "commands", `${name}.md`);
|
|
997
|
+
if (!fs8.existsSync(src)) throw new Error(`buildSyntheticPlugin: command not found in catalog: ${name}`);
|
|
998
|
+
fs8.copyFileSync(src, path7.join(dst, `${name}.md`));
|
|
714
999
|
}
|
|
715
1000
|
}
|
|
716
1001
|
if (cc.subagents.length > 0) {
|
|
717
|
-
const dst =
|
|
718
|
-
|
|
1002
|
+
const dst = path7.join(root, "agents");
|
|
1003
|
+
fs8.mkdirSync(dst, { recursive: true });
|
|
719
1004
|
for (const name of cc.subagents) {
|
|
720
|
-
const src =
|
|
721
|
-
if (!
|
|
722
|
-
|
|
1005
|
+
const src = path7.join(catalog, "agents", `${name}.md`);
|
|
1006
|
+
if (!fs8.existsSync(src)) throw new Error(`buildSyntheticPlugin: subagent not found in catalog: ${name}`);
|
|
1007
|
+
fs8.copyFileSync(src, path7.join(dst, `${name}.md`));
|
|
723
1008
|
}
|
|
724
1009
|
}
|
|
725
1010
|
if (cc.hooks.length > 0) {
|
|
726
|
-
const dst =
|
|
727
|
-
|
|
1011
|
+
const dst = path7.join(root, "hooks");
|
|
1012
|
+
fs8.mkdirSync(dst, { recursive: true });
|
|
728
1013
|
const merged = { hooks: {} };
|
|
729
1014
|
for (const name of cc.hooks) {
|
|
730
|
-
const src =
|
|
731
|
-
if (!
|
|
732
|
-
const parsed = JSON.parse(
|
|
1015
|
+
const src = path7.join(catalog, "hooks", `${name}.json`);
|
|
1016
|
+
if (!fs8.existsSync(src)) throw new Error(`buildSyntheticPlugin: hook not found in catalog: ${name}`);
|
|
1017
|
+
const parsed = JSON.parse(fs8.readFileSync(src, "utf-8"));
|
|
733
1018
|
for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
|
|
734
1019
|
if (!Array.isArray(entries)) continue;
|
|
735
1020
|
if (!merged.hooks[event]) merged.hooks[event] = [];
|
|
736
1021
|
merged.hooks[event].push(...entries);
|
|
737
1022
|
}
|
|
738
1023
|
}
|
|
739
|
-
|
|
1024
|
+
fs8.writeFileSync(path7.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
|
|
740
1025
|
`);
|
|
741
1026
|
}
|
|
742
1027
|
const manifest = {
|
|
@@ -747,17 +1032,17 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
747
1032
|
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
748
1033
|
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
749
1034
|
if (cc.subagents.length > 0) manifest.agents = cc.subagents.map((n) => `./agents/${n}.md`);
|
|
750
|
-
|
|
1035
|
+
fs8.writeFileSync(path7.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
751
1036
|
`);
|
|
752
1037
|
ctx.data.syntheticPluginPath = root;
|
|
753
1038
|
};
|
|
754
1039
|
function copyDir(src, dst) {
|
|
755
|
-
|
|
756
|
-
for (const ent of
|
|
757
|
-
const s =
|
|
758
|
-
const d =
|
|
1040
|
+
fs8.mkdirSync(dst, { recursive: true });
|
|
1041
|
+
for (const ent of fs8.readdirSync(src, { withFileTypes: true })) {
|
|
1042
|
+
const s = path7.join(src, ent.name);
|
|
1043
|
+
const d = path7.join(dst, ent.name);
|
|
759
1044
|
if (ent.isDirectory()) copyDir(s, d);
|
|
760
|
-
else if (ent.isFile())
|
|
1045
|
+
else if (ent.isFile()) fs8.copyFileSync(s, d);
|
|
761
1046
|
}
|
|
762
1047
|
}
|
|
763
1048
|
|
|
@@ -823,18 +1108,18 @@ function formatMissesForFeedback(misses) {
|
|
|
823
1108
|
}
|
|
824
1109
|
|
|
825
1110
|
// src/prompt.ts
|
|
826
|
-
import * as
|
|
827
|
-
import * as
|
|
1111
|
+
import * as fs9 from "fs";
|
|
1112
|
+
import * as path8 from "path";
|
|
828
1113
|
var CONVENTIONS_PER_FILE_MAX_BYTES = 3e4;
|
|
829
1114
|
var CONVENTION_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
830
1115
|
function loadProjectConventions(projectDir) {
|
|
831
1116
|
const out = [];
|
|
832
1117
|
for (const rel of CONVENTION_FILES) {
|
|
833
|
-
const abs =
|
|
834
|
-
if (!
|
|
1118
|
+
const abs = path8.join(projectDir, rel);
|
|
1119
|
+
if (!fs9.existsSync(abs)) continue;
|
|
835
1120
|
let content;
|
|
836
1121
|
try {
|
|
837
|
-
content =
|
|
1122
|
+
content = fs9.readFileSync(abs, "utf-8");
|
|
838
1123
|
} catch {
|
|
839
1124
|
continue;
|
|
840
1125
|
}
|
|
@@ -848,23 +1133,49 @@ function loadProjectConventions(projectDir) {
|
|
|
848
1133
|
}
|
|
849
1134
|
function parseAgentResult(finalText) {
|
|
850
1135
|
const text = (finalText || "").trim();
|
|
851
|
-
if (!text)
|
|
1136
|
+
if (!text)
|
|
1137
|
+
return {
|
|
1138
|
+
done: false,
|
|
1139
|
+
commitMessage: "",
|
|
1140
|
+
prSummary: "",
|
|
1141
|
+
feedbackActions: "",
|
|
1142
|
+
failureReason: "agent produced no final message"
|
|
1143
|
+
};
|
|
852
1144
|
const failedMatch = text.match(/(?:^|\n)\s*FAILED\s*:\s*(.+?)\s*$/s);
|
|
853
1145
|
if (failedMatch) {
|
|
854
|
-
return { done: false, commitMessage: "", prSummary: "", failureReason: failedMatch[1].trim() };
|
|
1146
|
+
return { done: false, commitMessage: "", prSummary: "", feedbackActions: "", failureReason: failedMatch[1].trim() };
|
|
855
1147
|
}
|
|
856
1148
|
if (!/(^|\n)\s*DONE\b/i.test(text)) {
|
|
857
|
-
return {
|
|
1149
|
+
return {
|
|
1150
|
+
done: false,
|
|
1151
|
+
commitMessage: "",
|
|
1152
|
+
prSummary: "",
|
|
1153
|
+
feedbackActions: "",
|
|
1154
|
+
failureReason: "no DONE or FAILED marker in agent output"
|
|
1155
|
+
};
|
|
858
1156
|
}
|
|
859
1157
|
const commitMatch = text.match(/^[ \t]*COMMIT_MSG\s*:\s*(.+)$/im);
|
|
860
1158
|
const commitMessage = commitMatch ? commitMatch[1].trim() : "";
|
|
1159
|
+
const feedbackActions = extractBlock(
|
|
1160
|
+
text,
|
|
1161
|
+
/(?:^|\n)[ \t]*FEEDBACK_ACTIONS\s*:[ \t]*\n/i,
|
|
1162
|
+
/(?:^|\n)[ \t]*(?:COMMIT_MSG|PR_SUMMARY)\s*:/i
|
|
1163
|
+
);
|
|
861
1164
|
const summaryStart = text.search(/(^|\n)[ \t]*PR_SUMMARY\s*:[ \t]*\n/i);
|
|
862
1165
|
let prSummary = "";
|
|
863
1166
|
if (summaryStart !== -1) {
|
|
864
1167
|
const afterMarker = text.slice(summaryStart).replace(/^[\s\S]*?PR_SUMMARY\s*:[ \t]*\n/i, "");
|
|
865
1168
|
prSummary = afterMarker.replace(/\n\s*```\s*$/g, "").replace(/```\s*$/g, "").trim();
|
|
866
1169
|
}
|
|
867
|
-
return { done: true, commitMessage, prSummary, failureReason: "" };
|
|
1170
|
+
return { done: true, commitMessage, prSummary, feedbackActions, failureReason: "" };
|
|
1171
|
+
}
|
|
1172
|
+
function extractBlock(text, startMarker, endMarker) {
|
|
1173
|
+
const startIdx = text.search(startMarker);
|
|
1174
|
+
if (startIdx === -1) return "";
|
|
1175
|
+
const afterStart = text.slice(startIdx).replace(startMarker, "");
|
|
1176
|
+
const endIdx = afterStart.search(endMarker);
|
|
1177
|
+
const body = endIdx === -1 ? afterStart : afterStart.slice(0, endIdx);
|
|
1178
|
+
return body.replace(/\n\s*```\s*$/g, "").trim();
|
|
868
1179
|
}
|
|
869
1180
|
|
|
870
1181
|
// src/scripts/checkCoverageWithRetry.ts
|
|
@@ -911,8 +1222,8 @@ import { execFileSync as execFileSync4 } from "child_process";
|
|
|
911
1222
|
|
|
912
1223
|
// src/commit.ts
|
|
913
1224
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
914
|
-
import * as
|
|
915
|
-
import * as
|
|
1225
|
+
import * as fs10 from "fs";
|
|
1226
|
+
import * as path9 from "path";
|
|
916
1227
|
var FORBIDDEN_PATH_PREFIXES = [
|
|
917
1228
|
".kody/",
|
|
918
1229
|
".kody-engine/",
|
|
@@ -967,18 +1278,18 @@ function tryGit(args, cwd) {
|
|
|
967
1278
|
}
|
|
968
1279
|
function abortUnfinishedGitOps(cwd) {
|
|
969
1280
|
const aborted = [];
|
|
970
|
-
const gitDir =
|
|
971
|
-
if (!
|
|
972
|
-
if (
|
|
1281
|
+
const gitDir = path9.join(cwd ?? process.cwd(), ".git");
|
|
1282
|
+
if (!fs10.existsSync(gitDir)) return aborted;
|
|
1283
|
+
if (fs10.existsSync(path9.join(gitDir, "MERGE_HEAD"))) {
|
|
973
1284
|
if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
|
|
974
1285
|
}
|
|
975
|
-
if (
|
|
1286
|
+
if (fs10.existsSync(path9.join(gitDir, "CHERRY_PICK_HEAD"))) {
|
|
976
1287
|
if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
|
|
977
1288
|
}
|
|
978
|
-
if (
|
|
1289
|
+
if (fs10.existsSync(path9.join(gitDir, "REVERT_HEAD"))) {
|
|
979
1290
|
if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
|
|
980
1291
|
}
|
|
981
|
-
if (
|
|
1292
|
+
if (fs10.existsSync(path9.join(gitDir, "rebase-merge")) || fs10.existsSync(path9.join(gitDir, "rebase-apply"))) {
|
|
982
1293
|
if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
|
|
983
1294
|
}
|
|
984
1295
|
try {
|
|
@@ -1020,7 +1331,7 @@ function normalizeCommitMessage(raw) {
|
|
|
1020
1331
|
function commitAndPush(branch, agentMessage, cwd) {
|
|
1021
1332
|
const allChanged = listChangedFiles(cwd);
|
|
1022
1333
|
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
|
|
1023
|
-
const mergeHeadExists =
|
|
1334
|
+
const mergeHeadExists = fs10.existsSync(path9.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
|
|
1024
1335
|
if (allowedFiles.length === 0 && !mergeHeadExists) {
|
|
1025
1336
|
return { committed: false, pushed: false, sha: "", message: "" };
|
|
1026
1337
|
}
|
|
@@ -1110,20 +1421,20 @@ function defaultCommitMessage(mode, data) {
|
|
|
1110
1421
|
}
|
|
1111
1422
|
|
|
1112
1423
|
// src/scripts/composePrompt.ts
|
|
1113
|
-
import * as
|
|
1114
|
-
import * as
|
|
1424
|
+
import * as fs11 from "fs";
|
|
1425
|
+
import * as path10 from "path";
|
|
1115
1426
|
var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
1116
1427
|
var composePrompt = async (ctx, profile) => {
|
|
1117
1428
|
const explicit = ctx.data.promptTemplate;
|
|
1118
1429
|
const mode = ctx.args.mode;
|
|
1119
1430
|
const candidates = [
|
|
1120
|
-
explicit ?
|
|
1121
|
-
mode ?
|
|
1122
|
-
|
|
1431
|
+
explicit ? path10.join(profile.dir, explicit) : null,
|
|
1432
|
+
mode ? path10.join(profile.dir, "prompts", `${mode}.md`) : null,
|
|
1433
|
+
path10.join(profile.dir, "prompt.md")
|
|
1123
1434
|
].filter(Boolean);
|
|
1124
1435
|
let templatePath = "";
|
|
1125
1436
|
for (const c of candidates) {
|
|
1126
|
-
if (
|
|
1437
|
+
if (fs11.existsSync(c)) {
|
|
1127
1438
|
templatePath = c;
|
|
1128
1439
|
break;
|
|
1129
1440
|
}
|
|
@@ -1131,7 +1442,7 @@ var composePrompt = async (ctx, profile) => {
|
|
|
1131
1442
|
if (!templatePath) {
|
|
1132
1443
|
throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
|
|
1133
1444
|
}
|
|
1134
|
-
const template =
|
|
1445
|
+
const template = fs11.readFileSync(templatePath, "utf-8");
|
|
1135
1446
|
const tokens = {
|
|
1136
1447
|
...stringifyAll(ctx.args, "args."),
|
|
1137
1448
|
...stringifyAll(ctx.data, ""),
|
|
@@ -1603,7 +1914,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd) {
|
|
|
1603
1914
|
|
|
1604
1915
|
// src/gha.ts
|
|
1605
1916
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
1606
|
-
import * as
|
|
1917
|
+
import * as fs12 from "fs";
|
|
1607
1918
|
function getRunUrl() {
|
|
1608
1919
|
const server = process.env.GITHUB_SERVER_URL;
|
|
1609
1920
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
@@ -1614,10 +1925,10 @@ function getRunUrl() {
|
|
|
1614
1925
|
function reactToTriggerComment(cwd) {
|
|
1615
1926
|
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
1616
1927
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
1617
|
-
if (!eventPath || !
|
|
1928
|
+
if (!eventPath || !fs12.existsSync(eventPath)) return;
|
|
1618
1929
|
let event = null;
|
|
1619
1930
|
try {
|
|
1620
|
-
event = JSON.parse(
|
|
1931
|
+
event = JSON.parse(fs12.readFileSync(eventPath, "utf-8"));
|
|
1621
1932
|
} catch {
|
|
1622
1933
|
return;
|
|
1623
1934
|
}
|
|
@@ -1843,35 +2154,35 @@ function tryPostPr2(prNumber, body, cwd) {
|
|
|
1843
2154
|
|
|
1844
2155
|
// src/scripts/initFlow.ts
|
|
1845
2156
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
1846
|
-
import * as
|
|
1847
|
-
import * as
|
|
2157
|
+
import * as fs14 from "fs";
|
|
2158
|
+
import * as path12 from "path";
|
|
1848
2159
|
|
|
1849
2160
|
// src/registry.ts
|
|
1850
|
-
import * as
|
|
1851
|
-
import * as
|
|
2161
|
+
import * as fs13 from "fs";
|
|
2162
|
+
import * as path11 from "path";
|
|
1852
2163
|
function getExecutablesRoot() {
|
|
1853
|
-
const here =
|
|
2164
|
+
const here = path11.dirname(new URL(import.meta.url).pathname);
|
|
1854
2165
|
const candidates = [
|
|
1855
|
-
|
|
2166
|
+
path11.join(here, "executables"),
|
|
1856
2167
|
// dev: src/
|
|
1857
|
-
|
|
2168
|
+
path11.join(here, "..", "executables"),
|
|
1858
2169
|
// built: dist/bin → dist/executables
|
|
1859
|
-
|
|
2170
|
+
path11.join(here, "..", "src", "executables")
|
|
1860
2171
|
// fallback
|
|
1861
2172
|
];
|
|
1862
2173
|
for (const c of candidates) {
|
|
1863
|
-
if (
|
|
2174
|
+
if (fs13.existsSync(c) && fs13.statSync(c).isDirectory()) return c;
|
|
1864
2175
|
}
|
|
1865
2176
|
return candidates[0];
|
|
1866
2177
|
}
|
|
1867
2178
|
function listExecutables(root = getExecutablesRoot()) {
|
|
1868
|
-
if (!
|
|
1869
|
-
const entries =
|
|
2179
|
+
if (!fs13.existsSync(root)) return [];
|
|
2180
|
+
const entries = fs13.readdirSync(root, { withFileTypes: true });
|
|
1870
2181
|
const out = [];
|
|
1871
2182
|
for (const ent of entries) {
|
|
1872
2183
|
if (!ent.isDirectory()) continue;
|
|
1873
|
-
const profilePath =
|
|
1874
|
-
if (
|
|
2184
|
+
const profilePath = path11.join(root, ent.name, "profile.json");
|
|
2185
|
+
if (fs13.existsSync(profilePath) && fs13.statSync(profilePath).isFile()) {
|
|
1875
2186
|
out.push({ name: ent.name, profilePath });
|
|
1876
2187
|
}
|
|
1877
2188
|
}
|
|
@@ -1879,8 +2190,8 @@ function listExecutables(root = getExecutablesRoot()) {
|
|
|
1879
2190
|
}
|
|
1880
2191
|
function hasExecutable(name, root = getExecutablesRoot()) {
|
|
1881
2192
|
if (!isSafeName(name)) return false;
|
|
1882
|
-
const profilePath =
|
|
1883
|
-
return
|
|
2193
|
+
const profilePath = path11.join(root, name, "profile.json");
|
|
2194
|
+
return fs13.existsSync(profilePath) && fs13.statSync(profilePath).isFile();
|
|
1884
2195
|
}
|
|
1885
2196
|
function isSafeName(name) {
|
|
1886
2197
|
return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
|
|
@@ -1909,9 +2220,9 @@ function parseGenericFlags(argv) {
|
|
|
1909
2220
|
|
|
1910
2221
|
// src/scripts/initFlow.ts
|
|
1911
2222
|
function detectPackageManager(cwd) {
|
|
1912
|
-
if (
|
|
1913
|
-
if (
|
|
1914
|
-
if (
|
|
2223
|
+
if (fs14.existsSync(path12.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
2224
|
+
if (fs14.existsSync(path12.join(cwd, "yarn.lock"))) return "yarn";
|
|
2225
|
+
if (fs14.existsSync(path12.join(cwd, "bun.lockb"))) return "bun";
|
|
1915
2226
|
return "npm";
|
|
1916
2227
|
}
|
|
1917
2228
|
function qualityCommandsFor(pm) {
|
|
@@ -2032,22 +2343,22 @@ function performInit(cwd, force) {
|
|
|
2032
2343
|
const pm = detectPackageManager(cwd);
|
|
2033
2344
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
2034
2345
|
const defaultBranch = defaultBranchFromGit(cwd);
|
|
2035
|
-
const configPath =
|
|
2036
|
-
if (
|
|
2346
|
+
const configPath = path12.join(cwd, "kody.config.json");
|
|
2347
|
+
if (fs14.existsSync(configPath) && !force) {
|
|
2037
2348
|
skipped.push("kody.config.json");
|
|
2038
2349
|
} else {
|
|
2039
2350
|
const cfg = makeConfig(pm, ownerRepo, defaultBranch);
|
|
2040
|
-
|
|
2351
|
+
fs14.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
2041
2352
|
`);
|
|
2042
2353
|
wrote.push("kody.config.json");
|
|
2043
2354
|
}
|
|
2044
|
-
const workflowDir =
|
|
2045
|
-
const workflowPath =
|
|
2046
|
-
if (
|
|
2355
|
+
const workflowDir = path12.join(cwd, ".github", "workflows");
|
|
2356
|
+
const workflowPath = path12.join(workflowDir, "kody2.yml");
|
|
2357
|
+
if (fs14.existsSync(workflowPath) && !force) {
|
|
2047
2358
|
skipped.push(".github/workflows/kody2.yml");
|
|
2048
2359
|
} else {
|
|
2049
|
-
|
|
2050
|
-
|
|
2360
|
+
fs14.mkdirSync(workflowDir, { recursive: true });
|
|
2361
|
+
fs14.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
2051
2362
|
wrote.push(".github/workflows/kody2.yml");
|
|
2052
2363
|
}
|
|
2053
2364
|
for (const exe of listExecutables()) {
|
|
@@ -2058,12 +2369,12 @@ function performInit(cwd, force) {
|
|
|
2058
2369
|
continue;
|
|
2059
2370
|
}
|
|
2060
2371
|
if (profile.kind !== "scheduled" || !profile.schedule) continue;
|
|
2061
|
-
const target =
|
|
2062
|
-
if (
|
|
2372
|
+
const target = path12.join(workflowDir, `kody2-${exe.name}.yml`);
|
|
2373
|
+
if (fs14.existsSync(target) && !force) {
|
|
2063
2374
|
skipped.push(`.github/workflows/kody2-${exe.name}.yml`);
|
|
2064
2375
|
continue;
|
|
2065
2376
|
}
|
|
2066
|
-
|
|
2377
|
+
fs14.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
|
|
2067
2378
|
wrote.push(`.github/workflows/kody2-${exe.name}.yml`);
|
|
2068
2379
|
}
|
|
2069
2380
|
return { wrote, skipped };
|
|
@@ -2277,17 +2588,19 @@ function renderStateComment(state) {
|
|
|
2277
2588
|
lines.push(STATE_BEGIN);
|
|
2278
2589
|
lines.push("");
|
|
2279
2590
|
lines.push("```json");
|
|
2280
|
-
lines.push(
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2591
|
+
lines.push(
|
|
2592
|
+
JSON.stringify(
|
|
2593
|
+
{
|
|
2594
|
+
schemaVersion: state.schemaVersion,
|
|
2595
|
+
core: state.core,
|
|
2596
|
+
artifacts: state.artifacts ?? {},
|
|
2597
|
+
executables: state.executables,
|
|
2598
|
+
history: state.history
|
|
2599
|
+
},
|
|
2600
|
+
null,
|
|
2601
|
+
2
|
|
2602
|
+
)
|
|
2603
|
+
);
|
|
2291
2604
|
lines.push("```");
|
|
2292
2605
|
lines.push("");
|
|
2293
2606
|
lines.push(STATE_END);
|
|
@@ -2337,11 +2650,7 @@ function writeTaskState(target, number, state, cwd) {
|
|
|
2337
2650
|
const existing = findStateComment(target, number, cwd);
|
|
2338
2651
|
try {
|
|
2339
2652
|
if (existing) {
|
|
2340
|
-
gh3(
|
|
2341
|
-
["api", `repos/{owner}/{repo}/issues/comments/${existing.id}`, "-X", "PATCH", "-F", "body=@-"],
|
|
2342
|
-
body,
|
|
2343
|
-
cwd
|
|
2344
|
-
);
|
|
2653
|
+
gh3(["api", `repos/{owner}/{repo}/issues/comments/${existing.id}`, "-X", "PATCH", "-F", "body=@-"], body, cwd);
|
|
2345
2654
|
} else {
|
|
2346
2655
|
const sub = target === "issue" ? "issue" : "pr";
|
|
2347
2656
|
gh3([sub, "comment", String(number), "--body-file", "-"], body, cwd);
|
|
@@ -2376,6 +2685,7 @@ var parseAgentResult2 = async (ctx, profile, agentResult) => {
|
|
|
2376
2685
|
ctx.data.agentDone = parsed.done;
|
|
2377
2686
|
ctx.data.commitMessage = parsed.commitMessage;
|
|
2378
2687
|
ctx.data.prSummary = parsed.prSummary;
|
|
2688
|
+
ctx.data.feedbackActions = parsed.feedbackActions;
|
|
2379
2689
|
ctx.data.agentFailureReason = parsed.failureReason;
|
|
2380
2690
|
ctx.data.agentOutcome = agentResult.outcome;
|
|
2381
2691
|
ctx.data.agentError = agentResult.error;
|
|
@@ -2479,6 +2789,28 @@ function postWith(type, n, body, cwd) {
|
|
|
2479
2789
|
}
|
|
2480
2790
|
}
|
|
2481
2791
|
|
|
2792
|
+
// src/scripts/postPlanComment.ts
|
|
2793
|
+
var postPlanComment = async (ctx) => {
|
|
2794
|
+
if (!ctx.data.agentDone) return;
|
|
2795
|
+
const targetType = ctx.data.commentTargetType;
|
|
2796
|
+
const targetNumber = Number(ctx.data.commentTargetNumber ?? 0);
|
|
2797
|
+
const plan = ctx.data.prSummary?.trim();
|
|
2798
|
+
if (targetType !== "issue" || !targetNumber || !plan) return;
|
|
2799
|
+
const body = renderPlanComment(targetNumber, plan);
|
|
2800
|
+
try {
|
|
2801
|
+
postIssueComment(targetNumber, body, ctx.cwd);
|
|
2802
|
+
} catch {
|
|
2803
|
+
}
|
|
2804
|
+
};
|
|
2805
|
+
function renderPlanComment(issueNumber, plan) {
|
|
2806
|
+
return `## Plan for issue #${issueNumber}
|
|
2807
|
+
|
|
2808
|
+
${plan}
|
|
2809
|
+
|
|
2810
|
+
---
|
|
2811
|
+
Comment \`@kody2 run\` to execute this plan.`;
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2482
2814
|
// src/scripts/postReviewResult.ts
|
|
2483
2815
|
function detectVerdict(body) {
|
|
2484
2816
|
const m = body.match(/##\s*Verdict\s*:\s*(PASS|CONCERNS|FAIL)\b/i);
|
|
@@ -2523,15 +2855,17 @@ var postReviewResult = async (ctx, _profile, agentResult) => {
|
|
|
2523
2855
|
const verdict = detectVerdict(reviewBody);
|
|
2524
2856
|
ctx.data.reviewVerdict = verdict;
|
|
2525
2857
|
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
2526
|
-
process.stdout.write(
|
|
2858
|
+
process.stdout.write(
|
|
2859
|
+
`
|
|
2527
2860
|
REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.repo}/pull/${prNumber} (verdict: ${verdict})
|
|
2528
|
-
`
|
|
2861
|
+
`
|
|
2862
|
+
);
|
|
2529
2863
|
};
|
|
2530
2864
|
|
|
2531
2865
|
// src/scripts/releaseFlow.ts
|
|
2532
2866
|
import { execFileSync as execFileSync11, spawnSync } from "child_process";
|
|
2533
|
-
import * as
|
|
2534
|
-
import * as
|
|
2867
|
+
import * as fs15 from "fs";
|
|
2868
|
+
import * as path13 from "path";
|
|
2535
2869
|
function bumpVersion(current, bump) {
|
|
2536
2870
|
const m = current.match(/^(\d+)\.(\d+)\.(\d+)(.*)$/);
|
|
2537
2871
|
if (!m) throw new Error(`cannot parse version '${current}' (expected x.y.z[-suffix])`);
|
|
@@ -2547,12 +2881,12 @@ function bumpVersion(current, bump) {
|
|
|
2547
2881
|
return `${major}.${minor}.${patch}`;
|
|
2548
2882
|
}
|
|
2549
2883
|
function updateVersionInFile(file, newVersion, cwd) {
|
|
2550
|
-
const abs =
|
|
2551
|
-
if (!
|
|
2552
|
-
const content =
|
|
2884
|
+
const abs = path13.join(cwd, file);
|
|
2885
|
+
if (!fs15.existsSync(abs)) return false;
|
|
2886
|
+
const content = fs15.readFileSync(abs, "utf-8");
|
|
2553
2887
|
const updated = content.replace(/"version"\s*:\s*"[^"]+"/, `"version": "${newVersion}"`);
|
|
2554
2888
|
if (updated === content) return false;
|
|
2555
|
-
|
|
2889
|
+
fs15.writeFileSync(abs, updated);
|
|
2556
2890
|
return true;
|
|
2557
2891
|
}
|
|
2558
2892
|
function generateChangelog(cwd, newVersion, lastTag) {
|
|
@@ -2600,19 +2934,19 @@ function generateChangelog(cwd, newVersion, lastTag) {
|
|
|
2600
2934
|
return parts.join("\n");
|
|
2601
2935
|
}
|
|
2602
2936
|
function prependChangelog(cwd, entry) {
|
|
2603
|
-
const p =
|
|
2937
|
+
const p = path13.join(cwd, "CHANGELOG.md");
|
|
2604
2938
|
const header = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n";
|
|
2605
|
-
if (
|
|
2606
|
-
const prior =
|
|
2939
|
+
if (fs15.existsSync(p)) {
|
|
2940
|
+
const prior = fs15.readFileSync(p, "utf-8");
|
|
2607
2941
|
if (/^#\s*Changelog\b/m.test(prior)) {
|
|
2608
2942
|
const idx = prior.indexOf("\n", prior.indexOf("# Changelog"));
|
|
2609
|
-
|
|
2943
|
+
fs15.writeFileSync(p, `${prior.slice(0, idx + 1)}
|
|
2610
2944
|
${entry}${prior.slice(idx + 1)}`);
|
|
2611
2945
|
} else {
|
|
2612
|
-
|
|
2946
|
+
fs15.writeFileSync(p, `${header}${entry}${prior}`);
|
|
2613
2947
|
}
|
|
2614
2948
|
} else {
|
|
2615
|
-
|
|
2949
|
+
fs15.writeFileSync(p, `${header}${entry}`);
|
|
2616
2950
|
}
|
|
2617
2951
|
}
|
|
2618
2952
|
function git3(args, cwd, timeout = 6e4) {
|
|
@@ -2663,13 +2997,13 @@ var releaseFlow = async (ctx) => {
|
|
|
2663
2997
|
};
|
|
2664
2998
|
async function runPrepare(args) {
|
|
2665
2999
|
const { cwd, bump, dryRun, versionFiles, ctx } = args;
|
|
2666
|
-
const pkgPath =
|
|
2667
|
-
if (!
|
|
3000
|
+
const pkgPath = path13.join(cwd, "package.json");
|
|
3001
|
+
if (!fs15.existsSync(pkgPath)) {
|
|
2668
3002
|
ctx.output.exitCode = 99;
|
|
2669
3003
|
ctx.output.reason = "release prepare: package.json not found";
|
|
2670
3004
|
return;
|
|
2671
3005
|
}
|
|
2672
|
-
const pkg = JSON.parse(
|
|
3006
|
+
const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf-8"));
|
|
2673
3007
|
if (typeof pkg.version !== "string") {
|
|
2674
3008
|
ctx.output.exitCode = 99;
|
|
2675
3009
|
ctx.output.reason = "release prepare: package.json has no version";
|
|
@@ -2723,10 +3057,10 @@ ${entry}
|
|
|
2723
3057
|
Merge this and then run \`kody2 release --mode finalize\`.`;
|
|
2724
3058
|
let prUrl = "";
|
|
2725
3059
|
try {
|
|
2726
|
-
prUrl = gh(
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
).trim();
|
|
3060
|
+
prUrl = gh(["pr", "create", "--head", releaseBranch, "--base", base, "--title", title, "--body-file", "-"], {
|
|
3061
|
+
input: body,
|
|
3062
|
+
cwd
|
|
3063
|
+
}).trim();
|
|
2730
3064
|
} catch (err) {
|
|
2731
3065
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2732
3066
|
ctx.output.exitCode = 4;
|
|
@@ -2740,8 +3074,8 @@ Merge this and then run \`kody2 release --mode finalize\`.`;
|
|
|
2740
3074
|
}
|
|
2741
3075
|
async function runFinalize(args) {
|
|
2742
3076
|
const { cwd, dryRun, timeoutMs, releaseCfg, ctx } = args;
|
|
2743
|
-
const pkgPath =
|
|
2744
|
-
const pkg = JSON.parse(
|
|
3077
|
+
const pkgPath = path13.join(cwd, "package.json");
|
|
3078
|
+
const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf-8"));
|
|
2745
3079
|
if (typeof pkg.version !== "string") {
|
|
2746
3080
|
ctx.output.exitCode = 99;
|
|
2747
3081
|
ctx.output.reason = "release finalize: package.json has no version";
|
|
@@ -2798,20 +3132,14 @@ ${truncate2(r.stderr, 2e3)}
|
|
|
2798
3132
|
}
|
|
2799
3133
|
let releaseUrl = "";
|
|
2800
3134
|
try {
|
|
2801
|
-
const releaseArgs = [
|
|
2802
|
-
"release",
|
|
2803
|
-
"create",
|
|
2804
|
-
tag,
|
|
2805
|
-
"--title",
|
|
2806
|
-
tag,
|
|
2807
|
-
"--notes",
|
|
2808
|
-
`Release ${tag} \u2014 automated by kody2.`
|
|
2809
|
-
];
|
|
3135
|
+
const releaseArgs = ["release", "create", tag, "--title", tag, "--notes", `Release ${tag} \u2014 automated by kody2.`];
|
|
2810
3136
|
if (releaseCfg.draftRelease) releaseArgs.push("--draft");
|
|
2811
3137
|
releaseUrl = gh(releaseArgs, { cwd }).trim();
|
|
2812
3138
|
} catch (err) {
|
|
2813
|
-
process.stderr.write(
|
|
2814
|
-
`)
|
|
3139
|
+
process.stderr.write(
|
|
3140
|
+
`[kody2 release] gh release create failed: ${err instanceof Error ? err.message : String(err)}
|
|
3141
|
+
`
|
|
3142
|
+
);
|
|
2815
3143
|
}
|
|
2816
3144
|
if (releaseCfg.notifyCommand && releaseCfg.notifyCommand.trim().length > 0) {
|
|
2817
3145
|
const cmd = releaseCfg.notifyCommand.replace(/\$VERSION/g, version);
|
|
@@ -2830,6 +3158,35 @@ ${truncate2(r.stderr, 2e3)}
|
|
|
2830
3158
|
`);
|
|
2831
3159
|
}
|
|
2832
3160
|
|
|
3161
|
+
// src/scripts/requireFeedbackActions.ts
|
|
3162
|
+
var MIN_ITEMS = 1;
|
|
3163
|
+
var requireFeedbackActions = async (ctx, profile) => {
|
|
3164
|
+
if (!ctx.data.agentDone) return;
|
|
3165
|
+
const actions = String(ctx.data.feedbackActions ?? "").trim();
|
|
3166
|
+
const items = countActionItems(actions);
|
|
3167
|
+
if (items >= MIN_ITEMS) return;
|
|
3168
|
+
const reason = actions.length === 0 ? "agent omitted required FEEDBACK_ACTIONS block \u2014 cannot verify that review feedback was addressed" : "agent FEEDBACK_ACTIONS block listed no items \u2014 cannot verify that review feedback was addressed";
|
|
3169
|
+
ctx.data.agentDone = false;
|
|
3170
|
+
ctx.data.agentFailureReason = reason;
|
|
3171
|
+
const modeSeg = profile.name.replace(/-/g, "_").toUpperCase();
|
|
3172
|
+
const failedAction = {
|
|
3173
|
+
type: `${modeSeg}_FAILED`,
|
|
3174
|
+
payload: { reason },
|
|
3175
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3176
|
+
};
|
|
3177
|
+
ctx.data.action = failedAction;
|
|
3178
|
+
};
|
|
3179
|
+
function countActionItems(block) {
|
|
3180
|
+
if (!block.trim()) return 0;
|
|
3181
|
+
const lines = block.split("\n");
|
|
3182
|
+
let count = 0;
|
|
3183
|
+
for (const raw of lines) {
|
|
3184
|
+
const line = raw.trim();
|
|
3185
|
+
if (/^[-*]\s+/.test(line)) count++;
|
|
3186
|
+
}
|
|
3187
|
+
return count;
|
|
3188
|
+
}
|
|
3189
|
+
|
|
2833
3190
|
// src/scripts/resolveArtifacts.ts
|
|
2834
3191
|
var resolveArtifacts = async (ctx, profile) => {
|
|
2835
3192
|
if (profile.inputArtifacts.length === 0) return;
|
|
@@ -3079,11 +3436,7 @@ var syncFlow = async (ctx) => {
|
|
|
3079
3436
|
ctx.output.reason = `merged origin/${baseBranch} into ${ctx.data.branch}`;
|
|
3080
3437
|
const runUrl = getRunUrl();
|
|
3081
3438
|
const runSuffix = runUrl ? ` ([logs](${runUrl}))` : "";
|
|
3082
|
-
tryPostPr5(
|
|
3083
|
-
prNumber,
|
|
3084
|
-
`\u2705 kody2 sync: merged \`origin/${baseBranch}\` into \`${ctx.data.branch}\`${runSuffix}`,
|
|
3085
|
-
ctx.cwd
|
|
3086
|
-
);
|
|
3439
|
+
tryPostPr5(prNumber, `\u2705 kody2 sync: merged \`origin/${baseBranch}\` into \`${ctx.data.branch}\`${runSuffix}`, ctx.cwd);
|
|
3087
3440
|
};
|
|
3088
3441
|
function bail2(ctx, prNumber, reason) {
|
|
3089
3442
|
ctx.output.exitCode = 1;
|
|
@@ -3123,7 +3476,7 @@ import { spawn as spawn2 } from "child_process";
|
|
|
3123
3476
|
var TAIL_CHARS = 4e3;
|
|
3124
3477
|
var COMMAND_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
3125
3478
|
function runCommand(command, cwd) {
|
|
3126
|
-
return new Promise((
|
|
3479
|
+
return new Promise((resolve4) => {
|
|
3127
3480
|
const start = Date.now();
|
|
3128
3481
|
const child = spawn2(command, {
|
|
3129
3482
|
cwd,
|
|
@@ -3152,11 +3505,11 @@ function runCommand(command, cwd) {
|
|
|
3152
3505
|
child.on("exit", (code) => {
|
|
3153
3506
|
clearTimeout(timer);
|
|
3154
3507
|
const tail = Buffer.concat(buffers).toString("utf-8").slice(-TAIL_CHARS);
|
|
3155
|
-
|
|
3508
|
+
resolve4({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
|
|
3156
3509
|
});
|
|
3157
3510
|
child.on("error", (err) => {
|
|
3158
3511
|
clearTimeout(timer);
|
|
3159
|
-
|
|
3512
|
+
resolve4({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
|
|
3160
3513
|
});
|
|
3161
3514
|
});
|
|
3162
3515
|
}
|
|
@@ -3215,19 +3568,7 @@ function readWatchConfig(ctx) {
|
|
|
3215
3568
|
function findStalePrs(cwd, staleDays, now = /* @__PURE__ */ new Date()) {
|
|
3216
3569
|
let raw = "";
|
|
3217
3570
|
try {
|
|
3218
|
-
raw = gh(
|
|
3219
|
-
[
|
|
3220
|
-
"pr",
|
|
3221
|
-
"list",
|
|
3222
|
-
"--state",
|
|
3223
|
-
"open",
|
|
3224
|
-
"--limit",
|
|
3225
|
-
"100",
|
|
3226
|
-
"--json",
|
|
3227
|
-
"number,title,url,updatedAt"
|
|
3228
|
-
],
|
|
3229
|
-
{ cwd }
|
|
3230
|
-
);
|
|
3571
|
+
raw = gh(["pr", "list", "--state", "open", "--limit", "100", "--json", "number,title,url,updatedAt"], { cwd });
|
|
3231
3572
|
} catch {
|
|
3232
3573
|
return [];
|
|
3233
3574
|
}
|
|
@@ -3273,8 +3614,10 @@ var watchStalePrsFlow = async (ctx) => {
|
|
|
3273
3614
|
try {
|
|
3274
3615
|
postIssueComment(reportIssueNumber, report, ctx.cwd);
|
|
3275
3616
|
} catch (err) {
|
|
3276
|
-
process.stderr.write(
|
|
3277
|
-
`)
|
|
3617
|
+
process.stderr.write(
|
|
3618
|
+
`[kody2 watch] failed to post to issue #${reportIssueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
3619
|
+
`
|
|
3620
|
+
);
|
|
3278
3621
|
}
|
|
3279
3622
|
}
|
|
3280
3623
|
ctx.output.exitCode = 0;
|
|
@@ -3282,7 +3625,7 @@ var watchStalePrsFlow = async (ctx) => {
|
|
|
3282
3625
|
};
|
|
3283
3626
|
|
|
3284
3627
|
// src/scripts/writeRunSummary.ts
|
|
3285
|
-
import * as
|
|
3628
|
+
import * as fs16 from "fs";
|
|
3286
3629
|
var writeRunSummary = async (ctx, profile) => {
|
|
3287
3630
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
3288
3631
|
if (!summaryPath) return;
|
|
@@ -3304,7 +3647,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
3304
3647
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
3305
3648
|
lines.push("");
|
|
3306
3649
|
try {
|
|
3307
|
-
|
|
3650
|
+
fs16.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
3308
3651
|
`);
|
|
3309
3652
|
} catch {
|
|
3310
3653
|
}
|
|
@@ -3331,11 +3674,13 @@ var preflightScripts = {
|
|
|
3331
3674
|
};
|
|
3332
3675
|
var postflightScripts = {
|
|
3333
3676
|
parseAgentResult: parseAgentResult2,
|
|
3677
|
+
requireFeedbackActions,
|
|
3334
3678
|
verify,
|
|
3335
3679
|
checkCoverageWithRetry,
|
|
3336
3680
|
commitAndPush: commitAndPush2,
|
|
3337
3681
|
ensurePr: ensurePr2,
|
|
3338
3682
|
postIssueComment: postIssueComment2,
|
|
3683
|
+
postPlanComment,
|
|
3339
3684
|
postReviewResult,
|
|
3340
3685
|
persistArtifacts,
|
|
3341
3686
|
writeRunSummary,
|
|
@@ -3448,9 +3793,9 @@ async function runExecutable(profileName, input) {
|
|
|
3448
3793
|
data: {},
|
|
3449
3794
|
output: { exitCode: 0 }
|
|
3450
3795
|
};
|
|
3451
|
-
const ndjsonDir =
|
|
3796
|
+
const ndjsonDir = path14.join(input.cwd, ".kody2");
|
|
3452
3797
|
const invokeAgent = async (prompt) => {
|
|
3453
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
3798
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path14.isAbsolute(p) ? p : path14.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
3454
3799
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
3455
3800
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
3456
3801
|
return runAgent({
|
|
@@ -3466,6 +3811,7 @@ async function runExecutable(profileName, input) {
|
|
|
3466
3811
|
mcpServers: profile.claudeCode.mcpServers,
|
|
3467
3812
|
pluginPaths: pluginPaths.length > 0 ? pluginPaths : void 0,
|
|
3468
3813
|
maxTurns: profile.claudeCode.maxTurns,
|
|
3814
|
+
maxThinkingTokens: profile.claudeCode.maxThinkingTokens,
|
|
3469
3815
|
systemPromptAppend: profile.claudeCode.systemPromptAppend,
|
|
3470
3816
|
settingSources: profile.claudeCode.settingSources
|
|
3471
3817
|
});
|
|
@@ -3516,17 +3862,17 @@ async function runExecutable(profileName, input) {
|
|
|
3516
3862
|
}
|
|
3517
3863
|
}
|
|
3518
3864
|
function resolveProfilePath(profileName) {
|
|
3519
|
-
const here =
|
|
3865
|
+
const here = path14.dirname(new URL(import.meta.url).pathname);
|
|
3520
3866
|
const candidates = [
|
|
3521
|
-
|
|
3867
|
+
path14.join(here, "executables", profileName, "profile.json"),
|
|
3522
3868
|
// same-dir sibling (dev)
|
|
3523
|
-
|
|
3869
|
+
path14.join(here, "..", "executables", profileName, "profile.json"),
|
|
3524
3870
|
// up one (prod: dist/bin → dist/executables)
|
|
3525
|
-
|
|
3871
|
+
path14.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
3526
3872
|
// fallback
|
|
3527
3873
|
];
|
|
3528
3874
|
for (const c of candidates) {
|
|
3529
|
-
if (
|
|
3875
|
+
if (fs17.existsSync(c)) return c;
|
|
3530
3876
|
}
|
|
3531
3877
|
return candidates[0];
|
|
3532
3878
|
}
|
|
@@ -3617,95 +3963,6 @@ function finish(out) {
|
|
|
3617
3963
|
return out;
|
|
3618
3964
|
}
|
|
3619
3965
|
|
|
3620
|
-
// src/kody2-cli.ts
|
|
3621
|
-
import { execFileSync as execFileSync15 } from "child_process";
|
|
3622
|
-
import * as fs16 from "fs";
|
|
3623
|
-
import * as path13 from "path";
|
|
3624
|
-
|
|
3625
|
-
// src/dispatch.ts
|
|
3626
|
-
import * as fs15 from "fs";
|
|
3627
|
-
function autoDispatch(opts) {
|
|
3628
|
-
const explicit = opts?.explicit;
|
|
3629
|
-
if (explicit?.issueNumber && explicit.issueNumber > 0) {
|
|
3630
|
-
return {
|
|
3631
|
-
executable: "run",
|
|
3632
|
-
cliArgs: { issue: explicit.issueNumber },
|
|
3633
|
-
target: explicit.issueNumber
|
|
3634
|
-
};
|
|
3635
|
-
}
|
|
3636
|
-
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
3637
|
-
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
3638
|
-
if (!eventName || !eventPath || !fs15.existsSync(eventPath)) return null;
|
|
3639
|
-
let event = {};
|
|
3640
|
-
try {
|
|
3641
|
-
event = JSON.parse(fs15.readFileSync(eventPath, "utf-8"));
|
|
3642
|
-
} catch {
|
|
3643
|
-
return null;
|
|
3644
|
-
}
|
|
3645
|
-
if (eventName === "workflow_dispatch") {
|
|
3646
|
-
const n = parseInt(String(event.inputs?.issue_number ?? ""), 10);
|
|
3647
|
-
if (!Number.isNaN(n) && n > 0) {
|
|
3648
|
-
return { executable: "run", cliArgs: { issue: n }, target: n };
|
|
3649
|
-
}
|
|
3650
|
-
return null;
|
|
3651
|
-
}
|
|
3652
|
-
if (eventName !== "issue_comment") return null;
|
|
3653
|
-
const body = String(event.comment?.body ?? "").toLowerCase();
|
|
3654
|
-
const targetNum = Number(event.issue?.number ?? 0);
|
|
3655
|
-
const isPr = !!event.issue?.pull_request;
|
|
3656
|
-
if (!targetNum) return null;
|
|
3657
|
-
const afterTag = extractAfterTag(body);
|
|
3658
|
-
if (isPr) {
|
|
3659
|
-
if (/\bfix-ci\b/.test(afterTag)) {
|
|
3660
|
-
return { executable: "fix-ci", cliArgs: { pr: targetNum }, target: targetNum };
|
|
3661
|
-
}
|
|
3662
|
-
if (/\bresolve\b/.test(afterTag)) {
|
|
3663
|
-
return { executable: "resolve", cliArgs: { pr: targetNum }, target: targetNum };
|
|
3664
|
-
}
|
|
3665
|
-
if (/\breview\b/.test(afterTag)) {
|
|
3666
|
-
return { executable: "review", cliArgs: { pr: targetNum }, target: targetNum };
|
|
3667
|
-
}
|
|
3668
|
-
if (/\bsync\b/.test(afterTag)) {
|
|
3669
|
-
return { executable: "sync", cliArgs: { pr: targetNum }, target: targetNum };
|
|
3670
|
-
}
|
|
3671
|
-
const feedback = extractFeedback(afterTag);
|
|
3672
|
-
return {
|
|
3673
|
-
executable: "fix",
|
|
3674
|
-
cliArgs: { pr: targetNum, ...feedback ? { feedback } : {} },
|
|
3675
|
-
target: targetNum
|
|
3676
|
-
};
|
|
3677
|
-
}
|
|
3678
|
-
const sub = extractSubcommand(afterTag);
|
|
3679
|
-
const defaultExec = opts?.config?.defaultExecutable ?? "run";
|
|
3680
|
-
if (!sub) {
|
|
3681
|
-
return asDispatch(defaultExec, targetNum);
|
|
3682
|
-
}
|
|
3683
|
-
if (sub === "orchestrate" || sub === "orchestrator") {
|
|
3684
|
-
return { executable: "orchestrator", cliArgs: { issue: targetNum }, target: targetNum };
|
|
3685
|
-
}
|
|
3686
|
-
if (sub === "build") {
|
|
3687
|
-
return { executable: "run", cliArgs: { issue: targetNum }, target: targetNum };
|
|
3688
|
-
}
|
|
3689
|
-
return asDispatch(sub, targetNum);
|
|
3690
|
-
}
|
|
3691
|
-
function asDispatch(executable, target) {
|
|
3692
|
-
return { executable, cliArgs: { issue: target }, target };
|
|
3693
|
-
}
|
|
3694
|
-
function extractAfterTag(body) {
|
|
3695
|
-
const idx = body.indexOf("@kody2");
|
|
3696
|
-
if (idx === -1) return body;
|
|
3697
|
-
return body.slice(idx + "@kody2".length).trim();
|
|
3698
|
-
}
|
|
3699
|
-
function extractSubcommand(afterTag) {
|
|
3700
|
-
const match = afterTag.match(/^([a-z][a-z0-9-]{1,40})\b/);
|
|
3701
|
-
if (!match) return null;
|
|
3702
|
-
return match[1];
|
|
3703
|
-
}
|
|
3704
|
-
function extractFeedback(afterTag) {
|
|
3705
|
-
const cleaned = afterTag.replace(/^(fix|please|kindly)[\s:,.-]+/i, "").trim();
|
|
3706
|
-
return cleaned.length > 0 ? cleaned : void 0;
|
|
3707
|
-
}
|
|
3708
|
-
|
|
3709
3966
|
// src/kody2-cli.ts
|
|
3710
3967
|
var CI_HELP = `kody2 ci \u2014 minimal-YAML autonomous engineer (CI preflight + run)
|
|
3711
3968
|
|
|
@@ -3791,9 +4048,9 @@ function resolveAuthToken(env = process.env) {
|
|
|
3791
4048
|
return token;
|
|
3792
4049
|
}
|
|
3793
4050
|
function detectPackageManager2(cwd) {
|
|
3794
|
-
if (
|
|
3795
|
-
if (
|
|
3796
|
-
if (
|
|
4051
|
+
if (fs18.existsSync(path15.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
4052
|
+
if (fs18.existsSync(path15.join(cwd, "yarn.lock"))) return "yarn";
|
|
4053
|
+
if (fs18.existsSync(path15.join(cwd, "bun.lockb"))) return "bun";
|
|
3797
4054
|
return "npm";
|
|
3798
4055
|
}
|
|
3799
4056
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -3873,11 +4130,11 @@ function configureGitIdentity(cwd) {
|
|
|
3873
4130
|
}
|
|
3874
4131
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
3875
4132
|
if (!issueNumber) return;
|
|
3876
|
-
const logPath =
|
|
4133
|
+
const logPath = path15.join(cwd, ".kody2", "last-run.jsonl");
|
|
3877
4134
|
let tail = "";
|
|
3878
4135
|
try {
|
|
3879
|
-
if (
|
|
3880
|
-
const content =
|
|
4136
|
+
if (fs18.existsSync(logPath)) {
|
|
4137
|
+
const content = fs18.readFileSync(logPath, "utf-8");
|
|
3881
4138
|
tail = content.slice(-3e3);
|
|
3882
4139
|
}
|
|
3883
4140
|
} catch {
|
|
@@ -3902,7 +4159,7 @@ async function runCi(argv) {
|
|
|
3902
4159
|
return 0;
|
|
3903
4160
|
}
|
|
3904
4161
|
const args = parseCiArgs(argv);
|
|
3905
|
-
const cwd = args.cwd ?
|
|
4162
|
+
const cwd = args.cwd ? path15.resolve(args.cwd) : process.cwd();
|
|
3906
4163
|
let earlyConfig;
|
|
3907
4164
|
try {
|
|
3908
4165
|
earlyConfig = loadConfig(cwd);
|
|
@@ -3990,6 +4247,148 @@ ${CI_HELP}`);
|
|
|
3990
4247
|
}
|
|
3991
4248
|
}
|
|
3992
4249
|
|
|
4250
|
+
// src/chat-cli.ts
|
|
4251
|
+
var DEFAULT_MODEL = "claude/claude-haiku-4-5-20251001";
|
|
4252
|
+
var CHAT_HELP = `kody2 chat \u2014 dashboard-driven chat session
|
|
4253
|
+
|
|
4254
|
+
Usage:
|
|
4255
|
+
kody2 chat [--session <id>] [--message <text>] [--model <provider/model>]
|
|
4256
|
+
[--dashboard-url <url>] [--cwd <path>] [--verbose|--quiet]
|
|
4257
|
+
|
|
4258
|
+
All inputs may also come from env: SESSION_ID, INIT_MESSAGE, MODEL, DASHBOARD_URL.
|
|
4259
|
+
CLI flags take precedence over env. SESSION_ID is required.
|
|
4260
|
+
|
|
4261
|
+
Exit codes:
|
|
4262
|
+
0 reply emitted successfully
|
|
4263
|
+
64 bad inputs (missing session, empty history)
|
|
4264
|
+
99 runtime failure (agent crash, LiteLLM failure)
|
|
4265
|
+
`;
|
|
4266
|
+
function parseChatArgs(argv, env = process.env) {
|
|
4267
|
+
const result = { errors: [] };
|
|
4268
|
+
for (let i = 0; i < argv.length; i++) {
|
|
4269
|
+
const arg = argv[i];
|
|
4270
|
+
if (arg === "--session") result.sessionId = argv[++i];
|
|
4271
|
+
else if (arg === "--message") result.initMessage = argv[++i];
|
|
4272
|
+
else if (arg === "--model") result.model = argv[++i];
|
|
4273
|
+
else if (arg === "--dashboard-url") result.dashboardUrl = argv[++i];
|
|
4274
|
+
else if (arg === "--cwd") result.cwd = argv[++i];
|
|
4275
|
+
else if (arg === "--verbose") result.verbose = true;
|
|
4276
|
+
else if (arg === "--quiet") result.quiet = true;
|
|
4277
|
+
else if (arg === "--help" || arg === "-h") result.errors.push("__HELP__");
|
|
4278
|
+
else if (arg?.startsWith("--")) result.errors.push(`unknown arg: ${arg}`);
|
|
4279
|
+
else if (arg) result.errors.push(`unexpected positional: ${arg}`);
|
|
4280
|
+
}
|
|
4281
|
+
result.sessionId = result.sessionId ?? env.SESSION_ID ?? void 0;
|
|
4282
|
+
result.initMessage = result.initMessage ?? env.INIT_MESSAGE ?? void 0;
|
|
4283
|
+
result.model = result.model ?? env.MODEL ?? void 0;
|
|
4284
|
+
result.dashboardUrl = result.dashboardUrl ?? env.DASHBOARD_URL ?? void 0;
|
|
4285
|
+
for (const key of ["sessionId", "initMessage", "model", "dashboardUrl"]) {
|
|
4286
|
+
const v = result[key];
|
|
4287
|
+
if (typeof v === "string" && v.trim() === "") result[key] = void 0;
|
|
4288
|
+
}
|
|
4289
|
+
if (!result.sessionId && !result.errors.includes("__HELP__")) {
|
|
4290
|
+
result.errors.push("--session <id> (or SESSION_ID env) is required");
|
|
4291
|
+
}
|
|
4292
|
+
return result;
|
|
4293
|
+
}
|
|
4294
|
+
function commitChatFiles(cwd, sessionId, verbose) {
|
|
4295
|
+
const sessionFile = path16.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
4296
|
+
const eventsFile = path16.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
4297
|
+
const paths = [sessionFile, eventsFile].filter((p) => fs19.existsSync(path16.join(cwd, p)));
|
|
4298
|
+
if (paths.length === 0) return;
|
|
4299
|
+
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
4300
|
+
try {
|
|
4301
|
+
execFileSync16("git", ["add", ...paths], opts);
|
|
4302
|
+
execFileSync16("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
|
|
4303
|
+
execFileSync16("git", ["push", "--quiet", "origin", "HEAD"], opts);
|
|
4304
|
+
} catch (err) {
|
|
4305
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4306
|
+
process.stderr.write(`[kody2:chat] commit/push skipped: ${msg}
|
|
4307
|
+
`);
|
|
4308
|
+
}
|
|
4309
|
+
}
|
|
4310
|
+
function tryLoadConfig(cwd) {
|
|
4311
|
+
try {
|
|
4312
|
+
return loadConfig(cwd);
|
|
4313
|
+
} catch {
|
|
4314
|
+
return null;
|
|
4315
|
+
}
|
|
4316
|
+
}
|
|
4317
|
+
function buildSink(cwd, sessionId, dashboardUrl) {
|
|
4318
|
+
const sinks = [new FileSink(eventsFilePath(cwd, sessionId))];
|
|
4319
|
+
if (dashboardUrl) sinks.push(new HttpSink(dashboardUrl, sessionId));
|
|
4320
|
+
return new TeeSink(sinks);
|
|
4321
|
+
}
|
|
4322
|
+
async function runChat(argv) {
|
|
4323
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
4324
|
+
process.stdout.write(CHAT_HELP);
|
|
4325
|
+
return 0;
|
|
4326
|
+
}
|
|
4327
|
+
const args = parseChatArgs(argv);
|
|
4328
|
+
if (args.errors.length > 0 && !args.errors.includes("__HELP__")) {
|
|
4329
|
+
for (const e of args.errors) process.stderr.write(`error: ${e}
|
|
4330
|
+
`);
|
|
4331
|
+
process.stderr.write(`
|
|
4332
|
+
${CHAT_HELP}`);
|
|
4333
|
+
return 64;
|
|
4334
|
+
}
|
|
4335
|
+
const cwd = args.cwd ? path16.resolve(args.cwd) : process.cwd();
|
|
4336
|
+
const sessionId = args.sessionId;
|
|
4337
|
+
const unpackedSecrets = unpackAllSecrets();
|
|
4338
|
+
if (unpackedSecrets > 0) {
|
|
4339
|
+
process.stdout.write(`\u2192 kody2: unpacked ${unpackedSecrets} secret(s) from ALL_SECRETS
|
|
4340
|
+
`);
|
|
4341
|
+
}
|
|
4342
|
+
resolveAuthToken();
|
|
4343
|
+
configureGitIdentity(cwd);
|
|
4344
|
+
const config = tryLoadConfig(cwd);
|
|
4345
|
+
const modelSpec = args.model ?? config?.agent.model ?? DEFAULT_MODEL;
|
|
4346
|
+
let model;
|
|
4347
|
+
try {
|
|
4348
|
+
model = parseProviderModel(modelSpec);
|
|
4349
|
+
} catch (err) {
|
|
4350
|
+
process.stderr.write(`error: invalid model '${modelSpec}': ${err instanceof Error ? err.message : String(err)}
|
|
4351
|
+
`);
|
|
4352
|
+
return 64;
|
|
4353
|
+
}
|
|
4354
|
+
let litellm = null;
|
|
4355
|
+
try {
|
|
4356
|
+
litellm = await startLitellmIfNeeded(model, cwd);
|
|
4357
|
+
} catch (err) {
|
|
4358
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4359
|
+
const sink2 = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
4360
|
+
await sink2.emit({
|
|
4361
|
+
event: "chat.error",
|
|
4362
|
+
payload: { sessionId, error: `litellm startup failed: ${msg}` },
|
|
4363
|
+
runId: makeRunId(sessionId, "error"),
|
|
4364
|
+
emittedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4365
|
+
});
|
|
4366
|
+
return 99;
|
|
4367
|
+
}
|
|
4368
|
+
const sessionFile = sessionFilePath(cwd, sessionId);
|
|
4369
|
+
if (args.initMessage) seedInitialMessage(sessionFile, args.initMessage);
|
|
4370
|
+
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
4371
|
+
try {
|
|
4372
|
+
const result = await runChatTurn({
|
|
4373
|
+
sessionId,
|
|
4374
|
+
sessionFile,
|
|
4375
|
+
cwd,
|
|
4376
|
+
model,
|
|
4377
|
+
litellmUrl: litellm?.url ?? null,
|
|
4378
|
+
sink,
|
|
4379
|
+
verbose: args.verbose,
|
|
4380
|
+
quiet: args.quiet
|
|
4381
|
+
});
|
|
4382
|
+
commitChatFiles(cwd, sessionId, args.verbose ?? false);
|
|
4383
|
+
return result.exitCode;
|
|
4384
|
+
} finally {
|
|
4385
|
+
try {
|
|
4386
|
+
litellm?.kill();
|
|
4387
|
+
} catch {
|
|
4388
|
+
}
|
|
4389
|
+
}
|
|
4390
|
+
}
|
|
4391
|
+
|
|
3993
4392
|
// src/entry.ts
|
|
3994
4393
|
var HELP_TEXT = `kody2 \u2014 single-session autonomous engineer
|
|
3995
4394
|
|
|
@@ -4001,6 +4400,7 @@ Usage:
|
|
|
4001
4400
|
kody2 review --pr <N> [--cwd <path>] [--verbose|--quiet]
|
|
4002
4401
|
kody2 <other> [--cwd <path>] [--verbose|--quiet]
|
|
4003
4402
|
kody2 ci --issue <N> [preflight flags \u2014 see: kody2 ci --help]
|
|
4403
|
+
kody2 chat [chat flags \u2014 see: kody2 chat --help]
|
|
4004
4404
|
kody2 help
|
|
4005
4405
|
kody2 version
|
|
4006
4406
|
|
|
@@ -4027,6 +4427,9 @@ function parseArgs(argv) {
|
|
|
4027
4427
|
if (cmd === "ci") {
|
|
4028
4428
|
return { ...result, command: "ci", ciArgv: argv.slice(1) };
|
|
4029
4429
|
}
|
|
4430
|
+
if (cmd === "chat") {
|
|
4431
|
+
return { ...result, command: "chat", chatArgv: argv.slice(1) };
|
|
4432
|
+
}
|
|
4030
4433
|
if (hasExecutable(cmd)) {
|
|
4031
4434
|
result.command = "__executable__";
|
|
4032
4435
|
result.executableName = cmd;
|
|
@@ -4067,6 +4470,18 @@ ${HELP_TEXT}`);
|
|
|
4067
4470
|
process.stderr.write(`[kody2] fatal: ${msg}
|
|
4068
4471
|
`);
|
|
4069
4472
|
if (err instanceof Error && err.stack) process.stderr.write(`${err.stack}
|
|
4473
|
+
`);
|
|
4474
|
+
return 99;
|
|
4475
|
+
}
|
|
4476
|
+
}
|
|
4477
|
+
if (args.command === "chat") {
|
|
4478
|
+
try {
|
|
4479
|
+
return await runChat(args.chatArgv ?? []);
|
|
4480
|
+
} catch (err) {
|
|
4481
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4482
|
+
process.stderr.write(`[kody2] fatal: ${msg}
|
|
4483
|
+
`);
|
|
4484
|
+
if (err instanceof Error && err.stack) process.stderr.write(`${err.stack}
|
|
4070
4485
|
`);
|
|
4071
4486
|
return 99;
|
|
4072
4487
|
}
|