Skip to content

Commit 082fd62

Browse files
committed
feat: run worktree.postSpawn for an existing builder
CLI: new `afx setup <builder-id>` command. Looks up the builder, reads worktree.postSpawn from .codev/config.json, and runs the configured commands sequentially in the builder's worktree via the existing runPostSpawnHooks helper. Empty postSpawn → friendly info; non-zero exit → aborts with run()'s native error (same semantics as the original spawn-time postSpawn). VSCode: new `codev.runWorktreeSetup` command in the builder right-click context menu. Opens a fresh VSCode integrated terminal in the worktree and sends each postSpawn command via terminal.sendText — output streams live (preferable to buffered exec for long-running installs like `pnpm install` or `uv sync` with progress bars). Naming: "Run" (not "Re-run") because the command serves both the first- time case (e.g. when postSpawn was added to config AFTER the builder spawned) and the repeat case (lockfile changed, recovery from aborted setup). "Re-run" privileged only the repeat case. Parallels the existing "Run Dev Server" naming. Context menu regrouped so worktree-touching commands cluster cleanly: Open Builder Terminal ───────────────────── Open Worktree Folder ───────────────────── View Diff ───────────────────── Run Worktree Setup Run Dev Server Stop Dev Server Four semantic groups (1_terminal / 2_files / 3_review / 4_worktree) with the "worktree commands" group at the bottom — all the actions that execute things inside the worktree live together. Per request: no confirmation prompt — the user invoked it explicitly. Docs (CLAUDE.md, AGENTS.md) and packages/vscode/CHANGELOG.md updated to list all six right-click commands plus the supporting fixes from this branch (theme-aware icon, palette tightening, etc.). Refs cluesmith#689, cluesmith#690.
1 parent 0b362f4 commit 082fd62

8 files changed

Lines changed: 192 additions & 12 deletions

File tree

AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,8 +312,9 @@ The same actions are available via right-click on any builder row in the Codev s
312312

313313
- **Codev: Open Builder Terminal** — opens that builder's AI terminal in a VSCode tab (same as left-clicking the row).
314314
- **Codev: Open Worktree Folder** — opens `.builders/<id>/` in the OS file manager (Finder on macOS, Explorer on Windows, xdg-open on Linux).
315+
- **Codev: Run Worktree Setup** — runs the configured `worktree.postSpawn` commands against the existing worktree. Useful when the lockfile changed (need to reinstall deps), `postSpawn` was extended after the builder spawned, or the original setup aborted mid-run. Opens a fresh VSCode terminal so install output streams live. Available via CLI too: `afx setup <builder-id>`.
315316
- **Codev: View Diff** — opens a single unified diff editor for `main...HEAD` of that builder's worktree, with a file-list pane on the left (matches VSCode's built-in Source Control "Working Tree" view). Status icons indicate added / modified / deleted. Empty diff → friendly toast.
316-
- **Codev: Run Dev Server** — reads `worktree.devCommand` from `.codev/config.json`, asks Tower to spawn a dev PTY in the builder's worktree, and opens it as a VSCode terminal tab named `Dev: <builder-id>`. If another builder's dev is already running, you get a modal asking whether to swap.
317+
- **Codev: Run Dev Server** — reads `worktree.devCommand` from `.codev/config.json`, asks Tower to spawn a dev PTY in the builder's worktree, and opens it as a VSCode terminal tab named `Codev: <name> (dev)`. If another builder's dev is already running, you get a modal asking whether to swap.
317318
- **Codev: Stop Dev Server** — kills the running dev PTY and closes its tab.
318319

319320
The three commands are also available from the command palette (Cmd+Shift+P). No default keybindings; bind via `keybindings.json` if you use them often.

CLAUDE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,8 +312,9 @@ The same actions are available via right-click on any builder row in the Codev s
312312

