feat(cli): add bare startup mode (#3448)

* feat(cli): add bare startup mode

Skip implicit startup discovery in bare mode while keeping explicit inputs such as include directories and extension overrides.

Add a repository plan document and targeted tests for config, startup, skills, extensions, and memory discovery.

* fix(bare): enforce explicit-only startup behavior

* fix(cli): preserve bare tools in non-interactive mode

* chore(docs): remove bare mode planning note
This commit is contained in:
易良 2026-04-20 10:01:59 +08:00 committed by GitHub
parent cfe142e9a3
commit 41f71ab7e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 750 additions and 72 deletions

View file

@ -579,6 +579,12 @@ describe('parseArguments', () => {
const argv = await parseArguments();
expect(argv.extensions).toEqual(['ext1', 'ext2']);
});
it('should parse --bare', async () => {
process.argv = ['node', 'script.js', '--bare'];
const argv = await parseArguments();
expect(argv.bare).toBe(true);
});
});
describe('loadCliConfig', () => {
@ -1242,6 +1248,17 @@ describe('Approval mode tool exclusion logic', () => {
}
});
it('should keep the bare toolset available in non-interactive bare mode', async () => {
process.argv = ['node', 'script.js', '--bare', '-p', 'test'];
const argv = await parseArguments();
const config = await loadCliConfig({}, argv, undefined, []);
const excludedTools = config.getPermissionsDeny();
expect(excludedTools).not.toContain(ToolNames.SHELL);
expect(excludedTools).not.toContain(ToolNames.EDIT);
expect(excludedTools).not.toContain(ToolNames.WRITE_FILE);
});
it('should merge approval mode exclusions with settings exclusions in auto-edit mode', async () => {
process.argv = [
'node',
@ -1648,6 +1665,90 @@ describe('loadCliConfig with includeDirectories', () => {
expected.length,
);
});
it('should ignore implicit startup context inputs in bare mode', async () => {
const mockCwd = path.resolve(path.sep, 'home', 'user', 'project');
const cliPath = path.resolve(path.sep, 'cli', 'path1');
const settingsPath = path.resolve(path.sep, 'settings', 'path1');
process.argv = [
'node',
'script.js',
'--bare',
'--include-directories',
cliPath,
];
const argv = await parseArguments();
const settings: Settings = {
context: {
includeDirectories: [settingsPath],
},
};
const config = await loadCliConfig(settings, argv, undefined, []);
expect(config.getWorkspaceContext().getDirectories()).toEqual([
mockCwd,
cliPath,
]);
});
it('should force minimal startup behavior in bare mode', async () => {
process.argv = ['node', 'script.js', '--bare'];
const argv = await parseArguments();
const settings: Settings = {
tools: {
core: [ToolNames.WEB_FETCH],
allowed: [ToolNames.WEB_FETCH],
exclude: [ToolNames.ASK_USER_QUESTION],
},
hooks: {
PreToolUse: [],
} as Record<string, unknown>,
memory: {
enableManagedAutoMemory: true,
},
security: {
allowedHttpHookUrls: ['https://hooks.example.com/*'],
},
mcp: {
allowed: ['test-server'],
},
mcpServers: {
'test-server': {
command: 'node',
args: ['server.js'],
},
},
};
const config = await loadCliConfig(settings, argv, undefined, []);
expect(config.getCoreTools()).toEqual([
ToolNames.READ_FILE,
ToolNames.EDIT,
ToolNames.SHELL,
]);
expect(config.getDisableAllHooks()).toBe(true);
expect(config.getManagedAutoMemoryEnabled()).toBe(false);
expect(config.getToolDiscoveryCommand()).toBeUndefined();
expect(config.getToolCallCommand()).toBeUndefined();
expect(config.getMcpServers()).toEqual({});
expect(config.getWebSearchConfig()).toBeUndefined();
expect(config.isLspEnabled()).toBe(false);
});
it('should ignore coreTools overrides in bare mode', async () => {
process.argv = ['node', 'script.js', '--bare', '--core-tools', 'web_fetch'];
const argv = await parseArguments();
const config = await loadCliConfig({}, argv, undefined, []);
expect(config.getCoreTools()).toEqual([
ToolNames.READ_FILE,
ToolNames.EDIT,
ToolNames.SHELL,
]);
});
});
describe('loadCliConfig chatCompression', () => {