@great-arrow/cli 0.10.1 → 0.10.4

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.
Files changed (2) hide show
  1. package/dist/index.js +101 -38
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10011,7 +10011,7 @@ async function clearStoredCredential() {
10011
10011
 
10012
10012
  // src/lib/version.ts
10013
10013
  var CLI_PACKAGE_NAME = "@great-arrow/cli";
10014
- var CLI_VERSION = "0.10.1";
10014
+ var CLI_VERSION = "0.10.4";
10015
10015
  var GAD_INSTALL_VERSION = "2026.08.08";
10016
10016
  var GAD_INSTALL_MIN_VERSION = "2026.06.22";
10017
10017
  var GAD_INSTALL_HEADER = "X-GAD-Install-Version";
@@ -33393,6 +33393,39 @@ import { readFileSync as readFileSync2 } from "node:fs";
33393
33393
  // src/lib/work-status.ts
33394
33394
  import { execFile as execFile3, execFileSync as execFileSync3 } from "node:child_process";
33395
33395
 
33396
+ // ../../src/lib/work-risk/local-git-truth.ts
33397
+ function isTimeoutError(error62) {
33398
+ if (!error62 || typeof error62 !== "object") return false;
33399
+ const e = error62;
33400
+ return e.code === "ETIMEDOUT" || e.killed === true || e.signal === "SIGTERM";
33401
+ }
33402
+ function contentAlreadyOnMain(head, run5) {
33403
+ const cherry = run5(["cherry", "origin/main", head]);
33404
+ if (cherry.status !== 0) return null;
33405
+ if (cherry.stdout.split("\n").some((line) => line.startsWith("+")))
33406
+ return false;
33407
+ const merges = run5(["rev-list", "--merges", `origin/main..${head}`]);
33408
+ if (merges.status !== 0) return null;
33409
+ for (const sha of merges.stdout.split("\n").filter(Boolean)) {
33410
+ const cc = run5(["diff-tree", "--cc", "--no-commit-id", "-p", sha]);
33411
+ if (cc.status !== 0) return null;
33412
+ if (cc.stdout.trim().length > 0) return false;
33413
+ }
33414
+ return true;
33415
+ }
33416
+ function contentOnMainWithinBudget(branches, run5, budgetMs, now = Date.now) {
33417
+ const deadline = now() + budgetMs;
33418
+ const verdicts = /* @__PURE__ */ new Map();
33419
+ for (const branch of branches) {
33420
+ if (now() >= deadline) {
33421
+ verdicts.set(branch, null);
33422
+ continue;
33423
+ }
33424
+ verdicts.set(branch, contentAlreadyOnMain(branch.head, run5));
33425
+ }
33426
+ return verdicts;
33427
+ }
33428
+
33396
33429
  // ../../src/lib/work-risk/push-state.ts
33397
33430
  var REMOTE_BRANCH_REF_PREFIX = "refs/remotes/origin/";
33398
33431
  var REMOTE_BRANCH_TIPS_FORMAT = "%(refname)%09%(objectname)";
@@ -33432,7 +33465,7 @@ function run(command, args, cwd) {
33432
33465
  status: e.status ?? 1,
33433
33466
  stdout: e.stdout ?? "",
33434
33467
  stderr: e.stderr ?? "",
33435
- timedOut: e.code === "ETIMEDOUT"
33468
+ timedOut: isTimeoutError(err2)
33436
33469
  };
33437
33470
  }
33438
33471
  }
@@ -33441,7 +33474,7 @@ function git(args, cwd) {
33441
33474
  }
33442
33475
  var WORKTREE_STATUS_CONCURRENCY = 8;
33443
33476
  var WORKTREE_STATUS_TIMEOUT_MS = 2e3;
