@afokapu/atdd-bun 0.10.114 → 0.10.115

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/integrity.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.10.114",
2
+ "version": "0.10.115",
3
3
  "files": {
4
4
  "HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
5
5
  "README.md": "8aefceaa24bee498f39081b2aaf4c0d5c54bc1ed9849c20e1d98d1427df56eab",
@@ -1269,7 +1269,7 @@
1269
1269
  "src/cli.ts": "395638ab87565bb10c2beb35988ff1acf686924d13515795156a2e8893b3da88",
1270
1270
  "src/docs-capability.ts": "bdeeb414649049b1cd4fa8008cada3a576318be51c43bec91bfa46a8e250c270",
1271
1271
  "src/enforce.ts": "c4811abe63efe4568f3cf26e21a152d1537e120cb3ce471daca32367cf6e2cda",
1272
- "src/hooks.ts": "afda4062002846e56d4c3ed0e7d4e18d9dfd59666a25d55eee4b68221f3411ef",
1272
+ "src/hooks.ts": "70094d7bb7afbddcf145d77b3003e3487c8a6d39066952251a18b5487594c310",
1273
1273
  "src/index.ts": "4081f8b08d1d3ee9e15a9964143e53d9cfb94a5616ab3e58a180d24cc16dc381",
1274
1274
  "src/integrity.ts": "f1ede7cb04924ae88e11917047277e264af6596b9759e381bd9c497cd34a797d",
1275
1275
  "src/journey-docs.ts": "994d229376244c84212cff951032823068a76565dd2fd427b9d44e341bd14853",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afokapu/atdd-bun",
3
- "version": "0.10.114",
3
+ "version": "0.10.115",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/afokapu/atdd-bun.git"
package/src/hooks.ts CHANGED
@@ -628,13 +628,12 @@ async function finishCandidate(state: Layout, cfg: HookPolicy): Promise<{ error:
628
628
  const current = await currentCommit(state.current);
629
629
  if (!current) return { error: "could not resolve the current Jujutsu revision" };
630
630
  let delivery = current, found = await branchForCommit(state.current, current, cfg.protected_branches);
631
- if ("error" in found && found.count === 0 && await mergedIntoPrimary(state, current, cfg)) return { branch: null, tip: current };
632
631
  if ("error" in found) {
632
+ // The established delivery path comes first: an empty working copy above a bookmarked parent.
633
633
  const changed = await jj(state.current, ["diff", "-r", "@", "--name-only"]);
634
634
  const parent = await jj(state.current, ["log", "-r", "@-", "--no-graph", "-T", "commit_id"]);
635
- if (changed.code || changed.out || parent.code || !parent.out) return found;
636
- const parentFound = await branchForCommit(state.current, parent.out, cfg.protected_branches);
637
- if ("error" in parentFound) return found;
635
+ const parentFound = changed.code || changed.out || parent.code || !parent.out ? found : await branchForCommit(state.current, parent.out, cfg.protected_branches);
636
+ if ("error" in parentFound) return found.count === 0 && await chainToPrimary(state, current, cfg, true) === "merged" ? { branch: null, tip: current } : found;
638
637
  delivery = parent.out;
639
638
  found = parentFound;
640
639
  }
@@ -656,21 +655,33 @@ async function jjRevision(root: string, commit: string): Promise<{ error: string
656
655
  return { empty: emptiness === "empty", conflict: conflict === "conflict", parents };
657
656
  }
658
657
 
659
- // A tip is delivered when it is an ancestor of the protected branch, or when it
660
- // reaches such an ancestor only through empty single-parent changes (nothing to
661
- // deliver). Content, conflicts, or merge shapes outside that chain fail closed.
662
- async function deliveryError(state: Layout, branch: string, tip: string, cfg: HookPolicy): Promise<string | null> {
663
- let commit = tip;
664
- for (;;) {
658
+ // Walks from a tip toward the protected branch through empty single-parent changes. "merged" means
659
+ // the walk reached an ancestor of the protected branch, so there is nothing to deliver. Content,
660
+ // conflicts, or merge shapes outside that chain fail closed. With refFree, an unmerged change that
661
+ // carries any refs/heads bookmark (protected or not) also fails closed, so no unique ref is left behind.
662
+ type ChainOutcome = "merged" | "conflict" | "unmerged" | { error: string };
663
+ async function chainToPrimary(state: Layout, tip: string, cfg: HookPolicy, refFree = false): Promise<ChainOutcome> {
664
+ for (let commit = tip;;) {
665
665
  const info = await jjRevision(state.primary, commit);
666
- if ("error" in info) return info.error;
667
- if (info.conflict) return `bookmark ${branch} has unresolved conflicts`;
668
- if (await mergedIntoPrimary(state, commit, cfg)) return null;
669
- if (!info.empty || info.parents.length !== 1) return `branch ${branch} is not merged into ${cfg.worktrees.primary_branch} in the primary checkout`;
666
+ if ("error" in info) return info;
667
+ if (info.conflict) return "conflict";
668
+ if (await mergedIntoPrimary(state, commit, cfg)) return "merged";
669
+ if (!info.empty || info.parents.length !== 1) return "unmerged";
670
+ if (refFree) {
671
+ const refs = await git(state.primary, ["for-each-ref", "--format=%(refname)", "--points-at", commit, "refs/heads/"]);
672
+ if (refs.code || refs.out) return "unmerged";
673
+ }
670
674
  commit = info.parents[0]!;
671
675
  }
672
676
  }
673
677
 
678
+ async function deliveryError(state: Layout, branch: string, tip: string, cfg: HookPolicy): Promise<string | null> {
679
+ const outcome = await chainToPrimary(state, tip, cfg);
680
+ if (outcome === "merged") return null;
681
+ if (outcome === "conflict") return `bookmark ${branch} has unresolved conflicts`;
682
+ return typeof outcome === "string" ? `branch ${branch} is not merged into ${cfg.worktrees.primary_branch} in the primary checkout` : outcome.error;
683
+ }
684
+
674
685
  // The workspace name is never derived from the bookmark (bookmarks can be
675
686
  // renamed). Resolve it from the JJ registry: exactly one registered workspace
676
687
  // must resolve, by real path, to the current directory.