@henryqw/pi-herdr-rename 2.1.7 → 2.1.8
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.
- package/README.md +2 -2
- package/extensions/rename.ts +46 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,6 +34,6 @@ Shared [`pi-task-models` config](../pi-task-models#config) at `~/.pi/agent/confi
|
|
|
34
34
|
|
|
35
35
|
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`.
|
|
36
36
|
|
|
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
|
|
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.
|
|
38
38
|
|
|
39
|
-
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.
|
|
39
|
+
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. Herdr and Git synchronization failures appear as warnings; cancellation by a newer rename remains silent. Older titles receive no migration.
|
package/extensions/rename.ts
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
type ExtensionAPI,
|
|
4
4
|
type ExtensionContext,
|
|
5
5
|
} from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import { createHerdrClient } from "@henryqw/pi-herdr";
|
|
6
|
+
import { createHerdrClient, withWorktreeLock } from "@henryqw/pi-herdr";
|
|
7
7
|
import {
|
|
8
8
|
readTaskModelsConfig,
|
|
9
9
|
resolveConfiguredTaskRoutes,
|
|
@@ -42,15 +42,22 @@ function configuredRenameRoutes(ctx: ExtensionContext): ResolvedTaskRoute[] {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
function validSubject(subject: string): boolean {
|
|
46
|
+
return /^[a-z0-9]+(?: [a-z0-9]+)*$/.test(subject)
|
|
47
|
+
&& subject.length <= DISPLAY_MAX_CHARS
|
|
48
|
+
&& subject.split(" ").length <= DISPLAY_MAX_WORDS;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isDisplayTitle(value: unknown): value is string {
|
|
52
|
+
if (typeof value !== "string" || !value) return false;
|
|
53
|
+
const subject = value[0].toLowerCase() + value.slice(1);
|
|
54
|
+
return validSubject(subject) && value === subject[0].toUpperCase() + subject.slice(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
45
57
|
function parseGeneratedTitle(title: string): GeneratedTitle | undefined {
|
|
46
|
-
const match = /^([a-z][a-z0-9-]*): (
|
|
47
|
-
if (!match) return undefined;
|
|
58
|
+
const match = /^([a-z][a-z0-9-]*): (.+)$/.exec(title);
|
|
59
|
+
if (!match || match[1].length > SEMANTIC_TYPE_MAX_CHARS || !validSubject(match[2])) return undefined;
|
|
48
60
|
const subject = match[2];
|
|
49
|
-
if (
|
|
50
|
-
match[1].length > SEMANTIC_TYPE_MAX_CHARS ||
|
|
51
|
-
subject.length > DISPLAY_MAX_CHARS ||
|
|
52
|
-
subject.split(" ").length > DISPLAY_MAX_WORDS
|
|
53
|
-
) return undefined;
|
|
54
61
|
return {
|
|
55
62
|
display: subject[0].toUpperCase() + subject.slice(1),
|
|
56
63
|
branch: `${match[1]}/${subject.replaceAll(" ", "-")}`,
|
|
@@ -63,7 +70,7 @@ function savedTitle(ctx: ExtensionContext): GeneratedTitle | undefined {
|
|
|
63
70
|
.find((candidate) => candidate.type === "custom" && candidate.customType === TITLE_STATE_TYPE);
|
|
64
71
|
if (entry?.type !== "custom" || !entry.data || typeof entry.data !== "object" || Array.isArray(entry.data)) return undefined;
|
|
65
72
|
const { display, branch } = entry.data as { display?: unknown; branch?: unknown };
|
|
66
|
-
return
|
|
73
|
+
return isDisplayTitle(display) && typeof branch === "string" && SEMANTIC_BRANCH.test(branch)
|
|
67
74
|
? { display, branch }
|
|
68
75
|
: undefined;
|
|
69
76
|
}
|
|
@@ -216,6 +223,7 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
216
223
|
displayTitle: string,
|
|
217
224
|
branchCandidate: string,
|
|
218
225
|
previousDisplayTitle: string | undefined,
|
|
226
|
+
forceWorkspaceRename: boolean,
|
|
219
227
|
request: number,
|
|
220
228
|
controller: AbortController,
|
|
221
229
|
): Promise<void> => {
|
|
@@ -247,6 +255,16 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
247
255
|
const workspaceName = workspace?.label;
|
|
248
256
|
if (typeof workspaceName !== "string") throw new Error("Herdr workspace response omitted label.");
|
|
249
257
|
const worktree = workspace?.worktree;
|
|
258
|
+
if (
|
|
259
|
+
workspaceName !== displayTitle &&
|
|
260
|
+
(forceWorkspaceRename ||
|
|
261
|
+
(worktree?.is_linked_worktree === true &&
|
|
262
|
+
(HERDR_DEFAULT_WORKTREE_NAME.test(workspaceName) || workspaceName === previousDisplayTitle))) &&
|
|
263
|
+
isCurrent(request, controller)
|
|
264
|
+
) {
|
|
265
|
+
await herdr.run(["workspace", "rename", workspaceId, displayTitle], { signal: controller.signal });
|
|
266
|
+
}
|
|
267
|
+
|
|
250
268
|
const checkoutPath = worktree?.checkout_path;
|
|
251
269
|
if (worktree?.is_linked_worktree !== true || typeof checkoutPath !== "string" || !checkoutPath) return;
|
|
252
270
|
|
|
@@ -258,22 +276,18 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
258
276
|
return result.stdout.trim();
|
|
259
277
|
};
|
|
260
278
|
|
|
261
|
-
|
|
262
|
-
if (!branch || branch.startsWith("worktree/")) {
|
|
279
|
+
await withWorktreeLock(checkoutPath, async () => {
|
|
263
280
|
if (!isCurrent(request, controller)) return;
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
) {
|
|
275
|
-
await herdr.run(["workspace", "rename", workspaceId, displayTitle], { signal: controller.signal });
|
|
276
|
-
}
|
|
281
|
+
const branch = await runGit(["branch", "--show-current"]);
|
|
282
|
+
if (!branch || branch.startsWith("worktree/")) {
|
|
283
|
+
if (!isCurrent(request, controller)) return;
|
|
284
|
+
const branches = (await runGit(["for-each-ref", "--format=%(refname:short)", "refs/heads"]))
|
|
285
|
+
.split("\n")
|
|
286
|
+
.filter(Boolean);
|
|
287
|
+
const semanticBranch = availableBranch(branchCandidate, branches);
|
|
288
|
+
await runGit(branch ? ["branch", "-m", semanticBranch] : ["switch", "-c", semanticBranch]);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
277
291
|
};
|
|
278
292
|
|
|
279
293
|
const begin = () => {
|
|
@@ -300,10 +314,10 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
300
314
|
const previousDisplayTitle = saved && pi.getSessionName() === saved.display ? saved.display : undefined;
|
|
301
315
|
pi.setSessionName(title.display);
|
|
302
316
|
pi.appendEntry(TITLE_STATE_TYPE, title);
|
|
303
|
-
await applyHerdr(title.display, title.branch, previousDisplayTitle, request, controller);
|
|
317
|
+
await applyHerdr(title.display, title.branch, previousDisplayTitle, manual, request, controller);
|
|
304
318
|
return title.display;
|
|
305
319
|
} catch (error) {
|
|
306
|
-
if (isCurrent(request, controller)
|
|
320
|
+
if (isCurrent(request, controller)) {
|
|
307
321
|
ctx.ui.notify(error instanceof Error ? error.message : "Rename failed.", "warning");
|
|
308
322
|
}
|
|
309
323
|
return undefined;
|
|
@@ -341,8 +355,12 @@ export default function herdrRenameExtension(pi: ExtensionAPI): void {
|
|
|
341
355
|
if (!title || title !== saved?.display) return;
|
|
342
356
|
|
|
343
357
|
const { request, controller } = begin();
|
|
344
|
-
void applyHerdr(title, saved.branch, saved.display, request, controller)
|
|
345
|
-
.catch(() =>
|
|
358
|
+
void applyHerdr(title, saved.branch, saved.display, false, request, controller)
|
|
359
|
+
.catch((error) => {
|
|
360
|
+
if (isCurrent(request, controller)) {
|
|
361
|
+
ctx.ui.notify(error instanceof Error ? error.message : "Rename failed.", "warning");
|
|
362
|
+
}
|
|
363
|
+
})
|
|
346
364
|
.finally(() => finish(request, controller));
|
|
347
365
|
});
|
|
348
366
|
|