Skip to content

Commit 187e33c

Browse files
committed
fix(pi-herdr-rename): rename workspace on command
1 parent 70e7c2d commit 187e33c

6 files changed

Lines changed: 48 additions & 42 deletions

File tree

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/pi-herdr-rename/CONTEXT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Git-safe branch combining task type with display-title words: `Update task logic
1313
_Avoid_: display title, raw generated branch, arbitrary Git mutation
1414

1515
**Generated worktree label**:
16-
Herdr linked-worktree label matching its `worktree-<adjective>-<noun>-<hex>` default. It is replaced with current display title; custom workspace labels remain unchanged.
16+
Herdr linked-worktree label matching its `worktree-<adjective>-<noun>-<hex>` default. It is replaced automatically with current display title; an explicit rename may also replace a custom workspace label.
1717
_Avoid_: semantic branch, custom workspace name
1818

1919
**Sole-pane tab**:

packages/pi-herdr-rename/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@ Shared [`pi-task-models` config](../pi-task-models#config) at `~/.pi/agent/confi
3434

3535
Display titles are natural task phrases, preferably three or four words and always at most four words and 20 characters. Model classification stays internal: `refactor: update task logic` displays as `Update task logic` and maps to Git branch `refactor/update-task-logic`.
3636

37-
In a linked worktree, a detached checkout or Herdr `worktree/...` branch is renamed; an existing non-generated branch stays. A generated workspace label such as `worktree-brave-meadow-4aa8` becomes display title; custom workspace names stay. Enclosing Herdr tab updates only when this pane is tab's only pane. Outside Herdr, only Pi session name changes.
37+
In a linked worktree, a detached checkout or Herdr `worktree/...` branch is renamed; an existing non-generated branch stays. A generated workspace label such as `worktree-brave-meadow-4aa8` becomes the display title automatically; `/rename` also replaces a custom workspace name. Enclosing Herdr tab updates only when this pane is tab's only pane. Outside Herdr, only Pi session name changes.
3838

3939
Tries assigned profile primary, then fallback, while honoring configured thinking level. Never substitutes current session model. No viable route leaves titles unchanged. Resuming a session created by this version reapplies saved display title and semantic branch without another model request. Older titles receive no migration.

packages/pi-herdr-rename/extensions/rename.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
216216
displayTitle: string,
217217
branchCandidate: string,
218218
previousDisplayTitle: string | undefined,
219+
forceWorkspaceRename: boolean,
219220
request: number,
220221
controller: AbortController,
221222
): Promise<void> => {
@@ -247,6 +248,16 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
247248
const workspaceName = workspace?.label;
248249
if (typeof workspaceName !== "string") throw new Error("Herdr workspace response omitted label.");
249250
const worktree = workspace?.worktree;
251+
if (
252+
workspaceName !== displayTitle &&
253+
(forceWorkspaceRename ||
254+
(worktree?.is_linked_worktree === true &&
255+
(HERDR_DEFAULT_WORKTREE_NAME.test(workspaceName) || workspaceName === previousDisplayTitle))) &&
256+
isCurrent(request, controller)
257+
) {
258+
await herdr.run(["workspace", "rename", workspaceId, displayTitle], { signal: controller.signal });
259+
}
260+
250261
const checkoutPath = worktree?.checkout_path;
251262
if (worktree?.is_linked_worktree !== true || typeof checkoutPath !== "string" || !checkoutPath) return;
252263

@@ -267,13 +278,6 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
267278
const semanticBranch = availableBranch(branchCandidate, branches);
268279
await runGit(branch ? ["branch", "-m", semanticBranch] : ["switch", "-c", semanticBranch]);
269280
}
270-
if (
271-
workspaceName !== displayTitle &&
272-
(HERDR_DEFAULT_WORKTREE_NAME.test(workspaceName) || workspaceName === previousDisplayTitle) &&
273-
isCurrent(request, controller)
274-
) {
275-
await herdr.run(["workspace", "rename", workspaceId, displayTitle], { signal: controller.signal });
276-
}
277281
};
278282

279283
const begin = () => {
@@ -300,7 +304,7 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
300304
const previousDisplayTitle = saved && pi.getSessionName() === saved.display ? saved.display : undefined;
301305
pi.setSessionName(title.display);
302306
pi.appendEntry(TITLE_STATE_TYPE, title);
303-
await applyHerdr(title.display, title.branch, previousDisplayTitle, request, controller);
307+
await applyHerdr(title.display, title.branch, previousDisplayTitle, manual, request, controller);
304308
return title.display;
305309
} catch (error) {
306310
if (isCurrent(request, controller) && (manual || error instanceof RenameModelError)) {
@@ -341,7 +345,7 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
341345
if (!title || title !== saved?.display) return;
342346

343347
const { request, controller } = begin();
344-
void applyHerdr(title, saved.branch, saved.display, request, controller)
348+
void applyHerdr(title, saved.branch, saved.display, false, request, controller)
345349
.catch(() => undefined)
346350
.finally(() => finish(request, controller));
347351
});

