@zenobius/pi-worktrees 0.4.0-next.16 → 0.4.0-next.19

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 CHANGED
@@ -15,7 +15,8 @@ When you’re doing multiple feature branches, hotfixes, or experiments, `git wo
15
15
 
16
16
  `pi-worktrees` gives you a guided interface inside Pi:
17
17
 
18
- - Create feature worktrees with consistent branch naming (`feature/<name>`)
18
+ - Create branch-first worktrees (`/worktree create <branch>`) with predictable naming
19
+ - Optionally generate branch names via explicit opt-in (`/worktree create --generate ...`)
19
20
  - List and inspect active worktrees
20
21
  - Remove worktrees safely (with confirmations)
21
22
  - Prune stale worktree references
@@ -59,14 +60,15 @@ pi install npm:@zenobius/pi-worktrees
59
60
 
60
61
  ```text
61
62
  /worktree init
62
- /worktree create auth-refactor
63
+ /worktree create feature/auth-refactor
64
+ /worktree create hotfix/login-timeout --name login-timeout
63
65
  /worktree list
64
66
  ```
65
67
 
66
68
  3. Optional: jump into it from your shell using the printed path:
67
69
 
68
70
  ```text
69
- /worktree cd auth-refactor
71
+ /worktree cd feature-auth-refactor
70
72
  ```
71
73
 
72
74
  ## Quick start
@@ -75,11 +77,12 @@ In Pi:
75
77
 
76
78
  ```text
77
79
  /worktree init
78
- /worktree create auth-refactor
80
+ /worktree create feature/auth-refactor
81
+ /worktree create spike/new-parser --name parser-spike
79
82
  /worktree list
80
83
  /worktree status
81
- /worktree cd auth-refactor
82
- /worktree remove auth-refactor
84
+ /worktree cd feature-auth-refactor
85
+ /worktree remove feature-auth-refactor
83
86
  /worktree prune
