Module 5 of 10 · CLI Comparison Track
50%
AI CLI Tools Compared  ·  M05

Multi-File Editing & Large Codebases

Context window size and file-loading syntax are the dominant variables when working with real codebases. This module stress-tests all three tools on a 50-file Node.js project — renaming a model, generating a new middleware file, and handling dangerous deletions.

Claude Code
Gemini CLI
GitHub Copilot CLI
Section 1

Context Loading: How Each Tool Reads Your Files

Why context loading syntax matters

BEFORE: You have a 50-file Node.js project. You want the AI to rename a model from User to Account. If the AI only sees one file at a time, it will rename the model in that file but miss every import, type annotation, test reference, and API route that also uses the name User.

PAIN: A partial rename is worse than no rename — your TypeScript compiler suddenly sees Account in one file and User in every other, breaking the entire type system. You now have to manually hunt down every usage.

MAPPING: All three tools have a mechanism for loading files into context — but they work completely differently. The difference between Claude Code's selective precision, Gemini CLI's bulk loading, and Copilot CLI's lack of a direct equivalent defines what each tool can accomplish on multi-file tasks.

Claude Code — /add command

Selectively adds files or directories to the active context. Claude reads them on demand and tracks which files it has seen. You can add individual files, glob patterns, or directories.

/add src/models/
/add src/**/*.ts
→ Claude reads & tracks all
Gemini CLI — @ syntax

The @ prefix in your prompt inlines a file or directory's content directly. With a 1M context window, Gemini can ingest the entire project in one shot. You can also use @ inside GEMINI.md for always-loaded context.

rename User to Account in @src/
@package.json tells me we use...
→ Gemini reads entire tree
Copilot CLI — No equivalent

Copilot CLI operates on your shell session context only. It cannot read project files, load a directory tree, or maintain multi-file awareness. For file-aware operations, you must use an agentic tool.

gh copilot suggest "rename User to Account"
→ suggests: find/sed command
→ YOU run it across files
Context loading — files ingested per tool on a 50-file project
Section 2

50-File Node.js Project — How Each Tool Handles It

Test project: a Node.js Express API with 50 TypeScript files totalling ~35,000 tokens. Well within any tool's context window individually — but all three handle it very differently.

Claude Code — selective precision

Claude Code reads the directory structure first, identifies the most relevant files for the task (typically 10-15), and loads those. It then expands to adjacent files if it discovers cross-references. Result: precise understanding of the exact files involved, with minimal token waste.

Trade-off: may miss an obscure file that also uses the renamed model if it wasn't surfaced in the initial directory scan.

Gemini CLI — bulk load

With @src/, Gemini ingests the entire src/ directory at once. All 50 files are in context simultaneously. It can cross-reference any file without a second pass. For this 35K token project, Gemini uses only 3.5% of its 1M window.

Trade-off: token cost is higher on very large projects; Gemini Flash is rate-limited at 1,500 req/day on the free tier.

Copilot CLI — zero file context

Copilot CLI cannot read project files in its CLI mode. It can suggest grep or sed commands to find and replace strings, but it has no awareness of TypeScript types, imports, or logical groupings. A sed rename will also rename string literals, comments, and package names containing "User".

When context window size is the deciding factor

For projects under 150K tokens (~110K lines of code), both Claude Code and Gemini CLI can handle the full codebase. Above that threshold, Claude Code must work in chunks while Gemini CLI (with its 1M window) can still load everything. This is the most common reason teams with large monorepos adopt Gemini CLI as their primary refactoring tool.

Section 3

Multi-File Rename: User → Account

Renaming a model across a codebase is a canonical multi-file operation. It requires: finding every import, every type annotation, every test reference, every database column name, and every API route — and updating them all consistently while not renaming unrelated strings like "username" or "UserAgent".

bash — rename User model to Account
# ─── Claude Code ──────────────────────────────────────────────────────────────
claude
> /add src/
> Rename the User model to Account across all files.
>   - Update imports, type annotations, test references, and API routes.
>   - Do NOT rename 'username' fields or 'UserAgent' strings.
>   - Show me a diff before writing.
# Claude reads directory → finds User usages in 14 files → shows diff → asks confirm

# ─── Gemini CLI ───────────────────────────────────────────────────────────────
gemini
> Rename the User model to Account in @src/
>   - Update all TypeScript imports, type annotations, and test files.
>   - Preserve 'username', 'UserAgent', 'createUser' function names.
# Gemini loads entire src/ → rewrites 14 files in one pass → reports changes