packages/pi-herdr-rename/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@henryqw/pi-herdr-rename",
3-
"version": "2.1.7",
3+
"version": "2.1.8",
44
"description": "Generate short Pi display titles and rename the current Herdr location.",
55
"keywords": [
66
"pi-package",

packages/pi-herdr-rename/test/rename.test.ts

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -270,38 +270,40 @@ test("saved display titles keep semantic branches, replace generated branches, a
270270
});
271271
});
272272

273-
test("manual rename updates its previous generated workspace title", async () => {
273+
test("manual rename updates generated and custom workspace titles", async () => {
274274
await withAgentDir(async () => {
275275
process.env.HERDR_PANE_ID = "pane-1";
276-
const app = harness({
277-
sessionName: "Saved title",
278-
branch: [
279-
{ type: "custom", customType: "pi-herdr-rename/title", data: { display: "Saved title", branch: "fix/saved-title" } },
280-
{ type: "message", message: { role: "user", content: "update task logic" } },
281-
],
282-
complete: async () => response("refactor: update task logic"),
283-
exec: async (args) => {
284-
if (args.join("\0") === "branch\0--show-current") return success("fix/saved-title\n");
285-
if (args[0] === "pane" && args[1] === "get") {
286-
return success(JSON.stringify({ result: { pane: { tab_id: "tab-1", workspace_id: "workspace-1" } } }));
287-
}
288-
if (args[0] === "tab" && args[1] === "get") {
289-
return success(JSON.stringify({ result: { tab: { pane_count: 1 } } }));
290-
}
291-
if (args[0] === "workspace" && args[1] === "get") {
292-
return success(JSON.stringify({ result: { workspace: { label: "Saved title", worktree: { checkout_path: "/repo/worktree", is_linked_worktree: true } } } }));
293-
}
294-
return success("{}");
295-
},
296-
});
297-
await app.handlers.get("session_start")?.({}, app.ctx);
298-
await eventually(() => app.execCalls.some((args) => args[0] === "workspace" && args[1] === "get"));
299-
await app.commands.get("rename")?.("", app.ctx);
276+
for (const workspaceName of ["Saved title", "Custom workspace"]) {
277+
const app = harness({
278+
sessionName: "Saved title",
279+
branch: [
280+
{ type: "custom", customType: "pi-herdr-rename/title", data: { display: "Saved title", branch: "fix/saved-title" } },
281+
{ type: "message", message: { role: "user", content: "update task logic" } },
282+
],
283+
complete: async () => response("refactor: update task logic"),
284+
exec: async (args) => {
285+
if (args.join("\0") === "branch\0--show-current") return success("fix/saved-title\n");
286+
if (args[0] === "pane" && args[1] === "get") {
287+
return success(JSON.stringify({ result: { pane: { tab_id: "tab-1", workspace_id: "workspace-1" } } }));
288+
}
289+
if (args[0] === "tab" && args[1] === "get") {
290+
return success(JSON.stringify({ result: { tab: { pane_count: 1 } } }));
291+
}
292+
if (args[0] === "workspace" && args[1] === "get") {
293+
return success(JSON.stringify({ result: { workspace: { label: workspaceName, worktree: { checkout_path: "/repo/worktree", is_linked_worktree: true } } } }));
294+
}
295+
return success("{}");
296+
},
297+
});
298+
await app.handlers.get("session_start")?.({}, app.ctx);
299+
await eventually(() => app.execCalls.some((args) => args[0] === "workspace" && args[1] === "get"));
300+
await app.commands.get("rename")?.("", app.ctx);
300301

301-
assert.deepEqual(
302-
app.execCalls.filter((args) => args[0] === "workspace" && args[1] === "rename"),
303-
[["workspace", "rename", "workspace-1", "Update task logic"]],
304-
);
302+
assert.deepEqual(
303+
app.execCalls.filter((args) => args[0] === "workspace" && args[1] === "rename"),
304+
[["workspace", "rename", "workspace-1", "Update task logic"]],
305+
);
306+
}
305307
});
306308
});
307309

0 commit comments

Comments
 (0)