@kody-ade/kody-engine 0.4.64 → 0.4.66

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 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.64",
867
+ version: "0.4.66",
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",
@@ -1754,177 +2303,177 @@ function parseCommentArgs(rest, inputs) {
1754
2303
  const boolHit = inputs.find((s) => s.type === "bool" && s.flag === `--${t}` && args[s.name] === void 0);
1755
2304
  if (boolHit) {
1756
2305
  args[boolHit.name] = true;
1757
- continue;
1758
- }
1759
- unmatched.push(t);
1760
- }
1761
- return { args, leftover: unmatched.join(" ") };
1762
- }
1763
- function findInputByFlag(inputs, key) {
1764
- return inputs.find((s) => s.name === key || s.flag === `--${key}`);
1765
- }
1766
- function coerceBare(spec, value) {
1767
- if (spec.type === "int") {
1768
- const n = parseInt(value, 10);
1769
- return Number.isNaN(n) ? value : n;
1770
- }
1771
- if (spec.type === "bool") {
1772
- const v = value.toLowerCase();
1773
- return v === "true" || v === "1" || v === "yes";
1774
- }
1775
- return value;
1776
- }
1777
-
1778
- // src/issue.ts
1779
- import { execFileSync as execFileSync3 } from "child_process";
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
- );
2306
+ continue;
2307
+ }
2308
+ unmatched.push(t);
1825
2309
  }
2310
+ return { args, leftover: unmatched.join(" ") };
1826
2311
  }