84
87
  ```
85
88
 
@@ -114,7 +117,7 @@ This creates a new Zellij tab with Neovim and Pi running in the new worktree pat
114
117
  | `/worktree settings` | Show all current settings |
115
118
  | `/worktree settings <key>` | Get one setting (`worktreeRoot`, `parentDir` alias, `onCreate`) |
116
119
  | `/worktree settings <key> <value>` | Set one setting |
117
- | `/worktree create <feature-name>` | Create a new worktree + branch `feature/<feature-name>` |
120
+ | `/worktree create <branch> [--name <worktree-name>]`<br/>`/worktree create --generate [--name <worktree-name>] <prompt-or-name>` | Create a new worktree from `<branch>` (default mode) or generate one via configured `branchNameGenerator` (opt-in with `--generate`) |
118
121
  | `/worktree list` | List all worktrees (`/worktree ls` alias) |
119
122
  | `/worktree status` | Show current repo/worktree status |
120
123
  | `/worktree cd <name>` | Print matching worktree path |
@@ -133,11 +136,12 @@ Settings live in `~/.pi/agent/pi-worktrees-settings.json`.
133
136
  "worktrees": {
134
137
  "github.com/org/repo": {
135
138
  "worktreeRoot": "~/work/org/repo.worktrees",
136
- "onCreate": ["mise install", "bun install"]
139
+ "onCreate": ["mise install", "bun install"],
137
140
  },
138
141
  "github.com/org/*": {
139
142
  "worktreeRoot": "~/work/org/shared.worktrees",
140
- "onCreate": "mise setup"
143
+ "onCreate": "mise setup",
144
+ "branchNameGenerator": "pi -p \"branch name for $PI_WORKTREE_PROMPT\" --model local/model"
141
145
  }
142
146
  },
143
147
  "matchingStrategy": "fail-on-tie",
@@ -168,6 +172,7 @@ Settings live in `~/.pi/agent/pi-worktrees-settings.json`.
168
172
  | `onCreateCmdDisplayPendingColor` | `string` | `dim` | Pi theme color name for pending/running command lines. |
169
173
  | `onCreateCmdDisplaySuccessColor` | `string` | `success` | Pi theme color name for successful command lines. |
170
174
  | `onCreateCmdDisplayErrorColor` | `string` | `error` | Pi theme color name for failed command lines. |
175
+ | `worktrees[*].branchNameGenerator` | `string` | unset | Optional command used only by `/worktree create --generate ...`. Must print exactly one branch name to stdout. Receives `$PI_WORKTREE_PROMPT` env var and supports `{{prompt}}` / `{prompt}` token replacement. |
171
176
  | `worktree` (legacy) | `WorktreeSettings` | n/a | Legacy fallback shape; migrated automatically. |
172
177
 
173
178
  ### Matching model
@@ -231,6 +236,63 @@ Where new worktrees are created.
231
236
 
232
237
  > Backward compatibility: `parentDir` is still accepted as a deprecated alias for `worktreeRoot`.
233
238
  > The extension will migrate existing `parentDir` values to `worktreeRoot` automatically.
239
+ ### Create command naming contract
240
+
241
+ `/worktree create` is branch-first:
242
+
243
+ - Required first argument is the **branch name** to create.
244
+ - Default worktree folder name is `slugify(branch)`.
245
+ - Optional `--name <worktree-name>` overrides the derived folder name.
246
+
247
+ ### Optional branch generator (safe opt-in)
248
+
249
+ Generator mode is **never automatic**. You must pass `--generate` explicitly:
250
+
251
+ ```text
252
+ /worktree create --generate login-flow
253
+ /worktree create --generate --name ui-login login-flow
254
+ ```
255
+
256
+ Safety behavior:
257
+ - Branch-first remains default source of truth.
258
+ - `branchNameGenerator` is ignored unless `--generate` is present.
259
+ - Generator command runs with a strict 5s timeout.
260
+ - On timeout, non-zero exit, empty stdout, or invalid branch output: command fails and no worktree is created.
261
+ - When a generated branch is used, Pi emits a provenance message before creation.
262
+ Examples:
263
+
264
+ ```text
265
+ /worktree create feature/login
266
+ # branch: feature/login, worktree folder: feature-login
267
+
268
+ /worktree create feature/login --name ui-login
269
+ # branch: feature/login, worktree folder: ui-login
270
+ ```
271
+
272
+ ### Migration from legacy `<feature-name>` usage
273
+
274
+ Old mental model:
275
+
276
+ ```text
277
+ /worktree create login
278
+ # previously implied branch feature/login
279
+ ```
280
+
281
+ Current behavior:
282
+
283
+ ```text
284
+ /worktree create login
285
+ # branch: login, worktree folder: login
286
+ ```
287
+
288
+ To preserve old semantics explicitly:
289
+
290
+ ```text
291
+ /worktree create feature/login --name login
292
+ ```
293
+
294
+ Current releases emit a warning when legacy-style single tokens are detected without `--name`.
295
+
234
296
  ### Template variables
235
297
 
236
298
  Available in `worktreeRoot` and `onCreate` values:
@@ -290,14 +352,14 @@ This extension does not apply a separate ad-hoc deprecation mechanism.
290
352
  | v
291
353
  | [Save settings] -----------------> [Idle]
292
354
  |
293
- +--> [create <name>] --> [Validate repo/name/branch/path]
355
+ +--> [create <branch> [--name <worktree-name>]] --> [Validate repo/name/branch/path]
294
356
  | |fail
295
357
  | v
296
358
  | [Error] -------------> [Idle]
297
359
  | |
298
360
  | pass
299
361
  | v
300
- | [git worktree add -b feature/<name>]
362
+ | [git worktree add -b <branch> <worktreePath>]
301
363
  | |fail
302
364
  | v
303
365
  | [Error] -------------> [Idle]
@@ -384,8 +446,8 @@ This extension does not apply a separate ad-hoc deprecation mechanism.
384
446
  ### `Not in a git repository`
385
447
  Run commands from inside a git repo (or one of its worktrees).
386
448
 
387
- ### `Branch 'feature/<name>' already exists`
388
- Choose another feature name or delete/rename the branch.
449
+ ### `Branch '<branch>' already exists`
450
+ Choose another branch name or delete/rename the existing branch.
389
451
 
390
452
  ### Can’t remove worktree due to changes
391
453
  Use `/worktree remove <name>`, then confirm the force remove prompt.
@@ -0,0 +1,21 @@
1
+ interface CreateCommandArgsBase {
2
+ worktreeName: string;
3
+ explicitName: boolean;
4
+ }
5
+ export interface CreateCommandBranchArgs extends CreateCommandArgsBase {
6
+ generate: false;
7
+ branch: string;
8
+ showLegacyWarning: boolean;
9
+ }
10
+ export interface CreateCommandGenerateArgs extends CreateCommandArgsBase {
11
+ generate: true;
12
+ generatorInput: string;
13
+ showLegacyWarning: false;
14
+ }
15
+ export type CreateCommandArgs = CreateCommandBranchArgs | CreateCommandGenerateArgs;
16
+ export interface CreateCommandArgError {
17
+ error: string;
18
+ }
19
+ export declare function slugifyBranch(branch: string): string;
20
+ export declare function parseCreateCommandArgs(args: string): CreateCommandArgs | CreateCommandArgError;
21
+ export {};
package/dist/index.js CHANGED
@@ -30,7 +30,8 @@ var WorktreeSettingsSchema = TypeObject({
30
30
  parentDir: Optional(TypeString()),
31
31
  onCreate: Optional(HookCommandsSchema),
32
32
  onSwitch: Optional(HookCommandsSchema),
33
- onBeforeRemove: Optional(HookCommandsSchema)
33
+ onBeforeRemove: Optional(HookCommandsSchema),
34
+ branchNameGenerator: Optional(TypeString())
34
35
  }, {
35
36
  $id: "WorktreeSettingsConfig",
36
37
  additionalProperties: false
@@ -960,21 +961,251 @@ var DefaultWorktreeSettings = {
960
961
  };
961
962
  var DefaultLogfileTemplate = DEFAULT_LOGFILE_TEMPLATE;
962
963
 
964
+ // src/cmds/createArgs.ts
965
+ var SIMPLE_NAME_PATTERN = /^[A-Za-z0-9._-]+$/;
966
+ var BRANCH_FIRST_USAGE = "/worktree create <branch> [--name <worktree-name>]";
967
+ var GENERATE_USAGE = "/worktree create --generate [--name <worktree-name>] <prompt-or-name>";
968
+ var CREATE_USAGE = `Usage: ${BRANCH_FIRST_USAGE} OR ${GENERATE_USAGE}`;
969
+ function slugifyBranch(branch) {
970
+ return branch.toLowerCase().replace(/[\s/_.]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
971
+ }
972
+ function isValidWorktreeName(name) {
973
+ return SIMPLE_NAME_PATTERN.test(name);
974
+ }
975
+ function isLegacyStyleToken(token) {
976
+ if (!token) {
977
+ return false;
978
+ }
979
+ if (token.includes("/")) {
980
+ return false;
981
+ }
982
+ return SIMPLE_NAME_PATTERN.test(token);
983
+ }
984
+ function parseCreateCommandArgs(args) {
985
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
986
+ if (tokens.length === 0) {
987
+ return { error: CREATE_USAGE };
988
+ }
989
+ let explicitName;
990
+ let useGenerator = false;
991
+ const positional = [];
992
+ for (let index = 0;index < tokens.length; index += 1) {
993
+ const token = tokens[index];
994
+ if (token === "--name") {
995
+ if (explicitName) {
996
+ return { error: "Duplicate --name option. Provide it only once." };
997
+ }
998
+ const value = tokens[index + 1];
999
+ if (!value || value.startsWith("--")) {
1000
+ return {
1001
+ error: `Missing value for --name. ${CREATE_USAGE}`
1002
+ };
1003
+ }
1004
+ explicitName = value;
1005
+ index += 1;
1006
+ continue;
1007
+ }
1008
+ if (token === "--generate") {
1009
+ if (useGenerator) {
1010
+ return { error: "Duplicate --generate option. Provide it only once." };
1011
+ }
1012
+ useGenerator = true;
1013
+ continue;
1014
+ }
1015
+ if (token.startsWith("--")) {
1016
+ return {
1017
+ error: `Unknown argument: ${token}. ${CREATE_USAGE}`
1018
+ };
1019
+ }
1020
+ positional.push(token);
1021
+ }
1022
+ if (positional.length !== 1) {
1023
+ return {
1024
+ error: `Expected exactly one ${useGenerator ? "<prompt-or-name>" : "<branch>"}. ${CREATE_USAGE}`
1025
+ };
1026
+ }
1027
+ if (explicitName && !isValidWorktreeName(explicitName)) {
1028
+ return {
1029
+ error: "Invalid worktree name for --name. Use only letters, numbers, '.', '_' or '-' (no '/')."
1030
+ };
1031
+ }
1032
+ const sourceToken = positional[0];
1033
+ const derivedName = slugifyBranch(sourceToken);
1034
+ if (!explicitName && !derivedName) {
1035
+ return {
1036
+ error: "Derived worktree name is empty after slugify. Use a source with letters/numbers or pass --name <worktree-name>."
1037
+ };
1038
+ }
1039
+ if (useGenerator) {
1040
+ return {
1041
+ generate: true,
1042
+ generatorInput: sourceToken,
1043
+ worktreeName: explicitName ?? derivedName,
1044
+ explicitName: Boolean(explicitName),
1045
+ showLegacyWarning: false
1046
+ };
1047
+ }
1048
+ return {
1049
+ generate: false,
1050
+ branch: sourceToken,
1051
+ worktreeName: explicitName ?? derivedName,
1052
+ explicitName: Boolean(explicitName),
1053
+ showLegacyWarning: !explicitName && isLegacyStyleToken(sourceToken)
1054
+ };
1055
+ }
1056
+
1057
+ // src/services/branchNameGenerator.ts
1058
+ import { spawn as spawn2 } from "child_process";
1059
+ var BRANCH_NAME_GENERATOR_TIMEOUT_MS = 5000;
1060
+ function shellQuote(value) {
1061
+ return `'${value.replace(/'/g, `'\\''`)}'`;
1062
+ }
1063
+ function renderCommand(template, input) {
1064
+ const quotedInput = shellQuote(input);
1065
+ return template.replace(/\{\{prompt\}\}|\{prompt\}/g, quotedInput);
1066
+ }
1067
+ async function validateBranchName(branchName, cwd) {
1068
+ const checker = spawn2("git", ["check-ref-format", "--branch", branchName], {
1069
+ cwd,
1070
+ shell: false,
1071
+ stdio: "ignore"
1072
+ });
1073
+ return new Promise((resolve2) => {
1074
+ checker.on("close", (code) => resolve2(code === 0));
1075
+ checker.on("error", () => resolve2(false));
1076
+ });
1077
+ }
1078
+ async function generateBranchName(params) {
1079
+ const timeoutMs = params.timeoutMs ?? BRANCH_NAME_GENERATOR_TIMEOUT_MS;
1080
+ if (!params.commandTemplate?.trim()) {
1081
+ return {
1082
+ ok: false,
1083
+ code: "missing-config",
1084
+ message: "No branchNameGenerator configured for this repository. Set worktrees.<pattern>.branchNameGenerator or run '/worktree create <branch>' without --generate."
1085
+ };
1086
+ }
1087
+ const command = renderCommand(params.commandTemplate, params.input);
1088
+ const result = await new Promise((resolve2) => {
1089
+ const child = spawn2(command, {
1090
+ cwd: params.cwd,
1091
+ shell: true,
1092
+ stdio: ["ignore", "pipe", "pipe"],
1093
+ env: {
1094
+ ...process.env,
1095
+ PI_WORKTREE_PROMPT: params.input
1096
+ }
1097
+ });
1098
+ let stdout = "";
1099
+ let stderr = "";
1100
+ let done = false;
1101
+ const timer = setTimeout(() => {
1102
+ if (done) {
1103
+ return;
1104
+ }
1105
+ done = true;
1106
+ child.kill("SIGKILL");
1107
+ resolve2({ kind: "timeout" });
1108
+ }, timeoutMs);
1109
+ child.stdout?.on("data", (chunk) => {
1110
+ stdout += chunk.toString();
1111
+ });
1112
+ child.stderr?.on("data", (chunk) => {
1113
+ stderr += chunk.toString();
1114
+ });
1115
+ child.on("error", (error) => {
1116
+ if (done) {
1117
+ return;
1118
+ }
1119
+ done = true;
1120
+ clearTimeout(timer);
1121
+ resolve2({ kind: "spawn-error", error: error.message });
1122
+ });
1123
+ child.on("close", (code) => {
1124
+ if (done) {
1125
+ return;
1126
+ }
1127
+ done = true;
1128
+ clearTimeout(timer);
1129
+ resolve2({ kind: "success", stdout, stderr, code: code ?? 1 });
1130
+ });
1131
+ });
1132
+ if (result.kind === "timeout") {
1133
+ return {
1134
+ ok: false,
1135
+ code: "timeout",
1136
+ message: `branchNameGenerator timed out after ${timeoutMs}ms. Make it faster or run '/worktree create <branch>' manually.`
1137
+ };
1138
+ }
1139
+ if (result.kind === "spawn-error") {
1140
+ return {
1141
+ ok: false,
1142
+ code: "spawn-error",
1143
+ message: `Failed to run branchNameGenerator command: ${result.error}`
1144
+ };
1145
+ }
1146
+ if (result.code !== 0) {
1147
+ const stderr = result.stderr.trim();
1148
+ return {
1149
+ ok: false,
1150
+ code: "non-zero-exit",
1151
+ message: `branchNameGenerator exited with code ${result.code}.${stderr ? ` stderr: ${stderr}` : ""}`
1152
+ };
1153
+ }
1154
+ const branchName = result.stdout.trim();
1155
+ if (!branchName) {
1156
+ return {
1157
+ ok: false,
1158
+ code: "empty-output",
1159
+ message: "branchNameGenerator produced empty output. Ensure the command prints exactly one branch name to stdout."
1160
+ };
1161
+ }
1162
+ const valid = await validateBranchName(branchName, params.cwd);
1163
+ if (!valid) {
1164
+ return {
1165
+ ok: false,
1166
+ code: "invalid-output",
1167
+ message: `branchNameGenerator output is not a valid branch name: '${branchName}'. Fix the command output or run '/worktree create <branch>' manually.`
1168
+ };
1169
+ }
1170
+ return {
1171
+ ok: true,
1172
+ branchName,
1173
+ command: params.commandTemplate
1174
+ };
1175
+ }
1176
+
963
1177
  // src/cmds/cmdCreate.ts