313313
- **Codev: Open Builder Terminal** — opens that builder's AI terminal in a VSCode tab (same as left-clicking the row).
314314
- **Codev: Open Worktree Folder** — opens `.builders/<id>/` in the OS file manager (Finder on macOS, Explorer on Windows, xdg-open on Linux).
315+
- **Codev: Run Worktree Setup** — runs the configured `worktree.postSpawn` commands against the existing worktree. Useful when the lockfile changed (need to reinstall deps), `postSpawn` was extended after the builder spawned, or the original setup aborted mid-run. Opens a fresh VSCode terminal so install output streams live. Available via CLI too: `afx setup <builder-id>`.
315316
- **Codev: View Diff** — opens a single unified diff editor for `main...HEAD` of that builder's worktree, with a file-list pane on the left (matches VSCode's built-in Source Control "Working Tree" view). Status icons indicate added / modified / deleted. Empty diff → friendly toast.
316-
- **Codev: Run Dev Server** — reads `worktree.devCommand` from `.codev/config.json`, asks Tower to spawn a dev PTY in the builder's worktree, and opens it as a VSCode terminal tab named `Dev: <builder-id>`. If another builder's dev is already running, you get a modal asking whether to swap.
317+
- **Codev: Run Dev Server** — reads `worktree.devCommand` from `.codev/config.json`, asks Tower to spawn a dev PTY in the builder's worktree, and opens it as a VSCode terminal tab named `Codev: <name> (dev)`. If another builder's dev is already running, you get a modal asking whether to swap.
317318
- **Codev: Stop Dev Server** — kills the running dev PTY and closes its tab.
318319

319320
The three commands are also available from the command palette (Cmd+Shift+P). No default keybindings; bind via `keybindings.json` if you use them often.

packages/codev/src/agent-farm/cli.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,20 @@ export async function runAgentFarm(args: string[]): Promise<void> {
203203
}
204204
});
205205

206+
// Setup command — run worktree.postSpawn for an existing builder (#689)
207+
program
208+
.command('setup [builder-id]')
209+
.description('Run worktree.postSpawn against an existing builder (e.g. after a lockfile change)')
210+
.action(async (builderId) => {
211+
const { setup } = await import('./commands/setup.js');
212+
try {
213+
await setup({ builderId });
214+
} catch (error) {
215+
logger.error(error instanceof Error ? error.message : String(error));
216+
process.exit(1);
217+
}
218+
});
219+
206220
// Spawn command
207221
const spawnCmd = program
208222
.command('spawn')
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* `afx setup <builder-id>` — run the configured `worktree.postSpawn`
3+
* commands against an existing builder's worktree.
4+
*
5+
* Use cases: lockfile changed and dependencies need reinstalling; a new
6+
* step was added to `worktree.postSpawn` after the builder was spawned;
7+
* the original spawn aborted mid-setup and the worktree needs recovery;
8+
* running setup for the first time on a builder that predates the config.
9+
*
10+
* No confirmation prompt — the user invoked this explicitly. If you want
11+
* a dry-run, read `.codev/config.json` directly.
12+
*/
13+
14+
import { logger } from '../utils/logger.js';
15+
import { getConfig, getWorktreeConfig } from '../utils/index.js';
16+
import { findBuilderById } from '../lib/builder-lookup.js';
17+
import { runPostSpawnHooks } from './spawn-worktree.js';
18+
19+
export interface SetupOptions {
20+
builderId?: string;
21+
}
22+
23+
export async function setup(options: SetupOptions): Promise<void> {
24+
if (!options.builderId) {
25+
throw new Error('Usage: afx setup <builder-id>');
26+
}
27+
28+
const builder = findBuilderById(options.builderId);
29+
if (!builder) {
30+
throw new Error(`No builder found matching "${options.builderId}". Try \`afx status\`.`);
31+
}
32+
if (!builder.worktree) {
33+
throw new Error(`Builder ${builder.id} has no worktree path on record — cannot re-run setup.`);
34+
}
35+
36+
const config = getConfig();
37+
const { postSpawn } = getWorktreeConfig(config.workspaceRoot);
38+
if (postSpawn.length === 0) {
39+
logger.info('No worktree.postSpawn configured in .codev/config.json. Nothing to do.');
40+
return;
41+
}
42+
43+
logger.info(`Running ${postSpawn.length} post-spawn hook(s) in ${builder.worktree}...`);
44+
await runPostSpawnHooks(builder.worktree, postSpawn);
45+
logger.success(`Setup complete for ${builder.id}`);
46+
}

