Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.4.0"
".": "0.5.0"
}
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## [0.5.0](https://github.com/zenobi-us/pi-worktrees/compare/v0.4.0...v0.5.0) (2026-04-01)


### Features

* **memory/epic:** add cli-only decoupling epic e9f4a1c2 ([4ce0639](https://github.com/zenobi-us/pi-worktrees/commit/4ce0639d4e09ee8f40b6baf2f20ba2848ef50ea8))
* **memory:** add epic for exposing subcommands as tools ([bcb9abc](https://github.com/zenobi-us/pi-worktrees/commit/bcb9abcaadf2d9bef5985c8eb064ba64724c22cc))
* **memory:** begin story 91 definition with task breakdown ([b75d1a1](https://github.com/zenobi-us/pi-worktrees/commit/b75d1a1020f89b814cea6cf83d70ecdfa81b81df))
* **memory:** define worktrees tool discovery backlog ([8c6489f](https://github.com/zenobi-us/pi-worktrees/commit/8c6489fe2b28a2c375131ec68965c3089f30cf87))
* **memory:** start cli-only execution planning ([43126bc](https://github.com/zenobi-us/pi-worktrees/commit/43126bcfd25c8f9f2778a81b003c4361fad98361))
* **worktree:** add branch-first create flow with explicit generator mode ([2fdb605](https://github.com/zenobi-us/pi-worktrees/commit/2fdb605edc5346ac5e0f836aa98cf695b310f690))
* **worktree:** improve interactive switching and lifecycle hooks (feat/interactive-ls-oncreate) ([#13](https://github.com/zenobi-us/pi-worktrees/issues/13)) ([b581f6e](https://github.com/zenobi-us/pi-worktrees/commit/b581f6eafa87badc4a4814125b09144df160d6ec))

## [0.4.0](https://github.com/zenobi-us/pi-worktrees/compare/v0.3.0...v0.4.0) (2026-03-21)


Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenobius/pi-worktrees",
"version": "0.4.0",
"version": "0.5.0",
"description": "Worktrees extension for Pi Coding Agent",
"author": {
"name": "Zenobius",
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Configuration (~/.pi/agent/pi-worktrees.config.json):
"onCreate": ["mise install", "bun install"],
"onSwitch": "mise run dev:resume",
"onBeforeRemove": "bun test",
"branchNameGenerator": "pi -p \"branch name for $PI_WORKTREE_PROMPT\" --model local/model",
"branchNameGenerator": "pi -p 'branch name for $PI_WORKTREE_PROMPT' --model local/model",
},
"github.com/org/*": {
"worktreeRoot": "~/work/org-other",
Expand Down
11 changes: 6 additions & 5 deletions src/services/branchNameGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ function renderCommand(template: string, input: string): string {
return template.replace(/\{\{prompt\}\}|\{prompt\}/g, quotedInput);
}


async function validateBranchName(branchName: string, cwd: string): Promise<boolean> {
const checker = spawn('git', ['check-ref-format', '--branch', branchName], {
cwd,
Expand All @@ -54,7 +53,9 @@ async function validateBranchName(branchName: string, cwd: string): Promise<bool
});
}

export async function generateBranchName(params: GenerateBranchNameParams): Promise<GenerateBranchNameResult> {
export async function generateBranchName(
params: GenerateBranchNameParams
): Promise<GenerateBranchNameResult> {
const timeoutMs = params.timeoutMs ?? BRANCH_NAME_GENERATOR_TIMEOUT_MS;
if (!params.commandTemplate?.trim()) {
return {
Expand Down Expand Up @@ -86,7 +87,7 @@ export async function generateBranchName(params: GenerateBranchNameParams): Prom
let stderr = '';
let done = false;

const timer = setTimeout(() => {
const timer = globalThis.setTimeout(() => {
if (done) {
return;
}
Expand All @@ -110,7 +111,7 @@ export async function generateBranchName(params: GenerateBranchNameParams): Prom
}

done = true;
clearTimeout(timer);
globalThis.clearTimeout(timer);
resolve({ kind: 'spawn-error', error: error.message });
});

Expand All @@ -120,7 +121,7 @@ export async function generateBranchName(params: GenerateBranchNameParams): Prom
}

done = true;
clearTimeout(timer);
globalThis.clearTimeout(timer);
resolve({ kind: 'success', stdout, stderr, code: code ?? 1 });
});
});
Expand Down
12 changes: 10 additions & 2 deletions tests/cmds/cmdCreate.branch-first.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,9 @@ describe('cmdCreate branch-first integration', () => {
);

const messages = notify.mock.calls.map(([message]) => String(message)).join('\n');
expect(messages).toContain("Using generated branch 'feature/from-generator' from branchNameGenerator");
expect(messages).toContain(
"Using generated branch 'feature/from-generator' from branchNameGenerator"
);
});

it('never uses generator when branch is explicitly provided without --generate', async () => {
Expand All @@ -189,7 +191,13 @@ describe('cmdCreate branch-first integration', () => {

expect(branchGeneratorService.generateBranchName).not.toHaveBeenCalled();
expect(gitService.git).toHaveBeenCalledWith(
['worktree', 'add', '-b', 'feature/direct-branch', '/tmp/repo.worktrees/feature-direct-branch'],
[
'worktree',
'add',
'-b',
'feature/direct-branch',
'/tmp/repo.worktrees/feature-direct-branch',
],
'/main/repo'
);
});
Expand Down
12 changes: 6 additions & 6 deletions tests/services/branchNameGenerator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { generateBranchName } from '../../src/services/branchNameGenerator.ts';
describe('branchNameGenerator', () => {
it('returns generated branch on success', async () => {
const result = await generateBranchName({
commandTemplate: "node -e \"process.stdout.write('feature/generated\\n')\"",
commandTemplate: 'node -e "process.stdout.write(\'feature/generated\\n\')"',
input: 'ignored',
cwd: process.cwd(),
timeoutMs: 200,
Expand All @@ -14,13 +14,13 @@ describe('branchNameGenerator', () => {
expect(result).toEqual({
ok: true,
branchName: 'feature/generated',
command: "node -e \"process.stdout.write('feature/generated\\n')\"",
command: 'node -e "process.stdout.write(\'feature/generated\\n\')"',
});
});

it('fails on timeout', async () => {
const result = await generateBranchName({
commandTemplate: "node -e \"setTimeout(() => process.stdout.write('feature/late'), 500)\"",
commandTemplate: 'node -e "setTimeout(() => process.stdout.write(\'feature/late\'), 500)"',
input: 'ignored',
cwd: process.cwd(),
timeoutMs: 25,
Expand All @@ -36,7 +36,7 @@ describe('branchNameGenerator', () => {

it('fails on non-zero exit', async () => {
const result = await generateBranchName({
commandTemplate: "node -e \"process.stderr.write('boom'); process.exit(2)\"",
commandTemplate: 'node -e "process.stderr.write(\'boom\'); process.exit(2)"',
input: 'ignored',
cwd: process.cwd(),
timeoutMs: 200,
Expand All @@ -52,7 +52,7 @@ describe('branchNameGenerator', () => {

it('fails on empty output', async () => {
const result = await generateBranchName({
commandTemplate: "node -e \"process.stdout.write(' ')\"",
commandTemplate: 'node -e "process.stdout.write(\' \')"',
input: 'ignored',
cwd: process.cwd(),
timeoutMs: 200,
Expand All @@ -68,7 +68,7 @@ describe('branchNameGenerator', () => {

it('fails on invalid branch output', async () => {
const result = await generateBranchName({
commandTemplate: "node -e \"process.stdout.write('not a branch')\"",
commandTemplate: 'node -e "process.stdout.write(\'not a branch\')"',
input: 'ignored',
cwd: process.cwd(),
timeoutMs: 200,
Expand Down
3 changes: 1 addition & 2 deletions tests/services/config.schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,5 @@ describe('PiWorktreeConfigSchema branchNameGenerator', () => {
},
},
});

});
});
});
Loading