1827
- function truncate2(s, maxBytes) {
1828
- if (s.length <= maxBytes) return s;
1829
- return `${s.slice(0, maxBytes)}\u2026 (+${s.length - maxBytes} chars)`;
2312
+ function findInputByFlag(inputs, key) {
2313
+ return inputs.find((s) => s.name === key || s.flag === `--${key}`);
1830
2314
  }
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;
2315
+ function coerceBare(spec, value) {
2316
+ if (spec.type === "int") {
2317
+ const n = parseInt(value, 10);
2318
+ return Number.isNaN(n) ? value : n;
2319
+ }
2320
+ if (spec.type === "bool") {
2321
+ const v = value.toLowerCase();
2322
+ return v === "true" || v === "1" || v === "yes";
2323
+ }
2324
+ return value;
1836
2325
  }
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`);
2326
+
2327
+ // src/kody-cli.ts
2328
+ init_issue();
2329
+
2330
+ // src/executor.ts
2331
+ import { execFileSync as execFileSync29, spawn as spawn5 } from "child_process";
2332
+ import * as fs31 from "fs";
2333
+ import * as path29 from "path";
2334
+ init_events();
2335
+
2336
+ // src/lifecycleLabels.ts
2337
+ init_issue();
2338
+
2339
+ // src/profile.ts
2340
+ import * as fs9 from "fs";
2341
+ import * as path8 from "path";
2342
+
2343
+ // src/profile-error.ts
2344
+ var ProfileError = class extends Error {
2345
+ constructor(profilePath, message) {
2346
+ super(`Invalid profile at ${profilePath}:
2347
+ ${message}`);
2348
+ this.profilePath = profilePath;
2349
+ this.name = "ProfileError";
1844
2350
  }
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
- };
2351
+ profilePath;
2352
+ };
2353
+
2354
+ // src/lifecycles/prBranch.ts
2355
+ var VALID_CONTEXTS = /* @__PURE__ */ new Set(["task", "ci-fix", "minimal"]);
2356
+ var CONTEXT_BUNDLES = {
2357
+ task: ["loadTaskState", "loadConventions", "loadPriorArt", "loadMemoryContext", "loadCoverageRules"],
2358
+ "ci-fix": ["loadTaskState", "loadConventions", "loadCoverageRules"],
2359
+ minimal: []
2360
+ };
2361
+ function prBranchLifecycle(profile, profilePath) {
2362
+ const cfg = validateConfig(profile.lifecycleConfig, profilePath);
2363
+ const before = [];
2364
+ if (cfg.sync) before.push({ script: "syncFlow" });
2365
+ before.push({
2366
+ script: "setLifecycleLabel",
2367
+ with: {
2368
+ label: cfg.label.name,
2369
+ color: cfg.label.color,
2370
+ description: cfg.label.description
2371
+ }
2372
+ });
2373
+ const contextBundle = buildContextBundle(cfg.context, cfg.contextExtras);
2374
+ const afterPreflight = cfg.context === "minimal" && cfg.contextExtras.length === 0 ? [{ script: "composePrompt" }] : [...contextBundle, { script: "composePrompt" }];
2375
+ profile.scripts.preflight = [...before, ...profile.scripts.preflight, ...afterPreflight];
2376
+ const beforePostflight = [{ script: "parseAgentResult" }];
2377
+ const verifyChain = cfg.verify ? [{ script: "verify" }, { script: "checkCoverageWithRetry" }, { script: "abortUnfinishedGitOps" }] : [];
2378
+ const tail = [
2379
+ ...verifyChain,
2380
+ { script: "commitAndPush" },
2381
+ { script: "ensurePr" },
2382
+ { script: "postIssueComment" },
2383
+ { script: "writeRunSummary" },
2384
+ { script: "saveTaskState" }
2385
+ ];
2386
+ if (cfg.mirrorState) tail.push({ script: "mirrorStateToPr" });
2387
+ if (cfg.advance) tail.push({ script: "advanceFlow" });
2388
+ profile.scripts.postflight = [...beforePostflight, ...profile.scripts.postflight, ...tail];
1853
2389
  }
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 "";
2390
+ function buildContextBundle(context, extras) {
2391
+ const base = CONTEXT_BUNDLES[context] ?? [];
2392
+ if (base.length === 0 && extras.length === 0) return [];
2393
+ const out = [];
2394
+ let extrasInserted = false;
2395
+ for (const name of base) {
2396
+ out.push({ script: name });
2397
+ if (name === "loadTaskState" && extras.length > 0) {
2398
+ for (const e of extras) out.push({ script: e });
2399
+ extrasInserted = true;
2400
+ }
1863
2401
  }
2402
+ if (!extrasInserted && extras.length > 0) {
2403
+ out.unshift(...extras.map((e) => ({ script: e })));
2404
+ }
2405
+ return out;
1864
2406
  }
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
- })
2407
+ function validateConfig(raw, profilePath) {
2408
+ if (!raw) {
2409
+ throw new ProfileError(profilePath, `lifecycle "pr-branch" requires "lifecycleConfig" with a "label" object`);
2410
+ }
2411
+ const label = raw.label;
2412
+ if (!label || typeof label !== "object" || Array.isArray(label)) {
2413
+ throw new ProfileError(profilePath, `lifecycle "pr-branch": lifecycleConfig.label must be an object`);
2414
+ }
2415
+ const lbl = label;
2416
+ for (const k of ["name", "color", "description"]) {
2417
+ if (typeof lbl[k] !== "string" || lbl[k].length === 0) {
2418
+ throw new ProfileError(profilePath, `lifecycle "pr-branch": lifecycleConfig.label.${k} must be a non-empty string`);
2419
+ }
2420
+ }
2421
+ const context = raw.context === void 0 ? "task" : raw.context;
2422
+ if (typeof context !== "string" || !VALID_CONTEXTS.has(context)) {
2423
+ throw new ProfileError(
2424
+ profilePath,
2425
+ `lifecycle "pr-branch": lifecycleConfig.context must be one of: ${[...VALID_CONTEXTS].join(" | ")}`
1877
2426
  );
1878
- } catch {
1879
- return [];
1880
2427
  }
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 [];
2428
+ let contextExtras = [];
2429
+ if (raw.contextExtras !== void 0) {
2430
+ if (!Array.isArray(raw.contextExtras) || raw.contextExtras.some((s) => typeof s !== "string" || !s)) {
2431
+ throw new ProfileError(
2432
+ profilePath,
2433
+ `lifecycle "pr-branch": lifecycleConfig.contextExtras must be an array of non-empty strings`
2434
+ );
2435
+ }
2436
+ contextExtras = raw.contextExtras;
1894
2437
  }
2438
+ return {
2439
+ label: {
2440
+ name: lbl.name,
2441
+ color: lbl.color,
2442
+ description: lbl.description
2443
+ },
2444
+ context,
2445
+ contextExtras,
2446
+ sync: parseBool(raw, profilePath, "sync", true),
2447
+ verify: parseBool(raw, profilePath, "verify", true),
2448
+ advance: parseBool(raw, profilePath, "advance", true),
2449
+ mirrorState: parseBool(raw, profilePath, "mirrorState", false)
2450
+ };
1895
2451
  }
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;
2452
+ function parseBool(raw, profilePath, key, def) {
2453
+ if (raw[key] === void 0) return def;
2454
+ if (typeof raw[key] !== "boolean") {
2455
+ throw new ProfileError(profilePath, `lifecycle "pr-branch": lifecycleConfig.${key} must be a boolean`);
2456
+ }
2457
+ return raw[key];
1907
2458
  }
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
- `
2459
+
2460
+ // src/lifecycles/index.ts
2461
+ var REGISTRY = {
2462
+ "pr-branch": prBranchLifecycle
2463
+ };
2464
+ function applyLifecycle(profile, profilePath) {
2465
+ if (!profile.lifecycle) return;
2466
+ const expander = REGISTRY[profile.lifecycle];
2467
+ if (!expander) {
2468
+ throw new ProfileError(
2469
+ profilePath,
2470
+ `unknown "lifecycle": "${profile.lifecycle}". Registered: ${Object.keys(REGISTRY).sort().join(", ")}`
1915
2471
  );
1916
2472
  }
2473
+ expander(profile, profilePath);
1917
2474
  }
1918
2475
 
1919
- // src/executor.ts
1920
- import { execFileSync as execFileSync29, spawn as spawn5 } from "child_process";
1921
- import * as fs31 from "fs";
1922
- import * as path29 from "path";
1923
- init_events();
1924
-
1925
2476
  // src/profile.ts
1926
- import * as fs9 from "fs";
1927
- import * as path8 from "path";
1928
2477
  var VALID_INPUT_TYPES = /* @__PURE__ */ new Set(["int", "string", "bool", "enum"]);
1929
2478
  var VALID_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
1930
2479
  var VALID_ROLES = /* @__PURE__ */ new Set(["primitive", "orchestrator", "container", "watch", "utility"]);
@@ -1940,6 +2489,8 @@ var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
1940
2489
  "inputs",
1941
2490
  "claudeCode",
1942
2491
  "cliTools",
2492
+ "lifecycle",
2493
+ "lifecycleConfig",
1943
2494
  "scripts",
1944
2495
  "outputContract",
1945
2496
  "inputArtifacts",
@@ -1949,17 +2500,9 @@ var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
1949
2500
  "output",
1950
2501
  // legacy JSON name for outputArtifacts source
1951
2502
  "children",
1952
- "resetBetweenChildren"
2503
+ "resetBetweenChildren",
2504
+ "preloadContext"
1953
2505
  ]);