964
1178
  async function cmdCreate(args, ctx, deps) {
965
- const featureName = args.trim();
966
- if (!featureName) {
967
- ctx.ui.notify("Usage: /worktree create <feature-name>", "error");
1179
+ const parsed = parseCreateCommandArgs(args);
1180
+ if ("error" in parsed) {
1181
+ ctx.ui.notify(parsed.error, "error");
968
1182
  return;
969
1183
  }
1184
+ const worktreeName = parsed.worktreeName;
970
1185
  if (!isGitRepo(ctx.cwd)) {
971
1186
  ctx.ui.notify("Not in a git repository", "error");
972
1187
  return;
973
1188
  }
974
1189
  const current = deps.configService.current(ctx);
975
- const worktreePath = join3(current.parentDir, featureName);
976
- const branchName = `feature/${featureName}`;
977
- const existingWorktree = listWorktrees(ctx.cwd).find((worktree) => worktree.path === worktreePath || basename3(worktree.path) === featureName || worktree.branch === branchName);
1190
+ let branchName = parsed.generate ? "" : parsed.branch;
1191
+ if (parsed.generate) {
1192
+ const generated = await generateBranchName({
1193
+ commandTemplate: current.branchNameGenerator,
1194
+ input: parsed.generatorInput,
1195
+ cwd: ctx.cwd
1196
+ });
1197
+ if (!generated.ok) {
1198
+ ctx.ui.notify(generated.message, "error");
1199
+ return;
1200
+ }
1201
+ branchName = generated.branchName;
1202
+ ctx.ui.notify(`Using generated branch '${branchName}' from branchNameGenerator (input: '${parsed.generatorInput}').`, "info");
1203
+ }
1204
+ if (!parsed.generate && parsed.showLegacyWarning) {
1205
+ ctx.ui.notify(`Legacy create style detected: '/worktree create <feature-name>' is deprecated. '${branchName}' is now treated as the branch name. If you want old semantics, run '/worktree create feature/${branchName}' (optionally '--name ${branchName}').`, "warning");
1206
+ }
1207
+ const worktreePath = join3(current.parentDir, worktreeName);
1208
+ const existingWorktree = listWorktrees(ctx.cwd).find((worktree) => worktree.path === worktreePath || basename3(worktree.path) === worktreeName || worktree.branch === branchName);
978
1209
  if (existingWorktree) {
979
1210
  if (!ctx.hasUI) {
980
1211
  ctx.ui.notify(`Worktree already exists at: ${worktreePath}`, "error");
@@ -1029,11 +1260,11 @@ Switch to this worktree?`;
1029
1260
  return;
1030
1261
  } catch {}
1031
1262
  ensureExcluded(ctx.cwd, current.parentDir);
1032
- const stopBusy = deps.statusService.busy(ctx, `Creating worktree: ${featureName}...`);
1263
+ const stopBusy = deps.statusService.busy(ctx, `Creating worktree: ${worktreeName}...`);
1033
1264
  try {
1034
1265
  git(["worktree", "add", "-b", branchName, worktreePath], current.mainWorktree);
1035
1266
  stopBusy();
1036
- deps.statusService.positive(ctx, `Created: ${featureName}`);
1267
+ deps.statusService.positive(ctx, `Created: ${worktreeName}`);
1037
1268
  } catch (err) {
1038
1269
  stopBusy();
1039
1270
  deps.statusService.critical(ctx, `Failed to create worktree`);
@@ -1042,12 +1273,12 @@ Switch to this worktree?`;
1042
1273
  }
1043
1274
  const createdCtx = {
1044
1275
  path: worktreePath,
1045
- name: featureName,
1276
+ name: worktreeName,
1046
1277
  branch: branchName,
1047
1278
  ...current
1048
1279
  };
1049
1280
  const sessionId = sanitizePathPart(ctx.sessionManager?.getSessionId?.() || "session");
1050
- const safeName = sanitizePathPart(featureName);
1281
+ const safeName = sanitizePathPart(worktreeName);
1051
1282
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
1052
1283
  const logPath = resolveLogfilePath(current.logfile ?? DefaultLogfileTemplate, {
1053
1284
  sessionId,
@@ -1689,7 +1920,8 @@ var HELP_TEXT = `
1689
1920
  Commands:
1690
1921
  /worktree init Configure worktree settings interactively
1691
1922
  /worktree settings [key] [val] Get/set individual settings
1692
- /worktree create <feature-name> Create new worktree with branch
1923
+ /worktree create <branch> [--name <worktree-name>] Create new worktree from branch
1924
+ /worktree create --generate [--name <worktree-name>] <prompt-or-name> Generate branch via config command
1693
1925
  /worktree list List worktrees and run onSwitch for a selection
1694
1926
  /worktree remove <name> Remove a worktree (runs onBeforeRemove if set)
1695
1927
  /worktree status Show current worktree info
@@ -1704,7 +1936,8 @@ Configuration (~/.pi/agent/pi-worktrees.config.json):
1704
1936
  "worktreeRoot": "~/work/org",
1705
1937
  "onCreate": ["mise install", "bun install"],
1706
1938
  "onSwitch": "mise run dev:resume",
1707
- "onBeforeRemove": "bun test"
1939
+ "onBeforeRemove": "bun test",
1940
+ "branchNameGenerator": "pi -p "branch name for $PI_WORKTREE_PROMPT" --model local/model",
1708
1941
  },
1709
1942
  "github.com/org/*": {
1710
1943
  "worktreeRoot": "~/work/org-other",
@@ -1730,6 +1963,11 @@ Pattern matching: exact URL > most-specific glob > fallback (worktree)
1730
1963
  Matching strategies: fail-on-tie | first-wins | last-wins
1731
1964
 
1732
1965
  Config note: parentDir is deprecated and supported as an alias for worktreeRoot.
1966
+ Naming note: default worktree name is slugify(branch); explicit '--name' takes precedence.
1967
+ Generator note: '--generate' is explicit opt-in and requires branchNameGenerator config.
1968
+ Generated branch output must be valid and is never used unless --generate is present.
1969
+ Migration note: legacy '/worktree create <feature-name>' is deprecated and now treats token as branch.
1970
+ Use '/worktree create feature/<name> --name <name>' to preserve old semantics.
1733
1971
  Hook vars: {{path}}, {{name}}, {{branch}}, {{project}}, {{mainWorktree}}
1734
1972
  Hooks: onCreate (new), onSwitch (existing), onBeforeRemove (pre-delete, non-zero blocks)
1735
1973
  Logfile vars: {sessionId} / {{sessionId}}, {name} / {{name}}, {timestamp} / {{timestamp}}
@@ -0,0 +1,20 @@
1
+ export declare const BRANCH_NAME_GENERATOR_TIMEOUT_MS = 5000;
2
+ export type BranchNameGeneratorErrorCode = 'missing-config' | 'timeout' | 'non-zero-exit' | 'empty-output' | 'invalid-output' | 'spawn-error';
3
+ export interface GenerateBranchNameParams {
4
+ commandTemplate: string | undefined;
5
+ input: string;
6
+ cwd: string;
7
+ timeoutMs?: number;
8
+ }
9
+ export interface GenerateBranchNameSuccess {
10
+ ok: true;
11
+ branchName: string;
12
+ command: string;
13
+ }
14
+ export interface GenerateBranchNameFailure {
15
+ ok: false;
16
+ code: BranchNameGeneratorErrorCode;
17
+ message: string;
18
+ }
19
+ export type GenerateBranchNameResult = GenerateBranchNameSuccess | GenerateBranchNameFailure;
20
+ export declare function generateBranchName(params: GenerateBranchNameParams): Promise<GenerateBranchNameResult>;
@@ -6,6 +6,7 @@ export declare function createPiWorktreeConfigService(): Promise<{
6
6
  worktreeRoot?: string | undefined;
7
7
  onSwitch?: string | string[] | undefined;
8
8
  onBeforeRemove?: string | string[] | undefined;
9
+ branchNameGenerator?: string | undefined;
9
10
  }>;
10
11
  current: (ctx: {
11
12
  cwd: string;
@@ -27,6 +28,7 @@ export declare function createPiWorktreeConfigService(): Promise<{
27
28
  worktreeRoot?: string | undefined;
28
29
  onSwitch?: string | string[] | undefined;
29
30
  onBeforeRemove?: string | string[] | undefined;
31
+ branchNameGenerator?: string | undefined;
30
32
  };
31
33
  save: (data: PiWorktreeConfig) => Promise<void>;
32
34
  config: {
@@ -37,6 +39,7 @@ export declare function createPiWorktreeConfigService(): Promise<{
37
39
  onCreate: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
38
40
  onSwitch: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
39
41
  onBeforeRemove: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
42
+ branchNameGenerator: import("typebox").TOptional<import("typebox").TString>;
40
43
  }>>>;
41
44
  matchingStrategy: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TLiteral<"fail-on-tie">, import("typebox").TLiteral<"first-wins">, import("typebox").TLiteral<"last-wins">]>>;
42
45
  logfile: import("typebox").TOptional<import("typebox").TString>;
@@ -53,6 +56,7 @@ export declare function createPiWorktreeConfigService(): Promise<{
53
56
  onCreate: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
54
57
  onSwitch: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
55
58
  onBeforeRemove: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
59
+ branchNameGenerator: import("typebox").TOptional<import("typebox").TString>;
56
60
  }>> | undefined;
57
61
  logfile?: string | undefined;
58
62
  onCreateDisplayOutputMaxLines?: number | undefined;
@@ -75,6 +79,7 @@ export declare function createPiWorktreeConfigService(): Promise<{
75
79
  onCreate: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
76
80
  onSwitch: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
77
81
  onBeforeRemove: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
82
+ branchNameGenerator: import("typebox").TOptional<import("typebox").TString>;
78
83
  }>>>;
79
84
  matchingStrategy: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TLiteral<"fail-on-tie">, import("typebox").TLiteral<"first-wins">, import("typebox").TLiteral<"last-wins">]>>;
80
85
  logfile: import("typebox").TOptional<import("typebox").TString>;
@@ -91,6 +96,7 @@ export declare function createPiWorktreeConfigService(): Promise<{
91
96
  onCreate: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
92
97
  onSwitch: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
93
98
  onBeforeRemove: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
99
+ branchNameGenerator: import("typebox").TOptional<import("typebox").TString>;
94
100
  }>> | undefined;
95
101
  logfile?: string | undefined;
96
102
  onCreateDisplayOutputMaxLines?: number | undefined;
@@ -5,6 +5,7 @@ declare const WorktreeSettingsSchema: import("typebox").TObject<{
5
5
  onCreate: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
6
6
  onSwitch: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
7
7
  onBeforeRemove: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
8
+ branchNameGenerator: import("typebox").TOptional<import("typebox").TString>;
8
9
  }>;
9
10
  declare const MatchingStrategySchema: import("typebox").TUnion<[import("typebox").TLiteral<"fail-on-tie">, import("typebox").TLiteral<"first-wins">, import("typebox").TLiteral<"last-wins">]>;
10
11
  declare const MatchStrategyResultSchema: import("typebox").TUnion<[import("typebox").TLiteral<"exact">, import("typebox").TLiteral<"unmatched">]>;
@@ -15,6 +16,7 @@ export declare const PiWorktreeConfigSchema: import("typebox").TObject<{
15
16
  onCreate: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
16
17
  onSwitch: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
17
18
  onBeforeRemove: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TString, import("typebox").TArray<import("typebox").TString>]>>;
19
+ branchNameGenerator: import("typebox").TOptional<import("typebox").TString>;
18
20
  }>>>;
19
21
  matchingStrategy: import("typebox").TOptional<import("typebox").TUnion<[import("typebox").TLiteral<"fail-on-tie">, import("typebox").TLiteral<"first-wins">, import("typebox").TLiteral<"last-wins">]>>;
20
22
  logfile: import("typebox").TOptional<import("typebox").TString>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenobius/pi-worktrees",
3
- "version": "0.4.0-next.16",
3
+ "version": "0.4.0-next.19",
4
4
  "description": "Worktrees extension for Pi Coding Agent",
5
5
  "author": {
6
6
  "name": "Zenobius",