@great-arrow/cli 0.10.0 → 0.10.2

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 +94 -45
  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.0";
10014
+ var CLI_VERSION = "0.10.2";
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)
@@ -51150,9 +51203,9 @@ var CLI_READ_HARDENING = [
51150
51203
  ".config/1Password",
51151
51204
  ".pypirc",
51152
51205
  ".cargo/credentials.toml",
51153
- ".terraform.d/credentials.tfrc.json"
51206
+ ".terraform.d/credentials.tfrc.json",
51207
+ "Library/Keychains"
51154
51208
  ];
51155
- var CLI_ONLY_READ_HARDENING = ["Library/Keychains"];
51156
51209
  function runsDirOf(layout) {
51157
51210
  return dirname8(dirname8(dirname8(layout.worktree)));
51158
51211
  }
@@ -51180,11 +51233,7 @@ function siblingPaths(layout) {
51180
51233
  ];
51181
51234
  }
51182
51235
  function cliOnlyDenyRead(layout) {
51183
- return [
51184
- ...CLI_ONLY_READ_HARDENING.map((rel) => join14(layout.home, rel)),
51185
- approvalsDir(layout),
51186
- ...siblingPaths(layout)
51187
- ];
51236
+ return [approvalsDir(layout), ...siblingPaths(layout)];
51188
51237
  }
51189
51238
  function denyRead(layout) {
51190
51239
  return [
@@ -56266,7 +56315,7 @@ var hasSearchParameters = (search) => {
56266
56315
  function isHTTPError(error62) {
56267
56316
  return error62 instanceof HTTPError || error62?.name === HTTPError.name;
56268
56317
  }
56269
- function isTimeoutError(error62) {
56318
+ function isTimeoutError2(error62) {
56270
56319
  return error62 instanceof TimeoutError || error62?.name === TimeoutError.name;
56271
56320
  }
56272
56321
 
@@ -56472,7 +56521,7 @@ var Ky = class _Ky {
56472
56521
  return this.#calculateDelay();
56473
56522
  }
56474
56523
  }
56475
- if (isTimeoutError(error62) && !this.#options.retry.retryOnTimeout) {
56524
+ if (isTimeoutError2(error62) && !this.#options.retry.retryOnTimeout) {
56476
56525
  throw error62;
56477
56526
  }
56478
56527
  if (isHTTPError(error62)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@great-arrow/cli",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "description": "GreatArrow.ai CLI tool",
5
5
  "type": "module",
6
6
  "bin": {