33444
- function gitAsync(args, cwd) {
33477
+ function gitAsync(args, cwd, timeoutMs = WORKTREE_STATUS_TIMEOUT_MS) {
33445
33478
  return new Promise((resolve7) => {
33446
33479
  execFile3(
33447
33480
  "git",
@@ -33450,14 +33483,14 @@ function gitAsync(args, cwd) {
33450
33483
  cwd,
33451
33484
  encoding: "utf8",
33452
33485
  maxBuffer: 10 * 1024 * 1024,
33453
- timeout: WORKTREE_STATUS_TIMEOUT_MS
33486
+ timeout: timeoutMs
33454
33487
  },
33455
33488
  (error62, stdout, stderr) => {
33456
33489
  resolve7({
33457
33490
  status: error62 ? 1 : 0,
33458
33491
  stdout: stdout ?? "",
33459
33492
  stderr: stderr ?? "",
33460
- timedOut: error62?.code === "ETIMEDOUT"
33493
+ timedOut: isTimeoutError(error62)
33461
33494
  });
33462
33495
  }
33463
33496
  );
@@ -33621,35 +33654,52 @@ async function auditWorktrees(root, localBranches) {
33621
33654
  const branchByHead = new Map(
33622
33655
  localBranches.map((branch) => [branch.head, branch])
33623
33656
  );
33624
- return mapConcurrent(
33625
- parseWorktrees(raw),
33657
+ const parsed = parseWorktrees(raw);
33658
+ const statusArgs = ["--no-optional-locks", "status", "--porcelain"];
33659
+ const statuses = await mapConcurrent(
33660
+ parsed,
33626
33661
  WORKTREE_STATUS_CONCURRENCY,
33627
- async (worktree) => {
33628
- const status = await gitAsync(
33629
- ["--no-optional-locks", "status", "--porcelain"],
33630
- worktree.path
33662
+ (worktree) => gitAsync(statusArgs, worktree.path)
33663
+ );
33664
+ for (const [index, worktree] of parsed.entries()) {
33665
+ if (statuses[index].timedOut)
33666
+ statuses[index] = await gitAsync(
33667
+ statusArgs,
33668
+ worktree.path,
33669
+ LOCAL_COMMAND_TIMEOUT_MS
33631
33670
  );
33632
- const branch = branchByName.get(worktree.branch) ?? branchByHead.get(worktree.head);
33633
- const baseAudit = {
33634
- path: worktree.path,
33635
- branch: worktree.branch,
33636
- head: worktree.head,
33637
- dirty: status.status !== 0 || status.stdout.trim().length > 0,
33638
- mergedToMain: branch?.mergedToMain ?? false,
33639
- upstream: branch?.upstream ?? null,
33640
- upstreamGone: branch?.upstreamGone ?? false,
33641
- remoteHasHead: branch?.remoteHasHead ?? false,
33642
- behindMain: branch?.behindMain ?? 0,
33643
- aheadMain: branch?.aheadMain ?? 0,
33644
- countsExact: branch?.countsExact ?? false
33645
- };
33646
- return { ...baseAudit, classification: classifyWorktree(baseAudit) };
33647
- }
33671
+ }
33672
+ return parsed.map((worktree, index) => {
33673
+ const status = statuses[index];
33674
+ const branch = branchByName.get(worktree.branch) ?? branchByHead.get(worktree.head);
33675
+ const baseAudit = {
33676
+ path: worktree.path,
33677
+ branch: worktree.branch,
33678
+ head: worktree.head,
33679
+ dirty: status.status !== 0 || status.stdout.trim().length > 0,
33680
+ mergedToMain: branch?.mergedToMain ?? false,
33681
+ upstream: branch?.upstream ?? null,
33682
+ upstreamGone: branch?.upstreamGone ?? false,
33683
+ remoteHasHead: branch?.remoteHasHead ?? false,
33684
+ behindMain: branch?.behindMain ?? 0,
33685
+ aheadMain: branch?.aheadMain ?? 0,
33686
+ countsExact: branch?.countsExact ?? false
33687
+ };
33688
+ return { ...baseAudit, classification: classifyWorktree(baseAudit) };
33689
+ });
33690
+ }
33691
+ var CONTENT_CHECK_BUDGET_MS = 6e4;
33692
+ function auditDetachedLocalBranches(localBranches, checkedOutBranches, root) {
33693
+ const detached = localBranches.filter(
33694
+ ({ branch }) => !checkedOutBranches.has(branch)
33648
33695
  );
33649
- }
33650
- function auditDetachedLocalBranches(localBranches, checkedOutBranches) {
33651
- return localBranches.filter(({ branch }) => !checkedOutBranches.has(branch)).map(
33652
- ({
33696
+ const contentOnMain = contentOnMainWithinBudget(
33697
+ detached.filter((b) => !b.mergedToMain && !b.remoteHasHead),
33698
+ (args) => git(args, root),
33699
+ CONTENT_CHECK_BUDGET_MS
33700
+ );
33701
+ return detached.map((detachedBranch) => {
33702
+ const {
33653
33703
  branch,
33654
33704
  head,
33655
33705
  mergedToMain,
@@ -33660,7 +33710,8 @@ function auditDetachedLocalBranches(localBranches, checkedOutBranches) {
33660
33710
  upstreamGone,
33661
33711
  remoteHasHead: remoteHasHead2,
33662
33712
  committedAt
33663
- }) => ({
33713
+ } = detachedBranch;
33714
+ return {
33664
33715
  branch,
33665
33716
  head,
33666
33717
  mergedToMain,
@@ -33676,9 +33727,10 @@ function auditDetachedLocalBranches(localBranches, checkedOutBranches) {
33676
33727
  remoteHasHead: remoteHasHead2,
33677
33728
  // Without this the grace window read `ageMs = Infinity` for every
33678
33729
  // branch, so a branch was "stranded" from its first second.
33679
- committedAt
33680
- })
33681
- );
33730
+ committedAt,
33731
+ contentOnMain: contentOnMain.get(detachedBranch) ?? null
33732
+ };
33733
+ });
33682
33734
  }
33683
33735
  function auditRemoteBranchesWithoutPullRequests(root, openPullRequests) {
33684
33736
  const openHeads = new Set(openPullRequests.map((pr) => pr.headRefName));
@@ -33772,7 +33824,8 @@ async function collectWorkStatus(opts) {
33772
33824
  );
33773
33825
  const detachedBranches = auditDetachedLocalBranches(
33774
33826
  localBranches,
33775
- checkedOutBranches
33827
+ checkedOutBranches,
33828
+ root
33776
33829
  );
33777
33830
  const openPullRequestResult = readOpenPullRequests(root);
33778
33831
  if (openPullRequestResult === null)
@@ -39052,6 +39105,8 @@ function normalizeGlance(raw) {
39052
39105
  }
39053
39106
  ];
39054
39107
  });
39108
+ const more = obj.needsYouMore;
39109
+ const needsYouMore = more && typeof more.count === "number" && more.count > 0 && typeof more.text === "string" && typeof more.href === "string" ? { count: more.count, text: more.text, href: more.href } : null;
39055
39110
  const atRisk = asArray2(obj.atRisk).flatMap((item) => {
39056
39111
  if (typeof item !== "object" || item === null) return [];
39057
39112
  const it = item;
@@ -39141,6 +39196,7 @@ function normalizeGlance(raw) {
39141
39196
  return typeof row.id === "string" && typeof row.name === "string" ? [{ id: row.id, name: row.name }] : [];
39142
39197
  }) : [],
39143
39198
  needsYou,
39199
+ ...needsYouMore ? { needsYouMore } : {},
39144
39200
  atRisk,
39145
39201
  topGoal,
39146
39202
  integrations,
@@ -39350,6 +39406,13 @@ function renderFooter(payload, apiUrl) {
39350
39406
  if (url2) lines.push(` ${c.dim("\u2192")} ${c.cyan(url2)}`);
39351
39407
  }
39352
39408
  }
39409
+ if (payload.needsYouMore) {
39410
+ const hidden = Math.max(0, payload.needsYou.length - 2) + payload.needsYouMore.count;
39411
+ const url2 = glanceItemUrl(payload.needsYouMore.href, apiUrl);
39412
+ lines.push(
39413
+ `${c.dim(`+${hidden} more needs you`)}${url2 ? ` ${c.dim("\u2192")} ${c.cyan(url2)}` : ""}`
39414
+ );
39415
+ }
39353
39416
  if (lines.length === 0) {
39354
39417
  const integration = payload.integrations[0];
39355
39418
  if (integration) {
@@ -56262,7 +56325,7 @@ var hasSearchParameters = (search) => {
56262
56325
  function isHTTPError(error62) {
56263
56326
  return error62 instanceof HTTPError || error62?.name === HTTPError.name;
56264
56327
  }
56265
- function isTimeoutError(error62) {
56328
+ function isTimeoutError2(error62) {
56266
56329
  return error62 instanceof TimeoutError || error62?.name === TimeoutError.name;
56267
56330
  }
56268
56331
 
@@ -56468,7 +56531,7 @@ var Ky = class _Ky {
56468
56531
  return this.#calculateDelay();
56469
56532
  }
56470
56533
  }
56471
- if (isTimeoutError(error62) && !this.#options.retry.retryOnTimeout) {
56534
+ if (isTimeoutError2(error62) && !this.#options.retry.retryOnTimeout) {
56472
56535
  throw error62;
56473
56536
  }
56474
56537
  if (isHTTPError(error62)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@great-arrow/cli",
3
- "version": "0.10.1",
3
+ "version": "0.10.4",
4
4
  "description": "GreatArrow.ai CLI tool",
5
5
  "type": "module",
6
6
  "bin": {