1954
- var ProfileError = class extends Error {
1955
- constructor(profilePath, message) {
1956
- super(`Invalid profile at ${profilePath}:
1957
- ${message}`);
1958
- this.profilePath = profilePath;
1959
- this.name = "ProfileError";
1960
- }
1961
- profilePath;
1962
- };
1963
2506
  function loadProfile(profilePath) {
1964
2507
  if (!fs9.existsSync(profilePath)) {
1965
2508
  throw new ProfileError(profilePath, "file not found");
@@ -1997,6 +2540,23 @@ function loadProfile(profilePath) {
1997
2540
  phase = r.phase;
1998
2541
  }
1999
2542
  const children = parseContainerChildren(profilePath, role, r.children);
2543
+ let lifecycle;
2544
+ if (r.lifecycle !== void 0) {
2545
+ if (typeof r.lifecycle !== "string" || r.lifecycle.length === 0) {
2546
+ throw new ProfileError(profilePath, `"lifecycle" must be a non-empty string`);
2547
+ }
2548
+ lifecycle = r.lifecycle;
2549
+ }
2550
+ let lifecycleConfig;
2551
+ if (r.lifecycleConfig !== void 0) {
2552
+ if (!r.lifecycleConfig || typeof r.lifecycleConfig !== "object" || Array.isArray(r.lifecycleConfig)) {
2553
+ throw new ProfileError(profilePath, `"lifecycleConfig" must be an object`);
2554
+ }
2555
+ lifecycleConfig = r.lifecycleConfig;
2556
+ }
2557
+ if (lifecycleConfig && !lifecycle) {
2558
+ throw new ProfileError(profilePath, `"lifecycleConfig" is only meaningful when "lifecycle" is set`);
2559
+ }
2000
2560
  const profile = {
2001
2561
  name: requireString(profilePath, r, "name"),
2002
2562
  describe: typeof r.describe === "string" ? r.describe : "",
@@ -2007,6 +2567,8 @@ function loadProfile(profilePath) {
2007
2567
  inputs: parseInputs(profilePath, r.inputs),
2008
2568
  claudeCode: parseClaudeCode(profilePath, r.claudeCode),
2009
2569
  cliTools: parseCliTools(profilePath, r.cliTools),
2570
+ lifecycle,
2571
+ lifecycleConfig,
2010
2572
  scripts: parseScripts(profilePath, r.scripts),
2011
2573
  outputContract: r.outputContract,
2012
2574
  inputArtifacts: parseInputArtifacts(profilePath, r.input),
@@ -2016,8 +2578,14 @@ function loadProfile(profilePath) {
2016
2578
  // container child sees a clean tracked tree (see executor.ts).
2017
2579
  // Containers opt out by setting `"resetBetweenChildren": false`.
2018
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,
2019
2584
  dir: path8.dirname(profilePath)
2020
2585
  };
2586
+ if (lifecycle) {
2587
+ applyLifecycle(profile, profilePath);
2588
+ }
2021
2589
  return profile;
2022
2590
  }
2023
2591
  function validateScriptReferences(profile, registeredScripts) {
@@ -3077,129 +3645,25 @@ function checkCoverage(addedFiles, requirements) {
3077
3645
  const expected = renderSiblingPath(file, req.requireSibling);
3078
3646
  if (!addedSet.has(expected)) {
3079
3647
  misses.push({ file, expectedTest: expected });
3080
- }
3081
- break;
3082
- }
3083
- }
3084
- return misses;
3085
- }
3086
- function formatMissesForFeedback(misses) {
3087
- if (misses.length === 0) return "";
3088
- const lines = ["The following files were added without a sibling test file:"];
3089
- for (const m of misses) lines.push(`- \`${m.file}\` \u2192 expected \`${m.expectedTest}\``);
3090
- lines.push("");
3091
- lines.push(
3092
- "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."
3093
- );
3094
- return lines.join("\n");
3095
- }
3096
-
3097
- // src/prompt.ts
3098
- import * as fs13 from "fs";
3099
- import * as path12 from "path";
3100
- var CONVENTIONS_PER_FILE_MAX_BYTES = 3e4;
3101
- var CONVENTION_FILES = ["CLAUDE.md", "AGENTS.md"];
3102
- function loadProjectConventions(projectDir) {
3103
- const out = [];
3104
- for (const rel of CONVENTION_FILES) {
3105
- const abs = path12.join(projectDir, rel);
3106
- if (!fs13.existsSync(abs)) continue;
3107
- let content;
3108
- try {
3109
- content = fs13.readFileSync(abs, "utf-8");
3110
- } catch {
3111
- continue;
3112
- }
3113
- const truncated = content.length > CONVENTIONS_PER_FILE_MAX_BYTES;
3114
- if (truncated) content = `${content.slice(0, CONVENTIONS_PER_FILE_MAX_BYTES)}
3115
-
3116
- \u2026 (truncated)`;
3117
- out.push({ path: rel, content, truncated });
3118
- }
3119
- return out;
3120
- }
3121
- function parseAgentResult(finalText) {
3122
- const text = (finalText || "").trim();
3123
- if (!text)
3124
- return {
3125
- done: false,
3126
- commitMessage: "",
3127
- prSummary: "",
3128
- feedbackActions: "",
3129
- planDeviations: "",
3130
- priorArt: "",
3131
- failureReason: "agent produced no final message",
3132
- markerMissing: false
3133
- };
3134
- const MARKDOWN_PREFIX = "[\\s>*_#`~\\-]*";
3135
- const FAILED_RE = new RegExp(`(?:^|\\n)${MARKDOWN_PREFIX}FAILED${MARKDOWN_PREFIX}\\s*:\\s*(.+?)\\s*$`, "is");
3136
- const DONE_RE = new RegExp(`(?:^|\\n)${MARKDOWN_PREFIX}DONE\\b`, "i");
3137
- const failedMatch = text.match(FAILED_RE);
3138
- if (failedMatch) {
3139
- return {
3140
- done: false,
3141
- commitMessage: "",
3142
- prSummary: "",
3143
- feedbackActions: "",
3144
- planDeviations: "",
3145
- priorArt: "",
3146
- failureReason: stripMarkdownEmphasis(failedMatch[1]),
3147
- markerMissing: false
3148
- };
3149
- }
3150
- const hasDoneMarker = DONE_RE.test(text);
3151
- const hasCommitMsg = /^[\s>*_#`~-]*COMMIT_MSG\s*:/im.test(text);
3152
- const hasPrSummary = /^[\s>*_#`~-]*PR_SUMMARY\s*:/im.test(text);
3153
- const markerMissing = !hasDoneMarker && !hasCommitMsg && !hasPrSummary;
3154
- const commitMatch = text.match(/^[\s>*_#`~-]*COMMIT_MSG[\s>*_#`~-]*\s*:\s*(.+)$/im);
3155
- const commitMessage = commitMatch ? stripMarkdownEmphasis(commitMatch[1]) : "";
3156
- const feedbackActions = extractBlock(
3157
- text,
3158
- /(?:^|\n)[ \t]*FEEDBACK_ACTIONS\s*:[ \t]*\n/i,
3159
- /(?:^|\n)[ \t]*(?:PLAN_DEVIATIONS|COMMIT_MSG|PR_SUMMARY|PRIOR_ART)\s*:/i
3160
- );
3161
- let planDeviations = extractBlock(
3162
- text,
3163
- /(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*\n/i,
3164
- /(?:^|\n)[ \t]*(?:COMMIT_MSG|PR_SUMMARY|FEEDBACK_ACTIONS|PRIOR_ART)\s*:/i
3165
- );
3166
- if (!planDeviations) {
3167
- const inline = text.match(/(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
3168
- if (inline) planDeviations = inline[1].trim();
3169
- }
3170
- let priorArt = "";
3171
- const priorArtInline = text.match(/(?:^|\n)[ \t]*PRIOR_ART\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
3172
- if (priorArtInline) priorArt = priorArtInline[1].trim();
3173
- const summaryStart = text.search(/(^|\n)[ \t]*PR_SUMMARY\s*:[ \t]*\n/i);
3174
- let prSummary = "";
3175
- if (summaryStart !== -1) {
3176
- const afterMarker = text.slice(summaryStart).replace(/^[\s\S]*?PR_SUMMARY\s*:[ \t]*\n/i, "");
3177
- prSummary = afterMarker.replace(/\n\s*```\s*$/g, "").replace(/```\s*$/g, "").trim();
3648
+ }
3649
+ break;
3650
+ }
3178
3651
  }
3179
- return {
3180
- done: true,
3181
- commitMessage,
3182
- prSummary,
3183
- feedbackActions,
3184
- planDeviations,
3185
- priorArt,
3186
- failureReason: "",
3187
- markerMissing
3188
- };
3189
- }
3190
- function stripMarkdownEmphasis(s) {
3191
- return s.trim().replace(/^[*_`~]+|[*_`~]+$/g, "").trim();
3652
+ return misses;
3192
3653
  }
3193
- function extractBlock(text, startMarker, endMarker) {
3194
- const startIdx = text.search(startMarker);
3195
- if (startIdx === -1) return "";
3196
- const afterStart = text.slice(startIdx).replace(startMarker, "");
3197
- const endIdx = afterStart.search(endMarker);
3198
- const body = endIdx === -1 ? afterStart : afterStart.slice(0, endIdx);
3199
- return body.replace(/\n\s*```\s*$/g, "").trim();
3654
+ function formatMissesForFeedback(misses) {
3655
+ if (misses.length === 0) return "";
3656
+ const lines = ["The following files were added without a sibling test file:"];
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");
3200
3663
  }
3201
3664
 
3202
3665
  // src/scripts/checkCoverageWithRetry.ts
3666
+ init_prompt();
3203
3667
  var checkCoverageWithRetry = async (ctx) => {
3204
3668
  const reqs = ctx.data.coverageRules ?? [];
3205
3669
  if (reqs.length === 0) {
@@ -3508,11 +3972,13 @@ function formatToolsUsage(profile) {
3508
3972
  }
3509
3973
 
3510
3974
  // src/scripts/createQaGoal.ts
3975
+ init_issue();
3511
3976
  import { execFileSync as execFileSync10 } from "child_process";
3512
3977
  import * as fs16 from "fs";
3513
3978
  import * as path16 from "path";
3514
3979
 
3515
3980
  // src/scripts/postReviewResult.ts
3981
+ init_issue();
3516
3982
  function detectVerdict(body) {
3517
3983
  const m = body.match(/##\s*Verdict\s*:\s*(PASS|CONCERNS|FAIL)\b/i);
3518
3984
  if (!m) return "UNKNOWN";
@@ -4051,6 +4517,9 @@ QA_GOAL_TARGETED=(no manifest issue) (id: ${goalId}, verdict: ${verdict})
4051
4517
  ctx.output.exitCode = verdict === "FAIL" ? 1 : 0;
4052
4518
  };
4053
4519
 
4520
+ // src/goal/operations.ts
4521
+ init_issue();
4522
+
4054
4523
  // src/goal/labels.ts
4055
4524
  function goalLabel(goalId) {
4056
4525
  return `goal:${goalId}`;
@@ -4955,7 +5424,11 @@ function stripQuotes(value) {
4955
5424
  return value;
4956
5425
  }
4957
5426
 
5427
+ // src/scripts/jobState/contentsApiBackend.ts
5428
+ init_issue();
5429
+
4958
5430
  // src/scripts/issueStateComment.ts
5431
+ init_issue();
4959
5432
  function isStateEnvelope(x) {
4960
5433
  if (x === null || typeof x !== "object") return false;
4961
5434
  const o = x;
@@ -5419,6 +5892,7 @@ function listJobSlugs(absDir) {
5419
5892
  }
5420
5893
 
5421
5894
  // src/scripts/dispatchJobTicks.ts
5895
+ init_issue();
5422
5896
  var dispatchJobTicks = async (ctx, _profile, args) => {
5423
5897
  ctx.skipAgent = true;
5424
5898
  const label = String(args?.label ?? "");
@@ -5506,6 +5980,7 @@ var dispatchNextTask = async (ctx) => {
5506
5980
  };
5507
5981
 
5508
5982
  // src/pr.ts
5983
+ init_issue();
5509
5984
  function prMergeStatus(prNumber, cwd) {
5510
5985
  try {
5511
5986
  const out = gh(
@@ -5852,6 +6327,7 @@ var finalizeGoal = async (ctx) => {
5852
6327
  };
5853
6328
 
5854
6329
  // src/scripts/finishFlow.ts
6330
+ init_issue();
5855
6331
  import { execFileSync as execFileSync14 } from "child_process";
5856
6332
  var TERMINAL_PHASE = {
5857
6333
  "review-passed": { phase: "shipped", status: "succeeded" },
@@ -6140,6 +6616,9 @@ function sleepMs(ms) {
6140
6616
  }
6141
6617
  }
6142
6618
 
6619
+ // src/scripts/fixCiFlow.ts
6620
+ init_issue();
6621
+
6143
6622
  // src/workflow.ts
6144
6623
  import { execFileSync as execFileSync17 } from "child_process";
6145
6624
  var GH_TIMEOUT_MS = 3e4;
@@ -6293,6 +6772,7 @@ function tryPostPr(prNumber, body, cwd) {
6293
6772
  }
6294
6773
 
6295
6774
  // src/scripts/fixFlow.ts
6775
+ init_issue();
6296
6776
  var fixFlow = async (ctx) => {
6297
6777
  const prNumber = ctx.args.pr;
6298
6778
  const pr = getPr(prNumber, ctx.cwd);
@@ -6659,22 +7139,9 @@ Nothing to do. All files already present. (Use --force to overwrite.)
6659
7139
  ctx.output.exitCode = 0;
6660
7140
  };
6661
7141
 
6662
- // src/scripts/loadConventions.ts
6663
- var loadConventions = async (ctx) => {
6664
- if (Array.isArray(ctx.data.conventions)) return;
6665
- const conventions = loadProjectConventions(ctx.cwd);
6666
- ctx.data.conventions = conventions;
6667
- if (conventions.length > 0) {
6668
- process.stderr.write(`[kody] loaded conventions: ${conventions.map((c) => c.path).join(", ")}
6669
- `);
6670
- }
6671
- };
6672
-
6673
- // src/scripts/loadCoverageRules.ts
6674
- var loadCoverageRules = async (ctx) => {
6675
- if (Array.isArray(ctx.data.coverageRules)) return;
6676
- ctx.data.coverageRules = ctx.config.testRequirements ?? [];
6677
- };
7142
+ // src/scripts/index.ts
7143
+ init_loadConventions();
7144
+ init_loadCoverageRules();
6678
7145
 
6679
7146
  // src/goal/state.ts
6680
7147
  import * as fs25 from "fs";
@@ -6791,6 +7258,7 @@ var loadGoalState = async (ctx) => {
6791
7258
  };
6792
7259
 
6793
7260
  // src/scripts/loadIssueContext.ts
7261
+ init_issue();
6794
7262
  var DEFAULT_COMMENT_LIMIT = 12;
6795
7263
  var DEFAULT_COMMENT_MAX_BYTES = 16e3;
6796
7264
  var loadIssueContext = async (ctx) => {
@@ -6820,6 +7288,7 @@ var loadIssueContext = async (ctx) => {
6820
7288
  };
6821
7289
 
6822
7290
  // src/scripts/loadIssueStateComment.ts
7291
+ init_issue();
6823
7292
  var loadIssueStateComment = async (ctx, _profile, args) => {
6824
7293
  const marker = String(args?.marker ?? "");
6825
7294
  if (!marker) {
@@ -6890,237 +7359,9 @@ function humanizeSlug(slug) {
6890
7359
  return slug.split(/[-_]+/).filter((s) => s.length > 0).map((s) => s[0].toUpperCase() + s.slice(1)).join(" ");
6891
7360
  }
6892
7361
 
6893
- // src/scripts/loadMemoryContext.ts
6894
- import * as fs27 from "fs";
6895
- import * as path26 from "path";
6896
- var MEMORY_DIR_RELATIVE = ".kody/memory";
6897
- var MAX_PAGES = 8;
6898
- var PER_PAGE_MAX_BYTES = 4e3;
6899
- var TOTAL_MAX_BYTES = 24e3;
6900
- var TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
6901
- var loadMemoryContext = async (ctx) => {
6902
- if (typeof ctx.data.memoryContext === "string") return;
6903
- const memoryAbs = path26.join(ctx.cwd, MEMORY_DIR_RELATIVE);
6904
- if (!fs27.existsSync(memoryAbs)) {
6905
- ctx.data.memoryContext = "";
6906
- return;
6907
- }
6908
- let pages = [];
6909
- try {
6910
- pages = collectPages(memoryAbs);
6911
- } catch {
6912
- ctx.data.memoryContext = "";
6913
- return;
6914
- }
6915
- if (pages.length === 0) {
6916
- ctx.data.memoryContext = "";
6917
- return;
6918
- }
6919
- const queryTerms = extractQueryTerms(ctx);
6920
- const ranked = queryTerms.length > 0 ? scorePages(pages, queryTerms) : sortByRecency(pages);
6921
- const top = ranked.slice(0, MAX_PAGES);
6922
- ctx.data.memoryContext = formatBlock(top);
6923
- };
6924
- function collectPages(memoryAbs) {
6925
- const out = [];
6926
- walkMd(memoryAbs, (file) => {
6927
- let stat;
6928
- try {
6929
- stat = fs27.statSync(file);
6930
- } catch {
6931
- return;
6932
- }
6933
- let raw;
6934
- try {
6935
- raw = fs27.readFileSync(file, "utf-8");
6936
- } catch {
6937
- return;
6938
- }
6939
- const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
6940
- const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path26.basename(file, ".md");
6941
- const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
6942
- out.push({
6943
- relPath: path26.relative(memoryAbs, file),
6944
- title,
6945
- updated,
6946
- content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX : raw,
6947
- mtime: stat.mtimeMs
6948
- });
6949
- });
6950
- return out;
6951
- }
6952
- function extractQueryTerms(ctx) {
6953
- const terms = [];
6954
- const issue = ctx.data.issue;
6955
- const pr = ctx.data.pr;
6956
- if (issue?.title) terms.push(...tokenize(issue.title));
6957
- if (pr?.title) terms.push(...tokenize(pr.title));
6958
- return Array.from(new Set(terms)).slice(0, 20);
6959
- }
6960
- function tokenize(s) {
6961
- return s.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((t) => t.length >= 3);
6962
- }
6963
- function scorePages(pages, terms) {
6964
- return pages.map((p) => {
6965
- const haystack = `${p.title} ${p.content}`.toLowerCase();
6966
- let score = 0;
6967
- for (const t of terms) {
6968
- if (haystack.includes(t)) score++;
6969
- }
6970
- return { p, score };
6971
- }).sort((a, b) => {
6972
- if (b.score !== a.score) return b.score - a.score;
6973
- return b.p.mtime - a.p.mtime;
6974
- }).map((x) => x.p);
6975
- }
6976
- function sortByRecency(pages) {
6977
- return [...pages].sort((a, b) => {
6978
- if (a.updated && b.updated && a.updated !== b.updated) {
6979
- return b.updated.localeCompare(a.updated);
6980
- }
6981
- return b.mtime - a.mtime;
6982
- });
6983
- }
6984
- function formatBlock(pages) {
6985
- if (pages.length === 0) return "";
6986
- const lines = [
6987
- "# Project memory (`.kody/memory/`)",
6988
- "",
6989
- "Pages from prior memorize ticks. Treat as advisory context \u2014 confirm against the codebase before acting.",
6990
- ""
6991
- ];
6992
- let total = 0;
6993
- for (const p of pages) {
6994
- const block = [`## ${p.title} \u2014 \`${p.relPath}\``, "", p.content].join("\n");
6995
- if (total + block.length > TOTAL_MAX_BYTES) {
6996
- lines.push("_\u2026 (further pages truncated to fit budget)_");
6997
- break;
6998
- }
6999
- lines.push(block);
7000
- lines.push("");
7001
- total += block.length;
7002
- }
7003
- return lines.join("\n");
7004
- }
7005
- function walkMd(root, visit) {
7006
- const stack = [root];
7007
- while (stack.length > 0) {
7008
- const dir = stack.pop();
7009
- let names;
7010
- try {
7011
- names = fs27.readdirSync(dir);
7012
- } catch {
7013
- continue;
7014
- }
7015
- for (const name of names) {
7016
- if (name.startsWith(".")) continue;
7017
- const full = path26.join(dir, name);
7018
- let stat;
7019
- try {
7020
- stat = fs27.statSync(full);
7021
- } catch {
7022
- continue;
7023
- }
7024
- if (stat.isDirectory()) {
7025
- stack.push(full);
7026
- continue;
7027
- }
7028
- if (stat.isFile() && full.endsWith(".md")) visit(full);
7029
- }
7030
- }
7031
- }
7032
-
7033
- // src/scripts/loadPriorArt.ts
7034
- var PER_PR_DIFF_MAX_BYTES = 8e3;
7035
- var TOTAL_MAX_BYTES2 = 3e4;
7036
- var TRUNCATED_SUFFIX2 = "\n\n\u2026 (truncated)";
7037
- var loadPriorArt = async (ctx, _profile, args) => {
7038
- if (typeof ctx.data.priorArt === "string") return;
7039
- const artifactName = typeof args?.artifactName === "string" ? args.artifactName : "priorArt";
7040
- const state = ctx.data.taskState;
7041
- const artifact = state?.artifacts?.[artifactName];
7042
- const prNumbers = parsePrNumbers(artifact?.content);
7043
- if (prNumbers.length === 0) {
7044
- ctx.data.priorArt = "";
7045
- return;
7046
- }
7047
- const blocks = [];
7048
- for (const n of prNumbers) {
7049
- const block = fetchPrBlock(n, ctx.cwd);
7050
- if (block) blocks.push(block);
7051
- }
7052
- const joined = blocks.join("\n\n---\n\n");
7053
- ctx.data.priorArt = joined.length > TOTAL_MAX_BYTES2 ? joined.slice(0, TOTAL_MAX_BYTES2) + TRUNCATED_SUFFIX2 : joined;
7054
- };
7055
- function parsePrNumbers(raw) {
7056
- if (!raw) return [];
7057
- const trimmed = raw.trim();
7058
- if (!trimmed) return [];
7059
- try {
7060
- const parsed = JSON.parse(trimmed);
7061
- if (!Array.isArray(parsed)) return [];
7062
- return parsed.filter((n) => typeof n === "number" && Number.isInteger(n) && n > 0);
7063
- } catch {
7064
- return [];
7065
- }
7066
- }
7067
- function fetchPrBlock(prNumber, cwd) {
7068
- try {
7069
- const metaRaw = gh(["pr", "view", String(prNumber), "--json", "title,state,url,mergedAt,closedAt"], { cwd });
7070
- const meta = JSON.parse(metaRaw);
7071
- const diff = truncate3(safeGh(["pr", "diff", String(prNumber)], cwd), PER_PR_DIFF_MAX_BYTES);
7072
- const commentsRaw = safeGh(["pr", "view", String(prNumber), "--json", "comments,reviews"], cwd);
7073
- const commentsBlock = formatReviewComments(commentsRaw);
7074
- const lines = [
7075
- `## Prior art: PR #${prNumber} \u2014 ${meta.title ?? "(no title)"} [${meta.state ?? "unknown"}]`,
7076
- meta.url ? meta.url : "",
7077
- "",
7078
- "### Diff",
7079
- "```diff",
7080
- diff || "(empty)",
7081
- "```"
7082
- ];
7083
- if (commentsBlock) {
7084
- lines.push("");
7085
- lines.push("### Review comments");
7086
- lines.push(commentsBlock);
7087
- }
7088
- return lines.filter((l) => l !== "").join("\n");
7089
- } catch (err) {
7090
- return `## Prior art: PR #${prNumber}
7091
- _Could not fetch \u2014 ${err instanceof Error ? err.message : String(err)}_`;
7092
- }
7093
- }
7094
- function safeGh(args, cwd) {
7095
- try {
7096
- return gh(args, { cwd });
7097
- } catch {
7098
- return "";
7099
- }
7100
- }
7101
- function truncate3(s, max) {
7102
- return s.length <= max ? s : s.slice(0, max) + TRUNCATED_SUFFIX2;
7103
- }
7104
- function formatReviewComments(raw) {
7105
- if (!raw) return "";
7106
- try {
7107
- const parsed = JSON.parse(raw);
7108
- const out = [];
7109
- for (const c of parsed.comments ?? []) {
7110
- if (!c.body) continue;
7111
- out.push(`- **${c.author?.login ?? "unknown"}**: ${c.body.replace(/\n/g, " ").slice(0, 500)}`);
7112
- }
7113
- for (const r of parsed.reviews ?? []) {
7114
- if (!r.body && !r.state) continue;
7115
- const state = r.state ? ` (${r.state})` : "";
7116
- const body = r.body ? `: ${r.body.replace(/\n/g, " ").slice(0, 500)}` : "";
7117
- out.push(`- **${r.author?.login ?? "unknown"}**${state}${body}`);
7118
- }
7119
- return out.join("\n");
7120
- } catch {
7121
- return "";
7122
- }
7123
- }
7362
+ // src/scripts/index.ts
7363
+ init_loadMemoryContext();
7364
+ init_loadPriorArt();
7124
7365
 
7125
7366
  // src/scripts/loadTaskContext.ts
7126
7367
  init_events();
@@ -7291,6 +7532,7 @@ function parsePrNumber3(prUrl) {
7291
7532
  }
7292
7533
 
7293
7534
  // src/scripts/notifyTerminal.ts
7535
+ init_issue();
7294
7536
  var notifyTerminal = async (ctx, _profile, _agentResult, args) => {
7295
7537
  const issueNumber = ctx.args.issue;
7296
7538
  if (!issueNumber || issueNumber <= 0) return;
@@ -7322,6 +7564,7 @@ function composeBody({ label, exit, prUrl, reason, dryRun }) {
7322
7564
  }
7323
7565
 
7324
7566
  // src/scripts/openQaIssue.ts
7567
+ init_issue();
7325
7568
  var QA_LABEL = "kody:qa-report";
7326
7569
  function qaAction2(verdict, payload) {
7327
7570
  const type = verdict === "PASS" ? "QA_PASS" : verdict === "CONCERNS" ? "QA_CONCERNS" : verdict === "FAIL" ? "QA_FAIL" : "QA_COMPLETED";
@@ -7423,6 +7666,7 @@ QA_REPORT_POSTED=${created.url} (verdict: ${verdict})
7423
7666
  };
7424
7667
 
7425
7668
  // src/scripts/parseAgentResult.ts
7669
+ init_prompt();
7426
7670
  var parseAgentResult2 = async (ctx, profile, agentResult) => {
7427
7671
  if (!agentResult) {
7428
7672
  ctx.data.agentDone = false;
@@ -7665,6 +7909,9 @@ var persistFlowState = async (ctx) => {
7665
7909
  }
7666
7910
  };
7667
7911
 
7912
+ // src/scripts/postIssueComment.ts
7913
+ init_issue();
7914
+
7668
7915
  // src/scripts/prOutcome.ts
7669
7916
  function readPrOutcome(data) {
7670
7917
  const raw = data.prResult;
@@ -7800,6 +8047,7 @@ function postWith(type, n, body, cwd) {
7800
8047
  }
7801
8048
 
7802
8049
  // src/scripts/postPlanComment.ts
8050
+ init_issue();
7803
8051
  var postPlanComment = async (ctx) => {
7804
8052
  if (!ctx.data.agentDone) return;
7805
8053
  const targetType = ctx.data.commentTargetType;
@@ -7830,6 +8078,7 @@ Comment \`kody run\` (prefixed with \`@\`) to execute this plan.`;
7830
8078
  }
7831
8079
 
7832
8080
  // src/scripts/postResearchComment.ts
8081
+ init_issue();
7833
8082
  var postResearchComment = async (ctx) => {
7834
8083
  if (!ctx.data.agentDone) return;
7835
8084
  const targetType = ctx.data.commentTargetType;
@@ -8011,6 +8260,7 @@ var resolveArtifacts = async (ctx, profile) => {
8011
8260
 
8012
8261
  // src/scripts/resolveFlow.ts
8013
8262
  import { execFileSync as execFileSync21 } from "child_process";
8263
+ init_issue();
8014
8264
  var CONFLICT_DIFF_MAX_BYTES = 4e4;
8015
8265
  var resolveFlow = async (ctx) => {
8016
8266
  const prNumber = ctx.args.pr;
@@ -8159,6 +8409,7 @@ function pushEmptyCommit(branch, cwd) {
8159
8409
  }
8160
8410
 
8161
8411
  // src/deployments.ts
8412
+ init_issue();
8162
8413
  function findPreviewDeploymentUrl(prNumber, cwd) {
8163
8414
  const sha = getPrHeadSha(prNumber, cwd);
8164
8415
  if (!sha) return null;
@@ -8311,6 +8562,7 @@ var resolveQaUrl = async (ctx) => {
8311
8562
 
8312
8563
  // src/scripts/revertFlow.ts
8313
8564
  import { execFileSync as execFileSync23 } from "child_process";
8565
+ init_issue();
8314
8566
  var SHA_RE = /^[0-9a-f]{4,40}$/i;
8315
8567
  var revertFlow = async (ctx) => {
8316
8568
  const prNumber = ctx.args.pr;
@@ -8423,6 +8675,7 @@ function tryPostPr4(prNumber, body, cwd) {
8423
8675
  }
8424
8676
 
8425
8677
  // src/scripts/reviewFlow.ts
8678
+ init_issue();
8426
8679
  var reviewFlow = async (ctx) => {
8427
8680
  const prNumber = ctx.args.pr;
8428
8681
  const pr = getPr(prNumber, ctx.cwd);
@@ -8450,6 +8703,7 @@ function tryPostPr5(prNumber, body, cwd) {
8450
8703
  }
8451
8704
 
8452
8705
  // src/scripts/runFlow.ts
8706
+ init_issue();
8453
8707
  var runFlow = async (ctx) => {
8454
8708
  const issueNumber = ctx.args.issue;
8455
8709
  const issue = getIssue(issueNumber, ctx.cwd);
@@ -8736,6 +8990,7 @@ var stageMergeConflicts = async (ctx) => {
8736
8990
  };
8737
8991
 
8738
8992
  // src/scripts/startFlow.ts
8993
+ init_issue();
8739
8994
  import { execFileSync as execFileSync25 } from "child_process";
8740
8995
  var API_TIMEOUT_MS9 = 3e4;
8741
8996
  var startFlow = async (ctx, profile, _agentResult, args) => {
@@ -8785,6 +9040,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
8785
9040
 
8786
9041
  // src/scripts/syncFlow.ts
8787
9042
  import { execFileSync as execFileSync26 } from "child_process";
9043
+ init_issue();
8788
9044
  var syncFlow = async (ctx, _profile, args) => {
8789
9045
  const announceOnSuccess = Boolean(args?.announceOnSuccess);
8790
9046
  const prNumber = ctx.args.pr;
@@ -9028,6 +9284,7 @@ function downgrade2(ctx, reason) {
9028
9284
  }
9029
9285
 
9030
9286
  // src/scripts/waitForCi.ts
9287
+ init_issue();
9031
9288
  import { execFileSync as execFileSync27 } from "child_process";
9032
9289
  var API_TIMEOUT_MS10 = 3e4;
9033
9290
  var waitForCi = async (ctx, _profile, _agentResult, args) => {
@@ -10040,6 +10297,32 @@ async function runContainerLoop(profile, ctx, input) {
10040
10297
  const runChild = input.__runChild ?? ((name, opts) => runExecutable(name, opts));
10041
10298
  const reader = input.__readTaskState ?? readTaskState;
10042
10299
  const issueNumber = ctx.args.issue;
10300
+ let preloadedSnapshot;
10301
+ if (profile.preloadContext) {
10302
+ try {
10303
+ const { loadConventions: loadConventions2 } = await Promise.resolve().then(() => (init_loadConventions(), loadConventions_exports));
10304
+ const { loadPriorArt: loadPriorArt2 } = await Promise.resolve().then(() => (init_loadPriorArt(), loadPriorArt_exports));
10305
+ const { loadMemoryContext: loadMemoryContext2 } = await Promise.resolve().then(() => (init_loadMemoryContext(), loadMemoryContext_exports));
10306
+ const { loadCoverageRules: loadCoverageRules2 } = await Promise.resolve().then(() => (init_loadCoverageRules(), loadCoverageRules_exports));
10307
+ await loadConventions2(ctx, profile);
10308
+ await loadPriorArt2(ctx, profile);
10309
+ await loadMemoryContext2(ctx, profile);
10310
+ await loadCoverageRules2(ctx, profile);
10311
+ preloadedSnapshot = {};
10312
+ for (const k of ["issue", "conventions", "priorArt", "memoryContext", "coverageRules", "taskContext"]) {
10313
+ if (ctx.data[k] !== void 0) preloadedSnapshot[k] = ctx.data[k];
10314
+ }
10315
+ process.stderr.write(
10316
+ `[kody container] preloadContext: snapshot keys=${Object.keys(preloadedSnapshot).join(",")}
10317
+ `
10318
+ );
10319
+ } catch (err) {
10320
+ const msg = err instanceof Error ? err.message : String(err);
10321
+ process.stderr.write(`[kody container] preloadContext failed (falling back to per-child loads): ${msg}
10322
+ `);
10323
+ preloadedSnapshot = void 0;
10324
+ }
10325
+ }
10043
10326
  let currentIdx = 0;
10044
10327
  let iteration = 0;
10045
10328
  let knownPrUrl;
@@ -10121,7 +10404,10 @@ async function runContainerLoop(profile, ctx, input) {
10121
10404
  config: input.config,
10122
10405
  skipConfig: input.skipConfig,
10123
10406
  verbose: input.verbose,
10124
- quiet: input.quiet
10407
+ quiet: input.quiet,
10408
+ // Phase 5 in-process handoff — undefined when preloadContext
10409
+ // is off, so children fall back to their own loaders.
10410
+ preloadedData: preloadedSnapshot
10125
10411
  });
10126
10412
  emitEvent(input.cwd, {
10127
10413
  executable: profile.name,
@@ -10287,6 +10573,7 @@ function flattenConfig(obj, prefix = "") {
10287
10573
  }
10288
10574
 
10289
10575
  // src/kody-cli.ts
10576
+ init_issue();
10290
10577
  var CI_HELP = `kody ci \u2014 minimal-YAML autonomous engineer (CI preflight + run)
10291
10578
 
10292
10579
  Usage: