mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-20 10:10:56 -07:00
feat(plan): promote planning feature to stable (#24282)
This commit is contained in:
@@ -1364,8 +1364,8 @@ describe('Approval mode tool exclusion logic', () => {
|
||||
'test',
|
||||
];
|
||||
const settings = createTestMergedSettings({
|
||||
experimental: {
|
||||
plan: true,
|
||||
general: {
|
||||
plan: { enabled: true },
|
||||
},
|
||||
});
|
||||
const argv = await parseArguments(createTestMergedSettings());
|
||||
@@ -1479,9 +1479,7 @@ describe('Approval mode tool exclusion logic', () => {
|
||||
const settings = createTestMergedSettings({
|
||||
general: {
|
||||
defaultApprovalMode: 'plan',
|
||||
},
|
||||
experimental: {
|
||||
plan: false,
|
||||
plan: { enabled: false },
|
||||
},
|
||||
});
|
||||
const argv = await parseArguments(settings);
|
||||
@@ -1489,14 +1487,12 @@ describe('Approval mode tool exclusion logic', () => {
|
||||
expect(config.getApprovalMode()).toBe(ApprovalMode.DEFAULT);
|
||||
});
|
||||
|
||||
it('should allow plan approval mode if experimental plan is enabled', async () => {
|
||||
it('should allow plan approval mode if plan is enabled', async () => {
|
||||
process.argv = ['node', 'script.js'];
|
||||
const settings = createTestMergedSettings({
|
||||
general: {
|
||||
defaultApprovalMode: 'plan',
|
||||
},
|
||||
experimental: {
|
||||
plan: true,
|
||||
plan: { enabled: true },
|
||||
},
|
||||
});
|
||||
const argv = await parseArguments(settings);
|
||||
@@ -2742,12 +2738,12 @@ describe('loadCliConfig approval mode', () => {
|
||||
expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.YOLO);
|
||||
});
|
||||
|
||||
it('should set Plan approval mode when --approval-mode=plan is used and experimental.plan is enabled', async () => {
|
||||
it('should set Plan approval mode when --approval-mode=plan is used and plan is enabled', async () => {
|
||||
process.argv = ['node', 'script.js', '--approval-mode', 'plan'];
|
||||
const argv = await parseArguments(createTestMergedSettings());
|
||||
const settings = createTestMergedSettings({
|
||||
experimental: {
|
||||
plan: true,
|
||||
general: {
|
||||
plan: { enabled: true },
|
||||
},
|
||||
});
|
||||
const config = await loadCliConfig(settings, 'test-session', argv);
|
||||
@@ -2767,12 +2763,12 @@ describe('loadCliConfig approval mode', () => {
|
||||
expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT);
|
||||
});
|
||||
|
||||
it('should throw error when --approval-mode=plan is used but experimental.plan is disabled', async () => {
|
||||
it('should throw error when --approval-mode=plan is used but plan is disabled', async () => {
|
||||
process.argv = ['node', 'script.js', '--approval-mode', 'plan'];
|
||||
const argv = await parseArguments(createTestMergedSettings());
|
||||
const settings = createTestMergedSettings({
|
||||
experimental: {
|
||||
plan: false,
|
||||
general: {
|
||||
plan: { enabled: false },
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2893,22 +2889,26 @@ describe('loadCliConfig approval mode', () => {
|
||||
expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.YOLO);
|
||||
});
|
||||
|
||||
it('should respect plan mode from settings when experimental.plan is enabled', async () => {
|
||||
it('should respect plan mode from settings when plan is enabled', async () => {
|
||||
process.argv = ['node', 'script.js'];
|
||||
const settings = createTestMergedSettings({
|
||||
general: { defaultApprovalMode: 'plan' },
|
||||
experimental: { plan: true },
|
||||
general: {
|
||||
defaultApprovalMode: 'plan',
|
||||
plan: { enabled: true },
|
||||
},
|
||||
});
|
||||
const argv = await parseArguments(settings);
|
||||
const config = await loadCliConfig(settings, 'test-session', argv);
|
||||
expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.PLAN);
|
||||
});
|
||||
|
||||
it('should throw error if plan mode is in settings but experimental.plan is disabled', async () => {
|
||||
it('should fall back to default if plan mode is in settings but disabled', async () => {
|
||||
process.argv = ['node', 'script.js'];
|
||||
const settings = createTestMergedSettings({
|
||||
general: { defaultApprovalMode: 'plan' },
|
||||
experimental: { plan: false },
|
||||
general: {
|
||||
defaultApprovalMode: 'plan',
|
||||
plan: { enabled: false },
|
||||
},
|
||||
});
|
||||
const argv = await parseArguments(settings);
|
||||
const config = await loadCliConfig(settings, 'test-session', argv);
|
||||
@@ -3696,7 +3696,9 @@ describe('loadCliConfig mcpEnabled', () => {
|
||||
it('should use plan directory from active extension when user has not specified one', async () => {
|
||||
process.argv = ['node', 'script.js'];
|
||||
const settings = createTestMergedSettings({
|
||||
experimental: { plan: true },
|
||||
general: {
|
||||
plan: { enabled: true },
|
||||
},
|
||||
});
|
||||
const argv = await parseArguments(settings);
|
||||
|
||||
@@ -3715,9 +3717,11 @@ describe('loadCliConfig mcpEnabled', () => {
|
||||
it('should NOT use plan directory from active extension when user has specified one', async () => {
|
||||
process.argv = ['node', 'script.js'];
|
||||
const settings = createTestMergedSettings({
|
||||
experimental: { plan: true },
|
||||
general: {
|
||||
plan: { directory: 'user-plans-dir' },
|
||||
plan: {
|
||||
enabled: true,
|
||||
directory: 'user-plans-dir',
|
||||
},
|
||||
},
|
||||
});
|
||||
const argv = await parseArguments(settings);
|
||||
@@ -3738,7 +3742,9 @@ describe('loadCliConfig mcpEnabled', () => {
|
||||
it('should NOT use plan directory from inactive extension', async () => {
|
||||
process.argv = ['node', 'script.js'];
|
||||
const settings = createTestMergedSettings({
|
||||
experimental: { plan: true },
|
||||
general: {
|
||||
plan: { enabled: true },
|
||||
},
|
||||
});
|
||||
const argv = await parseArguments(settings);
|
||||
|
||||
@@ -3759,7 +3765,9 @@ describe('loadCliConfig mcpEnabled', () => {
|
||||
it('should use default path if neither user nor extension settings provide a plan directory', async () => {
|
||||
process.argv = ['node', 'script.js'];
|
||||
const settings = createTestMergedSettings({
|
||||
experimental: { plan: true },
|
||||
general: {
|
||||
plan: { enabled: true },
|
||||
},
|
||||
});
|
||||
const argv = await parseArguments(settings);
|
||||
|
||||
|
||||
@@ -669,9 +669,9 @@ export async function loadCliConfig(
|
||||
approvalMode = ApprovalMode.AUTO_EDIT;
|
||||
break;
|
||||
case 'plan':
|
||||
if (!(settings.experimental?.plan ?? false)) {
|
||||
if (!(settings.general?.plan?.enabled ?? true)) {
|
||||
debugLogger.warn(
|
||||
'Approval mode "plan" is only available when experimental.plan is enabled. Falling back to "default".',
|
||||
'Approval mode "plan" is disabled in your settings. Falling back to "default".',
|
||||
);
|
||||
approvalMode = ApprovalMode.DEFAULT;
|
||||
} else {
|
||||
@@ -966,7 +966,7 @@ export async function loadCliConfig(
|
||||
extensionRegistryURI,
|
||||
enableExtensionReloading: settings.experimental?.extensionReloading,
|
||||
enableAgents: settings.experimental?.enableAgents,
|
||||
plan: settings.experimental?.plan,
|
||||
plan: settings.general?.plan?.enabled ?? true,
|
||||
tracker: settings.experimental?.taskTracker,
|
||||
directWebFetch: settings.experimental?.directWebFetch,
|
||||
planSettings: settings.general?.plan?.directory
|
||||
|
||||
@@ -1124,15 +1124,15 @@ function migrateExperimentalSettings(
|
||||
};
|
||||
let modified = false;
|
||||
|
||||
const migrateExperimental = (
|
||||
const migrateExperimental = <T = Record<string, unknown>>(
|
||||
oldKey: string,
|
||||
migrateFn: (oldValue: Record<string, unknown>) => void,
|
||||
migrateFn: (oldValue: T) => void,
|
||||
) => {
|
||||
const old = experimentalSettings[oldKey];
|
||||
if (old) {
|
||||
if (old !== undefined) {
|
||||
foundDeprecated?.push(`experimental.${oldKey}`);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
migrateFn(old as Record<string, unknown>);
|
||||
migrateFn(old as T);
|
||||
modified = true;
|
||||
}
|
||||
};
|
||||
@@ -1197,6 +1197,24 @@ function migrateExperimentalSettings(
|
||||
agentsOverrides['cli_help'] = override;
|
||||
});
|
||||
|
||||
// Migrate experimental.plan -> general.plan.enabled
|
||||
migrateExperimental<boolean>('plan', (planValue) => {
|
||||
const generalSettings =
|
||||
(settings.general as Record<string, unknown> | undefined) || {};
|
||||
const newGeneral = { ...generalSettings };
|
||||
const planSettings =
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
(newGeneral['plan'] as Record<string, unknown> | undefined) || {};
|
||||
const newPlan = { ...planSettings };
|
||||
|
||||
if (newPlan['enabled'] === undefined) {
|
||||
newPlan['enabled'] = planValue;
|
||||
newGeneral['plan'] = newPlan;
|
||||
loadedSettings.setValue(scope, 'general', newGeneral);
|
||||
modified = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (modified) {
|
||||
agentsSettings['overrides'] = agentsOverrides;
|
||||
loadedSettings.setValue(scope, 'agents', agentsSettings);
|
||||
@@ -1205,6 +1223,7 @@ function migrateExperimentalSettings(
|
||||
const newExperimental = { ...experimentalSettings };
|
||||
delete newExperimental['codebaseInvestigatorSettings'];
|
||||
delete newExperimental['cliHelpAgentSettings'];
|
||||
delete newExperimental['plan'];
|
||||
loadedSettings.setValue(scope, 'experimental', newExperimental);
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -418,14 +418,17 @@ describe('SettingsSchema', () => {
|
||||
});
|
||||
|
||||
it('should have plan setting in schema', () => {
|
||||
const setting = getSettingsSchema().experimental.properties.plan;
|
||||
const setting =
|
||||
getSettingsSchema().general.properties.plan.properties.enabled;
|
||||
expect(setting).toBeDefined();
|
||||
expect(setting.type).toBe('boolean');
|
||||
expect(setting.category).toBe('Experimental');
|
||||
expect(setting.category).toBe('General');
|
||||
expect(setting.default).toBe(true);
|
||||
expect(setting.requiresRestart).toBe(true);
|
||||
expect(setting.showInDialog).toBe(true);
|
||||
expect(setting.description).toBe('Enable Plan Mode.');
|
||||
expect(setting.description).toBe(
|
||||
'Enable Plan Mode for read-only safety during planning.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should have hooksConfig.notifications setting in schema', () => {
|
||||
|
||||
@@ -293,6 +293,16 @@ const SETTINGS_SCHEMA = {
|
||||
description: 'Planning features configuration.',
|
||||
showInDialog: false,
|
||||
properties: {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
label: 'Enable Plan Mode',
|
||||
category: 'General',
|
||||
requiresRestart: true,
|
||||
default: true,
|
||||
description:
|
||||
'Enable Plan Mode for read-only safety during planning.',
|
||||
showInDialog: true,
|
||||
},
|
||||
directory: {
|
||||
type: 'string',
|
||||
label: 'Plan Directory',
|
||||
@@ -2070,15 +2080,6 @@ const SETTINGS_SCHEMA = {
|
||||
'Use OSC 52 for copying. This may be more robust than the default system when using remote terminal sessions (if your terminal is configured to allow it).',
|
||||
showInDialog: true,
|
||||
},
|
||||
plan: {
|
||||
type: 'boolean',
|
||||
label: 'Plan',
|
||||
category: 'Experimental',
|
||||
requiresRestart: true,
|
||||
default: true,
|
||||
description: 'Enable Plan Mode.',
|
||||
showInDialog: true,
|
||||
},
|
||||
taskTracker: {
|
||||
type: 'boolean',
|
||||
label: 'Task Tracker',
|
||||
|
||||
Reference in New Issue
Block a user