Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b7db693
feat(kimi-code): add -w, --worktree [name] flag for isolated sessions
rrva Jun 16, 2026
3974aa3
docs(reference): document -w, --worktree flag in EN and ZH
rrva Jun 16, 2026
c61f5d5
feat(kimi-code): add bundled word lists for --worktree auto-generated…
rrva Jun 16, 2026
3bbf152
fix(kimi-code): harden --worktree startup cleanup and name validation
rrva Jun 16, 2026
a74eb19
fix(kimi-code): correct --worktree cleanup and prepareWorktree signature
rrva Jun 16, 2026
91be1d7
feat(agent-core): surface worktree metadata in agent system prompt
rrva Jun 16, 2026
6a86ada
fix(kimi-code): address Codex review feedback on --worktree flag
rrva Jun 16, 2026
ab83d9f
fix(kimi-code): preserve worktree metadata for /new sessions
rrva Jun 16, 2026
4710074
fix(kimi-code): address second round of Codex review feedback
rrva Jun 16, 2026
42902f6
fix(kimi-code): address third round of Codex review feedback
rrva Jun 16, 2026
950156e
feat(kimi-code): add -w, --worktree [name] flag for isolated sessions
rrva Jun 16, 2026
b8a2539
docs(reference): document -w, --worktree flag in EN and ZH
rrva Jun 16, 2026
751c395
feat(kimi-code): add bundled word lists for --worktree auto-generated…
rrva Jun 16, 2026
389a09b
fix(kimi-code): harden --worktree startup cleanup and name validation
rrva Jun 16, 2026
dfd32f2
fix(kimi-code): correct --worktree cleanup and prepareWorktree signature
rrva Jun 16, 2026
d24988a
feat(agent-core): surface worktree metadata in agent system prompt
rrva Jun 16, 2026
a80cdc2
fix(kimi-code): address Codex review feedback on --worktree flag
rrva Jun 16, 2026
b1e69b4
fix(kimi-code): preserve worktree metadata for /new sessions
rrva Jun 16, 2026
3c0958f
fix(kimi-code): address second round of Codex review feedback
rrva Jun 16, 2026
5f4ea9f
fix(kimi-code): address third round of Codex review feedback
rrva Jun 16, 2026
35f9612
Merge branch 'main' into feat/worktree-cli-flag
rrva Jun 18, 2026
8e4c290
Merge branch 'fork/feat/worktree-cli-flag' into feat/worktree-cli-flag
rrva Jun 18, 2026
0379c3a
Merge branch 'main' into feat/worktree-cli-flag
rrva Jun 22, 2026
3cea709
Merge branch 'main' into feat/worktree-cli-flag
rrva Jun 23, 2026
e60c17f
Merge branch 'main' into feat/worktree-cli-flag
rrva Jun 25, 2026
44b1296
refactor(kimi-code): isolate --worktree into feature modules
rrva Jun 25, 2026
49fa0b2
fix(kimi-code): address worktree review findings
rrva Jun 26, 2026
2a6e0aa
Merge remote-tracking branch 'origin/main' into feat/worktree-cli-flag
rrva Jun 26, 2026
f04656d
Merge branch 'main' into feat/worktree-cli-flag
rrva Jun 30, 2026
043de86
Merge branch 'main' into feat/worktree-cli-flag
rrva Jul 1, 2026
9cd5098
Merge branch 'main' into feat/worktree-cli-flag
rrva Jul 2, 2026
180c924
Merge remote-tracking branch 'origin/main' into feat/worktree-cli-flag
rrva Jul 3, 2026
b2a5dd9
Merge branch 'main' into feat/worktree-cli-flag
rrva Jul 5, 2026
7c4b24d
Merge branch 'main' into feat/worktree-cli-flag
rrva Jul 7, 2026
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
5 changes: 5 additions & 0 deletions .changeset/add-worktree-flag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": minor
---

Add `-w, --worktree [name]` flag to create a new git worktree for the session.
12 changes: 11 additions & 1 deletion apps/kimi-code/src/cli/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,13 @@ export function createProgram(
)
.addOption(new Option('--yes').hideHelp().default(false))
.addOption(new Option('--auto-approve').hideHelp().default(false))
.option('--plan', 'Start in plan mode.', false);
.option('--plan', 'Start in plan mode.', false)
.addOption(
new Option(
'-w, --worktree [name]',
'Create a new git worktree for this session (optionally specify a name).',
).argParser((val: string | boolean) => (val === true ? '' : (val as string))),
);

