Add worktree start-point support

This commit is contained in:
Dotta
2026-03-11 09:15:27 -05:00
parent 57d8d01079
commit d9492f02d6
4 changed files with 48 additions and 10 deletions

View File

@@ -115,6 +115,28 @@ describe("worktree helpers", () => {
).toEqual(["worktree", "add", "/tmp/feature-branch", "feature-branch"]);
});
it("builds git worktree add args with a start point", () => {
expect(
resolveGitWorktreeAddArgs({
branchName: "my-worktree",
targetPath: "/tmp/my-worktree",
branchExists: false,
startPoint: "public-gh/master",
}),
).toEqual(["worktree", "add", "-b", "my-worktree", "/tmp/my-worktree", "public-gh/master"]);
});
it("uses start point even when a local branch with the same name exists", () => {
expect(
resolveGitWorktreeAddArgs({
branchName: "my-worktree",
targetPath: "/tmp/my-worktree",
branchExists: true,
startPoint: "origin/main",
}),
).toEqual(["worktree", "add", "-b", "my-worktree", "/tmp/my-worktree", "origin/main"]);
});
it("rewrites loopback auth URLs to the new port only", () => {
expect(rewriteLocalUrlPort("http://127.0.0.1:3100", 3110)).toBe("http://127.0.0.1:3110/");
expect(rewriteLocalUrlPort("https://paperclip.example", 3110)).toBe("https://paperclip.example");

View File

@@ -62,7 +62,9 @@ type WorktreeInitOptions = {
force?: boolean;
};
type WorktreeMakeOptions = WorktreeInitOptions;
type WorktreeMakeOptions = WorktreeInitOptions & {
startPoint?: string;
};
type WorktreeEnvOptions = {
config?: string;
@@ -166,11 +168,13 @@ export function resolveGitWorktreeAddArgs(input: {
branchName: string;
targetPath: string;
branchExists: boolean;
startPoint?: string;
}): string[] {
if (input.branchExists) {
if (input.branchExists && !input.startPoint) {
return ["worktree", "add", input.targetPath, input.branchName];
}
return ["worktree", "add", "-b", input.branchName, input.targetPath, "HEAD"];
const commitish = input.startPoint ?? "HEAD";
return ["worktree", "add", "-b", input.branchName, input.targetPath, commitish];
}
function readPidFilePort(postmasterPidFile: string): number | null {
@@ -715,10 +719,25 @@ export async function worktreeMakeCommand(nameArg: string, opts: WorktreeMakeOpt
}
mkdirSync(path.dirname(targetPath), { recursive: true });
if (opts.startPoint) {
const [remote] = opts.startPoint.split("/", 1);
try {
execFileSync("git", ["fetch", remote], {
cwd: sourceCwd,
stdio: ["ignore", "pipe", "pipe"],
});
} catch (error) {
throw new Error(
`Failed to fetch from remote "${remote}": ${extractExecSyncErrorMessage(error) ?? String(error)}`,
);
}
}
const worktreeArgs = resolveGitWorktreeAddArgs({
branchName: name,
targetPath,
branchExists: localBranchExists(sourceCwd, name),
branchExists: !opts.startPoint && localBranchExists(sourceCwd, name),
startPoint: opts.startPoint,
});
const spinner = p.spinner();
@@ -775,6 +794,7 @@ export function registerWorktreeCommands(program: Command): void {
.command("worktree:make")
.description("Create ~/NAME as a git worktree, then initialize an isolated Paperclip instance inside it")
.argument("<name>", "Worktree directory and branch name (created at ~/NAME)")
.option("--start-point <ref>", "Remote ref to base the new branch on (e.g. origin/main)")
.option("--instance <id>", "Explicit isolated instance id")
.option("--home <path>", `Home root for worktree instances (default: ${DEFAULT_WORKTREE_HOME})`)
.option("--from-config <path>", "Source config.json to seed from")

View File

@@ -37,7 +37,7 @@ These decisions close open questions from `SPEC.md` for V1.
| Visibility | Full visibility to board and all agents in same company |
| Communication | Tasks + comments only (no separate chat system) |
| Task ownership | Single assignee; atomic checkout required for `in_progress` transition |
| Recovery | No automatic reassignment; stale work is surfaced, not silently fixed |
| Recovery | No automatic reassignment; work recovery stays manual/explicit |
| Agent adapters | Built-in `process` and `http` adapters |
| Auth | Mode-dependent human auth (`local_trusted` implicit board in current code; authenticated mode uses sessions), API keys for agents |
| Budget period | Monthly UTC calendar window |
@@ -106,7 +106,6 @@ A lightweight scheduler/worker in the server process handles:
- heartbeat trigger checks
- stuck run detection
- budget threshold checks
- stale task reporting generation
Separate queue infrastructure is not required for V1.
@@ -502,7 +501,6 @@ Dashboard payload must include:
- open/in-progress/blocked/done issue counts
- month-to-date spend and budget utilization
- pending approvals count
- stale task count
## 10.9 Error Semantics
@@ -681,7 +679,6 @@ Required UX behaviors:
- global company selector
- quick actions: pause/resume agent, create task, approve/reject request
- conflict toasts on atomic checkout failure
- clear stale-task indicators
- no silent background failures; every failed run visible in UI
## 15. Operational Requirements
@@ -780,7 +777,6 @@ A release candidate is blocked unless these pass:
- add company selector and org chart view
- add approvals and cost pages
- add operational dashboard and stale-task surfacing
## Milestone 6: Hardening and Release

View File

@@ -114,7 +114,7 @@ No section header — these are always at the top, below the company header.
My Issues
```
- **Inbox** — items requiring the board operator's attention. Badge count on the right. Includes: pending approvals, stale tasks, budget alerts, failed heartbeats. The number is the total unread/unresolved count.
- **Inbox** — items requiring the board operator's attention. Badge count on the right. Includes: pending approvals, budget alerts, failed heartbeats. The number is the total unread/unresolved count.
- **My Issues** — issues created by or assigned to the board operator.
### 3.3 Work Section