feat: rename /directory to /workspace and unify terminology

Renames the /directory command to /workspace, adds aliases for backward compatibility, and updates UI strings, settings, and prompts to use 'workspace' terminology consistently.

Linked to #20737. Per Core/UX chat on 2/18.
This commit is contained in:
Keith Guerin
2026-03-01 00:21:56 -08:00
parent 703759cfae
commit 043a08807b
56 changed files with 581 additions and 451 deletions
+3 -3
View File
@@ -1094,7 +1094,7 @@ describe('AppContainer State Management', () => {
const mockResumedSessionData = {
conversation: {
sessionId: 'test-session-123',
projectHash: 'test-project-hash',
workspaceHash: 'test-workspace-hash',
startTime: '2024-01-01T00:00:00Z',
lastUpdated: '2024-01-01T00:00:01Z',
messages: [
@@ -1283,7 +1283,7 @@ describe('AppContainer State Management', () => {
const resumedData = {
conversation: {
sessionId: 'resumed-session-456',
projectHash: 'project-hash',
workspaceHash: 'workspace-hash',
startTime: '2024-01-01T00:00:00Z',
lastUpdated: '2024-01-01T00:01:00Z',
messages: [
@@ -1339,7 +1339,7 @@ describe('AppContainer State Management', () => {
const resumedData = {
conversation: {
sessionId: 'test-session',
projectHash: 'project-hash',
workspaceHash: 'workspace-hash',
startTime: '2024-01-01T00:00:00Z',
lastUpdated: '2024-01-01T00:01:00Z',
messages: [],
+1 -1
View File
@@ -222,7 +222,7 @@ export function AuthDialog({
</Text>
<Box marginTop={1}>
<Text color={theme.text.primary}>
How would you like to authenticate for this project?
How would you like to authenticate for this workspace?
</Text>
</Box>
<Box marginTop={1}>
@@ -5,7 +5,7 @@ exports[`AuthDialog > Snapshots > renders correctly with auth error 1`] = `
│ │
│ ? Get started │
│ │
│ How would you like to authenticate for this project?
│ How would you like to authenticate for this workspace?
│ │
│ (selected) Login with Google(not selected) Use Gemini API Key(not selected) Vertex AI │
│ │
@@ -26,7 +26,7 @@ exports[`AuthDialog > Snapshots > renders correctly with default props 1`] = `
│ │
│ ? Get started │
│ │
│ How would you like to authenticate for this project?
│ How would you like to authenticate for this workspace?
│ │
│ (selected) Login with Google(not selected) Use Gemini API Key(not selected) Vertex AI │
│ │
@@ -45,7 +45,7 @@ exports[`AuthDialog > Snapshots > renders correctly with enforced auth type 1`]
│ │
│ ? Get started │
│ │
│ How would you like to authenticate for this project?
│ How would you like to authenticate for this workspace?
│ │
│ (selected) Use Gemini API Key │
│ │
@@ -78,7 +78,7 @@ describe('initCommand', () => {
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
{
type: 'info',
text: 'Empty GEMINI.md created. Now analyzing the project to populate it.',
text: 'Empty GEMINI.md created. Now analyzing the workspace to populate it.',
},
expect.any(Number),
);
+2 -2
View File
@@ -16,7 +16,7 @@ import { performInit } from '@google/gemini-cli-core';
export const initCommand: SlashCommand = {
name: 'init',
description: 'Analyzes the project and creates a tailored GEMINI.md file',
description: 'Analyzes the workspace and creates a tailored GEMINI.md file',
kind: CommandKind.BUILT_IN,
autoExecute: true,
action: async (
@@ -42,7 +42,7 @@ export const initCommand: SlashCommand = {
context.ui.addItem(
{
type: 'info',
text: 'Empty GEMINI.md created. Now analyzing the project to populate it.',
text: 'Empty GEMINI.md created. Now analyzing the workspace to populate it.',
},
Date.now(),
);
@@ -6,7 +6,7 @@
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import type { Mock } from 'vitest';
import { directoryCommand } from './directoryCommand.js';
import { workspaceCommand } from './workspaceCommand.js';
import {
expandHomeDir,
getDirectorySuggestions,
@@ -38,14 +38,14 @@ vi.mock('../utils/directoryUtils.js', async (importOriginal) => {
};
});
describe('directoryCommand', () => {
describe('workspaceCommand', () => {
let mockContext: CommandContext;
let mockConfig: Config;
let mockWorkspaceContext: WorkspaceContext;
const addCommand = directoryCommand.subCommands?.find(
const addCommand = workspaceCommand.subCommands?.find(
(c) => c.name === 'add',
);
const showCommand = directoryCommand.subCommands?.find(
const showCommand = workspaceCommand.subCommands?.find(
(c) => c.name === 'show',
);
@@ -126,7 +126,7 @@ describe('directoryCommand', () => {
type: 'message',
messageType: 'error',
content:
'The /directory add command is not supported in restrictive sandbox profiles. Please use --include-directories when starting the session instead.',
'The /workspace add command is not supported in restrictive sandbox profiles. Please use --include-directories when starting the session instead.',
});
});
@@ -77,9 +77,9 @@ async function finishAddingDirectories(
}
}
export const directoryCommand: SlashCommand = {
name: 'directory',
altNames: ['dir'],
export const workspaceCommand: SlashCommand = {
name: 'workspace',
altNames: ['directory', 'dir'],
description: 'Manage workspace directories',
kind: CommandKind.BUILT_IN,
subCommands: [
@@ -156,7 +156,7 @@ export const directoryCommand: SlashCommand = {
type: 'message' as const,
messageType: 'error' as const,
content:
'The /directory add command is not supported in restrictive sandbox profiles. Please use --include-directories when starting the session instead.',
'The /workspace add command is not supported in restrictive sandbox profiles. Please use --include-directories when starting the session instead.',
};
}
@@ -72,7 +72,8 @@ export const NewAgentsNotification = ({
New Agents Discovered
</Text>
<Text color={theme.text.primary}>
The following agents were found in this project. Please review them:
The following agents were found in this workspace. Please review
them:
</Text>
<Box
flexDirection="column"
@@ -55,7 +55,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const createConversation = (messages: MessageRecord[]): ConversationRecord => ({
sessionId: 'test-session',
projectHash: 'hash',
workspaceHash: 'hash',
startTime: new Date().toISOString(),
lastUpdated: new Date().toISOString(),
messages,
@@ -4,7 +4,7 @@ exports[`NewAgentsNotification > renders agent list 1`] = `
" ╭────────────────────────────────────────────────────────────────────────────────────────────────╮
│ │
│ New Agents Discovered │
│ The following agents were found in this project. Please review them:
│ The following agents were found in this workspace. Please review them: │
│ │
│ ┌────────────────────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
@@ -24,7 +24,7 @@ exports[`NewAgentsNotification > truncates list if more than 5 agents 1`] = `
" ╭────────────────────────────────────────────────────────────────────────────────────────────────╮
│ │
│ New Agents Discovered │
│ The following agents were found in this project. Please review them:
│ The following agents were found in this workspace. Please review them: │
│ │
│ ┌────────────────────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
@@ -172,12 +172,12 @@ export const TriageIssues = ({
async (issue: Issue): Promise<AnalysisResult> => {
const client = config.getBaseLlmClient();
const prompt = `
I am triaging GitHub issues for the Gemini CLI project. I need to identify issues that should be closed because they are:
I am triaging GitHub issues for the Gemini CLI workspace. I need to identify issues that should be closed because they are:
- Bogus (not a real issue/request)
- Not reproducible (insufficient info, "it doesn't work" without logs/details)
- Abusive or offensive
- Gibberish (nonsense text)
- Clearly out of scope for this project
- Clearly out of scope for this workspace
- Non-deterministic model output (e.g., "it gave me a wrong answer once", complaints about model quality without a reproducible test case)
<issue>
+7 -7
View File
@@ -19,7 +19,7 @@ export const INFORMATIVE_TIPS = [
'Hide the startup banner for a cleaner launch (/settings)…',
'Hide the context summary above the input (/settings)…',
'Reclaim vertical space by hiding the footer (/settings)…',
'Hide individual footer elements like CWD or sandbox status (/settings)…',
'Hide individual footer elements like Workspace Path or sandbox status (/settings)…',
'Hide the context window percentage in the footer (/settings)…',
'Show memory usage for performance monitoring (/settings)…',
'Show line numbers in the chat for easier reference (/settings)…',
@@ -33,7 +33,7 @@ export const INFORMATIVE_TIPS = [
'Control when chat history gets compressed based on token usage (settings.json)…',
'Define custom context file names, like CONTEXT.md (settings.json)…',
'Set max directories to scan for context files (/settings)…',
'Expand your workspace with additional directories (/directory)…',
'Expand your workspace with additional directories (/workspace)…',
'Control how /memory refresh loads context files (/settings)…',
'Toggle respect for .gitignore files in context (/settings)…',
'Toggle respect for .geminiignore files in context (/settings)…',
@@ -131,15 +131,15 @@ export const INFORMATIVE_TIPS = [
'Save tokens by summarizing the context with /compress…',
'Copy the last response to your clipboard with /copy…',
'Open the full documentation in your browser with /docs…',
'Add directories to your workspace with /directory add <path>…',
'Show all directories in your workspace with /directory show…',
'Use /dir as a shortcut for /directory…',
'Add directories to your workspace with /workspace add <path>…',
'Show all directories in your workspace with /workspace show…',
'Use /dir or /directory as a shortcut for /workspace…',
'Set your preferred external editor with /editor…',
'List all active extensions with /extensions list…',
'Update all or specific extensions with /extensions update…',
'Get help on commands with /help…',
'Manage IDE integration with /ide…',
'Create a project-specific GEMINI.md file with /init…',
'Create a workspace-specific GEMINI.md file with /init…',
'List configured MCP servers and tools with /mcp list…',
'Authenticate with an OAuth-enabled MCP server with /mcp auth…',
'Restart MCP servers with /mcp refresh…',
@@ -149,7 +149,7 @@ export const INFORMATIVE_TIPS = [
'List the paths of the GEMINI.md files in use with /memory list…',
'Choose your Gemini model with /model…',
'Display the privacy notice with /privacy…',
'Restore project files to a previous state with /restore…',
'Restore workspace files to a previous state with /restore…',
'Exit the CLI with /quit or /exit…',
'Check model-specific usage stats with /stats model…',
'Check tool-specific usage stats with /stats tools…',
@@ -154,7 +154,7 @@ describe('useFolderTrust', () => {
renderHook(() => useFolderTrust(mockSettings, onTrustChange, addItem));
expect(addItem).toHaveBeenCalledWith(
{
text: 'This folder is untrusted, project settings, hooks, MCPs, and GEMINI.md files will not be applied for this folder.\nUse the `/permissions` command to change the trust level.',
text: 'This folder is untrusted, workspace settings, hooks, MCPs, and GEMINI.md files will not be applied for this folder.\nUse the `/permissions` command to change the trust level.',
type: 'info',
},
expect.any(Number),
+1 -1
View File
@@ -59,7 +59,7 @@ export const useFolderTrust = (
addItem(
{
type: MessageType.INFO,
text: 'This folder is untrusted, project settings, hooks, MCPs, and GEMINI.md files will not be applied for this folder.\nUse the `/permissions` command to change the trust level.',
text: 'This folder is untrusted, workspace settings, hooks, MCPs, and GEMINI.md files will not be applied for this folder.\nUse the `/permissions` command to change the trust level.',
},
Date.now(),
);
+1 -1
View File
@@ -38,7 +38,7 @@ describe('useRewindLogic', () => {
const mockConversation: ConversationRecord = {
sessionId: 'conv-1',
projectHash: 'hash-1',
workspaceHash: 'hash-1',
startTime: new Date(1000).toISOString(),
lastUpdated: new Date(1001).toISOString(),
messages: [mockUserMessage, mockModelMessage],
@@ -78,7 +78,7 @@ describe('useSessionResume', () => {
const resumedData: ResumedSessionData = {
conversation: {
sessionId: 'test-123',
projectHash: 'project-123',
workspaceHash: 'project-123',
startTime: '2025-01-01T00:00:00Z',
lastUpdated: '2025-01-01T01:00:00Z',
messages: [] as MessageRecord[],
@@ -133,7 +133,7 @@ describe('useSessionResume', () => {
const resumedData: ResumedSessionData = {
conversation: {
sessionId: 'test-123',
projectHash: 'project-123',
workspaceHash: 'project-123',
startTime: '2025-01-01T00:00:00Z',
lastUpdated: '2025-01-01T01:00:00Z',
messages: [] as MessageRecord[],
@@ -160,7 +160,7 @@ describe('useSessionResume', () => {
const resumedData: ResumedSessionData = {
conversation: {
sessionId: 'test-123',
projectHash: 'project-123',
workspaceHash: 'project-123',
startTime: '2025-01-01T00:00:00Z',
lastUpdated: '2025-01-01T01:00:00Z',
messages: [] as MessageRecord[],
@@ -200,7 +200,7 @@ describe('useSessionResume', () => {
const resumedData: ResumedSessionData = {
conversation: {
sessionId: 'test-123',
projectHash: 'project-123',
workspaceHash: 'project-123',
startTime: '2025-01-01T00:00:00Z',
lastUpdated: '2025-01-01T01:00:00Z',
messages: [] as MessageRecord[],
@@ -240,7 +240,7 @@ describe('useSessionResume', () => {
const resumedData: ResumedSessionData = {
conversation: {
sessionId: 'test-123',
projectHash: 'project-123',
workspaceHash: 'project-123',
startTime: '2025-01-01T00:00:00Z',
lastUpdated: '2025-01-01T01:00:00Z',
messages: [] as MessageRecord[],
@@ -306,7 +306,7 @@ describe('useSessionResume', () => {
it('should not resume when user is authenticating', () => {
const conversation: ConversationRecord = {
sessionId: 'auto-resume-123',
projectHash: 'project-123',
workspaceHash: 'project-123',
startTime: '2025-01-01T00:00:00Z',
lastUpdated: '2025-01-01T01:00:00Z',
messages: [
@@ -338,7 +338,7 @@ describe('useSessionResume', () => {
it('should not resume when Gemini client is not initialized', () => {
const conversation: ConversationRecord = {
sessionId: 'auto-resume-123',
projectHash: 'project-123',
workspaceHash: 'project-123',
startTime: '2025-01-01T00:00:00Z',
lastUpdated: '2025-01-01T01:00:00Z',
messages: [
@@ -370,7 +370,7 @@ describe('useSessionResume', () => {
it('should automatically resume session when resumedSessionData is provided', async () => {
const conversation: ConversationRecord = {
sessionId: 'auto-resume-123',
projectHash: 'project-123',
workspaceHash: 'project-123',
startTime: '2025-01-01T00:00:00Z',
lastUpdated: '2025-01-01T01:00:00Z',
messages: [
@@ -425,7 +425,7 @@ describe('useSessionResume', () => {
it('should only resume once even if props change', async () => {
const conversation: ConversationRecord = {
sessionId: 'auto-resume-123',
projectHash: 'project-123',
workspaceHash: 'project-123',
startTime: '2025-01-01T00:00:00Z',
lastUpdated: '2025-01-01T01:00:00Z',
messages: [
@@ -480,7 +480,7 @@ describe('useSessionResume', () => {
it('should convert session messages correctly during auto-resume', async () => {
const conversation: ConversationRecord = {
sessionId: 'auto-resume-with-tools',
projectHash: 'project-123',
workspaceHash: 'project-123',
startTime: '2025-01-01T00:00:00Z',
lastUpdated: '2025-01-01T01:00:00Z',
messages: [
+10 -10
View File
@@ -245,22 +245,22 @@ const saveFileWithXclip = async (tempFilePath: string) => {
};
/**
* Gets the directory where clipboard images should be stored for a specific project.
* Gets the directory where clipboard images should be stored for a specific workspace.
*
* This uses the global temporary directory but creates a project-specific subdirectory
* based on the hash of the project path (via `Storage.getProjectTempDir()`).
* This prevents path conflicts between different projects while keeping the images
* outside of the user's project directory.
* This uses the global temporary directory but creates a workspace-specific subdirectory
* based on the hash of the workspace path (via `Storage.getProjectTempDir()`).
* This prevents path conflicts between different workspaces while keeping the images
* outside of the user's workspace directory.
*
* @param targetDir The root directory of the current project.
* @param targetDir The root directory of the current workspace.
* @returns The absolute path to the images directory.
*/
async function getProjectClipboardImagesDir(
async function getWorkspaceClipboardImagesDir(
targetDir: string,
): Promise<string> {
const storage = new Storage(targetDir);
await storage.initialize();
const baseDir = storage.getProjectTempDir();
const baseDir = storage.getWorkspaceTempDir();
return path.join(baseDir, 'images');
}
@@ -273,7 +273,7 @@ export async function saveClipboardImage(
targetDir: string,
): Promise<string | null> {
try {
const tempDir = await getProjectClipboardImagesDir(targetDir);
const tempDir = await getWorkspaceClipboardImagesDir(targetDir);
await fs.mkdir(tempDir, { recursive: true });
// Generate a unique filename with timestamp
@@ -398,7 +398,7 @@ export async function cleanupOldClipboardImages(
targetDir: string,
): Promise<void> {
try {
const tempDir = await getProjectClipboardImagesDir(targetDir);
const tempDir = await getWorkspaceClipboardImagesDir(targetDir);
const files = await fs.readdir(tempDir);
const oneHourAgo = Date.now() - 60 * 60 * 1000;