# ─── Copilot CLI ──────────────────────────────────────────────────────────────
gh copilot suggest "rename User model to Account across all TypeScript files"
# Copilot suggests:
#   find src/ -name "*.ts" | xargs sed -i 's/\bUser\b/Account/g'
# Problem: \bUser\b regex also renames 'UserService', 'createUser', etc.
# No TypeScript type awareness — cannot distinguish 'User' the model from 'user' the variable.
PowerShell
# ─── Claude Code (PowerShell) ─────────────────────────────────────────────────
claude
# /add src/
# Prompt: Rename User model to Account across all files.
# Do NOT rename 'username' or 'UserAgent'. Show diff first.
# → Claude shows a colour diff, waits for confirmation

# ─── Gemini CLI (PowerShell) ──────────────────────────────────────────────────
gemini
# Prompt: Rename the User model to Account in @src/
# → Gemini bulk-loads all .ts files, rewrites in one pass

# ─── Copilot CLI (PowerShell) ─────────────────────────────────────────────────
gh copilot suggest "rename User model to Account in all TypeScript files"
# Suggested command:
Get-ChildItem -Recurse -Path src -Filter "*.ts" |
  ForEach-Object { (Get-Content $_.FullName) -replace '\bUser\b','Account' |
  Set-Content $_.FullName }
# Same caveat: regex has no TypeScript type context — may replace wrong occurrences.
Why regex rename is dangerous for TypeScript

A regex like \bUser\b cannot distinguish between User the TypeScript interface, createUser() the function, user the variable, and "User" in a JSON response field name. Claude Code and Gemini CLI understand the TypeScript AST semantics and rename at the correct granularity. Copilot CLI's suggested sed/Get-ChildItem command will likely produce a broken build that requires manual cleanup.

Section 4

New File Generation: Context-Aware Middleware

The second test: generate a new rate-limiting middleware file that is consistent with the existing middleware already in src/middleware/. This requires reading existing conventions — function signatures, error response format, export style — before generating the new file.

TypeScript — prompt to all three tools
# Prompt (identical for all tools):
# "Create a middleware file for rate limiting, consistent with
#  the existing middleware in src/middleware/"

# ─── Claude Code result ───────────────────────────────────────────────────────
# Claude: reads src/middleware/ → sees auth.ts, cors.ts, logger.ts
#         → matches their export pattern (export const X: RequestHandler)
#         → matches error response shape ({ error: string, retryAfter: number })
#         → generates src/middleware/rateLimit.ts, fully consistent

# ─── Gemini CLI result ────────────────────────────────────────────────────────
# Gemini: @src/middleware/ inlines all 3 existing middleware files
#         → same analysis, generates consistent file
#         → also adds Redis integration (detects redis dep in package.json via @package.json)

# ─── Copilot CLI result ───────────────────────────────────────────────────────
# gh copilot suggest "create a rate limiting middleware file for express"
# → suggests: npm install express-rate-limit
#             + copy-paste snippet from express-rate-limit README
# → no awareness of YOUR existing middleware conventions
# → you must manually adjust to match your project's patterns

The key distinction here is context-aware generation versus generic snippet retrieval. Claude Code and Gemini CLI read your actual code to understand your conventions before generating. Copilot CLI in the terminal retrieves a generic pattern from its training data.

Section 5

File Deletion Safety: How Each Tool Handles Danger

All three tools can suggest or execute file deletions. What happens when you ask them to clean up files — and what are the safety guarantees?

Safety model for destructive operations — confirmation gates per tool
Claude Code — explicit confirmation gate

Before deleting any file, Claude Code displays the full path, explains why it's being deleted, and waits for your explicit yes confirmation. It will not batch-delete multiple files in a single unconfirmed step.

The CLAUDE.md ## Allowed operations section can expand or restrict defaults. Permission gates can also be configured in .claude/settings.json.

Gemini CLI — checkpoint before destruct

Gemini CLI's Plan Mode shows all planned changes — including deletions — before executing. Without Plan Mode, Gemini may proceed with deletions; with it enabled, every destructive step requires approval.

Recommendation: always use gemini --plan when performing operations that include file deletions.

Copilot CLI — you run it yourself

Copilot CLI suggests the rm or Remove-Item command. You decide whether to run it. This is the safest model by design — no automated execution means no automated deletion. It also means no undo support — if you run rm -rf, that's on you.

bash — "delete all .js files that have a .ts equivalent"
# Claude Code:
> Delete all compiled .js files in src/ that have a .ts equivalent.
# Claude: scans src/, finds 12 .js/.ts pairs, lists them all
# Prompt: "Delete these 12 files? [y/N]"
# On 'y': deletes. On 'N': aborts cleanly.

# Gemini CLI (with --plan):
gemini --plan
> Delete all .js files in @src/ that have a matching .ts file.
# Plan: "I will delete: src/auth.js, src/routes.js, ... (12 files)"
# Approve? [y/N] → On 'y': executes. On 'N': discards plan.

# Copilot CLI:
gh copilot suggest "delete js files in src/ that have a typescript equivalent"
# Suggested:
find src/ -name "*.js" -exec sh -c \
  'test -f "${1%.js}.ts" && echo "Would delete: $1"' _ {} \;
