@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 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.65",
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/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
- );
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 spawn5 } from "child_process";
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/loadConventions.ts
6813
- var loadConventions = async (ctx) => {
6814
- if (Array.isArray(ctx.data.conventions)) return;
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/loadPriorArt.ts
7184
- var PER_PR_DIFF_MAX_BYTES = 8e3;
7185
- var TOTAL_MAX_BYTES2 = 3e4;
7186
- var TRUNCATED_SUFFIX2 = "\n\n\u2026 (truncated)";
7187
- var loadPriorArt = async (ctx, _profile, args) => {
7188
- if (typeof ctx.data.priorArt === "string") return;
7189
- const artifactName = typeof args?.artifactName === "string" ? args.artifactName : "priorArt";
7190
- const state = ctx.data.taskState;
7191
- const artifact = state?.artifacts?.[artifactName];
7192
- const prNumbers = parsePrNumbers(artifact?.content);
7193
- if (prNumbers.length === 0) {
7194
- ctx.data.priorArt = "";
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 blocks = [];
7198
- for (const n of prNumbers) {
7199
- const block = fetchPrBlock(n, ctx.cwd);
7200
- if (block) blocks.push(block);
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 joined = blocks.join("\n\n---\n\n");
7203
- ctx.data.priorArt = joined.length > TOTAL_MAX_BYTES2 ? joined.slice(0, TOTAL_MAX_BYTES2) + TRUNCATED_SUFFIX2 : joined;
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
- function parsePrNumbers(raw) {
7206
- if (!raw) return [];
7207
- const trimmed = raw.trim();
7208
- if (!trimmed) return [];
7209
- try {
7210
- const parsed = JSON.parse(trimmed);
7211
- if (!Array.isArray(parsed)) return [];
7212
- return parsed.filter((n) => typeof n === "number" && Number.isInteger(n) && n > 0);
7213
- } catch {
7214
- return [];
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
- function fetchPrBlock(prNumber, cwd) {
7218
- try {
7219
- const metaRaw = gh(["pr", "view", String(prNumber), "--json", "title,state,url,mergedAt,closedAt"], { cwd });
7220
- const meta = JSON.parse(metaRaw);
7221
- const diff = truncate3(safeGh(["pr", "diff", String(prNumber)], cwd), PER_PR_DIFF_MAX_BYTES);
7222
- const commentsRaw = safeGh(["pr", "view", String(prNumber), "--json", "comments,reviews"], cwd);
7223
- const commentsBlock = formatReviewComments(commentsRaw);
7224
- const lines = [
7225
- `## Prior art: PR #${prNumber} \u2014 ${meta.title ?? "(no title)"} [${meta.state ?? "unknown"}]`,
7226
- meta.url ? meta.url : "",
7227
- "",
7228
- "### Diff",
7229
- "```diff",
7230
- diff || "(empty)",
7231
- "```"
7232
- ];
7233
- if (commentsBlock) {
7234
- lines.push("");
7235
- lines.push("### Review comments");
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
- function safeGh(args, cwd) {
7245
- try {
7246
- return gh(args, { cwd });
7247
- } catch {
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 truncate3(s, max) {
7252
- return s.length <= max ? s : s.slice(0, max) + TRUNCATED_SUFFIX2;
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 spawn3 } from "child_process";
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 = spawn3(command, {
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 sleep2(initialWaitSeconds * 1e3);
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 sleep2(pollSeconds * 1e3);
9467
+ await sleep3(pollSeconds * 1e3);
9203
9468
  continue;
9204
9469
  }
9205
9470
  if (rows.length === 0) {
9206
- await sleep2(pollSeconds * 1e3);
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 sleep2(pollSeconds * 1e3);
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 sleep2(ms) {
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 spawn4 } from "child_process";
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 = spawn4(command, args, {
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 = spawn5("bash", [shellPath, ...positional], {
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 detectPackageManager2(cwd) {
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 ?? detectPackageManager2(cwd);
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 ?? detectPackageManager2(cwd);
11093
+ const pm = args.packageManager ?? detectPackageManager3(cwd);
10798
11094
  process.stdout.write(`\u2192 kody: package manager = ${pm}
10799
11095
  `);
10800
11096
  if (!args.skipInstall) {