packages/vscode/CHANGELOG.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@ What's changed in the Codev VS Code extension, version by version, written for t
66

77
### What's new
88

9-
- **Right-click any builder → review and run.** Three new commands on the Codev sidebar's Builders and Needs Attention views (#690):
10-
- **Codev: View Diff** opens VSCode's native side-by-side diff editor showing `main ↔ <builder>` for every changed file. Works across worktrees because each `.builders/<id>/` is a real git worktree sharing the parent repo's object database.
11-
- **Codev: Run Dev Server** reads `worktree.devCommand` from `.codev/config.json`, asks Tower to spawn the dev process in the builder's worktree, and opens it as a VSCode terminal tab labeled `Dev: <builder-id>`. If another builder's dev is already running, a modal asks whether to swap — confirming kills the old PTY, waits for it to exit, then starts the new one.
12-
- **Codev: Stop Dev Server** kills the running dev PTY and closes its VSCode tab.
13-
- Pairs with `afx dev <builder-id>` (CLI, #689) for users who prefer the terminal. Same Tower API, same `Dev: <builder-id>` tab convention; either entry point produces the same result.
9+
- **Right-click any builder → six review/test/setup actions.** New context-menu surface on the Codev sidebar's Builders and Needs Attention views (#690), backed by the runnable-worktrees primitives from #689:
10+
- **Codev: Open Builder Terminal** — opens that builder's AI terminal (same action as left-clicking the row, now also discoverable via right-click).
11+
- **Codev: Open Worktree Folder** — opens `.builders/<id>/` in the OS file manager (Finder / Explorer / xdg-open).
12+
- **Codev: Run Worktree Setup** — runs the configured `worktree.postSpawn` commands against the existing worktree. Use when the lockfile changed and dependencies need reinstalling, when `postSpawn` was extended after the builder spawned, or to recover from an aborted setup. Opens a fresh VSCode terminal so install output streams live. CLI equivalent: `afx setup <builder-id>`.
13+
- **Codev: View Diff** — opens a single unified diff editor showing `main ↔ <builder>` with a file-list pane and status icons (added / modified / deleted). One tab regardless of how many files changed; matches VSCode's built-in "Working Tree" view. Works across worktrees because each `.builders/<id>/` is a real git worktree sharing the parent repo's object database.
14+
- **Codev: Run Dev Server** — reads `worktree.devCommand` from `.codev/config.json`, asks Tower to spawn the dev process in the builder's worktree, and opens it as a VSCode terminal tab labeled `Codev: <name> (dev)`. If another builder's dev is already running, a modal asks whether to swap — confirming kills the old PTY, waits for it to exit, then starts the new one.
15+
- **Codev: Stop Dev Server** — kills the running dev PTY and closes its VSCode tab.
16+
- Each builder action pairs with a CLI equivalent (`afx dev <id>`, `afx dev --stop`, `afx setup <id>`) for users who prefer the terminal. Same Tower API, same conventions.
17+
- **Theme-aware Codev brand icon** on terminal tabs. The single-SVG approach added in 3.0.2 rendered as solid black on dark themes (VSCode doesn't resolve `currentColor` on terminal-tab icons); we now ship `codev-light.svg` + `codev-dark.svg` and pass them as the `{ light, dark }` pair to `createTerminal`.
18+
- **Command palette tightened.** `codev.openBuilderById` is now declared but hidden from the palette (it needs a builder-id arg and would silently fail). `codev.addReviewComment` only appears when a markdown file is active. `codev.helloWorld` renamed to "Codev: Show Connection State" so its palette entry actually says what it does.
1419

1520
## [3.0.2] - 2026-05-10
1621

packages/vscode/package.json

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@
123123
{
124124
"command": "codev.openWorktreeFolder",
125125
"title": "Codev: Open Worktree Folder"
126+
},
127+
{
128+
"command": "codev.runWorktreeSetup",
129+
"title": "Codev: Run Worktree Setup"
126130
}
127131
],
128132
"menus": {
@@ -140,27 +144,32 @@
140144
{
141145
"command": "codev.openBuilderById",
142146
"when": "view =~ /^codev\\.(builders|needsAttention)$/ && viewItem =~ /^(builder|blocked-builder)$/",
143-
"group": "1_open@1"
147+
"group": "1_terminal@1"
144148
},
145149
{
146150
"command": "codev.openWorktreeFolder",
147151
"when": "view =~ /^codev\\.(builders|needsAttention)$/ && viewItem =~ /^(builder|blocked-builder)$/",
148-
"group": "1_open@2"
152+
"group": "2_files@1"
149153
},
150154
{
151155
"command": "codev.reviewDiff",
152156
"when": "view =~ /^codev\\.(builders|needsAttention)$/ && viewItem =~ /^(builder|blocked-builder)$/",
153-
"group": "2_review@1"
157+
"group": "3_review@1"
158+
},
159+
{
160+
"command": "codev.runWorktreeSetup",
161+
"when": "view =~ /^codev\\.(builders|needsAttention)$/ && viewItem =~ /^(builder|blocked-builder)$/",
162+
"group": "4_worktree@1"
154163
},
155164
{
156165
"command": "codev.runWorktreeDev",
157166
"when": "view =~ /^codev\\.(builders|needsAttention)$/ && viewItem =~ /^(builder|blocked-builder)$/",
158-
"group": "3_dev@1"
167+
"group": "4_worktree@2"
159168
},
160169
{
161170
"command": "codev.stopWorktreeDev",
162171
"when": "view =~ /^codev\\.(builders|needsAttention)$/ && viewItem =~ /^(builder|blocked-builder)$/",
163-
"group": "3_dev@2"
172+
"group": "4_worktree@3"
164173
}
165174
],
166175
"view/title": [
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* Codev: Run Worktree Setup — execute `worktree.postSpawn` against an
3+
* existing builder's worktree, without recreating it.
4+
*
5+
* Use cases: lockfile changed and dependencies need reinstalling; a new
6+
* step was added to `worktree.postSpawn` after the builder spawned;
7+
* setup aborted mid-spawn and the worktree needs recovery; running
8+
* setup for the first time on a builder that predates the config.
9+
*
10+
* Opens a fresh VSCode terminal in the worktree and chains the configured
11+
* commands so output streams live (preferable to buffered execution for
12+
* long-running installs).
13+
*/
14+
15+
import * as vscode from 'vscode';
16+
import { readFile } from 'node:fs/promises';
17+
import * as path from 'node:path';
18+
import type { ConnectionManager } from '../connection-manager.js';
19+
20+
export async function runWorktreeSetup(
21+
connectionManager: ConnectionManager,
22+
builderIdArg: string | undefined,
23+
): Promise<void> {
24+
const client = connectionManager.getClient();
25+
const workspacePath = connectionManager.getWorkspacePath();
26+
if (!client || !workspacePath || connectionManager.getState() !== 'connected') {
27+
vscode.window.showErrorMessage('Codev: Not connected to Tower');
28+
return;
29+
}
30+
31+
const overview = await client.getOverview(workspacePath);
32+
const builders = overview?.builders ?? [];
33+
if (builders.length === 0) {
34+
vscode.window.showInformationMessage('Codev: No builders available');
35+
return;
36+
}
37+
38+
const builder = builderIdArg
39+
? builders.find(b => b.id === builderIdArg)
40+
: await pickBuilder(builders);
41+
if (!builder) {
42+
if (builderIdArg) {
43+
vscode.window.showErrorMessage(`Codev: No builder found for "${builderIdArg}"`);
44+
}
45+
return;
46+
}
47+
if (!builder.worktreePath) {
48+
vscode.window.showErrorMessage(`Codev: Builder ${builder.id} has no worktree on record`);
49+
return;
50+
}
51+
52+
const postSpawn = await readPostSpawn(workspacePath);
53+
if (postSpawn.length === 0) {
54+
vscode.window.showInformationMessage(
55+
'Codev: No worktree.postSpawn configured in .codev/config.json. Nothing to do.',
56+
);
57+
return;
58+
}
59+
60+
// Open a fresh VSCode integrated terminal scoped to the worktree.
61+
// sendText streams each command live — for long-running installs the
62+
// reviewer sees pnpm progress, uv resolver output, etc. in real time.
63+
const terminal = vscode.window.createTerminal({
64+
name: `Codev: Setup ${builder.id}`,
65+
cwd: builder.worktreePath,
66+
});
67+
terminal.show();
68+
for (const cmd of postSpawn) {
69+
terminal.sendText(cmd);
70+
}
71+
}
72+
73+
async function readPostSpawn(workspacePath: string): Promise<string[]> {
74+
const configPath = path.join(workspacePath, '.codev', 'config.json');
75+
try {
76+
const raw = await readFile(configPath, 'utf-8');
77+
const parsed = JSON.parse(raw) as { worktree?: { postSpawn?: unknown } };
78+
const list = parsed.worktree?.postSpawn;
79+
if (!Array.isArray(list)) { return []; }
80+
return list.filter((x): x is string => typeof x === 'string' && x.length > 0);
81+
} catch {
82+
return [];
83+
}
84+
}
85+
86+
interface BuilderLike {
87+
id: string;
88+
issueId: string | null;
89+
issueTitle: string | null;
90+
}
91+
92+
async function pickBuilder<T extends BuilderLike>(builders: T[]): Promise<T | undefined> {
93+
const picked = await vscode.window.showQuickPick(
94+
builders.map(b => ({
95+
label: `#${b.issueId ?? b.id} ${b.issueTitle ?? ''}`,
96+
builder: b,
97+
})),
98+
{ placeHolder: 'Select builder whose worktree to re-setup' },
99+
);
100+
return picked?.builder;
101+
}

packages/vscode/src/extension.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { reviewDiff } from './commands/review-diff.js';
1010
import { runWorktreeDev } from './commands/run-worktree-dev.js';
1111
import { stopWorktreeDev } from './commands/stop-worktree-dev.js';
1212
import { openWorktreeFolder } from './commands/open-worktree-folder.js';
13+
import { runWorktreeSetup } from './commands/run-worktree-setup.js';
1314
import { connectTunnel, disconnectTunnel } from './commands/tunnel.js';
1415
import { listCronTasks } from './commands/cron.js';
1516
import { addReviewComment } from './commands/review.js';
@@ -215,6 +216,8 @@ export async function activate(context: vscode.ExtensionContext) {
215216
stopWorktreeDev(connectionManager!, terminalManager!)),
216217
vscode.commands.registerCommand('codev.openWorktreeFolder', (arg: vscode.TreeItem | string | undefined) =>
217218
openWorktreeFolder(connectionManager!, extractBuilderId(arg))),
219+
vscode.commands.registerCommand('codev.runWorktreeSetup', (arg: vscode.TreeItem | string | undefined) =>
220+
runWorktreeSetup(connectionManager!, extractBuilderId(arg))),
218221
vscode.commands.registerCommand('codev.refreshOverview', () => overviewCache.refresh()),
219222
vscode.commands.registerCommand('codev.reconnect', () => connectionManager?.reconnect()),
220223
vscode.commands.registerCommand('codev.connectTunnel', () => connectTunnel(connectionManager!)),

0 commit comments

Comments
 (0)