@kynver-app/runtime 0.1.10 → 0.1.11

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/index.js CHANGED
@@ -594,6 +594,92 @@ function ensureGitRepo(repo) {
594
594
  function gitStatusShort(worktreePath) {
595
595
  return git(worktreePath, ["status", "--short"], { allowFailure: true }).split("\n").map((line) => line.trim()).filter(Boolean);
596
596
  }
597
+ function gitCapture(cwd, args) {
598
+ try {
599
+ const res = spawnSync("git", args, { cwd, encoding: "utf8" });
600
+ return {
601
+ status: res.status,
602
+ stdout: res.stdout || "",
603
+ stderr: res.stderr || "",
604
+ error: res.error ? res.error.message : null
605
+ };
606
+ } catch (error) {
607
+ return {
608
+ status: null,
609
+ stdout: "",
610
+ stderr: "",
611
+ error: error.message
612
+ };
613
+ }
614
+ }
615
+ function gitIsAncestor(cwd, ancestor, descendant) {
616
+ const res = gitCapture(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]);
617
+ if (res.status === 0) return { isAncestor: true, error: null };
618
+ if (res.status === 1) return { isAncestor: false, error: null };
619
+ return { isAncestor: null, error: res.error || res.stderr || res.stdout || `git exited ${res.status}` };
620
+ }
621
+ function computeGitAncestry(worktreePath, base = "origin/main") {
622
+ if (!worktreePath) {
623
+ return unknownAncestry(base, "missing worktree path");
624
+ }
625
+ const head = gitCapture(worktreePath, ["rev-parse", "HEAD"]);
626
+ if (head.status !== 0) return unknownAncestry(base, head.error || head.stderr || head.stdout || "failed to resolve HEAD");
627
+ const baseHead = gitCapture(worktreePath, ["rev-parse", base]);
628
+ if (baseHead.status !== 0) {
629
+ return unknownAncestry(base, baseHead.error || baseHead.stderr || baseHead.stdout || `failed to resolve ${base}`, head.stdout.trim());
630
+ }
631
+ const headSha = head.stdout.trim();
632
+ const baseSha = baseHead.stdout.trim();
633
+ if (headSha === baseSha) {
634
+ return {
635
+ checked: true,
636
+ base,
637
+ head: headSha,
638
+ baseHead: baseSha,
639
+ baseIsAncestorOfHead: true,
640
+ headIsAncestorOfBase: true,
641
+ relation: "synced"
642
+ };
643
+ }
644
+ const baseIsAncestorOfHead = gitIsAncestor(worktreePath, baseSha, headSha);
645
+ const headIsAncestorOfBase = gitIsAncestor(worktreePath, headSha, baseSha);
646
+ const error = baseIsAncestorOfHead.error || headIsAncestorOfBase.error || void 0;
647
+ if (baseIsAncestorOfHead.isAncestor == null || headIsAncestorOfBase.isAncestor == null) {
648
+ return {
649
+ checked: false,
650
+ base,
651
+ head: headSha,
652
+ baseHead: baseSha,
653
+ baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
654
+ headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
655
+ relation: "unknown",
656
+ ...error ? { error } : {}
657
+ };
658
+ }
659
+ const relation = baseIsAncestorOfHead.isAncestor ? "ahead" : headIsAncestorOfBase.isAncestor ? "merged" : "diverged";
660
+ return {
661
+ checked: true,
662
+ base,
663
+ head: headSha,
664
+ baseHead: baseSha,
665
+ baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
666
+ headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
667
+ relation,
668
+ ...error ? { error } : {}
669
+ };
670
+ }
671
+ function unknownAncestry(base, error, head = null) {
672
+ return {
673
+ checked: false,
674
+ base,
675
+ head,
676
+ baseHead: null,
677
+ baseIsAncestorOfHead: null,
678
+ headIsAncestorOfBase: null,
679
+ relation: "unknown",
680
+ error
681
+ };
682
+ }
597
683
  function scrubClaudeEnv(env) {
598
684
  const next = { ...env };
599
685
  delete next.ANTHROPIC_API_KEY;
@@ -620,7 +706,7 @@ function computeAttention(input) {
620
706
  }
621
707
  return { state: "ok", reason: "recent activity" };
622
708
  }
623
- function computeWorkerStatus(worker) {
709
+ function computeWorkerStatus(worker, options = {}) {
624
710
  const parsed = parseClaudeStream(worker.stdoutPath);
625
711
  const heartbeat = parseHeartbeat(worker.heartbeatPath);
626
712
  const alive = isPidAlive(worker.pid);
@@ -628,6 +714,7 @@ function computeWorkerStatus(worker) {
628
714
  const stderrBytes = fileSize(worker.stderrPath);
629
715
  const heartbeatBytes = fileSize(worker.heartbeatPath);
630
716
  const changedFiles = gitStatusShort(worker.worktreePath);
717
+ const gitAncestry = computeGitAncestry(worker.worktreePath, options.base);
631
718
  const lastActivityAt = latestIso([
632
719
  parsed.lastEventAt,
633
720
  heartbeat.lastHeartbeatAt,
@@ -669,7 +756,8 @@ function computeWorkerStatus(worker) {
669
756
  heartbeatBlocker: heartbeat.heartbeatBlocker,
670
757
  finalResult: parsed.finalResult,
671
758
  error: parsed.error || (!alive && !parsed.finalResult ? tailFile(worker.stderrPath, 10).trim() || void 0 : void 0),
672
- changedFiles
759
+ changedFiles,
760
+ gitAncestry
673
761
  };
674
762
  }
675
763
  function isFinishedWorkerStatus(status) {
@@ -1458,7 +1546,7 @@ function runStatus(args) {
1458
1546
  if (!worker) {
1459
1547
  return { worker: name, status: "missing", attention: "needs_attention", attentionReason: "worker.json not found" };
1460
1548
  }
1461
- const status = computeWorkerStatus(worker);
1549
+ const status = computeWorkerStatus(worker, { base: run.base });
1462
1550
  return {
1463
1551
  worker: status.worker,
1464
1552
  status: status.status,
@@ -1472,7 +1560,9 @@ function runStatus(args) {
1472
1560
  lastHeartbeatSummary: status.lastHeartbeatSummary,
1473
1561
  heartbeatBlocker: status.heartbeatBlocker,
1474
1562
  changedFileCount: status.changedFiles.length,
1475
- branch: status.branch
1563
+ branch: status.branch,
1564
+ ancestry: status.gitAncestry.relation,
1565
+ ancestryChecked: status.gitAncestry.checked
1476
1566
  };
1477
1567
  });
1478
1568
  const board = {