fix(HAI-092): use generateWorktreeName for fresh worktree directories
- Replace task ID with generateWorktreeName for worktree directory paths - Worktrees now use human-friendly adjective-noun names (e.g. swift-falcon) - Add tests for fresh worktree naming, task ID exclusion, and resumed task reuse
This commit is contained in:
@@ -32,6 +32,7 @@ import { createHaiAgent } from "./pi.js";
|
||||
import { execSync } from "node:child_process";
|
||||
import { findWorktreeUser, aiMergeTask } from "./merger.js";
|
||||
import { WorktreePool } from "./worktree-pool.js";
|
||||
import { generateWorktreeName } from "./worktree-names.js";
|
||||
import type { Column, Task, TaskDetail } from "@hai/core";
|
||||
|
||||
const mockedCreateHaiAgent = vi.mocked(createHaiAgent);
|
||||
@@ -349,6 +350,78 @@ describe("TaskExecutor worktreeInitCommand", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("TaskExecutor worktree naming", () => {
|
||||
const makeTask = (id = "HAI-030", worktree?: string) => ({
|
||||
id,
|
||||
title: "Test",
|
||||
description: "Test",
|
||||
column: "in-progress" as const,
|
||||
dependencies: [],
|
||||
steps: [],
|
||||
currentStep: 0,
|
||||
log: [],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
...(worktree ? { worktree } : {}),
|
||||
});
|
||||
|
||||
const mockedGenerateWorktreeName = vi.mocked(generateWorktreeName);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockedExistsSync.mockReturnValue(false);
|
||||
mockedGenerateWorktreeName.mockReturnValue("swift-falcon");
|
||||
mockedCreateHaiAgent.mockResolvedValue({
|
||||
session: {
|
||||
prompt: vi.fn().mockResolvedValue(undefined),
|
||||
dispose: vi.fn(),
|
||||
},
|
||||
} as any);
|
||||
});
|
||||
|
||||
it("uses generateWorktreeName for fresh worktree directories", async () => {
|
||||
const store = createMockStore();
|
||||
const executor = new TaskExecutor(store, "/tmp/test");
|
||||
|
||||
await executor.execute(makeTask());
|
||||
|
||||
// The worktree path stored should use the generated name, not the task ID
|
||||
expect(store.updateTask).toHaveBeenCalledWith("HAI-030", {
|
||||
worktree: "/tmp/test/.worktrees/swift-falcon",
|
||||
});
|
||||
expect(mockedGenerateWorktreeName).toHaveBeenCalledWith("/tmp/test");
|
||||
});
|
||||
|
||||
it("does NOT use task ID as worktree directory name for fresh worktrees", async () => {
|
||||
const store = createMockStore();
|
||||
const executor = new TaskExecutor(store, "/tmp/test");
|
||||
|
||||
await executor.execute(makeTask("HAI-099"));
|
||||
|
||||
// Verify the worktree path does NOT contain the task ID
|
||||
const updateCalls = store.updateTask.mock.calls;
|
||||
const worktreeUpdate = updateCalls.find(
|
||||
(call: any[]) => call[1]?.worktree !== undefined,
|
||||
);
|
||||
expect(worktreeUpdate).toBeDefined();
|
||||
expect(worktreeUpdate![1].worktree).not.toContain("HAI-099");
|
||||
expect(worktreeUpdate![1].worktree).toContain("swift-falcon");
|
||||
});
|
||||
|
||||
it("reuses stored worktree path for resumed tasks", async () => {
|
||||
const existingPath = "/tmp/test/.worktrees/calm-river";
|
||||
mockedExistsSync.mockReturnValue(true);
|
||||
|
||||
const store = createMockStore();
|
||||
const executor = new TaskExecutor(store, "/tmp/test");
|
||||
|
||||
await executor.execute(makeTask("HAI-031", existingPath));
|
||||
|
||||
// Should NOT generate a new name — reuse the stored path
|
||||
expect(mockedGenerateWorktreeName).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("TaskExecutor worktree pool integration", () => {
|
||||
const makeTask = (id = "HAI-020") => ({
|
||||
id,
|
||||
|
||||
@@ -271,7 +271,9 @@ export class TaskExecutor {
|
||||
|
||||
// Create or reuse worktree — try pool first when recycling is enabled
|
||||
const branchName = `hai/${task.id.toLowerCase()}`;
|
||||
let worktreePath = task.worktree || join(this.rootDir, ".worktrees", task.id);
|
||||
// Use generateWorktreeName for human-friendly directory names (adjective-noun pattern)
|
||||
// instead of task.id, so worktrees are named like ".worktrees/swift-falcon"
|
||||
let worktreePath = task.worktree || join(this.rootDir, ".worktrees", generateWorktreeName(this.rootDir));
|
||||
let isResume = existsSync(worktreePath);
|
||||
let acquiredFromPool = false;
|
||||
const settings = await this.store.getSettings();
|
||||
|
||||
Reference in New Issue
Block a user