@snappedly-tools/shipyard 0.9.1 → 0.9.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.
@@ -5,7 +5,10 @@ import { existsSync } from "node:fs";
5
5
  import * as shipyard from "@snappedly-tools/shipyard";
6
6
  import { docker } from "@snappedly-tools/shipyard/sandboxes/docker";
7
7
  import { z } from "zod";
8
- import { resolvePlannerBranch } from "./planner-branch.mjs";
8
+ import {
9
+ fastForwardPlannerBranch,
10
+ resolvePlannerBranch,
11
+ } from "./planner-branch.mjs";
9
12
 
10
13
  if (process.loadEnvFile && existsSync(".shipyard/.env"))
11
14
  process.loadEnvFile(".shipyard/.env");
@@ -205,6 +208,7 @@ for (let iteration = 0; iteration < 10; iteration++) {
205
208
  .split(/\r?\n/)
206
209
  .filter(Boolean);
207
210
  const plannerBranch = await resolvePlannerBranch(localBranches);
211
+ fastForwardPlannerBranch(plannerBranch, targetBranch);
208
212
  const plan = await shipyard.run({
209
213
  hooks,
210
214
  sandbox: sandboxProvider,
@@ -52,6 +52,46 @@ const reusablePlannerBranch = (
52
52
  )?.branch;
53
53
  };
54
54
 
55
+ /** Keep a reused planner branch current without discarding its own commits. */
56
+ export const fastForwardPlannerBranch = (
57
+ plannerBranch: string,
58
+ targetBranch: string,
59
+ repoDir = process.cwd(),
60
+ ): void => {
61
+ if (plannerBranch === targetBranch) return;
62
+ let plannerHead: string;
63
+ try {
64
+ plannerHead = execFileSync(
65
+ "git",
66
+ ["rev-parse", "--verify", `refs/heads/${plannerBranch}`],
67
+ { cwd: repoDir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] },
68
+ ).trim();
69
+ } catch {
70
+ return; // WorktreeManager creates a new planner branch from HEAD.
71
+ }
72
+ const targetHead = execFileSync(
73
+ "git",
74
+ ["rev-parse", "--verify", `refs/heads/${targetBranch}`],
75
+ { cwd: repoDir, encoding: "utf8" },
76
+ ).trim();
77
+ if (plannerHead === targetHead) return;
78
+ try {
79
+ execFileSync(
80
+ "git",
81
+ ["merge-base", "--is-ancestor", plannerHead, targetHead],
82
+ { cwd: repoDir, stdio: "ignore" },
83
+ );
84
+ } catch {
85
+ throw new Error(
86
+ `Planner branch '${plannerBranch}' has commits outside '${targetBranch}'. Review and integrate them before restarting Shipyard.`,
87
+ );
88
+ }
89
+ execFileSync("git", ["branch", "-f", plannerBranch, targetHead], {
90
+ cwd: repoDir,
91
+ stdio: "ignore",
92
+ });
93
+ };
94
+
55
95
  export const resolvePlannerBranch = async (
56
96
  localBranches: readonly string[],
57
97
  options: {
@@ -5,7 +5,10 @@ import { existsSync } from "node:fs";
5
5
  import * as shipyard from "@snappedly-tools/shipyard";
6
6
  import { docker } from "@snappedly-tools/shipyard/sandboxes/docker";
7
7
  import { z } from "zod";
8
- import { resolvePlannerBranch } from "./planner-branch.mjs";
8
+ import {
9
+ fastForwardPlannerBranch,
10
+ resolvePlannerBranch,
11
+ } from "./planner-branch.mjs";
9
12
 
10
13
  if (process.loadEnvFile && existsSync(".shipyard/.env"))
11
14
  process.loadEnvFile(".shipyard/.env");
@@ -228,6 +231,7 @@ for (let iteration = 0; iteration < 10; iteration++) {
228
231
  .split(/\r?\n/)
229
232
  .filter(Boolean);
230
233
  const plannerBranch = await resolvePlannerBranch(localBranches);
234
+ fastForwardPlannerBranch(plannerBranch, targetBranch);
231
235
  const plan = await shipyard.run({
232
236
  hooks,
233
237
  sandbox: sandboxProvider,
@@ -52,6 +52,46 @@ const reusablePlannerBranch = (
52
52
  )?.branch;
53
53
  };
54
54
 
55
+ /** Keep a reused planner branch current without discarding its own commits. */
56
+ export const fastForwardPlannerBranch = (
57
+ plannerBranch: string,
58
+ targetBranch: string,
59
+ repoDir = process.cwd(),
60
+ ): void => {
61
+ if (plannerBranch === targetBranch) return;
62
+ let plannerHead: string;
63
+ try {
64
+ plannerHead = execFileSync(
65
+ "git",
66
+ ["rev-parse", "--verify", `refs/heads/${plannerBranch}`],
67
+ { cwd: repoDir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] },
68
+ ).trim();
69
+ } catch {
70
+ return; // WorktreeManager creates a new planner branch from HEAD.
71
+ }
72
+ const targetHead = execFileSync(
73
+ "git",
74
+ ["rev-parse", "--verify", `refs/heads/${targetBranch}`],
75
+ { cwd: repoDir, encoding: "utf8" },
76
+ ).trim();
77
+ if (plannerHead === targetHead) return;
78
+ try {
79
+ execFileSync(
80
+ "git",
81
+ ["merge-base", "--is-ancestor", plannerHead, targetHead],
82
+ { cwd: repoDir, stdio: "ignore" },
83
+ );
84
+ } catch {
85
+ throw new Error(
86
+ `Planner branch '${plannerBranch}' has commits outside '${targetBranch}'. Review and integrate them before restarting Shipyard.`,
87
+ );
88
+ }
89
+ execFileSync("git", ["branch", "-f", plannerBranch, targetHead], {
90
+ cwd: repoDir,
91
+ stdio: "ignore",
92
+ });
93
+ };
94
+
55
95
  export const resolvePlannerBranch = async (
56
96
  localBranches: readonly string[],
57
97
  options: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snappedly-tools/shipyard",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "Run AI coding agents in isolated sandboxes",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",