@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/cli.js CHANGED
@@ -593,6 +593,92 @@ function ensureGitRepo(repo) {
593
593
  function gitStatusShort(worktreePath) {
594
594
  return git(worktreePath, ["status", "--short"], { allowFailure: true }).split("\n").map((line) => line.trim()).filter(Boolean);
595
595
  }
596
+ function gitCapture(cwd, args) {
597
+ try {
598
+ const res = spawnSync("git", args, { cwd, encoding: "utf8" });
599
+ return {
600
+ status: res.status,
601
+ stdout: res.stdout || "",
602
+ stderr: res.stderr || "",
603
+ error: res.error ? res.error.message : null
604
+ };
605
+ } catch (error) {
606
+ return {
607
+ status: null,
608
+ stdout: "",
609
+ stderr: "",
610
+ error: error.message
611
+ };
612
+ }
613
+ }
614
+ function gitIsAncestor(cwd, ancestor, descendant) {
615
+ const res = gitCapture(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]);
616
+ if (res.status === 0) return { isAncestor: true, error: null };
617
+ if (res.status === 1) return { isAncestor: false, error: null };
618
+ return { isAncestor: null, error: res.error || res.stderr || res.stdout || `git exited ${res.status}` };
619
+ }
620
+ function computeGitAncestry(worktreePath, base = "origin/main") {
621
+ if (!worktreePath) {
622
+ return unknownAncestry(base, "missing worktree path");
623
+ }
624
+ const head = gitCapture(worktreePath, ["rev-parse", "HEAD"]);
625
+ if (head.status !== 0) return unknownAncestry(base, head.error || head.stderr || head.stdout || "failed to resolve HEAD");
626
+ const baseHead = gitCapture(worktreePath, ["rev-parse", base]);
627
+ if (baseHead.status !== 0) {
628
+ return unknownAncestry(base, baseHead.error || baseHead.stderr || baseHead.stdout || `failed to resolve ${base}`, head.stdout.trim());
629
+ }
630
+ const headSha = head.stdout.trim();
631
+ const baseSha = baseHead.stdout.trim();
632
+ if (headSha === baseSha) {
633
+ return {
634
+ checked: true,
635
+ base,
636
+ head: headSha,
637
+ baseHead: baseSha,
638
+ baseIsAncestorOfHead: true,
639
+ headIsAncestorOfBase: true,
640
+ relation: "synced"
641
+ };
642
+ }
643
+ const baseIsAncestorOfHead = gitIsAncestor(worktreePath, baseSha, headSha);
644
+ const headIsAncestorOfBase = gitIsAncestor(worktreePath, headSha, baseSha);
645
+ const error = baseIsAncestorOfHead.error || headIsAncestorOfBase.error || void 0;
646
+ if (baseIsAncestorOfHead.isAncestor == null || headIsAncestorOfBase.isAncestor == null) {
647
+ return {
648
+ checked: false,
649
+ base,
650
+ head: headSha,
651
+ baseHead: baseSha,
652
+ baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
653
+ headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
654
+ relation: "unknown",
655
+ ...error ? { error } : {}
656
+ };
657
+ }
658
+ const relation = baseIsAncestorOfHead.isAncestor ? "ahead" : headIsAncestorOfBase.isAncestor ? "merged" : "diverged";
659
+ return {
660
+ checked: true,
661
+ base,
662
+ head: headSha,
663
+ baseHead: baseSha,
664
+ baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
665
+ headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
666
+ relation,
667
+ ...error ? { error } : {}
668
+ };
669
+ }
670
+ function unknownAncestry(base, error, head = null) {
671
+ return {
672
+ checked: false,
673
+ base,
674
+ head,
675
+ baseHead: null,
676
+ baseIsAncestorOfHead: null,
677
+ headIsAncestorOfBase: null,
678
+ relation: "unknown",
679
+ error
680
+ };
681
+ }
596
682
  function scrubClaudeEnv(env) {
597
683
  const next = { ...env };
598
684
  delete next.ANTHROPIC_API_KEY;
@@ -619,7 +705,7 @@ function computeAttention(input) {
619
705
  }
620
706
  return { state: "ok", reason: "recent activity" };
621
707
  }
622
- function computeWorkerStatus(worker) {
708
+ function computeWorkerStatus(worker, options = {}) {
623
709
  const parsed = parseClaudeStream(worker.stdoutPath);
624
710
  const heartbeat = parseHeartbeat(worker.heartbeatPath);
625
711
  const alive = isPidAlive(worker.pid);
@@ -627,6 +713,7 @@ function computeWorkerStatus(worker) {
627
713
  const stderrBytes = fileSize(worker.stderrPath);
628
714
  const heartbeatBytes = fileSize(worker.heartbeatPath);
629
715
  const changedFiles = gitStatusShort(worker.worktreePath);
716
+ const gitAncestry = computeGitAncestry(worker.worktreePath, options.base);
630
717
  const lastActivityAt = latestIso([
631
718
  parsed.lastEventAt,
632
719
  heartbeat.lastHeartbeatAt,
@@ -668,7 +755,8 @@ function computeWorkerStatus(worker) {
668
755
  heartbeatBlocker: heartbeat.heartbeatBlocker,
669
756
  finalResult: parsed.finalResult,
670
757
  error: parsed.error || (!alive && !parsed.finalResult ? tailFile(worker.stderrPath, 10).trim() || void 0 : void 0),
671
- changedFiles
758
+ changedFiles,
759
+ gitAncestry
672
760
  };
673
761
  }
674
762
  function isFinishedWorkerStatus(status) {
@@ -1430,7 +1518,7 @@ function runStatus(args) {
1430
1518
  if (!worker) {
1431
1519
  return { worker: name, status: "missing", attention: "needs_attention", attentionReason: "worker.json not found" };
1432
1520
  }
1433
- const status = computeWorkerStatus(worker);
1521
+ const status = computeWorkerStatus(worker, { base: run.base });
1434
1522
  return {
1435
1523
  worker: status.worker,
1436
1524
  status: status.status,
@@ -1444,7 +1532,9 @@ function runStatus(args) {
1444
1532
  lastHeartbeatSummary: status.lastHeartbeatSummary,
1445
1533
  heartbeatBlocker: status.heartbeatBlocker,
1446
1534
  changedFileCount: status.changedFiles.length,
1447
- branch: status.branch
1535
+ branch: status.branch,
1536
+ ancestry: status.gitAncestry.relation,
1537
+ ancestryChecked: status.gitAncestry.checked
1448
1538
  };
1449
1539
  });
1450
1540
  const board = {