registerExportCommand(program);
registerProviderCommand(program);
Expand Down Expand Up @@ -111,6 +117,9 @@ export function createProgram(
const yoloValue = raw['yolo'] === true || raw['yes'] === true || raw['autoApprove'] === true;
const autoValue = raw['auto'] === true;

const rawWorktree = raw['worktree'];
const worktreeValue = rawWorktree === true ? '' : (rawWorktree as string | undefined);

const opts: CLIOptions = {
session: sessionValue,
continue: raw['continue'] as boolean,
Expand All @@ -121,6 +130,7 @@ export function createProgram(
outputFormat: raw['outputFormat'] as CLIOptions['outputFormat'],
prompt: raw['prompt'] as string | undefined,
skillsDirs: raw['skillsDir'] as string[],
worktree: worktreeValue,
};

onMain(opts);
Expand Down
11 changes: 11 additions & 0 deletions apps/kimi-code/src/cli/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ export interface CLIOptions {
outputFormat: PromptOutputFormat | undefined;
prompt: string | undefined;
skillsDirs: string[];
worktree?: string;
/** Populated during startup when --worktree is used. */
worktreePath?: string;
/** Populated during startup when --worktree is used. */
parentRepoPath?: string;
}

export interface ValidatedOptions {
Expand Down Expand Up @@ -55,5 +60,11 @@ export function validateOptions(opts: CLIOptions): ValidatedOptions {
if (opts.yolo && opts.auto) {
throw new OptionConflictError('Cannot combine --yolo with --auto.');
}
if (opts.worktree !== undefined && opts.session !== undefined) {
throw new OptionConflictError('Cannot combine --worktree with --session.');
}
if (opts.worktree !== undefined && opts.continue) {
throw new OptionConflictError('Cannot combine --worktree with --continue.');
}
return { options: opts, uiMode: promptMode ? 'print' : 'shell' };
}
11 changes: 10 additions & 1 deletion apps/kimi-code/src/cli/run-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,16 @@ async function resolvePromptSession(
}

const model = requireConfiguredModel(opts.model, defaultModel);
const session = await harness.createSession({ workDir, model, permission: 'auto' });
const metadata =
opts.worktreePath !== undefined && opts.parentRepoPath !== undefined
? { worktreePath: opts.worktreePath, parentRepoPath: opts.parentRepoPath }
: undefined;
// Note: --prompt mode intentionally does not auto-remove the worktree on
// exit. Unlike the TUI, a prompt session always produces at least a user
// prompt and assistant response, so the "empty session" cleanup rule does
// not apply; leaving the worktree makes the non-interactive output
// inspectable after the fact.
const session = await harness.createSession({ workDir, model, permission: 'auto', metadata });
installHeadlessHandlers(session);
return {
session,
Expand Down
14 changes: 14 additions & 0 deletions apps/kimi-code/src/cli/run-shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { execSync } from 'node:child_process';
import { homedir } from 'node:os';
import { join } from 'node:path';

import { removeWorktree } from '#/utils/git/worktree';

import {
setCrashPhase,
setTelemetryContext,
Expand Down Expand Up @@ -136,6 +138,18 @@ export async function runShell(
const hasContent = tui.hasSessionContent();
setCrashPhase('shutdown');
trackLifecycle('exit', { duration_s: (Date.now() - startedAt) / 1000 });

// Clean up the git worktree for empty sessions so abandoned worktrees
// do not accumulate.
if (!hasContent && opts.worktreePath !== undefined && opts.parentRepoPath !== undefined) {
try {
removeWorktree(opts.parentRepoPath, opts.worktreePath);
Comment thread
rrva marked this conversation as resolved.
Outdated
} catch (cleanupError) {
// Best-effort cleanup only; do not let cleanup failures prevent a clean exit.
log.warn('Failed to clean up git worktree on exit', cleanupError);
}
}

await shutdownTelemetry({ timeoutMs: CLI_SHUTDOWN_TIMEOUT_MS });
const gutter = ' '.repeat(CHROME_GUTTER);
process.stdout.write(`${gutter}Bye!\n`);
Expand Down
50 changes: 45 additions & 5 deletions apps/kimi-code/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import type { CLIOptions } from './cli/options';
import { OptionConflictError, validateOptions } from './cli/options';
import { runPrompt } from './cli/run-prompt';
import { runShell } from './cli/run-shell';
import { createWorktree, findGitRoot, removeWorktree, WorktreeError } from './utils/git/worktree';
import { formatStartupError } from './cli/startup-error';
import { runPluginNodeEntry } from './cli/sub/plugin-run-node';
import { handleUpgrade } from './cli/sub/upgrade';
Expand All @@ -38,6 +39,17 @@ import { cleanupStaleNativeCacheForCurrent } from './native/native-assets';
import { installNativeModuleHook } from './native/module-hook';
import { runNativeAssetSmokeIfRequested } from './native/smoke';

function prepareWorktree(worktreeName: string): { worktreePath: string; parentRepoPath: string } {
const cwd = process.cwd();
const repoRoot = findGitRoot(cwd);
if (repoRoot === null) {
throw new WorktreeError('--worktree requires the working directory to be inside a git repository.');
}
const worktreePath = createWorktree(repoRoot, worktreeName || undefined);
process.chdir(worktreePath);
Comment thread
rrva marked this conversation as resolved.
Outdated
Comment thread
rrva marked this conversation as resolved.
Outdated
return { worktreePath, parentRepoPath: repoRoot };
}

export async function handleMainCommand(opts: CLIOptions, version: string): Promise<void> {
let validated: ReturnType<typeof validateOptions>;
try {
Expand All @@ -50,6 +62,20 @@ export async function handleMainCommand(opts: CLIOptions, version: string): Prom
throw error;
}

if (opts.worktree !== undefined) {
try {
const { worktreePath, parentRepoPath } = await prepareWorktree(opts.worktree);
Comment thread
rrva marked this conversation as resolved.
Outdated
opts.worktreePath = worktreePath;
opts.parentRepoPath = parentRepoPath;
} catch (error) {
if (error instanceof WorktreeError) {
process.stderr.write(`error: ${error.message}\n`);
process.exit(1);
}
throw error;
}
}

const preflightResult = await runUpdatePreflight(
version,
validated.uiMode === 'print' ? { track, isTTY: false } : { track },
Expand All @@ -58,12 +84,26 @@ export async function handleMainCommand(opts: CLIOptions, version: string): Prom
process.exit(0);
}

if (validated.uiMode === 'print') {
await runPrompt(validated.options, version);
return;
}
try {
if (validated.uiMode === 'print') {
await runPrompt(validated.options, version);
return;
}

await runShell(validated.options, version);
await runShell(validated.options, version);
} catch (error) {
// If the runner failed during startup after we created a worktree, the
// worktree is still empty (no session ran), so clean it up to avoid leaks.
if (opts.worktreePath !== undefined && opts.parentRepoPath !== undefined) {
try {
removeWorktree(opts.parentRepoPath, opts.worktreePath);
Comment thread
rrva marked this conversation as resolved.
Outdated
} catch (cleanupError) {
// Best-effort cleanup only; do not let cleanup failures mask the original error.
log.warn('Failed to clean up git worktree after runner startup failed', cleanupError);
}
}
throw error;
}
}

/** `kimi migrate`: launch the migration screen only, then exit. */
Expand Down
13 changes: 13 additions & 0 deletions apps/kimi-code/src/tui/kimi-tui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
ApprovalResponse,
BackgroundTaskInfo,
CreateSessionOptions,
JsonObject,
KimiHarness,
PermissionMode,
PromptPart,
Expand Down Expand Up @@ -195,6 +196,16 @@ function createInitialAppState(input: KimiTUIStartupInput): AppState {
};
}

function buildSessionMetadata(cliOptions: CLIOptions): JsonObject | undefined {
if (cliOptions.worktreePath === undefined || cliOptions.parentRepoPath === undefined) {
return undefined;
}
return {
worktreePath: cliOptions.worktreePath,
parentRepoPath: cliOptions.parentRepoPath,
};
}

interface SendMessageOptions {
readonly parts?: readonly PromptPart[];
readonly imageAttachmentIds?: readonly number[];
Expand Down Expand Up @@ -268,6 +279,7 @@ export class KimiTUI {
plan: startupInput.cliOptions.plan,
model: startupInput.cliOptions.model,
startupNotice: startupInput.startupNotice,
metadata: buildSessionMetadata(startupInput.cliOptions),
Comment thread
rrva marked this conversation as resolved.
Outdated
},
};
this.options = tuiOptions;
Expand Down Expand Up @@ -562,6 +574,7 @@ export class KimiTUI {
model: startup.model,
permission: startup.auto ? 'auto' : startup.yolo ? 'yolo' : undefined,
planMode: startup.plan ? true : undefined,
metadata: startup.metadata,
};

try {
Expand Down
2 changes: 2 additions & 0 deletions apps/kimi-code/src/tui/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {
GoalChange,
GoalSnapshot,
JsonObject,
ModelAlias,
PermissionMode,
ProviderConfig,
Expand Down Expand Up @@ -199,6 +200,7 @@ export interface TUIStartupOptions {
readonly plan: boolean;
readonly model?: string;
readonly startupNotice?: string;
readonly metadata?: JsonObject;
}

export type TUIStartupState = 'pending' | 'ready' | 'picker';
Expand Down
Loading