diff --git a/.gemini/commands/strict-development-rules.md b/.gemini/commands/strict-development-rules.md
index 3bc00c2717..f1b8364c91 100644
--- a/.gemini/commands/strict-development-rules.md
+++ b/.gemini/commands/strict-development-rules.md
@@ -136,16 +136,12 @@ Gemini CLI project.
## UI Text & Telemetry
-- **Precise Conciseness**: Keep status and error messages under 5 words whenever
- possible to prevent "information snowblindness."
-- **Telemetry over Etiquette**: Favor status data and raw telemetry over polite
- filler. Avoid "Please wait," "I'm sorry," or "We're still on it."
-- **Attribution**: Correctly attribute the source of a delay or error:
- - Use **Gemini** for generative analysis or thinking latency.
- - Use **System** or **API** for infrastructure, quota, or network issues.
-- **Tone**: Use a friendly, helpful, and humble tone. Use sentence-style
- capitalization. Solitary sentences aren't punctuated. Speak to the user
- ("you"). Avoid first-person pronouns ("I" or "we").
+- **UX Writing Standards**: All user-facing strings, error messages, and status
+ telemetry must adhere to the project's UX writing standards.
+- **Reference**: Follow the rules defined in the [string-reviewer skill](../skills/string-reviewer/SKILL.md).
+- **Core Goal**: Prioritize **Precise Conciseness** (under 5 words) and
+ **Telemetry over Etiquette** (raw data over "Please wait").
+
## Code Cleanup
diff --git a/.gemini/skills/string-reviewer/SKILL.md b/.gemini/skills/string-reviewer/SKILL.md
index f37d83b4ad..6bd8eb84be 100644
--- a/.gemini/skills/string-reviewer/SKILL.md
+++ b/.gemini/skills/string-reviewer/SKILL.md
@@ -40,7 +40,9 @@ Use this checklist to audit UI strings and AI responses.
### Identity and voice
- **Eliminate the "I":** Remove all first-person pronouns (I, me, my, mine).
- **Subject attribution:** Refer to the AI as Gemini and the infrastructure as
- the - system or the CLI.
+ the system or the CLI. Use visual cues (the Gemini icon/spinner) for
+ attribution of active thoughts; avoid redundant 3rd-person narrator
+ prefixes (e.g., "Gemini is thinking about...") in favor of direct telemetry.
- **Active voice:** Ensure the subject (Gemini or the system) is clearly
performing the action.
- **Ownership rule:** Use the system for execution (doing) and Gemini for
diff --git a/docs/get-started/gemini-3.md b/docs/get-started/gemini-3.md
index 8e0af1a9ce..d52161d649 100644
--- a/docs/get-started/gemini-3.md
+++ b/docs/get-started/gemini-3.md
@@ -59,9 +59,8 @@ or fallback to Gemini 2.5 Pro.
> [!NOTE]
> The **Keep trying** option uses exponential backoff, in which Gemini
-> CLI waits longer between each retry, when the system is busy. If the retry
-> doesn't happen immediately, please wait a few minutes for the request to
-> process.
+> CLI waits longer between each retry, when the system is busy. Retry after
+> a few minutes if results are not immediate.
### Model selection and routing types
diff --git a/packages/cli/src/ui/components/LoadingIndicator.test.tsx b/packages/cli/src/ui/components/LoadingIndicator.test.tsx
index 089e371c80..9616cba303 100644
--- a/packages/cli/src/ui/components/LoadingIndicator.test.tsx
+++ b/packages/cli/src/ui/components/LoadingIndicator.test.tsx
@@ -50,7 +50,7 @@ const renderWithContext = async (
describe('', () => {
const defaultProps = {
- currentLoadingPhrase: 'Gemini is thinking...',
+ currentLoadingPhrase: 'Thinking...',
elapsedTime: 5,
};
@@ -71,7 +71,7 @@ describe('', () => {
await waitUntilReady();
const output = lastFrame();
expect(output).toContain('MockRespondingSpinner');
- expect(output).toContain('Gemini is thinking...');
+ expect(output).toContain('Thinking...');
expect(output).toContain('(esc to cancel, 5s)');
});
@@ -108,7 +108,7 @@ describe('', () => {
it('should display the elapsedTime correctly when Responding', async () => {
const props = {
- currentLoadingPhrase: 'Gemini is thinking...',
+ currentLoadingPhrase: 'Thinking...',
elapsedTime: 60,
};
const { lastFrame, unmount, waitUntilReady } = await renderWithContext(
@@ -120,9 +120,9 @@ describe('', () => {
unmount();
});
- it('should display the elapsedTime correctly in human-readable format', async () => {
+ it('should display the elapsedTime correctly in minutes and OMIT seconds for > 1m', async () => {
const props = {
- currentLoadingPhrase: 'Gemini is thinking...',
+ currentLoadingPhrase: 'Thinking...',
elapsedTime: 125,
};
const { lastFrame, unmount, waitUntilReady } = await renderWithContext(
@@ -130,7 +130,9 @@ describe('', () => {
StreamingState.Responding,
);
await waitUntilReady();
- expect(lastFrame()).toContain('(esc to cancel, 2m 5s)');
+ const output = lastFrame();
+ expect(output).toContain('(esc to cancel, 2m)');
+ expect(output).not.toContain('5s');
unmount();
});
@@ -229,7 +231,7 @@ describe('', () => {
it('should display fallback phrase if thought is empty', async () => {
const props = {
thought: null,
- currentLoadingPhrase: 'Gemini is thinking...',
+ currentLoadingPhrase: 'Thinking...',
elapsedTime: 5,
};
const { lastFrame, unmount, waitUntilReady } = await renderWithContext(
@@ -238,7 +240,7 @@ describe('', () => {
);
await waitUntilReady();
const output = lastFrame();
- expect(output).toContain('Gemini is thinking...');
+ expect(output).toContain('Thinking...');
unmount();
});
@@ -258,15 +260,13 @@ describe('', () => {
const output = lastFrame();
expect(output).toBeDefined();
if (output) {
- expect(output).toContain(
- 'Gemini is thinking about Thinking about something...',
- );
- expect(output).not.toContain('and other stuff.');
+ expect(output).not.toContain('Gemini is thinking');
+ expect(output).toContain('Thinking about something...');
}
unmount();
});
- it('should use "Gemini is thinking about" if a subject is provided', async () => {
+ it('should NOT prepend "Thinking... " if a subject is provided', async () => {
const props = {
thought: {
subject: 'Planning the response...',
@@ -280,13 +280,12 @@ describe('', () => {
);
await waitUntilReady();
const output = lastFrame();
- expect(output).toContain(
- 'Gemini is thinking about Planning the response...',
- );
+ expect(output).toContain('Planning the response...');
+ expect(output).not.toContain('Thinking... ');
unmount();
});
- it('should prioritize thought.subject over currentLoadingPhrase using the new Gemini pattern', async () => {
+ it('should prioritize thought.subject over currentLoadingPhrase', async () => {
const props = {
thought: {
subject: 'This should be displayed',
@@ -301,9 +300,7 @@ describe('', () => {
);
await waitUntilReady();
const output = lastFrame();
- expect(output).toContain(
- 'Gemini is thinking about This should be displayed',
- );
+ expect(output).toContain('This should be displayed');
expect(output).not.toContain('This should not be displayed');
unmount();
});
@@ -351,7 +348,7 @@ describe('', () => {
const output = lastFrame();
// Check for single line output
expect(output?.trim().includes('\n')).toBe(false);
- expect(output).toContain('Gemini is thinking...');
+ expect(output).toContain('Thinking...');
expect(output).toContain('(esc to cancel, 5s)');
expect(output).toContain('Right');
unmount();
@@ -375,7 +372,7 @@ describe('', () => {
// 3. Right Content
expect(lines).toHaveLength(3);
if (lines) {
- expect(lines[0]).toContain('Gemini is thinking...');
+ expect(lines[0]).toContain('Thinking...');
expect(lines[0]).not.toContain('(esc to cancel, 5s)');
expect(lines[1]).toContain('(esc to cancel, 5s)');
expect(lines[2]).toContain('Right');
@@ -411,7 +408,7 @@ describe('', () => {
elapsedTime={5}
wittyPhrase="I am witty"
showWit={true}
- currentLoadingPhrase="Gemini is thinking..."
+ currentLoadingPhrase="Thinking..."
/>,
StreamingState.Responding,
120,
@@ -419,7 +416,7 @@ describe('', () => {
await waitUntilReady();
const output = lastFrame();
// Sequence should be: Primary Text -> Cancel/Timer -> Witty Phrase
- expect(output).toContain('Gemini is thinking... (esc to cancel, 5s) I am witty');
+ expect(output).toContain('Thinking... (esc to cancel, 5s) I am witty');
unmount();
});
@@ -429,7 +426,7 @@ describe('', () => {
elapsedTime={5}
wittyPhrase="I am witty"
showWit={true}
- currentLoadingPhrase="Gemini is thinking..."
+ currentLoadingPhrase="Thinking..."
/>,
StreamingState.Responding,
79,
@@ -443,7 +440,7 @@ describe('', () => {
// 3. Witty Phrase
expect(lines).toHaveLength(3);
if (lines) {
- expect(lines[0]).toContain('Gemini is thinking...');
+ expect(lines[0]).toContain('Thinking...');
expect(lines[1]).toContain('(esc to cancel, 5s)');
expect(lines[2]).toContain('I am witty');
}
diff --git a/packages/cli/src/ui/components/LoadingIndicator.tsx b/packages/cli/src/ui/components/LoadingIndicator.tsx
index 673ab8c998..7777af81d9 100644
--- a/packages/cli/src/ui/components/LoadingIndicator.tsx
+++ b/packages/cli/src/ui/components/LoadingIndicator.tsx
@@ -11,7 +11,6 @@ import { theme } from '../semantic-colors.js';
import { useStreamingContext } from '../contexts/StreamingContext.js';
import { StreamingState } from '../types.js';
import { GeminiRespondingSpinner } from './GeminiRespondingSpinner.js';
-import { formatDuration } from '../utils/formatters.js';
import { useTerminalSize } from '../hooks/useTerminalSize.js';
import { isNarrowWidth } from '../utils/isNarrowWidth.js';
import { INTERACTIVE_SHELL_WAITING_PHRASE } from '../hooks/usePhraseCycler.js';
@@ -65,23 +64,27 @@ export const LoadingIndicator: React.FC = ({
currentLoadingPhrase === INTERACTIVE_SHELL_WAITING_PHRASE
? currentLoadingPhrase
: thought?.subject
- ? `Gemini is thinking about ${thoughtLabel ?? thought.subject.trim()}`
+ ? (thoughtLabel ?? thought.subject.trim())
: currentLoadingPhrase ||
(streamingState === StreamingState.Responding
- ? 'Gemini is thinking...'
+ ? 'Thinking...'
: undefined);
const cancelAndTimerContent =
showCancelAndTimer &&
streamingState !== StreamingState.WaitingForConfirmation
- ? `(esc to cancel, ${elapsedTime < 60 ? `${elapsedTime}s` : formatDuration(elapsedTime * 1000)})`
+ ? `(esc to cancel, ${
+ elapsedTime < 60
+ ? `${elapsedTime}s`
+ : `${Math.floor(elapsedTime / 60)}m`
+ })`
: null;
const wittyPhraseNode =
!forceRealStatusOnly &&
showWit &&
wittyPhrase &&
- (primaryText === 'Thinking...' || primaryText === 'Gemini is thinking...') ? (
+ primaryText === 'Thinking...' ? (
{wittyPhrase}
diff --git a/packages/cli/src/ui/components/triage/TriageIssues.tsx b/packages/cli/src/ui/components/triage/TriageIssues.tsx
index 62c0f50e1c..b19d158569 100644
--- a/packages/cli/src/ui/components/triage/TriageIssues.tsx
+++ b/packages/cli/src/ui/components/triage/TriageIssues.tsx
@@ -208,8 +208,8 @@ INSTRUCTIONS:
3. If it seems like a legitimate bug or feature request that needs triage by a human, recommend "keep".
4. Provide a brief reason for your recommendation.
5. If recommending "close", provide a polite, professional, and helpful 'suggested_comment' explaining why it's being closed and what the user can do (e.g., provide more logs, follow contributing guidelines).
-6. CRITICAL: If the reason for closing is "Non-deterministic model output", you MUST use the following text EXACTLY as the 'suggested_comment':
-"Thank you for the report. Model outputs are non-deterministic, and we are unable to troubleshoot isolated quality issues that lack a repeatable test case. We are closing this issue while we continue to work on overall model performance and reliability. If you find a way to consistently reproduce this specific issue, please let us know and we can take another look."
+L211- 6. CRITICAL: If the reason for closing is "Non-deterministic model output", you MUST use the following text EXACTLY as the 'suggested_comment':
+"Closing: model outputs are non-deterministic. Re-open with a repeatable test case once available."
Return a JSON object with:
- "recommendation": "close" or "keep"
diff --git a/packages/core/src/prompts/snippets.ts b/packages/core/src/prompts/snippets.ts
index 27c1fa60a1..66f160cb65 100644
--- a/packages/core/src/prompts/snippets.ts
+++ b/packages/core/src/prompts/snippets.ts
@@ -364,7 +364,8 @@ export function renderOperationalGuidelines(
? 'per-tool explanations.'
: 'mechanical tool-use narration (e.g., "I will now call...").'
}
-- **Concise & Direct:** Adopt a professional, direct, and concise tone suitable for a CLI environment.
+- **Precise Conciseness:** Prioritize **Precise Conciseness** (under 5 words) and **Telemetry over Etiquette** (raw data over polite filler like "Please wait").
+- **Attribution:** Use visual cues (the Gemini icon/spinner) for attribution of active thoughts; avoid redundant 3rd-person narrator prefixes (e.g., "Gemini is thinking about...") in favor of direct telemetry.
- **Minimal Output:** Aim for fewer than 3 lines of text output (excluding tool use/code generation) per response whenever practical.
- **No Chitchat:** Avoid conversational filler, preambles ("Okay, I will now..."), or postambles ("I have finished the changes...") unless they are ${
options.topicUpdateNarration