diff --git a/cli/src/__tests__/worktree.test.ts b/cli/src/__tests__/worktree.test.ts index 106cbc74..8493f897 100644 --- a/cli/src/__tests__/worktree.test.ts +++ b/cli/src/__tests__/worktree.test.ts @@ -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"); diff --git a/cli/src/commands/worktree.ts b/cli/src/commands/worktree.ts index 2ef42abf..d511881e 100644 --- a/cli/src/commands/worktree.ts +++ b/cli/src/commands/worktree.ts @@ -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("", "Worktree directory and branch name (created at ~/NAME)") + .option("--start-point ", "Remote ref to base the new branch on (e.g. origin/main)") .option("--instance ", "Explicit isolated instance id") .option("--home ", `Home root for worktree instances (default: ${DEFAULT_WORKTREE_HOME})`) .option("--from-config ", "Source config.json to seed from") diff --git a/doc/SPEC-implementation.md b/doc/SPEC-implementation.md index 430dcabb..efaf6518 100644 --- a/doc/SPEC-implementation.md +++ b/doc/SPEC-implementation.md @@ -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 diff --git a/doc/spec/ui.md b/doc/spec/ui.md index c7779393..c2ffdb7c 100644 --- a/doc/spec/ui.md +++ b/doc/spec/ui.md @@ -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