@kody-ade/kody-engine 0.4.65 → 0.4.67
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/kody.js +917 -621
- package/dist/executables/bug/profile.json +1 -0
- package/dist/executables/chore/profile.json +1 -0
- package/dist/executables/feature/profile.json +1 -0
- package/dist/executables/goal-scheduler/scheduler.sh +0 -0
- package/dist/executables/release-deploy/deploy.sh +0 -0
- package/dist/executables/release-prepare/prepare.sh +0 -0
- package/dist/executables/release-publish/publish.sh +0 -0
- package/dist/executables/resolve/apply-prefer.sh +0 -0
- package/dist/executables/revert/revert.sh +0 -0
- package/dist/executables/types.ts +14 -0
- package/dist/executables/ui-review/profile.json +1 -0
- package/package.json +15 -14
package/dist/bin/kody.js
CHANGED
|
@@ -312,10 +312,559 @@ var init_verifyMcp = __esm({
|
|
|
312
312
|
}
|
|
313
313
|
});
|
|
314
314
|
|
|
315
|
+
// src/issue.ts
|
|
316
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
317
|
+
function ghToken() {
|
|
318
|
+
return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
319
|
+
}
|
|
320
|
+
function gh(args, options) {
|
|
321
|
+
const token = ghToken();
|
|
322
|
+
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
323
|
+
return execFileSync3("gh", args, {
|
|
324
|
+
encoding: "utf-8",
|
|
325
|
+
timeout: API_TIMEOUT_MS,
|
|
326
|
+
cwd: options?.cwd,
|
|
327
|
+
env,
|
|
328
|
+
input: options?.input,
|
|
329
|
+
stdio: options?.input ? ["pipe", "pipe", "pipe"] : ["inherit", "pipe", "pipe"]
|
|
330
|
+
}).trim();
|
|
331
|
+
}
|
|
332
|
+
function getIssue(issueNumber, cwd) {
|
|
333
|
+
const output = gh(["issue", "view", String(issueNumber), "--json", "number,title,body,comments,labels"], { cwd });
|
|
334
|
+
const parsed = JSON.parse(output);
|
|
335
|
+
if (typeof parsed?.title !== "string") {
|
|
336
|
+
throw new Error(`Issue #${issueNumber}: unexpected response shape`);
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
number: parsed.number ?? issueNumber,
|
|
340
|
+
title: parsed.title,
|
|
341
|
+
body: parsed.body ?? "",
|
|
342
|
+
comments: (parsed.comments ?? []).map((c) => ({
|
|
343
|
+
body: c.body ?? "",
|
|
344
|
+
author: c.author?.login ?? "unknown",
|
|
345
|
+
createdAt: c.createdAt ?? ""
|
|
346
|
+
})),
|
|
347
|
+
labels: Array.isArray(parsed.labels) ? parsed.labels.map((l) => l.name ?? "").filter((n) => n.length > 0) : []
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function stripKodyMentions(body) {
|
|
351
|
+
return body.replace(/(@)(kody)/gi, "$1\u200B$2");
|
|
352
|
+
}
|
|
353
|
+
function postIssueComment(issueNumber, body, cwd) {
|
|
354
|
+
try {
|
|
355
|
+
gh(["issue", "comment", String(issueNumber), "--body-file", "-"], { input: stripKodyMentions(body), cwd });
|
|
356
|
+
} catch (err) {
|
|
357
|
+
process.stderr.write(
|
|
358
|
+
`[kody] failed to post comment on #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
359
|
+
`
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function truncate2(s, maxBytes) {
|
|
364
|
+
if (s.length <= maxBytes) return s;
|
|
365
|
+
return `${s.slice(0, maxBytes)}\u2026 (+${s.length - maxBytes} chars)`;
|
|
366
|
+
}
|
|
367
|
+
function parsePrNumber(url) {
|
|
368
|
+
const m = url.match(/\/pull\/(\d+)(?:[/?#]|$)/);
|
|
369
|
+
if (!m) return null;
|
|
370
|
+
const n = parseInt(m[1], 10);
|
|
371
|
+
return Number.isFinite(n) ? n : null;
|
|
372
|
+
}
|
|
373
|
+
function getPr(prNumber, cwd) {
|
|
374
|
+
const output = gh(["pr", "view", String(prNumber), "--json", "number,title,body,headRefName,baseRefName,state"], {
|
|
375
|
+
cwd
|
|
376
|
+
});
|
|
377
|
+
const parsed = JSON.parse(output);
|
|
378
|
+
if (typeof parsed?.title !== "string") {
|
|
379
|
+
throw new Error(`PR #${prNumber}: unexpected response shape`);
|
|
380
|
+
}
|
|
381
|
+
return {
|
|
382
|
+
number: parsed.number ?? prNumber,
|
|
383
|
+
title: parsed.title,
|
|
384
|
+
body: parsed.body ?? "",
|
|
385
|
+
headRefName: String(parsed.headRefName ?? ""),
|
|
386
|
+
baseRefName: String(parsed.baseRefName ?? ""),
|
|
387
|
+
state: String(parsed.state ?? "")
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function getPrDiff(prNumber, cwd) {
|
|
391
|
+
try {
|
|
392
|
+
return gh(["pr", "diff", String(prNumber)], { cwd });
|
|
393
|
+
} catch (err) {
|
|
394
|
+
process.stderr.write(
|
|
395
|
+
`[kody] failed to fetch diff for PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
396
|
+
`
|
|
397
|
+
);
|
|
398
|
+
return "";
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
function getPrReviews(prNumber, cwd) {
|
|
402
|
+
try {
|
|
403
|
+
const output = gh(["pr", "view", String(prNumber), "--json", "reviews"], { cwd });
|
|
404
|
+
const parsed = JSON.parse(output);
|
|
405
|
+
if (!Array.isArray(parsed?.reviews)) return [];
|
|
406
|
+
return parsed.reviews.map(
|
|
407
|
+
(r) => ({
|
|
408
|
+
body: r.body ?? "",
|
|
409
|
+
state: r.state ?? "",
|
|
410
|
+
author: r.author?.login ?? "unknown",
|
|
411
|
+
submittedAt: r.submittedAt ?? ""
|
|
412
|
+
})
|
|
413
|
+
);
|
|
414
|
+
} catch {
|
|
415
|
+
return [];
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function getPrComments(prNumber, cwd) {
|
|
419
|
+
try {
|
|
420
|
+
const output = gh(["pr", "view", String(prNumber), "--json", "comments"], { cwd });
|
|
421
|
+
const parsed = JSON.parse(output);
|
|
422
|
+
if (!Array.isArray(parsed?.comments)) return [];
|
|
423
|
+
return parsed.comments.map((c) => ({
|
|
424
|
+
body: c.body ?? "",
|
|
425
|
+
author: c.author?.login ?? "unknown",
|
|
426
|
+
createdAt: c.createdAt ?? ""
|
|
427
|
+
})).filter((c) => c.body.trim().length > 0);
|
|
428
|
+
} catch {
|
|
429
|
+
return [];
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
function isReviewShaped(body) {
|
|
433
|
+
return VERDICT_HEADING.test(body);
|
|
434
|
+
}
|
|
435
|
+
function getPrLatestReviewBody(prNumber, cwd) {
|
|
436
|
+
const reviews = getPrReviews(prNumber, cwd).filter((r) => r.body.trim().length > 0).map((r) => ({ body: r.body, at: r.submittedAt }));
|
|
437
|
+
const comments = getPrComments(prNumber, cwd).filter((c) => isReviewShaped(c.body)).map((c) => ({ body: c.body, at: c.createdAt }));
|
|
438
|
+
const all = [...reviews, ...comments].sort((a, b) => (b.at || "").localeCompare(a.at || ""));
|
|
439
|
+
if (all.length > 0) return all[0].body;
|
|
440
|
+
const pr = getPr(prNumber, cwd);
|
|
441
|
+
return pr.body;
|
|
442
|
+
}
|
|
443
|
+
function postPrReviewComment(prNumber, body, cwd) {
|
|
444
|
+
try {
|
|
445
|
+
gh(["pr", "comment", String(prNumber), "--body-file", "-"], { input: stripKodyMentions(body), cwd });
|
|
446
|
+
} catch (err) {
|
|
447
|
+
process.stderr.write(
|
|
448
|
+
`[kody] failed to post review comment on PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
449
|
+
`
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
var API_TIMEOUT_MS, VERDICT_HEADING;
|
|
454
|
+
var init_issue = __esm({
|
|
455
|
+
"src/issue.ts"() {
|
|
456
|
+
"use strict";
|
|
457
|
+
API_TIMEOUT_MS = 3e4;
|
|
458
|
+
VERDICT_HEADING = /(^|\n)\s*#{1,6}\s*Verdict\s*:/i;
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// src/prompt.ts
|
|
463
|
+
import * as fs13 from "fs";
|
|
464
|
+
import * as path12 from "path";
|
|
465
|
+
function loadProjectConventions(projectDir) {
|
|
466
|
+
const out = [];
|
|
467
|
+
for (const rel of CONVENTION_FILES) {
|
|
468
|
+
const abs = path12.join(projectDir, rel);
|
|
469
|
+
if (!fs13.existsSync(abs)) continue;
|
|
470
|
+
let content;
|
|
471
|
+
try {
|
|
472
|
+
content = fs13.readFileSync(abs, "utf-8");
|
|
473
|
+
} catch {
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
const truncated = content.length > CONVENTIONS_PER_FILE_MAX_BYTES;
|
|
477
|
+
if (truncated) content = `${content.slice(0, CONVENTIONS_PER_FILE_MAX_BYTES)}
|
|
478
|
+
|
|
479
|
+
\u2026 (truncated)`;
|
|
480
|
+
out.push({ path: rel, content, truncated });
|
|
481
|
+
}
|
|
482
|
+
return out;
|
|
483
|
+
}
|
|
484
|
+
function parseAgentResult(finalText) {
|
|
485
|
+
const text = (finalText || "").trim();
|
|
486
|
+
if (!text)
|
|
487
|
+
return {
|
|
488
|
+
done: false,
|
|
489
|
+
commitMessage: "",
|
|
490
|
+
prSummary: "",
|
|
491
|
+
feedbackActions: "",
|
|
492
|
+
planDeviations: "",
|
|
493
|
+
priorArt: "",
|
|
494
|
+
failureReason: "agent produced no final message",
|
|
495
|
+
markerMissing: false
|
|
496
|
+
};
|
|
497
|
+
const MARKDOWN_PREFIX = "[\\s>*_#`~\\-]*";
|
|
498
|
+
const FAILED_RE = new RegExp(`(?:^|\\n)${MARKDOWN_PREFIX}FAILED${MARKDOWN_PREFIX}\\s*:\\s*(.+?)\\s*$`, "is");
|
|
499
|
+
const DONE_RE = new RegExp(`(?:^|\\n)${MARKDOWN_PREFIX}DONE\\b`, "i");
|
|
500
|
+
const failedMatch = text.match(FAILED_RE);
|
|
501
|
+
if (failedMatch) {
|
|
502
|
+
return {
|
|
503
|
+
done: false,
|
|
504
|
+
commitMessage: "",
|
|
505
|
+
prSummary: "",
|
|
506
|
+
feedbackActions: "",
|
|
507
|
+
planDeviations: "",
|
|
508
|
+
priorArt: "",
|
|
509
|
+
failureReason: stripMarkdownEmphasis(failedMatch[1]),
|
|
510
|
+
markerMissing: false
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
const hasDoneMarker = DONE_RE.test(text);
|
|
514
|
+
const hasCommitMsg = /^[\s>*_#`~-]*COMMIT_MSG\s*:/im.test(text);
|
|
515
|
+
const hasPrSummary = /^[\s>*_#`~-]*PR_SUMMARY\s*:/im.test(text);
|
|
516
|
+
const markerMissing = !hasDoneMarker && !hasCommitMsg && !hasPrSummary;
|
|
517
|
+
const commitMatch = text.match(/^[\s>*_#`~-]*COMMIT_MSG[\s>*_#`~-]*\s*:\s*(.+)$/im);
|
|
518
|
+
const commitMessage = commitMatch ? stripMarkdownEmphasis(commitMatch[1]) : "";
|
|
519
|
+
const feedbackActions = extractBlock(
|
|
520
|
+
text,
|
|
521
|
+
/(?:^|\n)[ \t]*FEEDBACK_ACTIONS\s*:[ \t]*\n/i,
|
|
522
|
+
/(?:^|\n)[ \t]*(?:PLAN_DEVIATIONS|COMMIT_MSG|PR_SUMMARY|PRIOR_ART)\s*:/i
|
|
523
|
+
);
|
|
524
|
+
let planDeviations = extractBlock(
|
|
525
|
+
text,
|
|
526
|
+
/(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*\n/i,
|
|
527
|
+
/(?:^|\n)[ \t]*(?:COMMIT_MSG|PR_SUMMARY|FEEDBACK_ACTIONS|PRIOR_ART)\s*:/i
|
|
528
|
+
);
|
|
529
|
+
if (!planDeviations) {
|
|
530
|
+
const inline = text.match(/(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
|
|
531
|
+
if (inline) planDeviations = inline[1].trim();
|
|
532
|
+
}
|
|
533
|
+
let priorArt = "";
|
|
534
|
+
const priorArtInline = text.match(/(?:^|\n)[ \t]*PRIOR_ART\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
|
|
535
|
+
if (priorArtInline) priorArt = priorArtInline[1].trim();
|
|
536
|
+
const summaryStart = text.search(/(^|\n)[ \t]*PR_SUMMARY\s*:[ \t]*\n/i);
|
|
537
|
+
let prSummary = "";
|
|
538
|
+
if (summaryStart !== -1) {
|
|
539
|
+
const afterMarker = text.slice(summaryStart).replace(/^[\s\S]*?PR_SUMMARY\s*:[ \t]*\n/i, "");
|
|
540
|
+
prSummary = afterMarker.replace(/\n\s*```\s*$/g, "").replace(/```\s*$/g, "").trim();
|
|
541
|
+
}
|
|
542
|
+
return {
|
|
543
|
+
done: true,
|
|
544
|
+
commitMessage,
|
|
545
|
+
prSummary,
|
|
546
|
+
feedbackActions,
|
|
547
|
+
planDeviations,
|
|
548
|
+
priorArt,
|
|
549
|
+
failureReason: "",
|
|
550
|
+
markerMissing
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
function stripMarkdownEmphasis(s) {
|
|
554
|
+
return s.trim().replace(/^[*_`~]+|[*_`~]+$/g, "").trim();
|
|
555
|
+
}
|
|
556
|
+
function extractBlock(text, startMarker, endMarker) {
|
|
557
|
+
const startIdx = text.search(startMarker);
|
|
558
|
+
if (startIdx === -1) return "";
|
|
559
|
+
const afterStart = text.slice(startIdx).replace(startMarker, "");
|
|
560
|
+
const endIdx = afterStart.search(endMarker);
|
|
561
|
+
const body = endIdx === -1 ? afterStart : afterStart.slice(0, endIdx);
|
|
562
|
+
return body.replace(/\n\s*```\s*$/g, "").trim();
|
|
563
|
+
}
|
|
564
|
+
var CONVENTIONS_PER_FILE_MAX_BYTES, CONVENTION_FILES;
|
|
565
|
+
var init_prompt = __esm({
|
|
566
|
+
"src/prompt.ts"() {
|
|
567
|
+
"use strict";
|
|
568
|
+
CONVENTIONS_PER_FILE_MAX_BYTES = 3e4;
|
|
569
|
+
CONVENTION_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
// src/scripts/loadConventions.ts
|
|
574
|
+
var loadConventions_exports = {};
|
|
575
|
+
__export(loadConventions_exports, {
|
|
576
|
+
loadConventions: () => loadConventions
|
|
577
|
+
});
|
|
578
|
+
var loadConventions;
|
|
579
|
+
var init_loadConventions = __esm({
|
|
580
|
+
"src/scripts/loadConventions.ts"() {
|
|
581
|
+
"use strict";
|
|
582
|
+
init_prompt();
|
|
583
|
+
loadConventions = async (ctx) => {
|
|
584
|
+
if (Array.isArray(ctx.data.conventions)) return;
|
|
585
|
+
const conventions = loadProjectConventions(ctx.cwd);
|
|
586
|
+
ctx.data.conventions = conventions;
|
|
587
|
+
if (conventions.length > 0) {
|
|
588
|
+
process.stderr.write(`[kody] loaded conventions: ${conventions.map((c) => c.path).join(", ")}
|
|
589
|
+
`);
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
// src/scripts/loadCoverageRules.ts
|
|
596
|
+
var loadCoverageRules_exports = {};
|
|
597
|
+
__export(loadCoverageRules_exports, {
|
|
598
|
+
loadCoverageRules: () => loadCoverageRules
|
|
599
|
+
});
|
|
600
|
+
var loadCoverageRules;
|
|
601
|
+
var init_loadCoverageRules = __esm({
|
|
602
|
+
"src/scripts/loadCoverageRules.ts"() {
|
|
603
|
+
"use strict";
|
|
604
|
+
loadCoverageRules = async (ctx) => {
|
|
605
|
+
if (Array.isArray(ctx.data.coverageRules)) return;
|
|
606
|
+
ctx.data.coverageRules = ctx.config.testRequirements ?? [];
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
// src/scripts/loadMemoryContext.ts
|
|
612
|
+
var loadMemoryContext_exports = {};
|
|
613
|
+
__export(loadMemoryContext_exports, {
|
|
614
|
+
loadMemoryContext: () => loadMemoryContext
|
|
615
|
+
});
|
|
616
|
+
import * as fs27 from "fs";
|
|
617
|
+
import * as path26 from "path";
|
|
618
|
+
function collectPages(memoryAbs) {
|
|
619
|
+
const out = [];
|
|
620
|
+
walkMd(memoryAbs, (file) => {
|
|
621
|
+
let stat;
|
|
622
|
+
try {
|
|
623
|
+
stat = fs27.statSync(file);
|
|
624
|
+
} catch {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
let raw;
|
|
628
|
+
try {
|
|
629
|
+
raw = fs27.readFileSync(file, "utf-8");
|
|
630
|
+
} catch {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
634
|
+
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path26.basename(file, ".md");
|
|
635
|
+
const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
|
|
636
|
+
out.push({
|
|
637
|
+
relPath: path26.relative(memoryAbs, file),
|
|
638
|
+
title,
|
|
639
|
+
updated,
|
|
640
|
+
content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX : raw,
|
|
641
|
+
mtime: stat.mtimeMs
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
return out;
|
|
645
|
+
}
|
|
646
|
+
function extractQueryTerms(ctx) {
|
|
647
|
+
const terms = [];
|
|
648
|
+
const issue = ctx.data.issue;
|
|
649
|
+
const pr = ctx.data.pr;
|
|
650
|
+
if (issue?.title) terms.push(...tokenize(issue.title));
|
|
651
|
+
if (pr?.title) terms.push(...tokenize(pr.title));
|
|
652
|
+
return Array.from(new Set(terms)).slice(0, 20);
|
|
653
|
+
}
|
|
654
|
+
function tokenize(s) {
|
|
655
|
+
return s.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((t) => t.length >= 3);
|
|
656
|
+
}
|
|
657
|
+
function scorePages(pages, terms) {
|
|
658
|
+
return pages.map((p) => {
|
|
659
|
+
const haystack = `${p.title} ${p.content}`.toLowerCase();
|
|
660
|
+
let score = 0;
|
|
661
|
+
for (const t of terms) {
|
|
662
|
+
if (haystack.includes(t)) score++;
|
|
663
|
+
}
|
|
664
|
+
return { p, score };
|
|
665
|
+
}).sort((a, b) => {
|
|
666
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
667
|
+
return b.p.mtime - a.p.mtime;
|
|
668
|
+
}).map((x) => x.p);
|
|
669
|
+
}
|
|
670
|
+
function sortByRecency(pages) {
|
|
671
|
+
return [...pages].sort((a, b) => {
|
|
672
|
+
if (a.updated && b.updated && a.updated !== b.updated) {
|
|
673
|
+
return b.updated.localeCompare(a.updated);
|
|
674
|
+
}
|
|
675
|
+
return b.mtime - a.mtime;
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
function formatBlock(pages) {
|
|
679
|
+
if (pages.length === 0) return "";
|
|
680
|
+
const lines = [
|
|
681
|
+
"# Project memory (`.kody/memory/`)",
|
|
682
|
+
"",
|
|
683
|
+
"Pages from prior memorize ticks. Treat as advisory context \u2014 confirm against the codebase before acting.",
|
|
684
|
+
""
|
|
685
|
+
];
|
|
686
|
+
let total = 0;
|
|
687
|
+
for (const p of pages) {
|
|
688
|
+
const block = [`## ${p.title} \u2014 \`${p.relPath}\``, "", p.content].join("\n");
|
|
689
|
+
if (total + block.length > TOTAL_MAX_BYTES) {
|
|
690
|
+
lines.push("_\u2026 (further pages truncated to fit budget)_");
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
lines.push(block);
|
|
694
|
+
lines.push("");
|
|
695
|
+
total += block.length;
|
|
696
|
+
}
|
|
697
|
+
return lines.join("\n");
|
|
698
|
+
}
|
|
699
|
+
function walkMd(root, visit) {
|
|
700
|
+
const stack = [root];
|
|
701
|
+
while (stack.length > 0) {
|
|
702
|
+
const dir = stack.pop();
|
|
703
|
+
let names;
|
|
704
|
+
try {
|
|
705
|
+
names = fs27.readdirSync(dir);
|
|
706
|
+
} catch {
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
for (const name of names) {
|
|
710
|
+
if (name.startsWith(".")) continue;
|
|
711
|
+
const full = path26.join(dir, name);
|
|
712
|
+
let stat;
|
|
713
|
+
try {
|
|
714
|
+
stat = fs27.statSync(full);
|
|
715
|
+
} catch {
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
if (stat.isDirectory()) {
|
|
719
|
+
stack.push(full);
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
if (stat.isFile() && full.endsWith(".md")) visit(full);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
var MEMORY_DIR_RELATIVE, MAX_PAGES, PER_PAGE_MAX_BYTES, TOTAL_MAX_BYTES, TRUNCATED_SUFFIX, loadMemoryContext;
|
|
727
|
+
var init_loadMemoryContext = __esm({
|
|
728
|
+
"src/scripts/loadMemoryContext.ts"() {
|
|
729
|
+
"use strict";
|
|
730
|
+
MEMORY_DIR_RELATIVE = ".kody/memory";
|
|
731
|
+
MAX_PAGES = 8;
|
|
732
|
+
PER_PAGE_MAX_BYTES = 4e3;
|
|
733
|
+
TOTAL_MAX_BYTES = 24e3;
|
|
734
|
+
TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
|
|
735
|
+
loadMemoryContext = async (ctx) => {
|
|
736
|
+
if (typeof ctx.data.memoryContext === "string") return;
|
|
737
|
+
const memoryAbs = path26.join(ctx.cwd, MEMORY_DIR_RELATIVE);
|
|
738
|
+
if (!fs27.existsSync(memoryAbs)) {
|
|
739
|
+
ctx.data.memoryContext = "";
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
let pages = [];
|
|
743
|
+
try {
|
|
744
|
+
pages = collectPages(memoryAbs);
|
|
745
|
+
} catch {
|
|
746
|
+
ctx.data.memoryContext = "";
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
if (pages.length === 0) {
|
|
750
|
+
ctx.data.memoryContext = "";
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
const queryTerms = extractQueryTerms(ctx);
|
|
754
|
+
const ranked = queryTerms.length > 0 ? scorePages(pages, queryTerms) : sortByRecency(pages);
|
|
755
|
+
const top = ranked.slice(0, MAX_PAGES);
|
|
756
|
+
ctx.data.memoryContext = formatBlock(top);
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
// src/scripts/loadPriorArt.ts
|
|
762
|
+
var loadPriorArt_exports = {};
|
|
763
|
+
__export(loadPriorArt_exports, {
|
|
764
|
+
loadPriorArt: () => loadPriorArt
|
|
765
|
+
});
|
|
766
|
+
function parsePrNumbers(raw) {
|
|
767
|
+
if (!raw) return [];
|
|
768
|
+
const trimmed = raw.trim();
|
|
769
|
+
if (!trimmed) return [];
|
|
770
|
+
try {
|
|
771
|
+
const parsed = JSON.parse(trimmed);
|
|
772
|
+
if (!Array.isArray(parsed)) return [];
|
|
773
|
+
return parsed.filter((n) => typeof n === "number" && Number.isInteger(n) && n > 0);
|
|
774
|
+
} catch {
|
|
775
|
+
return [];
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
function fetchPrBlock(prNumber, cwd) {
|
|
779
|
+
try {
|
|
780
|
+
const metaRaw = gh(["pr", "view", String(prNumber), "--json", "title,state,url,mergedAt,closedAt"], { cwd });
|
|
781
|
+
const meta = JSON.parse(metaRaw);
|
|
782
|
+
const diff = truncate3(safeGh(["pr", "diff", String(prNumber)], cwd), PER_PR_DIFF_MAX_BYTES);
|
|
783
|
+
const commentsRaw = safeGh(["pr", "view", String(prNumber), "--json", "comments,reviews"], cwd);
|
|
784
|
+
const commentsBlock = formatReviewComments(commentsRaw);
|
|
785
|
+
const lines = [
|
|
786
|
+
`## Prior art: PR #${prNumber} \u2014 ${meta.title ?? "(no title)"} [${meta.state ?? "unknown"}]`,
|
|
787
|
+
meta.url ? meta.url : "",
|
|
788
|
+
"",
|
|
789
|
+
"### Diff",
|
|
790
|
+
"```diff",
|
|
791
|
+
diff || "(empty)",
|
|
792
|
+
"```"
|
|
793
|
+
];
|
|
794
|
+
if (commentsBlock) {
|
|
795
|
+
lines.push("");
|
|
796
|
+
lines.push("### Review comments");
|
|
797
|
+
lines.push(commentsBlock);
|
|
798
|
+
}
|
|
799
|
+
return lines.filter((l) => l !== "").join("\n");
|
|
800
|
+
} catch (err) {
|
|
801
|
+
return `## Prior art: PR #${prNumber}
|
|
802
|
+
_Could not fetch \u2014 ${err instanceof Error ? err.message : String(err)}_`;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
function safeGh(args, cwd) {
|
|
806
|
+
try {
|
|
807
|
+
return gh(args, { cwd });
|
|
808
|
+
} catch {
|
|
809
|
+
return "";
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
function truncate3(s, max) {
|
|
813
|
+
return s.length <= max ? s : s.slice(0, max) + TRUNCATED_SUFFIX2;
|
|
814
|
+
}
|
|
815
|
+
function formatReviewComments(raw) {
|
|
816
|
+
if (!raw) return "";
|
|
817
|
+
try {
|
|
818
|
+
const parsed = JSON.parse(raw);
|
|
819
|
+
const out = [];
|
|
820
|
+
for (const c of parsed.comments ?? []) {
|
|
821
|
+
if (!c.body) continue;
|
|
822
|
+
out.push(`- **${c.author?.login ?? "unknown"}**: ${c.body.replace(/\n/g, " ").slice(0, 500)}`);
|
|
823
|
+
}
|
|
824
|
+
for (const r of parsed.reviews ?? []) {
|
|
825
|
+
if (!r.body && !r.state) continue;
|
|
826
|
+
const state = r.state ? ` (${r.state})` : "";
|
|
827
|
+
const body = r.body ? `: ${r.body.replace(/\n/g, " ").slice(0, 500)}` : "";
|
|
828
|
+
out.push(`- **${r.author?.login ?? "unknown"}**${state}${body}`);
|
|
829
|
+
}
|
|
830
|
+
return out.join("\n");
|
|
831
|
+
} catch {
|
|
832
|
+
return "";
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
var PER_PR_DIFF_MAX_BYTES, TOTAL_MAX_BYTES2, TRUNCATED_SUFFIX2, loadPriorArt;
|
|
836
|
+
var init_loadPriorArt = __esm({
|
|
837
|
+
"src/scripts/loadPriorArt.ts"() {
|
|
838
|
+
"use strict";
|
|
839
|
+
init_issue();
|
|
840
|
+
PER_PR_DIFF_MAX_BYTES = 8e3;
|
|
841
|
+
TOTAL_MAX_BYTES2 = 3e4;
|
|
842
|
+
TRUNCATED_SUFFIX2 = "\n\n\u2026 (truncated)";
|
|
843
|
+
loadPriorArt = async (ctx, _profile, args) => {
|
|
844
|
+
if (typeof ctx.data.priorArt === "string") return;
|
|
845
|
+
const artifactName = typeof args?.artifactName === "string" ? args.artifactName : "priorArt";
|
|
846
|
+
const state = ctx.data.taskState;
|
|
847
|
+
const artifact = state?.artifacts?.[artifactName];
|
|
848
|
+
const prNumbers = parsePrNumbers(artifact?.content);
|
|
849
|
+
if (prNumbers.length === 0) {
|
|
850
|
+
ctx.data.priorArt = "";
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
const blocks = [];
|
|
854
|
+
for (const n of prNumbers) {
|
|
855
|
+
const block = fetchPrBlock(n, ctx.cwd);
|
|
856
|
+
if (block) blocks.push(block);
|
|
857
|
+
}
|
|
858
|
+
const joined = blocks.join("\n\n---\n\n");
|
|
859
|
+
ctx.data.priorArt = joined.length > TOTAL_MAX_BYTES2 ? joined.slice(0, TOTAL_MAX_BYTES2) + TRUNCATED_SUFFIX2 : joined;
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
|
|
315
864
|
// package.json
|
|
316
865
|
var package_default = {
|
|
317
866
|
name: "@kody-ade/kody-engine",
|
|
318
|
-
version: "0.4.
|
|
867
|
+
version: "0.4.67",
|
|
319
868
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
320
869
|
license: "MIT",
|
|
321
870
|
type: "module",
|
|
@@ -1775,153 +2324,18 @@ function coerceBare(spec, value) {
|
|
|
1775
2324
|
return value;
|
|
1776
2325
|
}
|
|
1777
2326
|
|
|
1778
|
-
// src/
|
|
1779
|
-
|
|
1780
|
-
var API_TIMEOUT_MS = 3e4;
|
|
1781
|
-
function ghToken() {
|
|
1782
|
-
return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
1783
|
-
}
|
|
1784
|
-
function gh(args, options) {
|
|
1785
|
-
const token = ghToken();
|
|
1786
|
-
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
1787
|
-
return execFileSync3("gh", args, {
|
|
1788
|
-
encoding: "utf-8",
|
|
1789
|
-
timeout: API_TIMEOUT_MS,
|
|
1790
|
-
cwd: options?.cwd,
|
|
1791
|
-
env,
|
|
1792
|
-
input: options?.input,
|
|
1793
|
-
stdio: options?.input ? ["pipe", "pipe", "pipe"] : ["inherit", "pipe", "pipe"]
|
|
1794
|
-
}).trim();
|
|
1795
|
-
}
|
|
1796
|
-
function getIssue(issueNumber, cwd) {
|
|
1797
|
-
const output = gh(["issue", "view", String(issueNumber), "--json", "number,title,body,comments,labels"], { cwd });
|
|
1798
|
-
const parsed = JSON.parse(output);
|
|
1799
|
-
if (typeof parsed?.title !== "string") {
|
|
1800
|
-
throw new Error(`Issue #${issueNumber}: unexpected response shape`);
|
|
1801
|
-
}
|
|
1802
|
-
return {
|
|
1803
|
-
number: parsed.number ?? issueNumber,
|
|
1804
|
-
title: parsed.title,
|
|
1805
|
-
body: parsed.body ?? "",
|
|
1806
|
-
comments: (parsed.comments ?? []).map((c) => ({
|
|
1807
|
-
body: c.body ?? "",
|
|
1808
|
-
author: c.author?.login ?? "unknown",
|
|
1809
|
-
createdAt: c.createdAt ?? ""
|
|
1810
|
-
})),
|
|
1811
|
-
labels: Array.isArray(parsed.labels) ? parsed.labels.map((l) => l.name ?? "").filter((n) => n.length > 0) : []
|
|
1812
|
-
};
|
|
1813
|
-
}
|
|
1814
|
-
function stripKodyMentions(body) {
|
|
1815
|
-
return body.replace(/(@)(kody)/gi, "$1\u200B$2");
|
|
1816
|
-
}
|
|
1817
|
-
function postIssueComment(issueNumber, body, cwd) {
|
|
1818
|
-
try {
|
|
1819
|
-
gh(["issue", "comment", String(issueNumber), "--body-file", "-"], { input: stripKodyMentions(body), cwd });
|
|
1820
|
-
} catch (err) {
|
|
1821
|
-
process.stderr.write(
|
|
1822
|
-
`[kody] failed to post comment on #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
1823
|
-
`
|
|
1824
|
-
);
|
|
1825
|
-
}
|
|
1826
|
-
}
|
|
1827
|
-
function truncate2(s, maxBytes) {
|
|
1828
|
-
if (s.length <= maxBytes) return s;
|
|
1829
|
-
return `${s.slice(0, maxBytes)}\u2026 (+${s.length - maxBytes} chars)`;
|
|
1830
|
-
}
|
|
1831
|
-
function parsePrNumber(url) {
|
|
1832
|
-
const m = url.match(/\/pull\/(\d+)(?:[/?#]|$)/);
|
|
1833
|
-
if (!m) return null;
|
|
1834
|
-
const n = parseInt(m[1], 10);
|
|
1835
|
-
return Number.isFinite(n) ? n : null;
|
|
1836
|
-
}
|
|
1837
|
-
function getPr(prNumber, cwd) {
|
|
1838
|
-
const output = gh(["pr", "view", String(prNumber), "--json", "number,title,body,headRefName,baseRefName,state"], {
|
|
1839
|
-
cwd
|
|
1840
|
-
});
|
|
1841
|
-
const parsed = JSON.parse(output);
|
|
1842
|
-
if (typeof parsed?.title !== "string") {
|
|
1843
|
-
throw new Error(`PR #${prNumber}: unexpected response shape`);
|
|
1844
|
-
}
|
|
1845
|
-
return {
|
|
1846
|
-
number: parsed.number ?? prNumber,
|
|
1847
|
-
title: parsed.title,
|
|
1848
|
-
body: parsed.body ?? "",
|
|
1849
|
-
headRefName: String(parsed.headRefName ?? ""),
|
|
1850
|
-
baseRefName: String(parsed.baseRefName ?? ""),
|
|
1851
|
-
state: String(parsed.state ?? "")
|
|
1852
|
-
};
|
|
1853
|
-
}
|
|
1854
|
-
function getPrDiff(prNumber, cwd) {
|
|
1855
|
-
try {
|
|
1856
|
-
return gh(["pr", "diff", String(prNumber)], { cwd });
|
|
1857
|
-
} catch (err) {
|
|
1858
|
-
process.stderr.write(
|
|
1859
|
-
`[kody] failed to fetch diff for PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
1860
|
-
`
|
|
1861
|
-
);
|
|
1862
|
-
return "";
|
|
1863
|
-
}
|
|
1864
|
-
}
|
|
1865
|
-
function getPrReviews(prNumber, cwd) {
|
|
1866
|
-
try {
|
|
1867
|
-
const output = gh(["pr", "view", String(prNumber), "--json", "reviews"], { cwd });
|
|
1868
|
-
const parsed = JSON.parse(output);
|
|
1869
|
-
if (!Array.isArray(parsed?.reviews)) return [];
|
|
1870
|
-
return parsed.reviews.map(
|
|
1871
|
-
(r) => ({
|
|
1872
|
-
body: r.body ?? "",
|
|
1873
|
-
state: r.state ?? "",
|
|
1874
|
-
author: r.author?.login ?? "unknown",
|
|
1875
|
-
submittedAt: r.submittedAt ?? ""
|
|
1876
|
-
})
|
|
1877
|
-
);
|
|
1878
|
-
} catch {
|
|
1879
|
-
return [];
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
function getPrComments(prNumber, cwd) {
|
|
1883
|
-
try {
|
|
1884
|
-
const output = gh(["pr", "view", String(prNumber), "--json", "comments"], { cwd });
|
|
1885
|
-
const parsed = JSON.parse(output);
|
|
1886
|
-
if (!Array.isArray(parsed?.comments)) return [];
|
|
1887
|
-
return parsed.comments.map((c) => ({
|
|
1888
|
-
body: c.body ?? "",
|
|
1889
|
-
author: c.author?.login ?? "unknown",
|
|
1890
|
-
createdAt: c.createdAt ?? ""
|
|
1891
|
-
})).filter((c) => c.body.trim().length > 0);
|
|
1892
|
-
} catch {
|
|
1893
|
-
return [];
|
|
1894
|
-
}
|
|
1895
|
-
}
|
|
1896
|
-
var VERDICT_HEADING = /(^|\n)\s*#{1,6}\s*Verdict\s*:/i;
|
|
1897
|
-
function isReviewShaped(body) {
|
|
1898
|
-
return VERDICT_HEADING.test(body);
|
|
1899
|
-
}
|
|
1900
|
-
function getPrLatestReviewBody(prNumber, cwd) {
|
|
1901
|
-
const reviews = getPrReviews(prNumber, cwd).filter((r) => r.body.trim().length > 0).map((r) => ({ body: r.body, at: r.submittedAt }));
|
|
1902
|
-
const comments = getPrComments(prNumber, cwd).filter((c) => isReviewShaped(c.body)).map((c) => ({ body: c.body, at: c.createdAt }));
|
|
1903
|
-
const all = [...reviews, ...comments].sort((a, b) => (b.at || "").localeCompare(a.at || ""));
|
|
1904
|
-
if (all.length > 0) return all[0].body;
|
|
1905
|
-
const pr = getPr(prNumber, cwd);
|
|
1906
|
-
return pr.body;
|
|
1907
|
-
}
|
|
1908
|
-
function postPrReviewComment(prNumber, body, cwd) {
|
|
1909
|
-
try {
|
|
1910
|
-
gh(["pr", "comment", String(prNumber), "--body-file", "-"], { input: stripKodyMentions(body), cwd });
|
|
1911
|
-
} catch (err) {
|
|
1912
|
-
process.stderr.write(
|
|
1913
|
-
`[kody] failed to post review comment on PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
1914
|
-
`
|
|
1915
|
-
);
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
2327
|
+
// src/kody-cli.ts
|
|
2328
|
+
init_issue();
|
|
1918
2329
|
|
|
1919
2330
|
// src/executor.ts
|
|
1920
|
-
import { execFileSync as execFileSync29, spawn as
|
|
2331
|
+
import { execFileSync as execFileSync29, spawn as spawn6 } from "child_process";
|
|
1921
2332
|
import * as fs31 from "fs";
|
|
1922
2333
|
import * as path29 from "path";
|
|
1923
2334
|
init_events();
|
|
1924
2335
|
|
|
2336
|
+
// src/lifecycleLabels.ts
|
|
2337
|
+
init_issue();
|
|
2338
|
+
|
|
1925
2339
|
// src/profile.ts
|
|
1926
2340
|
import * as fs9 from "fs";
|
|
1927
2341
|
import * as path8 from "path";
|
|
@@ -2086,7 +2500,8 @@ var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
|
|
|
2086
2500
|
"output",
|
|
2087
2501
|
// legacy JSON name for outputArtifacts source
|
|
2088
2502
|
"children",
|
|
2089
|
-
"resetBetweenChildren"
|
|
2503
|
+
"resetBetweenChildren",
|
|
2504
|
+
"preloadContext"
|
|
2090
2505
|
]);
|
|
2091
2506
|
function loadProfile(profilePath) {
|
|
2092
2507
|
if (!fs9.existsSync(profilePath)) {
|
|
@@ -2163,6 +2578,9 @@ function loadProfile(profilePath) {
|
|
|
2163
2578
|
// container child sees a clean tracked tree (see executor.ts).
|
|
2164
2579
|
// Containers opt out by setting `"resetBetweenChildren": false`.
|
|
2165
2580
|
resetBetweenChildren: typeof r.resetBetweenChildren === "boolean" ? r.resetBetweenChildren : true,
|
|
2581
|
+
// Phase 5 in-process handoff opt-in. Default false; containers
|
|
2582
|
+
// flip to true after end-to-end verification.
|
|
2583
|
+
preloadContext: r.preloadContext === true,
|
|
2166
2584
|
dir: path8.dirname(profilePath)
|
|
2167
2585
|
};
|
|
2168
2586
|
if (lifecycle) {
|
|
@@ -3236,120 +3654,16 @@ function checkCoverage(addedFiles, requirements) {
|
|
|
3236
3654
|
function formatMissesForFeedback(misses) {
|
|
3237
3655
|
if (misses.length === 0) return "";
|
|
3238
3656
|
const lines = ["The following files were added without a sibling test file:"];
|
|
3239
|
-
for (const m of misses) lines.push(`- \`${m.file}\` \u2192 expected \`${m.expectedTest}\``);
|
|
3240
|
-
lines.push("");
|
|
3241
|
-
lines.push(
|
|
3242
|
-
"Add the missing test files. Each should cover the new file's public API with at least a happy path and one failure path. Then re-emit DONE / COMMIT_MSG / PR_SUMMARY."
|
|
3243
|
-
);
|
|
3244
|
-
return lines.join("\n");
|
|
3245
|
-
}
|
|
3246
|
-
|
|
3247
|
-
// src/prompt.ts
|
|
3248
|
-
import * as fs13 from "fs";
|
|
3249
|
-
import * as path12 from "path";
|
|
3250
|
-
var CONVENTIONS_PER_FILE_MAX_BYTES = 3e4;
|
|
3251
|
-
var CONVENTION_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
3252
|
-
function loadProjectConventions(projectDir) {
|
|
3253
|
-
const out = [];
|
|
3254
|
-
for (const rel of CONVENTION_FILES) {
|
|
3255
|
-
const abs = path12.join(projectDir, rel);
|
|
3256
|
-
if (!fs13.existsSync(abs)) continue;
|
|
3257
|
-
let content;
|
|
3258
|
-
try {
|
|
3259
|
-
content = fs13.readFileSync(abs, "utf-8");
|
|
3260
|
-
} catch {
|
|
3261
|
-
continue;
|
|
3262
|
-
}
|
|
3263
|
-
const truncated = content.length > CONVENTIONS_PER_FILE_MAX_BYTES;
|
|
3264
|
-
if (truncated) content = `${content.slice(0, CONVENTIONS_PER_FILE_MAX_BYTES)}
|
|
3265
|
-
|
|
3266
|
-
\u2026 (truncated)`;
|
|
3267
|
-
out.push({ path: rel, content, truncated });
|
|
3268
|
-
}
|
|
3269
|
-
return out;
|
|
3270
|
-
}
|
|
3271
|
-
function parseAgentResult(finalText) {
|
|
3272
|
-
const text = (finalText || "").trim();
|
|
3273
|
-
if (!text)
|
|
3274
|
-
return {
|
|
3275
|
-
done: false,
|
|
3276
|
-
commitMessage: "",
|
|
3277
|
-
prSummary: "",
|
|
3278
|
-
feedbackActions: "",
|
|
3279
|
-
planDeviations: "",
|
|
3280
|
-
priorArt: "",
|
|
3281
|
-
failureReason: "agent produced no final message",
|
|
3282
|
-
markerMissing: false
|
|
3283
|
-
};
|
|
3284
|
-
const MARKDOWN_PREFIX = "[\\s>*_#`~\\-]*";
|
|
3285
|
-
const FAILED_RE = new RegExp(`(?:^|\\n)${MARKDOWN_PREFIX}FAILED${MARKDOWN_PREFIX}\\s*:\\s*(.+?)\\s*$`, "is");
|
|
3286
|
-
const DONE_RE = new RegExp(`(?:^|\\n)${MARKDOWN_PREFIX}DONE\\b`, "i");
|
|
3287
|
-
const failedMatch = text.match(FAILED_RE);
|
|
3288
|
-
if (failedMatch) {
|
|
3289
|
-
return {
|
|
3290
|
-
done: false,
|
|
3291
|
-
commitMessage: "",
|
|
3292
|
-
prSummary: "",
|
|
3293
|
-
feedbackActions: "",
|
|
3294
|
-
planDeviations: "",
|
|
3295
|
-
priorArt: "",
|
|
3296
|
-
failureReason: stripMarkdownEmphasis(failedMatch[1]),
|
|
3297
|
-
markerMissing: false
|
|
3298
|
-
};
|
|
3299
|
-
}
|
|
3300
|
-
const hasDoneMarker = DONE_RE.test(text);
|
|
3301
|
-
const hasCommitMsg = /^[\s>*_#`~-]*COMMIT_MSG\s*:/im.test(text);
|
|
3302
|
-
const hasPrSummary = /^[\s>*_#`~-]*PR_SUMMARY\s*:/im.test(text);
|
|
3303
|
-
const markerMissing = !hasDoneMarker && !hasCommitMsg && !hasPrSummary;
|
|
3304
|
-
const commitMatch = text.match(/^[\s>*_#`~-]*COMMIT_MSG[\s>*_#`~-]*\s*:\s*(.+)$/im);
|
|
3305
|
-
const commitMessage = commitMatch ? stripMarkdownEmphasis(commitMatch[1]) : "";
|
|
3306
|
-
const feedbackActions = extractBlock(
|
|
3307
|
-
text,
|
|
3308
|
-
/(?:^|\n)[ \t]*FEEDBACK_ACTIONS\s*:[ \t]*\n/i,
|
|
3309
|
-
/(?:^|\n)[ \t]*(?:PLAN_DEVIATIONS|COMMIT_MSG|PR_SUMMARY|PRIOR_ART)\s*:/i
|
|
3310
|
-
);
|
|
3311
|
-
let planDeviations = extractBlock(
|
|
3312
|
-
text,
|
|
3313
|
-
/(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*\n/i,
|
|
3314
|
-
/(?:^|\n)[ \t]*(?:COMMIT_MSG|PR_SUMMARY|FEEDBACK_ACTIONS|PRIOR_ART)\s*:/i
|
|
3315
|
-
);
|
|
3316
|
-
if (!planDeviations) {
|
|
3317
|
-
const inline = text.match(/(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
|
|
3318
|
-
if (inline) planDeviations = inline[1].trim();
|
|
3319
|
-
}
|
|
3320
|
-
let priorArt = "";
|
|
3321
|
-
const priorArtInline = text.match(/(?:^|\n)[ \t]*PRIOR_ART\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
|
|
3322
|
-
if (priorArtInline) priorArt = priorArtInline[1].trim();
|
|
3323
|
-
const summaryStart = text.search(/(^|\n)[ \t]*PR_SUMMARY\s*:[ \t]*\n/i);
|
|
3324
|
-
let prSummary = "";
|
|
3325
|
-
if (summaryStart !== -1) {
|
|
3326
|
-
const afterMarker = text.slice(summaryStart).replace(/^[\s\S]*?PR_SUMMARY\s*:[ \t]*\n/i, "");
|
|
3327
|
-
prSummary = afterMarker.replace(/\n\s*```\s*$/g, "").replace(/```\s*$/g, "").trim();
|
|
3328
|
-
}
|
|
3329
|
-
return {
|
|
3330
|
-
done: true,
|
|
3331
|
-
commitMessage,
|
|
3332
|
-
prSummary,
|
|
3333
|
-
feedbackActions,
|
|
3334
|
-
planDeviations,
|
|
3335
|
-
priorArt,
|
|
3336
|
-
failureReason: "",
|
|
3337
|
-
markerMissing
|
|
3338
|
-
};
|
|
3339
|
-
}
|
|
3340
|
-
function stripMarkdownEmphasis(s) {
|
|
3341
|
-
return s.trim().replace(/^[*_`~]+|[*_`~]+$/g, "").trim();
|
|
3342
|
-
}
|
|
3343
|
-
function extractBlock(text, startMarker, endMarker) {
|
|
3344
|
-
const startIdx = text.search(startMarker);
|
|
3345
|
-
if (startIdx === -1) return "";
|
|
3346
|
-
const afterStart = text.slice(startIdx).replace(startMarker, "");
|
|
3347
|
-
const endIdx = afterStart.search(endMarker);
|
|
3348
|
-
const body = endIdx === -1 ? afterStart : afterStart.slice(0, endIdx);
|
|
3349
|
-
return body.replace(/\n\s*```\s*$/g, "").trim();
|
|
3657
|
+
for (const m of misses) lines.push(`- \`${m.file}\` \u2192 expected \`${m.expectedTest}\``);
|
|
3658
|
+
lines.push("");
|
|
3659
|
+
lines.push(
|
|
3660
|
+
"Add the missing test files. Each should cover the new file's public API with at least a happy path and one failure path. Then re-emit DONE / COMMIT_MSG / PR_SUMMARY."
|
|
3661
|
+
);
|
|
3662
|
+
return lines.join("\n");
|
|
3350
3663
|
}
|
|
3351
3664
|
|
|
3352
3665
|
// src/scripts/checkCoverageWithRetry.ts
|
|
3666
|
+
init_prompt();
|
|
3353
3667
|
var checkCoverageWithRetry = async (ctx) => {
|
|
3354
3668
|
const reqs = ctx.data.coverageRules ?? [];
|
|
3355
3669
|
if (reqs.length === 0) {
|
|
@@ -3658,11 +3972,13 @@ function formatToolsUsage(profile) {
|
|
|
3658
3972
|
}
|
|
3659
3973
|
|
|
3660
3974
|
// src/scripts/createQaGoal.ts
|
|
3975
|
+
init_issue();
|
|
3661
3976
|
import { execFileSync as execFileSync10 } from "child_process";
|
|
3662
3977
|
import * as fs16 from "fs";
|
|
3663
3978
|
import * as path16 from "path";
|
|
3664
3979
|
|
|
3665
3980
|
// src/scripts/postReviewResult.ts
|
|
3981
|
+
init_issue();
|
|
3666
3982
|
function detectVerdict(body) {
|
|
3667
3983
|
const m = body.match(/##\s*Verdict\s*:\s*(PASS|CONCERNS|FAIL)\b/i);
|
|
3668
3984
|
if (!m) return "UNKNOWN";
|
|
@@ -4201,6 +4517,9 @@ QA_GOAL_TARGETED=(no manifest issue) (id: ${goalId}, verdict: ${verdict})
|
|
|
4201
4517
|
ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
|
|
4202
4518
|
};
|
|
4203
4519
|
|
|
4520
|
+
// src/goal/operations.ts
|
|
4521
|
+
init_issue();
|
|
4522
|
+
|
|
4204
4523
|
// src/goal/labels.ts
|
|
4205
4524
|
function goalLabel(goalId) {
|
|
4206
4525
|
return `goal:${goalId}`;
|
|
@@ -5105,7 +5424,11 @@ function stripQuotes(value) {
|
|
|
5105
5424
|
return value;
|
|
5106
5425
|
}
|
|
5107
5426
|
|
|
5427
|
+
// src/scripts/jobState/contentsApiBackend.ts
|
|
5428
|
+
init_issue();
|
|
5429
|
+
|
|
5108
5430
|
// src/scripts/issueStateComment.ts
|
|
5431
|
+
init_issue();
|
|
5109
5432
|
function isStateEnvelope(x) {
|
|
5110
5433
|
if (x === null || typeof x !== "object") return false;
|
|
5111
5434
|
const o = x;
|
|
@@ -5569,6 +5892,7 @@ function listJobSlugs(absDir) {
|
|
|
5569
5892
|
}
|
|
5570
5893
|
|
|
5571
5894
|
// src/scripts/dispatchJobTicks.ts
|
|
5895
|
+
init_issue();
|
|
5572
5896
|
var dispatchJobTicks = async (ctx, _profile, args) => {
|
|
5573
5897
|
ctx.skipAgent = true;
|
|
5574
5898
|
const label = String(args?.label ?? "");
|
|
@@ -5656,6 +5980,7 @@ var dispatchNextTask = async (ctx) => {
|
|
|
5656
5980
|
};
|
|
5657
5981
|
|
|
5658
5982
|
// src/pr.ts
|
|
5983
|
+
init_issue();
|
|
5659
5984
|
function prMergeStatus(prNumber, cwd) {
|
|
5660
5985
|
try {
|
|
5661
5986
|
const out = gh(
|
|
@@ -6002,6 +6327,7 @@ var finalizeGoal = async (ctx) => {
|
|
|
6002
6327
|
};
|
|
6003
6328
|
|
|
6004
6329
|
// src/scripts/finishFlow.ts
|
|
6330
|
+
init_issue();
|
|
6005
6331
|
import { execFileSync as execFileSync14 } from "child_process";
|
|
6006
6332
|
var TERMINAL_PHASE = {
|
|
6007
6333
|
"review-passed": { phase: "shipped", status: "succeeded" },
|
|
@@ -6290,6 +6616,9 @@ function sleepMs(ms) {
|
|
|
6290
6616
|
}
|
|
6291
6617
|
}
|
|
6292
6618
|
|
|
6619
|
+
// src/scripts/fixCiFlow.ts
|
|
6620
|
+
init_issue();
|
|
6621
|
+
|
|
6293
6622
|
// src/workflow.ts
|
|
6294
6623
|
import { execFileSync as execFileSync17 } from "child_process";
|
|
6295
6624
|
var GH_TIMEOUT_MS = 3e4;
|
|
@@ -6443,6 +6772,7 @@ function tryPostPr(prNumber, body, cwd) {
|
|
|
6443
6772
|
}
|
|
6444
6773
|
|
|
6445
6774
|
// src/scripts/fixFlow.ts
|
|
6775
|
+
init_issue();
|
|
6446
6776
|
var fixFlow = async (ctx) => {
|
|
6447
6777
|
const prNumber = ctx.args.pr;
|
|
6448
6778
|
const pr = getPr(prNumber, ctx.cwd);
|
|
@@ -6809,22 +7139,9 @@ Nothing to do. All files already present. (Use --force to overwrite.)
|
|
|
6809
7139
|
ctx.output.exitCode = 0;
|
|
6810
7140
|
};
|
|
6811
7141
|
|
|
6812
|
-
// src/scripts/
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
const conventions = loadProjectConventions(ctx.cwd);
|
|
6816
|
-
ctx.data.conventions = conventions;
|
|
6817
|
-
if (conventions.length > 0) {
|
|
6818
|
-
process.stderr.write(`[kody] loaded conventions: ${conventions.map((c) => c.path).join(", ")}
|
|
6819
|
-
`);
|
|
6820
|
-
}
|
|
6821
|
-
};
|
|
6822
|
-
|
|
6823
|
-
// src/scripts/loadCoverageRules.ts
|
|
6824
|
-
var loadCoverageRules = async (ctx) => {
|
|
6825
|
-
if (Array.isArray(ctx.data.coverageRules)) return;
|
|
6826
|
-
ctx.data.coverageRules = ctx.config.testRequirements ?? [];
|
|
6827
|
-
};
|
|
7142
|
+
// src/scripts/index.ts
|
|
7143
|
+
init_loadConventions();
|
|
7144
|
+
init_loadCoverageRules();
|
|
6828
7145
|
|
|
6829
7146
|
// src/goal/state.ts
|
|
6830
7147
|
import * as fs25 from "fs";
|
|
@@ -6922,356 +7239,130 @@ var loadGoalState = async (ctx) => {
|
|
|
6922
7239
|
ctx.data.goal = {
|
|
6923
7240
|
id: goalId,
|
|
6924
7241
|
state: state.state,
|
|
6925
|
-
lastDispatchedIssue: state.lastDispatchedIssue,
|
|
6926
|
-
// Cache the full parsed object so saveGoalState can preserve `extra`.
|
|
6927
|
-
raw: state,
|
|
6928
|
-
// `phase`, `childTasks`, `openTaskPrs`, `leafPr` are populated by
|
|
6929
|
-
// deriveGoalPhase later in the chain. Initialize to undefined so
|
|
6930
|
-
// runWhen on `data.goal.phase` can match correctly.
|
|
6931
|
-
phase: void 0,
|
|
6932
|
-
defaultBranch: ctx.config.git.defaultBranch
|
|
6933
|
-
};
|
|
6934
|
-
} catch (err) {
|
|
6935
|
-
process.stdout.write(`[goal-tick] ${err instanceof Error ? err.message : String(err)}
|
|
6936
|
-
`);
|
|
6937
|
-
ctx.skipAgent = true;
|
|
6938
|
-
ctx.output.exitCode = 0;
|
|
6939
|
-
ctx.output.reason = "no goal state to tick";
|
|
6940
|
-
}
|
|
6941
|
-
};
|
|
6942
|
-
|
|
6943
|
-
// src/scripts/loadIssueContext.ts
|
|
6944
|
-
var DEFAULT_COMMENT_LIMIT = 12;
|
|
6945
|
-
var DEFAULT_COMMENT_MAX_BYTES = 16e3;
|
|
6946
|
-
var loadIssueContext = async (ctx) => {
|
|
6947
|
-
const issueNumber = ctx.args.issue;
|
|
6948
|
-
if (typeof issueNumber !== "number" || issueNumber <= 0) {
|
|
6949
|
-
throw new Error("loadIssueContext: ctx.args.issue (positive integer) is required");
|
|
6950
|
-
}
|
|
6951
|
-
const preloaded = ctx.data.issue;
|
|
6952
|
-
if (preloaded && typeof preloaded === "object" && preloaded.number === issueNumber) {
|
|
6953
|
-
if (!ctx.data.commentTargetType) ctx.data.commentTargetType = "issue";
|
|
6954
|
-
if (!ctx.data.commentTargetNumber) ctx.data.commentTargetNumber = issueNumber;
|
|
6955
|
-
return;
|
|
6956
|
-
}
|
|
6957
|
-
const issue = getIssue(issueNumber, ctx.cwd);
|
|
6958
|
-
const cfgCtx = ctx.config.issueContext ?? {};
|
|
6959
|
-
const limit = cfgCtx.commentLimit ?? DEFAULT_COMMENT_LIMIT;
|
|
6960
|
-
const maxBytes = cfgCtx.commentMaxBytes ?? DEFAULT_COMMENT_MAX_BYTES;
|
|
6961
|
-
const sorted = [...issue.comments].sort((a, b) => a.createdAt < b.createdAt ? 1 : -1);
|
|
6962
|
-
const kept = sorted.slice(0, limit);
|
|
6963
|
-
const commentsFormatted = kept.length === 0 ? "(no comments yet)" : kept.map((c) => `- **${c.author}** (${c.createdAt}):
|
|
6964
|
-
${truncate2(c.body, maxBytes).replace(/\n/g, "\n ")}`).join("\n\n");
|
|
6965
|
-
const labels = issue.labels ?? [];
|
|
6966
|
-
const labelsFormatted = labels.length === 0 ? "(no labels)" : labels.map((l) => `\`${l}\``).join(", ");
|
|
6967
|
-
ctx.data.issue = { ...issue, commentsFormatted, labelsFormatted };
|
|
6968
|
-
ctx.data.commentTargetType = "issue";
|
|
6969
|
-
ctx.data.commentTargetNumber = issueNumber;
|
|
6970
|
-
};
|
|
6971
|
-
|
|
6972
|
-
// src/scripts/loadIssueStateComment.ts
|
|
6973
|
-
var loadIssueStateComment = async (ctx, _profile, args) => {
|
|
6974
|
-
const marker = String(args?.marker ?? "");
|
|
6975
|
-
if (!marker) {
|
|
6976
|
-
throw new Error("loadIssueStateComment: `with.marker` is required");
|
|
6977
|
-
}
|
|
6978
|
-
const issueArg = String(args?.issueArg ?? "issue");
|
|
6979
|
-
const issueNumber = Number(ctx.args[issueArg]);
|
|
6980
|
-
if (!Number.isFinite(issueNumber) || issueNumber <= 0) {
|
|
6981
|
-
throw new Error(`loadIssueStateComment: ctx.args.${issueArg} must be a positive integer`);
|
|
6982
|
-
}
|
|
6983
|
-
const owner = ctx.config.github.owner;
|
|
6984
|
-
const repo = ctx.config.github.repo;
|
|
6985
|
-
if (!owner || !repo) {
|
|
6986
|
-
throw new Error("loadIssueStateComment: ctx.config.github.owner/repo must be set");
|
|
6987
|
-
}
|
|
6988
|
-
const issue = getIssue(issueNumber, ctx.cwd);
|
|
6989
|
-
const loaded = findStateComment2(owner, repo, issueNumber, marker, ctx.cwd);
|
|
6990
|
-
ctx.data.stateMarker = marker;
|
|
6991
|
-
ctx.data.issueIntent = issue.body;
|
|
6992
|
-
ctx.data.issueTitle = issue.title;
|
|
6993
|
-
ctx.data.issueNumber = String(issueNumber);
|
|
6994
|
-
ctx.data.issueStateComment = loaded;
|
|
6995
|
-
ctx.data.issueStateJson = loaded ? JSON.stringify(loaded.state, null, 2) : "null";
|
|
6996
|
-
};
|
|
6997
|
-
|
|
6998
|
-
// src/scripts/loadJobFromFile.ts
|
|
6999
|
-
import * as fs26 from "fs";
|
|
7000
|
-
import * as path25 from "path";
|
|
7001
|
-
var loadJobFromFile = async (ctx, _profile, args) => {
|
|
7002
|
-
const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
|
|
7003
|
-
const slugArg = String(args?.slugArg ?? "job");
|
|
7004
|
-
const slug = String(ctx.args[slugArg] ?? "").trim();
|
|
7005
|
-
if (!slug) {
|
|
7006
|
-
throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
|
|
7007
|
-
}
|
|
7008
|
-
const absPath = path25.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
7009
|
-
if (!fs26.existsSync(absPath)) {
|
|
7010
|
-
throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
|
|
7011
|
-
}
|
|
7012
|
-
const raw = fs26.readFileSync(absPath, "utf-8");
|
|
7013
|
-
const { title, body } = parseJobFile(raw, slug);
|
|
7014
|
-
const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, jobsDir });
|
|
7015
|
-
const loaded = await backend.load(slug);
|
|
7016
|
-
ctx.data.jobSlug = slug;
|
|
7017
|
-
ctx.data.jobTitle = title;
|
|
7018
|
-
ctx.data.jobIntent = body;
|
|
7019
|
-
ctx.data.jobState = loaded;
|
|
7020
|
-
ctx.data.jobStateJson = JSON.stringify(loaded.state, null, 2);
|
|
7021
|
-
};
|
|
7022
|
-
function parseJobFile(raw, slug) {
|
|
7023
|
-
let stripped = raw;
|
|
7024
|
-
if (stripped.startsWith("---\n")) {
|
|
7025
|
-
const end = stripped.indexOf("\n---\n", 4);
|
|
7026
|
-
if (end !== -1) {
|
|
7027
|
-
stripped = stripped.slice(end + 5);
|
|
7028
|
-
}
|
|
7029
|
-
}
|
|
7030
|
-
const trimmed = stripped.trim();
|
|
7031
|
-
const firstLine2 = trimmed.split("\n", 1)[0] ?? "";
|
|
7032
|
-
const h1 = /^#\s+(.+?)\s*$/.exec(firstLine2);
|
|
7033
|
-
if (h1) {
|
|
7034
|
-
const rest = trimmed.slice(firstLine2.length).replace(/^\n+/, "");
|
|
7035
|
-
return { title: h1[1].trim(), body: rest };
|
|
7036
|
-
}
|
|
7037
|
-
return { title: humanizeSlug(slug), body: trimmed };
|
|
7038
|
-
}
|
|
7039
|
-
function humanizeSlug(slug) {
|
|
7040
|
-
return slug.split(/[-_]+/).filter((s) => s.length > 0).map((s) => s[0].toUpperCase() + s.slice(1)).join(" ");
|
|
7041
|
-
}
|
|
7042
|
-
|
|
7043
|
-
// src/scripts/loadMemoryContext.ts
|
|
7044
|
-
import * as fs27 from "fs";
|
|
7045
|
-
import * as path26 from "path";
|
|
7046
|
-
var MEMORY_DIR_RELATIVE = ".kody/memory";
|
|
7047
|
-
var MAX_PAGES = 8;
|
|
7048
|
-
var PER_PAGE_MAX_BYTES = 4e3;
|
|
7049
|
-
var TOTAL_MAX_BYTES = 24e3;
|
|
7050
|
-
var TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
|
|
7051
|
-
var loadMemoryContext = async (ctx) => {
|
|
7052
|
-
if (typeof ctx.data.memoryContext === "string") return;
|
|
7053
|
-
const memoryAbs = path26.join(ctx.cwd, MEMORY_DIR_RELATIVE);
|
|
7054
|
-
if (!fs27.existsSync(memoryAbs)) {
|
|
7055
|
-
ctx.data.memoryContext = "";
|
|
7056
|
-
return;
|
|
7057
|
-
}
|
|
7058
|
-
let pages = [];
|
|
7059
|
-
try {
|
|
7060
|
-
pages = collectPages(memoryAbs);
|
|
7061
|
-
} catch {
|
|
7062
|
-
ctx.data.memoryContext = "";
|
|
7063
|
-
return;
|
|
7064
|
-
}
|
|
7065
|
-
if (pages.length === 0) {
|
|
7066
|
-
ctx.data.memoryContext = "";
|
|
7067
|
-
return;
|
|
7068
|
-
}
|
|
7069
|
-
const queryTerms = extractQueryTerms(ctx);
|
|
7070
|
-
const ranked = queryTerms.length > 0 ? scorePages(pages, queryTerms) : sortByRecency(pages);
|
|
7071
|
-
const top = ranked.slice(0, MAX_PAGES);
|
|
7072
|
-
ctx.data.memoryContext = formatBlock(top);
|
|
7073
|
-
};
|
|
7074
|
-
function collectPages(memoryAbs) {
|
|
7075
|
-
const out = [];
|
|
7076
|
-
walkMd(memoryAbs, (file) => {
|
|
7077
|
-
let stat;
|
|
7078
|
-
try {
|
|
7079
|
-
stat = fs27.statSync(file);
|
|
7080
|
-
} catch {
|
|
7081
|
-
return;
|
|
7082
|
-
}
|
|
7083
|
-
let raw;
|
|
7084
|
-
try {
|
|
7085
|
-
raw = fs27.readFileSync(file, "utf-8");
|
|
7086
|
-
} catch {
|
|
7087
|
-
return;
|
|
7088
|
-
}
|
|
7089
|
-
const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
7090
|
-
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path26.basename(file, ".md");
|
|
7091
|
-
const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
|
|
7092
|
-
out.push({
|
|
7093
|
-
relPath: path26.relative(memoryAbs, file),
|
|
7094
|
-
title,
|
|
7095
|
-
updated,
|
|
7096
|
-
content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX : raw,
|
|
7097
|
-
mtime: stat.mtimeMs
|
|
7098
|
-
});
|
|
7099
|
-
});
|
|
7100
|
-
return out;
|
|
7101
|
-
}
|
|
7102
|
-
function extractQueryTerms(ctx) {
|
|
7103
|
-
const terms = [];
|
|
7104
|
-
const issue = ctx.data.issue;
|
|
7105
|
-
const pr = ctx.data.pr;
|
|
7106
|
-
if (issue?.title) terms.push(...tokenize(issue.title));
|
|
7107
|
-
if (pr?.title) terms.push(...tokenize(pr.title));
|
|
7108
|
-
return Array.from(new Set(terms)).slice(0, 20);
|
|
7109
|
-
}
|
|
7110
|
-
function tokenize(s) {
|
|
7111
|
-
return s.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((t) => t.length >= 3);
|
|
7112
|
-
}
|
|
7113
|
-
function scorePages(pages, terms) {
|
|
7114
|
-
return pages.map((p) => {
|
|
7115
|
-
const haystack = `${p.title} ${p.content}`.toLowerCase();
|
|
7116
|
-
let score = 0;
|
|
7117
|
-
for (const t of terms) {
|
|
7118
|
-
if (haystack.includes(t)) score++;
|
|
7119
|
-
}
|
|
7120
|
-
return { p, score };
|
|
7121
|
-
}).sort((a, b) => {
|
|
7122
|
-
if (b.score !== a.score) return b.score - a.score;
|
|
7123
|
-
return b.p.mtime - a.p.mtime;
|
|
7124
|
-
}).map((x) => x.p);
|
|
7125
|
-
}
|
|
7126
|
-
function sortByRecency(pages) {
|
|
7127
|
-
return [...pages].sort((a, b) => {
|
|
7128
|
-
if (a.updated && b.updated && a.updated !== b.updated) {
|
|
7129
|
-
return b.updated.localeCompare(a.updated);
|
|
7130
|
-
}
|
|
7131
|
-
return b.mtime - a.mtime;
|
|
7132
|
-
});
|
|
7133
|
-
}
|
|
7134
|
-
function formatBlock(pages) {
|
|
7135
|
-
if (pages.length === 0) return "";
|
|
7136
|
-
const lines = [
|
|
7137
|
-
"# Project memory (`.kody/memory/`)",
|
|
7138
|
-
"",
|
|
7139
|
-
"Pages from prior memorize ticks. Treat as advisory context \u2014 confirm against the codebase before acting.",
|
|
7140
|
-
""
|
|
7141
|
-
];
|
|
7142
|
-
let total = 0;
|
|
7143
|
-
for (const p of pages) {
|
|
7144
|
-
const block = [`## ${p.title} \u2014 \`${p.relPath}\``, "", p.content].join("\n");
|
|
7145
|
-
if (total + block.length > TOTAL_MAX_BYTES) {
|
|
7146
|
-
lines.push("_\u2026 (further pages truncated to fit budget)_");
|
|
7147
|
-
break;
|
|
7148
|
-
}
|
|
7149
|
-
lines.push(block);
|
|
7150
|
-
lines.push("");
|
|
7151
|
-
total += block.length;
|
|
7152
|
-
}
|
|
7153
|
-
return lines.join("\n");
|
|
7154
|
-
}
|
|
7155
|
-
function walkMd(root, visit) {
|
|
7156
|
-
const stack = [root];
|
|
7157
|
-
while (stack.length > 0) {
|
|
7158
|
-
const dir = stack.pop();
|
|
7159
|
-
let names;
|
|
7160
|
-
try {
|
|
7161
|
-
names = fs27.readdirSync(dir);
|
|
7162
|
-
} catch {
|
|
7163
|
-
continue;
|
|
7164
|
-
}
|
|
7165
|
-
for (const name of names) {
|
|
7166
|
-
if (name.startsWith(".")) continue;
|
|
7167
|
-
const full = path26.join(dir, name);
|
|
7168
|
-
let stat;
|
|
7169
|
-
try {
|
|
7170
|
-
stat = fs27.statSync(full);
|
|
7171
|
-
} catch {
|
|
7172
|
-
continue;
|
|
7173
|
-
}
|
|
7174
|
-
if (stat.isDirectory()) {
|
|
7175
|
-
stack.push(full);
|
|
7176
|
-
continue;
|
|
7177
|
-
}
|
|
7178
|
-
if (stat.isFile() && full.endsWith(".md")) visit(full);
|
|
7179
|
-
}
|
|
7242
|
+
lastDispatchedIssue: state.lastDispatchedIssue,
|
|
7243
|
+
// Cache the full parsed object so saveGoalState can preserve `extra`.
|
|
7244
|
+
raw: state,
|
|
7245
|
+
// `phase`, `childTasks`, `openTaskPrs`, `leafPr` are populated by
|
|
7246
|
+
// deriveGoalPhase later in the chain. Initialize to undefined so
|
|
7247
|
+
// runWhen on `data.goal.phase` can match correctly.
|
|
7248
|
+
phase: void 0,
|
|
7249
|
+
defaultBranch: ctx.config.git.defaultBranch
|
|
7250
|
+
};
|
|
7251
|
+
} catch (err) {
|
|
7252
|
+
process.stdout.write(`[goal-tick] ${err instanceof Error ? err.message : String(err)}
|
|
7253
|
+
`);
|
|
7254
|
+
ctx.skipAgent = true;
|
|
7255
|
+
ctx.output.exitCode = 0;
|
|
7256
|
+
ctx.output.reason = "no goal state to tick";
|
|
7180
7257
|
}
|
|
7181
|
-
}
|
|
7258
|
+
};
|
|
7182
7259
|
|
|
7183
|
-
// src/scripts/
|
|
7184
|
-
|
|
7185
|
-
var
|
|
7186
|
-
var
|
|
7187
|
-
var
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
const
|
|
7193
|
-
if (
|
|
7194
|
-
ctx.data.
|
|
7260
|
+
// src/scripts/loadIssueContext.ts
|
|
7261
|
+
init_issue();
|
|
7262
|
+
var DEFAULT_COMMENT_LIMIT = 12;
|
|
7263
|
+
var DEFAULT_COMMENT_MAX_BYTES = 16e3;
|
|
7264
|
+
var loadIssueContext = async (ctx) => {
|
|
7265
|
+
const issueNumber = ctx.args.issue;
|
|
7266
|
+
if (typeof issueNumber !== "number" || issueNumber <= 0) {
|
|
7267
|
+
throw new Error("loadIssueContext: ctx.args.issue (positive integer) is required");
|
|
7268
|
+
}
|
|
7269
|
+
const preloaded = ctx.data.issue;
|
|
7270
|
+
if (preloaded && typeof preloaded === "object" && preloaded.number === issueNumber) {
|
|
7271
|
+
if (!ctx.data.commentTargetType) ctx.data.commentTargetType = "issue";
|
|
7272
|
+
if (!ctx.data.commentTargetNumber) ctx.data.commentTargetNumber = issueNumber;
|
|
7195
7273
|
return;
|
|
7196
7274
|
}
|
|
7197
|
-
const
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7275
|
+
const issue = getIssue(issueNumber, ctx.cwd);
|
|
7276
|
+
const cfgCtx = ctx.config.issueContext ?? {};
|
|
7277
|
+
const limit = cfgCtx.commentLimit ?? DEFAULT_COMMENT_LIMIT;
|
|
7278
|
+
const maxBytes = cfgCtx.commentMaxBytes ?? DEFAULT_COMMENT_MAX_BYTES;
|
|
7279
|
+
const sorted = [...issue.comments].sort((a, b) => a.createdAt < b.createdAt ? 1 : -1);
|
|
7280
|
+
const kept = sorted.slice(0, limit);
|
|
7281
|
+
const commentsFormatted = kept.length === 0 ? "(no comments yet)" : kept.map((c) => `- **${c.author}** (${c.createdAt}):
|
|
7282
|
+
${truncate2(c.body, maxBytes).replace(/\n/g, "\n ")}`).join("\n\n");
|
|
7283
|
+
const labels = issue.labels ?? [];
|
|
7284
|
+
const labelsFormatted = labels.length === 0 ? "(no labels)" : labels.map((l) => `\`${l}\``).join(", ");
|
|
7285
|
+
ctx.data.issue = { ...issue, commentsFormatted, labelsFormatted };
|
|
7286
|
+
ctx.data.commentTargetType = "issue";
|
|
7287
|
+
ctx.data.commentTargetNumber = issueNumber;
|
|
7288
|
+
};
|
|
7289
|
+
|
|
7290
|
+
// src/scripts/loadIssueStateComment.ts
|
|
7291
|
+
init_issue();
|
|
7292
|
+
var loadIssueStateComment = async (ctx, _profile, args) => {
|
|
7293
|
+
const marker = String(args?.marker ?? "");
|
|
7294
|
+
if (!marker) {
|
|
7295
|
+
throw new Error("loadIssueStateComment: `with.marker` is required");
|
|
7296
|
+
}
|
|
7297
|
+
const issueArg = String(args?.issueArg ?? "issue");
|
|
7298
|
+
const issueNumber = Number(ctx.args[issueArg]);
|
|
7299
|
+
if (!Number.isFinite(issueNumber) || issueNumber <= 0) {
|
|
7300
|
+
throw new Error(`loadIssueStateComment: ctx.args.${issueArg} must be a positive integer`);
|
|
7301
|
+
}
|
|
7302
|
+
const owner = ctx.config.github.owner;
|
|
7303
|
+
const repo = ctx.config.github.repo;
|
|
7304
|
+
if (!owner || !repo) {
|
|
7305
|
+
throw new Error("loadIssueStateComment: ctx.config.github.owner/repo must be set");
|
|
7201
7306
|
}
|
|
7202
|
-
const
|
|
7203
|
-
|
|
7307
|
+
const issue = getIssue(issueNumber, ctx.cwd);
|
|
7308
|
+
const loaded = findStateComment2(owner, repo, issueNumber, marker, ctx.cwd);
|
|
7309
|
+
ctx.data.stateMarker = marker;
|
|
7310
|
+
ctx.data.issueIntent = issue.body;
|
|
7311
|
+
ctx.data.issueTitle = issue.title;
|
|
7312
|
+
ctx.data.issueNumber = String(issueNumber);
|
|
7313
|
+
ctx.data.issueStateComment = loaded;
|
|
7314
|
+
ctx.data.issueStateJson = loaded ? JSON.stringify(loaded.state, null, 2) : "null";
|
|
7204
7315
|
};
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7316
|
+
|
|
7317
|
+
// src/scripts/loadJobFromFile.ts
|
|
7318
|
+
import * as fs26 from "fs";
|
|
7319
|
+
import * as path25 from "path";
|
|
7320
|
+
var loadJobFromFile = async (ctx, _profile, args) => {
|
|
7321
|
+
const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
|
|
7322
|
+
const slugArg = String(args?.slugArg ?? "job");
|
|
7323
|
+
const slug = String(ctx.args[slugArg] ?? "").trim();
|
|
7324
|
+
if (!slug) {
|
|
7325
|
+
throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
|
|
7215
7326
|
}
|
|
7216
|
-
}
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
lines.push(commentsBlock);
|
|
7327
|
+
const absPath = path25.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
7328
|
+
if (!fs26.existsSync(absPath)) {
|
|
7329
|
+
throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
|
|
7330
|
+
}
|
|
7331
|
+
const raw = fs26.readFileSync(absPath, "utf-8");
|
|
7332
|
+
const { title, body } = parseJobFile(raw, slug);
|
|
7333
|
+
const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, jobsDir });
|
|
7334
|
+
const loaded = await backend.load(slug);
|
|
7335
|
+
ctx.data.jobSlug = slug;
|
|
7336
|
+
ctx.data.jobTitle = title;
|
|
7337
|
+
ctx.data.jobIntent = body;
|
|
7338
|
+
ctx.data.jobState = loaded;
|
|
7339
|
+
ctx.data.jobStateJson = JSON.stringify(loaded.state, null, 2);
|
|
7340
|
+
};
|
|
7341
|
+
function parseJobFile(raw, slug) {
|
|
7342
|
+
let stripped = raw;
|
|
7343
|
+
if (stripped.startsWith("---\n")) {
|
|
7344
|
+
const end = stripped.indexOf("\n---\n", 4);
|
|
7345
|
+
if (end !== -1) {
|
|
7346
|
+
stripped = stripped.slice(end + 5);
|
|
7237
7347
|
}
|
|
7238
|
-
return lines.filter((l) => l !== "").join("\n");
|
|
7239
|
-
} catch (err) {
|
|
7240
|
-
return `## Prior art: PR #${prNumber}
|
|
7241
|
-
_Could not fetch \u2014 ${err instanceof Error ? err.message : String(err)}_`;
|
|
7242
7348
|
}
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
return
|
|
7349
|
+
const trimmed = stripped.trim();
|
|
7350
|
+
const firstLine2 = trimmed.split("\n", 1)[0] ?? "";
|
|
7351
|
+
const h1 = /^#\s+(.+?)\s*$/.exec(firstLine2);
|
|
7352
|
+
if (h1) {
|
|
7353
|
+
const rest = trimmed.slice(firstLine2.length).replace(/^\n+/, "");
|
|
7354
|
+
return { title: h1[1].trim(), body: rest };
|
|
7249
7355
|
}
|
|
7356
|
+
return { title: humanizeSlug(slug), body: trimmed };
|
|
7250
7357
|
}
|
|
7251
|
-
function
|
|
7252
|
-
return s.length
|
|
7253
|
-
}
|
|
7254
|
-
function formatReviewComments(raw) {
|
|
7255
|
-
if (!raw) return "";
|
|
7256
|
-
try {
|
|
7257
|
-
const parsed = JSON.parse(raw);
|
|
7258
|
-
const out = [];
|
|
7259
|
-
for (const c of parsed.comments ?? []) {
|
|
7260
|
-
if (!c.body) continue;
|
|
7261
|
-
out.push(`- **${c.author?.login ?? "unknown"}**: ${c.body.replace(/\n/g, " ").slice(0, 500)}`);
|
|
7262
|
-
}
|
|
7263
|
-
for (const r of parsed.reviews ?? []) {
|
|
7264
|
-
if (!r.body && !r.state) continue;
|
|
7265
|
-
const state = r.state ? ` (${r.state})` : "";
|
|
7266
|
-
const body = r.body ? `: ${r.body.replace(/\n/g, " ").slice(0, 500)}` : "";
|
|
7267
|
-
out.push(`- **${r.author?.login ?? "unknown"}**${state}${body}`);
|
|
7268
|
-
}
|
|
7269
|
-
return out.join("\n");
|
|
7270
|
-
} catch {
|
|
7271
|
-
return "";
|
|
7272
|
-
}
|
|
7358
|
+
function humanizeSlug(slug) {
|
|
7359
|
+
return slug.split(/[-_]+/).filter((s) => s.length > 0).map((s) => s[0].toUpperCase() + s.slice(1)).join(" ");
|
|
7273
7360
|
}
|
|
7274
7361
|
|
|
7362
|
+
// src/scripts/index.ts
|
|
7363
|
+
init_loadMemoryContext();
|
|
7364
|
+
init_loadPriorArt();
|
|
7365
|
+
|
|
7275
7366
|
// src/scripts/loadTaskContext.ts
|
|
7276
7367
|
init_events();
|
|
7277
7368
|
|
|
@@ -7441,6 +7532,7 @@ function parsePrNumber3(prUrl) {
|
|
|
7441
7532
|
}
|
|
7442
7533
|
|
|
7443
7534
|
// src/scripts/notifyTerminal.ts
|
|
7535
|
+
init_issue();
|
|
7444
7536
|
var notifyTerminal = async (ctx, _profile, _agentResult, args) => {
|
|
7445
7537
|
const issueNumber = ctx.args.issue;
|
|
7446
7538
|
if (!issueNumber || issueNumber <= 0) return;
|
|
@@ -7472,6 +7564,7 @@ function composeBody({ label, exit, prUrl, reason, dryRun }) {
|
|
|
7472
7564
|
}
|
|
7473
7565
|
|
|
7474
7566
|
// src/scripts/openQaIssue.ts
|
|
7567
|
+
init_issue();
|
|
7475
7568
|
var QA_LABEL = "kody:qa-report";
|
|
7476
7569
|
function qaAction2(verdict, payload) {
|
|
7477
7570
|
const type = verdict === "PASS" ? "QA_PASS" : verdict === "CONCERNS" ? "QA_CONCERNS" : verdict === "FAIL" ? "QA_FAIL" : "QA_COMPLETED";
|
|
@@ -7573,6 +7666,7 @@ QA_REPORT_POSTED=${created.url} (verdict: ${verdict})
|
|
|
7573
7666
|
};
|
|
7574
7667
|
|
|
7575
7668
|
// src/scripts/parseAgentResult.ts
|
|
7669
|
+
init_prompt();
|
|
7576
7670
|
var parseAgentResult2 = async (ctx, profile, agentResult) => {
|
|
7577
7671
|
if (!agentResult) {
|
|
7578
7672
|
ctx.data.agentDone = false;
|
|
@@ -7815,6 +7909,9 @@ var persistFlowState = async (ctx) => {
|
|
|
7815
7909
|
}
|
|
7816
7910
|
};
|
|
7817
7911
|
|
|
7912
|
+
// src/scripts/postIssueComment.ts
|
|
7913
|
+
init_issue();
|
|
7914
|
+
|
|
7818
7915
|
// src/scripts/prOutcome.ts
|
|
7819
7916
|
function readPrOutcome(data) {
|
|
7820
7917
|
const raw = data.prResult;
|
|
@@ -7950,6 +8047,7 @@ function postWith(type, n, body, cwd) {
|
|
|
7950
8047
|
}
|
|
7951
8048
|
|
|
7952
8049
|
// src/scripts/postPlanComment.ts
|
|
8050
|
+
init_issue();
|
|
7953
8051
|
var postPlanComment = async (ctx) => {
|
|
7954
8052
|
if (!ctx.data.agentDone) return;
|
|
7955
8053
|
const targetType = ctx.data.commentTargetType;
|
|
@@ -7980,6 +8078,7 @@ Comment \`kody run\` (prefixed with \`@\`) to execute this plan.`;
|
|
|
7980
8078
|
}
|
|
7981
8079
|
|
|
7982
8080
|
// src/scripts/postResearchComment.ts
|
|
8081
|
+
init_issue();
|
|
7983
8082
|
var postResearchComment = async (ctx) => {
|
|
7984
8083
|
if (!ctx.data.agentDone) return;
|
|
7985
8084
|
const targetType = ctx.data.commentTargetType;
|
|
@@ -8161,6 +8260,7 @@ var resolveArtifacts = async (ctx, profile) => {
|
|
|
8161
8260
|
|
|
8162
8261
|
// src/scripts/resolveFlow.ts
|
|
8163
8262
|
import { execFileSync as execFileSync21 } from "child_process";
|
|
8263
|
+
init_issue();
|
|
8164
8264
|
var CONFLICT_DIFF_MAX_BYTES = 4e4;
|
|
8165
8265
|
var resolveFlow = async (ctx) => {
|
|
8166
8266
|
const prNumber = ctx.args.pr;
|
|
@@ -8309,6 +8409,7 @@ function pushEmptyCommit(branch, cwd) {
|
|
|
8309
8409
|
}
|
|
8310
8410
|
|
|
8311
8411
|
// src/deployments.ts
|
|
8412
|
+
init_issue();
|
|
8312
8413
|
function findPreviewDeploymentUrl(prNumber, cwd) {
|
|
8313
8414
|
const sha = getPrHeadSha(prNumber, cwd);
|
|
8314
8415
|
if (!sha) return null;
|
|
@@ -8461,6 +8562,7 @@ var resolveQaUrl = async (ctx) => {
|
|
|
8461
8562
|
|
|
8462
8563
|
// src/scripts/revertFlow.ts
|
|
8463
8564
|
import { execFileSync as execFileSync23 } from "child_process";
|
|
8565
|
+
init_issue();
|
|
8464
8566
|
var SHA_RE = /^[0-9a-f]{4,40}$/i;
|
|
8465
8567
|
var revertFlow = async (ctx) => {
|
|
8466
8568
|
const prNumber = ctx.args.pr;
|
|
@@ -8573,6 +8675,7 @@ function tryPostPr4(prNumber, body, cwd) {
|
|
|
8573
8675
|
}
|
|
8574
8676
|
|
|
8575
8677
|
// src/scripts/reviewFlow.ts
|
|
8678
|
+
init_issue();
|
|
8576
8679
|
var reviewFlow = async (ctx) => {
|
|
8577
8680
|
const prNumber = ctx.args.pr;
|
|
8578
8681
|
const pr = getPr(prNumber, ctx.cwd);
|
|
@@ -8600,6 +8703,7 @@ function tryPostPr5(prNumber, body, cwd) {
|
|
|
8600
8703
|
}
|
|
8601
8704
|
|
|
8602
8705
|
// src/scripts/runFlow.ts
|
|
8706
|
+
init_issue();
|
|
8603
8707
|
var runFlow = async (ctx) => {
|
|
8604
8708
|
const issueNumber = ctx.args.issue;
|
|
8605
8709
|
const issue = getIssue(issueNumber, ctx.cwd);
|
|
@@ -8886,6 +8990,7 @@ var stageMergeConflicts = async (ctx) => {
|
|
|
8886
8990
|
};
|
|
8887
8991
|
|
|
8888
8992
|
// src/scripts/startFlow.ts
|
|
8993
|
+
init_issue();
|
|
8889
8994
|
import { execFileSync as execFileSync25 } from "child_process";
|
|
8890
8995
|
var API_TIMEOUT_MS9 = 3e4;
|
|
8891
8996
|
var startFlow = async (ctx, profile, _agentResult, args) => {
|
|
@@ -8933,8 +9038,167 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
|
|
|
8933
9038
|
}
|
|
8934
9039
|
}
|
|
8935
9040
|
|
|
9041
|
+
// src/scripts/startLocalServer.ts
|
|
9042
|
+
import { spawn as spawn3 } from "child_process";
|
|
9043
|
+
import { existsSync as existsSync26, readFileSync as readFileSync24 } from "fs";
|
|
9044
|
+
import { join as join27 } from "path";
|
|
9045
|
+
var READY_TIMEOUT_MS = 12e4;
|
|
9046
|
+
var READY_POLL_MS = 1e3;
|
|
9047
|
+
var INSTALL_TIMEOUT_MS = 3e5;
|
|
9048
|
+
var FETCH_TIMEOUT_MS = 2e3;
|
|
9049
|
+
var DEFAULT_PORT = 3e3;
|
|
9050
|
+
var startLocalServer = async (ctx) => {
|
|
9051
|
+
if (urlAlreadyResolved(ctx)) {
|
|
9052
|
+
process.stderr.write("[kody startLocalServer] preview URL already set \u2014 skipping\n");
|
|
9053
|
+
return;
|
|
9054
|
+
}
|
|
9055
|
+
const pkg = readPackageJson(ctx.cwd);
|
|
9056
|
+
if (!pkg) return;
|
|
9057
|
+
const scriptName = pickScript(pkg.scripts);
|
|
9058
|
+
if (!scriptName) {
|
|
9059
|
+
process.stderr.write("[kody startLocalServer] no dev/start script in package.json \u2014 skipping\n");
|
|
9060
|
+
return;
|
|
9061
|
+
}
|
|
9062
|
+
const pm = detectPackageManager2(ctx.cwd);
|
|
9063
|
+
const port = pickPort();
|
|
9064
|
+
await ensureDependenciesInstalled(pm, ctx.cwd);
|
|
9065
|
+
const url = `http://localhost:${port}`;
|
|
9066
|
+
process.stderr.write(`[kody startLocalServer] spawning '${pm} run ${scriptName}' on ${url}
|
|
9067
|
+
`);
|
|
9068
|
+
const child = spawnServer(pm, scriptName, port, ctx.cwd);
|
|
9069
|
+
registerProcessCleanup(child);
|
|
9070
|
+
const ready = await waitForReady(url, READY_TIMEOUT_MS);
|
|
9071
|
+
if (!ready) {
|
|
9072
|
+
safeKill(child);
|
|
9073
|
+
throw new Error(
|
|
9074
|
+
`startLocalServer: dev server at ${url} did not become reachable within ${READY_TIMEOUT_MS}ms`
|
|
9075
|
+
);
|
|
9076
|
+
}
|
|
9077
|
+
process.env.PREVIEW_URL = url;
|
|
9078
|
+
ctx.data.qaServerPid = child.pid;
|
|
9079
|
+
ctx.data.qaServerScript = scriptName;
|
|
9080
|
+
process.stderr.write(`[kody startLocalServer] ready: ${url}
|
|
9081
|
+
`);
|
|
9082
|
+
};
|
|
9083
|
+
function urlAlreadyResolved(ctx) {
|
|
9084
|
+
const fromFlag = typeof ctx.args.previewUrl === "string" ? ctx.args.previewUrl.trim() : "";
|
|
9085
|
+
if (fromFlag.length > 0) return true;
|
|
9086
|
+
const fromEnv = (process.env.PREVIEW_URL ?? "").trim();
|
|
9087
|
+
return fromEnv.length > 0;
|
|
9088
|
+
}
|
|
9089
|
+
function readPackageJson(cwd) {
|
|
9090
|
+
const path32 = join27(cwd, "package.json");
|
|
9091
|
+
if (!existsSync26(path32)) {
|
|
9092
|
+
process.stderr.write("[kody startLocalServer] no package.json \u2014 skipping\n");
|
|
9093
|
+
return null;
|
|
9094
|
+
}
|
|
9095
|
+
try {
|
|
9096
|
+
const raw = JSON.parse(readFileSync24(path32, "utf8"));
|
|
9097
|
+
return { scripts: raw.scripts ?? {} };
|
|
9098
|
+
} catch (err) {
|
|
9099
|
+
process.stderr.write(
|
|
9100
|
+
`[kody startLocalServer] could not parse package.json: ${err.message}
|
|
9101
|
+
`
|
|
9102
|
+
);
|
|
9103
|
+
return null;
|
|
9104
|
+
}
|
|
9105
|
+
}
|
|
9106
|
+
function pickScript(scripts) {
|
|
9107
|
+
if (typeof scripts.dev === "string" && scripts.dev.length > 0) return "dev";
|
|
9108
|
+
if (typeof scripts.start === "string" && scripts.start.length > 0) return "start";
|
|
9109
|
+
return null;
|
|
9110
|
+
}
|
|
9111
|
+
function detectPackageManager2(cwd) {
|
|
9112
|
+
if (existsSync26(join27(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
9113
|
+
if (existsSync26(join27(cwd, "yarn.lock"))) return "yarn";
|
|
9114
|
+
return "npm";
|
|
9115
|
+
}
|
|
9116
|
+
function pickPort() {
|
|
9117
|
+
const fromEnv = Number((process.env.PREVIEW_PORT ?? "").trim());
|
|
9118
|
+
if (Number.isInteger(fromEnv) && fromEnv > 0) return fromEnv;
|
|
9119
|
+
return DEFAULT_PORT;
|
|
9120
|
+
}
|
|
9121
|
+
async function ensureDependenciesInstalled(pm, cwd) {
|
|
9122
|
+
if (existsSync26(join27(cwd, "node_modules"))) return;
|
|
9123
|
+
process.stderr.write(`[kody startLocalServer] installing dependencies via ${pm}
|
|
9124
|
+
`);
|
|
9125
|
+
await runOnce(pm, installArgs(pm), cwd, INSTALL_TIMEOUT_MS);
|
|
9126
|
+
}
|
|
9127
|
+
function installArgs(pm) {
|
|
9128
|
+
if (pm === "pnpm") return ["install", "--frozen-lockfile"];
|
|
9129
|
+
if (pm === "yarn") return ["install", "--frozen-lockfile"];
|
|
9130
|
+
return ["ci"];
|
|
9131
|
+
}
|
|
9132
|
+
function spawnServer(pm, scriptName, port, cwd) {
|
|
9133
|
+
const child = spawn3(pm, ["run", scriptName], {
|
|
9134
|
+
cwd,
|
|
9135
|
+
env: { ...process.env, PORT: String(port), NODE_ENV: "development" },
|
|
9136
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
9137
|
+
detached: false
|
|
9138
|
+
});
|
|
9139
|
+
child.stdout?.on("data", (b) => process.stderr.write(`[qa-server] ${b.toString("utf8")}`));
|
|
9140
|
+
child.stderr?.on("data", (b) => process.stderr.write(`[qa-server] ${b.toString("utf8")}`));
|
|
9141
|
+
return child;
|
|
9142
|
+
}
|
|
9143
|
+
function registerProcessCleanup(child) {
|
|
9144
|
+
const cleanup = () => safeKill(child);
|
|
9145
|
+
process.on("exit", cleanup);
|
|
9146
|
+
process.on("SIGINT", cleanup);
|
|
9147
|
+
process.on("SIGTERM", cleanup);
|
|
9148
|
+
}
|
|
9149
|
+
function safeKill(child) {
|
|
9150
|
+
try {
|
|
9151
|
+
child.kill("SIGTERM");
|
|
9152
|
+
} catch {
|
|
9153
|
+
}
|
|
9154
|
+
}
|
|
9155
|
+
async function runOnce(cmd, args, cwd, timeoutMs) {
|
|
9156
|
+
await new Promise((resolve4, reject) => {
|
|
9157
|
+
const c = spawn3(cmd, args, { cwd, stdio: "inherit" });
|
|
9158
|
+
const timer = setTimeout(() => {
|
|
9159
|
+
try {
|
|
9160
|
+
c.kill("SIGKILL");
|
|
9161
|
+
} catch {
|
|
9162
|
+
}
|
|
9163
|
+
reject(new Error(`${cmd} ${args.join(" ")} timed out after ${timeoutMs}ms`));
|
|
9164
|
+
}, timeoutMs);
|
|
9165
|
+
c.on("exit", (code) => {
|
|
9166
|
+
clearTimeout(timer);
|
|
9167
|
+
if (code === 0) {
|
|
9168
|
+
resolve4();
|
|
9169
|
+
return;
|
|
9170
|
+
}
|
|
9171
|
+
reject(new Error(`${cmd} ${args.join(" ")} exited with code ${code}`));
|
|
9172
|
+
});
|
|
9173
|
+
c.on("error", (err) => {
|
|
9174
|
+
clearTimeout(timer);
|
|
9175
|
+
reject(err);
|
|
9176
|
+
});
|
|
9177
|
+
});
|
|
9178
|
+
}
|
|
9179
|
+
async function waitForReady(url, timeoutMs) {
|
|
9180
|
+
const deadline = Date.now() + timeoutMs;
|
|
9181
|
+
while (Date.now() < deadline) {
|
|
9182
|
+
if (await probe(url)) return true;
|
|
9183
|
+
await sleep2(READY_POLL_MS);
|
|
9184
|
+
}
|
|
9185
|
+
return false;
|
|
9186
|
+
}
|
|
9187
|
+
async function probe(url) {
|
|
9188
|
+
try {
|
|
9189
|
+
const res = await fetch(url, { method: "GET", signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
|
|
9190
|
+
return res.status < 500;
|
|
9191
|
+
} catch {
|
|
9192
|
+
return false;
|
|
9193
|
+
}
|
|
9194
|
+
}
|
|
9195
|
+
function sleep2(ms) {
|
|
9196
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
9197
|
+
}
|
|
9198
|
+
|
|
8936
9199
|
// src/scripts/syncFlow.ts
|
|
8937
9200
|
import { execFileSync as execFileSync26 } from "child_process";
|
|
9201
|
+
init_issue();
|
|
8938
9202
|
var syncFlow = async (ctx, _profile, args) => {
|
|
8939
9203
|
const announceOnSuccess = Boolean(args?.announceOnSuccess);
|
|
8940
9204
|
const prNumber = ctx.args.pr;
|
|
@@ -9062,7 +9326,7 @@ var verify = async (ctx) => {
|
|
|
9062
9326
|
};
|
|
9063
9327
|
|
|
9064
9328
|
// src/scripts/verifyReproFails.ts
|
|
9065
|
-
import { spawn as
|
|
9329
|
+
import { spawn as spawn4 } from "child_process";
|
|
9066
9330
|
var TEST_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
9067
9331
|
var TAIL_CHARS2 = 8e3;
|
|
9068
9332
|
var ANSI_RE2 = /\x1B\[[0-?]*[ -/]*[@-~]/g;
|
|
@@ -9131,7 +9395,7 @@ function stripAnsi2(s) {
|
|
|
9131
9395
|
}
|
|
9132
9396
|
function runCommand2(command, cwd) {
|
|
9133
9397
|
return new Promise((resolve4) => {
|
|
9134
|
-
const child =
|
|
9398
|
+
const child = spawn4(command, {
|
|
9135
9399
|
cwd,
|
|
9136
9400
|
shell: true,
|
|
9137
9401
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" },
|
|
@@ -9178,6 +9442,7 @@ function downgrade2(ctx, reason) {
|
|
|
9178
9442
|
}
|
|
9179
9443
|
|
|
9180
9444
|
// src/scripts/waitForCi.ts
|
|
9445
|
+
init_issue();
|
|
9181
9446
|
import { execFileSync as execFileSync27 } from "child_process";
|
|
9182
9447
|
var API_TIMEOUT_MS10 = 3e4;
|
|
9183
9448
|
var waitForCi = async (ctx, _profile, _agentResult, args) => {
|
|
@@ -9193,17 +9458,17 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
|
|
|
9193
9458
|
return;
|
|
9194
9459
|
}
|
|
9195
9460
|
const fixCiAttempts = state?.core.attempts?.["fix-ci"] ?? 0;
|
|
9196
|
-
await
|
|
9461
|
+
await sleep3(initialWaitSeconds * 1e3);
|
|
9197
9462
|
const deadline = Date.now() + timeoutMinutes * 6e4;
|
|
9198
9463
|
let lastSummary = "";
|
|
9199
9464
|
while (Date.now() < deadline) {
|
|
9200
9465
|
const rows = fetchChecks(prNumber, ctx.cwd);
|
|
9201
9466
|
if (rows === null) {
|
|
9202
|
-
await
|
|
9467
|
+
await sleep3(pollSeconds * 1e3);
|
|
9203
9468
|
continue;
|
|
9204
9469
|
}
|
|
9205
9470
|
if (rows.length === 0) {
|
|
9206
|
-
await
|
|
9471
|
+
await sleep3(pollSeconds * 1e3);
|
|
9207
9472
|
continue;
|
|
9208
9473
|
}
|
|
9209
9474
|
const summary = summarize(rows);
|
|
@@ -9246,7 +9511,7 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
|
|
|
9246
9511
|
tryPostPr7(prNumber, `\u2705 kody waitForCi: all ${rows.length} checks green on PR #${prNumber}`, ctx.cwd);
|
|
9247
9512
|
return;
|
|
9248
9513
|
}
|
|
9249
|
-
await
|
|
9514
|
+
await sleep3(pollSeconds * 1e3);
|
|
9250
9515
|
}
|
|
9251
9516
|
ctx.data.action = mkAction("CI_TIMEOUT", {
|
|
9252
9517
|
reason: `CI did not complete within ${timeoutMinutes} minutes`,
|
|
@@ -9298,12 +9563,12 @@ function tryPostPr7(prNumber, body, cwd) {
|
|
|
9298
9563
|
} catch {
|
|
9299
9564
|
}
|
|
9300
9565
|
}
|
|
9301
|
-
function
|
|
9566
|
+
function sleep3(ms) {
|
|
9302
9567
|
return new Promise((res) => setTimeout(res, ms));
|
|
9303
9568
|
}
|
|
9304
9569
|
|
|
9305
9570
|
// src/scripts/warmupMcp.ts
|
|
9306
|
-
import { spawn as
|
|
9571
|
+
import { spawn as spawn5 } from "child_process";
|
|
9307
9572
|
var PER_SERVER_TIMEOUT_MS = 6e4;
|
|
9308
9573
|
var PER_REQUEST_TIMEOUT_MS = 2e4;
|
|
9309
9574
|
var warmupMcp = async (_ctx, profile) => {
|
|
@@ -9325,7 +9590,7 @@ var warmupMcp = async (_ctx, profile) => {
|
|
|
9325
9590
|
}
|
|
9326
9591
|
};
|
|
9327
9592
|
async function warmupOne(command, args, env) {
|
|
9328
|
-
const child =
|
|
9593
|
+
const child = spawn5(command, args, {
|
|
9329
9594
|
stdio: ["pipe", "pipe", "pipe"],
|
|
9330
9595
|
env: env ? { ...process.env, ...env } : process.env
|
|
9331
9596
|
});
|
|
@@ -9565,6 +9830,7 @@ var preflightScripts = {
|
|
|
9565
9830
|
buildSyntheticPlugin,
|
|
9566
9831
|
resolveArtifacts,
|
|
9567
9832
|
discoverQaContext,
|
|
9833
|
+
startLocalServer,
|
|
9568
9834
|
resolvePreviewUrl,
|
|
9569
9835
|
resolveQaUrl,
|
|
9570
9836
|
composePrompt,
|
|
@@ -10087,7 +10353,7 @@ async function runShellEntry(entry, ctx, profile) {
|
|
|
10087
10353
|
env[`KODY_CFG_${k}`] = v;
|
|
10088
10354
|
}
|
|
10089
10355
|
const timeoutMs = resolveShellTimeoutMs(entry);
|
|
10090
|
-
const child =
|
|
10356
|
+
const child = spawn6("bash", [shellPath, ...positional], {
|
|
10091
10357
|
cwd: ctx.cwd,
|
|
10092
10358
|
env,
|
|
10093
10359
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -10190,6 +10456,32 @@ async function runContainerLoop(profile, ctx, input) {
|
|
|
10190
10456
|
const runChild = input.__runChild ?? ((name, opts) => runExecutable(name, opts));
|
|
10191
10457
|
const reader = input.__readTaskState ?? readTaskState;
|
|
10192
10458
|
const issueNumber = ctx.args.issue;
|
|
10459
|
+
let preloadedSnapshot;
|
|
10460
|
+
if (profile.preloadContext) {
|
|
10461
|
+
try {
|
|
10462
|
+
const { loadConventions: loadConventions2 } = await Promise.resolve().then(() => (init_loadConventions(), loadConventions_exports));
|
|
10463
|
+
const { loadPriorArt: loadPriorArt2 } = await Promise.resolve().then(() => (init_loadPriorArt(), loadPriorArt_exports));
|
|
10464
|
+
const { loadMemoryContext: loadMemoryContext2 } = await Promise.resolve().then(() => (init_loadMemoryContext(), loadMemoryContext_exports));
|
|
10465
|
+
const { loadCoverageRules: loadCoverageRules2 } = await Promise.resolve().then(() => (init_loadCoverageRules(), loadCoverageRules_exports));
|
|
10466
|
+
await loadConventions2(ctx, profile);
|
|
10467
|
+
await loadPriorArt2(ctx, profile);
|
|
10468
|
+
await loadMemoryContext2(ctx, profile);
|
|
10469
|
+
await loadCoverageRules2(ctx, profile);
|
|
10470
|
+
preloadedSnapshot = {};
|
|
10471
|
+
for (const k of ["issue", "conventions", "priorArt", "memoryContext", "coverageRules", "taskContext"]) {
|
|
10472
|
+
if (ctx.data[k] !== void 0) preloadedSnapshot[k] = ctx.data[k];
|
|
10473
|
+
}
|
|
10474
|
+
process.stderr.write(
|
|
10475
|
+
`[kody container] preloadContext: snapshot keys=${Object.keys(preloadedSnapshot).join(",")}
|
|
10476
|
+
`
|
|
10477
|
+
);
|
|
10478
|
+
} catch (err) {
|
|
10479
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10480
|
+
process.stderr.write(`[kody container] preloadContext failed (falling back to per-child loads): ${msg}
|
|
10481
|
+
`);
|
|
10482
|
+
preloadedSnapshot = void 0;
|
|
10483
|
+
}
|
|
10484
|
+
}
|
|
10193
10485
|
let currentIdx = 0;
|
|
10194
10486
|
let iteration = 0;
|
|
10195
10487
|
let knownPrUrl;
|
|
@@ -10271,7 +10563,10 @@ async function runContainerLoop(profile, ctx, input) {
|
|
|
10271
10563
|
config: input.config,
|
|
10272
10564
|
skipConfig: input.skipConfig,
|
|
10273
10565
|
verbose: input.verbose,
|
|
10274
|
-
quiet: input.quiet
|
|
10566
|
+
quiet: input.quiet,
|
|
10567
|
+
// Phase 5 in-process handoff — undefined when preloadContext
|
|
10568
|
+
// is off, so children fall back to their own loaders.
|
|
10569
|
+
preloadedData: preloadedSnapshot
|
|
10275
10570
|
});
|
|
10276
10571
|
emitEvent(input.cwd, {
|
|
10277
10572
|
executable: profile.name,
|
|
@@ -10437,6 +10732,7 @@ function flattenConfig(obj, prefix = "") {
|
|
|
10437
10732
|
}
|
|
10438
10733
|
|
|
10439
10734
|
// src/kody-cli.ts
|
|
10735
|
+
init_issue();
|
|
10440
10736
|
var CI_HELP = `kody ci \u2014 minimal-YAML autonomous engineer (CI preflight + run)
|
|
10441
10737
|
|
|
10442
10738
|
Usage:
|
|
@@ -10520,7 +10816,7 @@ function resolveAuthToken(env = process.env) {
|
|
|
10520
10816
|
if (token && !env.GH_TOKEN) env.GH_TOKEN = token;
|
|
10521
10817
|
return token;
|
|
10522
10818
|
}
|
|
10523
|
-
function
|
|
10819
|
+
function detectPackageManager3(cwd) {
|
|
10524
10820
|
if (fs32.existsSync(path30.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
10525
10821
|
if (fs32.existsSync(path30.join(cwd, "yarn.lock"))) return "yarn";
|
|
10526
10822
|
if (fs32.existsSync(path30.join(cwd, "bun.lockb"))) return "bun";
|
|
@@ -10722,7 +11018,7 @@ ${CI_HELP}`);
|
|
|
10722
11018
|
`);
|
|
10723
11019
|
resolveAuthToken();
|
|
10724
11020
|
reactToTriggerComment(cwd);
|
|
10725
|
-
const pm = args.packageManager ??
|
|
11021
|
+
const pm = args.packageManager ?? detectPackageManager3(cwd);
|
|
10726
11022
|
process.stdout.write(`\u2192 kody: package manager = ${pm}
|
|
10727
11023
|
`);
|
|
10728
11024
|
if (!args.skipInstall) {
|
|
@@ -10794,7 +11090,7 @@ async function runScheduledFanOut(cwd, args, opts) {
|
|
|
10794
11090
|
if (n > 0) process.stdout.write(`\u2192 kody: unpacked ${n} secret(s) from ALL_SECRETS
|
|
10795
11091
|
`);
|
|
10796
11092
|
resolveAuthToken();
|
|
10797
|
-
const pm = args.packageManager ??
|
|
11093
|
+
const pm = args.packageManager ?? detectPackageManager3(cwd);
|
|
10798
11094
|
process.stdout.write(`\u2192 kody: package manager = ${pm}
|
|
10799
11095
|
`);
|
|
10800
11096
|
if (!args.skipInstall) {
|