@helmsmith/workbrokr 0.3.0 → 0.3.1

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/SKILL.md CHANGED
@@ -65,3 +65,5 @@ work pr <area> --title "feat(area): what changed" --body-file /tmp/pr-body.md
65
65
  ```
66
66
 
67
67
  `work pr` pushes the branch first, then opens the PR through `pritty pr --no-push --auto-approve --json` against the manifest's base branch, and stores `{ number, url, draft }` on the effort (`work status` shows it). Run it again and it returns the same PR; pass `--title`/`--body-file` again to update the PR's words in place. If the stored PR was merged or closed it refuses with `PR_CLOSED`; `--force` opens a new one. Write the body in the repository's PR conventions; the attribution footer comes from pritty's `prFooter`, not from you. Always pass a title and a body: pritty's AI path is the fallback, never the default. Needs `@helmsmith/pritty` 0.1.1 or newer on PATH.
68
+
69
+ `--story` takes a path relative to the manifest's Storybook host (`app-ui` in skoolscout): a folder, a story or module file, or a bare stem such as `components/discover-schools/dashboards/charts/ChartSkeleton` (storybrokr 0.3.1 or newer resolves a stem to its file).
package/dist/cli.js CHANGED
@@ -165,6 +165,22 @@ var Git = class _Git {
165
165
  else if (this.remoteBranchExists(this.currentBranch())) ref = `origin/${this.currentBranch()}`;
166
166
  return this.lines(["rev-list", `${ref}..HEAD`]).length;
167
167
  }
168
+ /** Non-merge commits on HEAD that origin/<base> does not have: the branch's own work (sync merges do not count). */
169
+ ownCommitCount(base) {
170
+ const r = this.tryRun(["rev-list", "--count", "--no-merges", `origin/${base}..HEAD`]);
171
+ return r.code === 0 ? Number(r.out.trim()) || 0 : 0;
172
+ }
173
+ /** `branch` is already in origin/<base>: an ancestor of it, patch-equivalent (squash-merged), or without own commits. */
174
+ branchMergedInto(branch, base) {
175
+ const target = `origin/${base}`;
176
+ if (this.isAncestor(branch, target)) return true;
177
+ const own = this.tryRun(["rev-list", "--count", "--no-merges", `${target}..${branch}`]);
178
+ if (own.code === 0 && Number(own.out.trim()) === 0) return true;
179
+ const cherry = this.tryRun(["cherry", target, branch]);
180
+ if (cherry.code !== 0) return false;
181
+ const lines = cherry.out.split("\n").map((l) => l.trim()).filter(Boolean);
182
+ return lines.length > 0 && lines.every((l) => l.startsWith("- "));
183
+ }
168
184
  /** Commits on origin/<base> that HEAD does not have (0 when the ref is unknown). */
169
185
  behindCount(base) {
170
186
  const r = this.tryRun(["rev-list", "--count", `HEAD..origin/${base}`]);
@@ -1630,7 +1646,7 @@ var storybrokrCli = {
1630
1646
  };
1631
1647
 
1632
1648
  // src/lib/pritty.ts
1633
- var MIN_PRITTY_VERSION = "0.1.1";
1649
+ var MIN_PRITTY_VERSION = "0.1.2";
1634
1650
  function assertPrittyVersion(client) {
1635
1651
  const found = client.version();
1636
1652
  if (compareSemver(found, MIN_PRITTY_VERSION) < 0) {
@@ -1696,11 +1712,11 @@ var prittyCli = {
1696
1712
  }
1697
1713
  );
1698
1714
  }
1699
- const j = lastJsonLine(
1700
- r.out,
1701
- "pr --check"
1702
- );
1703
- return j.open ? { number: j.open.number, url: j.open.url, draft: Boolean(j.open.draft) } : null;
1715
+ const j = lastJsonLine(r.out, "pr --check");
1716
+ return {
1717
+ open: j.open ? { number: j.open.number, url: j.open.url, draft: Boolean(j.open.draft) } : null,
1718
+ last: j.last ?? null
1719
+ };
1704
1720
  }
1705
1721
  };
1706
1722
 
@@ -2563,7 +2579,9 @@ async function finishEffort(ctx, o) {
2563
2579
  );
2564
2580
  }
2565
2581
  }
2566
- if (!o.intoArea) {
2582
+ fetchBaseOnce(ctx);
2583
+ const shouldPush = !o.intoArea && (g.ownCommitCount(m.baseBranch) > 0 || g.remoteBranchExists(effort.branch));
2584
+ if (shouldPush) {
2567
2585
  try {
2568
2586
  g.push(effort.branch);
2569
2587
  } catch (err) {
@@ -2597,14 +2615,14 @@ async function finishEffort(ctx, o) {
2597
2615
  ctx.registry.removeEfforts((e) => e.id === effort.id);
2598
2616
  return {
2599
2617
  effort: finished2,
2600
- pushed: !o.intoArea,
2618
+ pushed: shouldPush,
2601
2619
  parked,
2602
2620
  mergedIntoArea: Boolean(o.intoArea),
2603
2621
  notices
2604
2622
  };
2605
2623
  }
2606
2624
  const finished = ctx.registry.updateEffort(effort.id, { state: "finished", lastActivity: now() });
2607
- return { effort: finished, pushed: true, parked, notices };
2625
+ return { effort: finished, pushed: shouldPush, parked, notices };
2608
2626
  }
2609
2627
  async function reapLeases(ctx, o = {}) {
2610
2628
  const m = ctx.manifest;
@@ -2719,10 +2737,12 @@ async function retireArea(ctx, o) {
2719
2737
  const branchesDeleted = [];
2720
2738
  const branchesKept = [];
2721
2739
  const allAreaBranches = m.branch.types.map((type) => `${type}/${o.area}`);
2740
+ fetchBaseOnce(ctx);
2722
2741
  for (const branch of allAreaBranches) {
2723
2742
  if (!main.branchExists(branch)) continue;
2724
2743
  const onOrigin = main.remoteBranchExists(branch) && main.tryRun(["rev-list", "--count", `origin/${branch}..${branch}`]).out.trim() === "0";
2725
- if (onOrigin) {
2744
+ const mergedIntoBase = !onOrigin && main.branchMergedInto(branch, m.baseBranch);
2745
+ if (onOrigin || mergedIntoBase) {
2726
2746
  main.deleteLocalBranch(branch);
2727
2747
  branchesDeleted.push(branch);
2728
2748
  } else branchesKept.push(branch);
@@ -2784,7 +2804,7 @@ async function prEffort(ctx, o) {
2784
2804
  return updated;
2785
2805
  };
2786
2806
  if (effort.pr) {
2787
- const open = await ctx.pritty.check({ cwd: effort.worktree, base: m.baseBranch });
2807
+ const { open, last } = await ctx.pritty.check({ cwd: effort.worktree, base: m.baseBranch });
2788
2808
  if (open) {
2789
2809
  if (!given) {
2790
2810
  return { effort: store(open), pr: open, created: false, behindBase: behind, warnings };
@@ -2801,10 +2821,11 @@ async function prEffort(ctx, o) {
2801
2821
  return { effort: store(pr2), pr: pr2, created: false, behindBase: behind, warnings };
2802
2822
  }
2803
2823
  if (!o.force) {
2824
+ const fate = last?.number === effort.pr.number ? last.state === "MERGED" ? "was merged" : "was closed without merging" : "is no longer open";
2804
2825
  throw new WorkkitError(
2805
2826
  "PR_CLOSED",
2806
- `PR #${effort.pr.number} for ${label} is no longer open (${effort.pr.url}); pass --force to open a new one`,
2807
- { pr: effort.pr }
2827
+ `PR #${effort.pr.number} for ${label} ${fate} (${effort.pr.url}); pass --force to open a new one`,
2828
+ { pr: effort.pr, state: last?.state }
2808
2829
  );
2809
2830
  }
2810
2831
  }