# → You review the list, then run again with -delete instead of echo
# → Full manual control; Copilot only helps you write the find command
PowerShell
# Claude Code:
# > Delete all compiled .js files in src/ that have a .ts equivalent.
# → Lists 12 files, requires y/N confirmation

# Gemini CLI:
# gemini --plan
# → Shows plan including all deletions, requires approval

# Copilot CLI:
gh copilot suggest "delete .js files in src that have a matching .ts file"
# Suggested:
Get-ChildItem -Path src -Filter "*.js" -Recurse |
  Where-Object { Test-Path ($_.FullName -replace '\.js$', '.ts') } |
  Select-Object FullName  # review first, then pipe to Remove-Item to execute
Section 6

Full Comparison Table

Capability Claude Code Gemini CLI Copilot CLI
Max context 200K tokens (~150K LOC) 1M tokens (~750K LOC) ★ Shell session only
File loading syntax /add src/ or /add **/*.ts @src/ inline in prompt No file context
Multi-file edit Type-aware, selective, diffs shown Bulk load + type-aware Regex suggest only (no type awareness)
New file generation Reads existing conventions, matches patterns Same + detects deps via @package.json Generic snippet, not context-aware
Undo support Git-aware, shows diffs before write Plan Mode checkpoint before execute N/A — you run the command, you own undo
Deletion safety Explicit y/N gate per deletion batch Plan Mode gate (recommended, not default) You run the command — inherently safe
What Just Happened?

You now understand the file operation capabilities of each tool at a concrete level:

  • For projects under 150K tokens: Claude Code and Gemini CLI are equally capable on multi-file tasks.
  • For projects over 150K tokens: Gemini CLI's 1M context window becomes the decisive advantage.
  • For TypeScript-aware renaming: both agentic tools are far safer than Copilot CLI's sed/regex suggestion.
  • Copilot CLI's you-run-it model is the safest for destructive ops — but puts all reasoning responsibility on you.

Next: M06 covers agentic workflows — the biggest architectural difference between the tools. Subagents, Plan Mode, and what "agentic" actually means in practice.

Knowledge Check

Five questions on multi-file editing, context loading, and file operation safety.

Question 1 of 5
A developer has a 400K-token TypeScript monorepo and needs to rename an interface across all files. Which tool is best positioned to handle this in a single pass, and why?
Correct. At 400K tokens, the codebase exceeds Claude Code's 200K context window. Claude would need to read it in chunks, risking missed cross-file dependencies. Gemini CLI's 1M window can hold the full 400K project and reason about all files simultaneously. Copilot CLI's sed suggestion lacks TypeScript type awareness and would cause incorrect renames (e.g., renaming the interface name in a string literal or comment).
Question 2 of 5
What is the key danger of using sed -i 's/\bUser\b/Account/g' to rename a TypeScript model, as Copilot CLI might suggest?
Correct. The regex \bUser\b matches the word "User" as a whole word — but "whole word" in regex means alphanumeric boundaries, so "UserService" contains "User" as a prefix and may or may not match depending on the regex engine and flags used. More critically, sed cannot understand TypeScript type context: it will rename "User" in JSDoc comments, in string literals like "Error: User not found", and in any identifier that contains User as a component. Claude Code and Gemini CLI understand the TypeScript AST and rename at the type-declaration level.
Question 3 of 5
Gemini CLI detects that your project uses Redis when you include @package.json in your prompt. Why does this make its new file generation better?
Correct. Context-aware generation means the tool adapts to your specific technology stack. If you're running a Node.js cluster or multiple instances behind a load balancer, in-memory rate limiting state doesn't work — each instance has its own counter. Redis provides shared state across instances. By reading @package.json and seeing ioredis or redis in your dependencies, Gemini generates a rate limiter that actually works in your architecture rather than a generic example that would fail silently in production.
Question 4 of 5
What is the recommended way to handle file deletions when using Gemini CLI, and why?
Correct. Gemini CLI's default mode can proceed with destructive operations without confirmation. Plan Mode (--plan) inserts a review checkpoint: Gemini presents the full execution plan, including all files that will be deleted, before taking any action. You can review the list, object to specific items, and approve only when satisfied. This is especially important for bulk operations where Gemini might delete more files than you intended.
Question 5 of 5
Why is Copilot CLI's "you run the command" model for file operations simultaneously its safest and most limiting characteristic?
Correct. Copilot CLI's architecture is intentionally conservative: it suggests a command, you review it, you run it. This means an AI bug can never delete your files without your explicit involvement. The limitation is the flip side: since Copilot CLI doesn't read your project files, its suggestions are generic patterns from its training data. It cannot generate a middleware file that matches your project's conventions, cannot understand which "User" occurrences are the TypeScript interface versus the variable, and cannot build up a mental model of your architecture across multiple turns.