CCAF logo

Domain 2 · Task 2.5

Built-in Tools

Select and apply built-in tools (Read, Write, Edit, Bash, Grep, Glob).

The built-in tools each have a sharp, specific job, and choosing the right one is the whole skill. Grep searches content (function names, error strings, imports). Glob matches file paths by name/extension. Read/Write operate on whole files; Edit makes a targeted change anchored on a unique text match. Knowing which tool a task calls for — and the Edit→Read+Write fallback — is what the exam probes.

Key concept

Content → Grep. Paths → Glob. Precise edit with a unique anchor → Edit; when the anchor isn't unique, fall back to Read + Write.

What you need to know

Grep vs Glob: content vs paths

The most-tested distinction:

  • Grep = content search. Use it to search *inside* files — all callers of a function, an error message, an import statement. "Find all callers of refund()" is a Grep job.
  • Glob = file-path pattern matching. Use it to match file *names/extensions* regardless of location — "find every test file" is Glob **/*.test.tsx.

Mixing them up is the classic mistake: Grep searches text; Glob searches paths.

bash
# Content search — every caller of refund():
Grep  pattern="refund\("

# Path pattern — every test file, anywhere in the tree:
Glob  pattern="**/*.test.tsx"

Edit and the Read + Write fallback

Edit makes a targeted change via a unique text match — you give it the exact text to replace, and it must match exactly one place. When the snippet you're anchoring on appears many times, Edit fails on the non-unique match. The fallback is Read + Write: read the whole file, transform its contents in memory, and write it back. So the rule is: precise edit with a unique anchor → Edit; otherwise Read + Write (or expand the anchor to make it unique).

python
# Edit fails: the snippet "return result" appears many times (non-unique anchor).
# Fallback — Read the whole file, transform, Write it back:
contents = Read(path)
updated = contents.replace("old config value", "new config value")
Write(path, updated)

Build understanding incrementally

Don't read every file up front. Build understanding incrementally: Grep the entry points → Read the files that matter → follow imports to the next hop. This keeps context focused on what's relevant instead of drowning it in whole-repo dumps. To trace how something is used across wrapper modules, enumerate the exported names first, then search each name across the codebase — that finds indirect callers a single Grep would miss.

A quick selection map

TaskTool
Find all callers of a function / an error stringGrep
Find every file matching a name patternGlob
Read a whole fileRead
Create or overwrite a whole fileWrite
Change a specific spot with a unique surrounding anchorEdit
Change a spot whose anchor isn't uniqueRead + Write

Match the task to the tool's job — content, path, whole-file, or anchored change — and the choice is unambiguous.

Exam traps

The trapThe reality
To find every caller of refund(), use Glob to locate the relevant files.Glob matches paths, not contents. Finding callers is a content search — use Grep.
To find every test file regardless of location, Grep for test in file contents.That's a path/name pattern, not a content match. Use Glob **/*.test.tsx.
When Edit keeps failing because the snippet appears many times, the fix is to retry Edit.The anchor is non-unique, so Edit can't target it. Fall back to Read + Write (or expand the anchor until it's unique).
The safest way to understand a codebase is to Read all files up front.Reading everything floods context. Build understanding incrementally — Grep entry points, Read what matters, follow imports.

Practice scenario

Real questions from the bank that test this topic — the correct answer is highlighted.

An engineer needs to edit a 150-line utility file, inserting a new helper function between two existing ones. The file has repetitive docstrings and similar function signatures throughout. The agent's Edit tool repeatedly fails because the old_string parameter cannot find a unique enough text anchor for the insertion point.

ARepeat the Edit call with progressively longer old_string values that capture 30+ lines of context for uniqueness.
BUse Edit with replace_all to target a common pattern and inject the new function in the replacement text.
CUse Read to load the full file, modify it in memory at the appropriate insertion point, then use Write to save the full updated file.Correct
DUse Bash to append the new function to the end of the file via heredoc syntax.

Why: Read + Write fallback when Edit can't find unique anchor text (Task 2.5). Long old_strings (A) are brittle; replace_all (B) is dangerous; Bash append (D) puts code in wrong location.

An engineer asks the agent to find all callers of a function before removing it. The function is defined in a core library but is also exposed through wrapper modules that rename the function for domain-specific use (e.g., calculateTax in the library becomes computeOrderTax in the orders module). What exploration strategy will most reliably identify all callers?

ARead the library and wrapper modules to identify all exposed names for the function, then Grep for each name across the codebase.Correct
BUse Grep to find all files that import from the library or wrapper modules, then read each file to check whether it uses the function.
CUse Grep to search for the function's original name across the codebase.
DSearch for the function name in project documentation to understand intended usage patterns and navigate to documented integration points.

Why: You have to enumerate every name the function is exposed under — otherwise renamed wrappers hide callers. Read the relevant modules, gather all aliases, then grep for each.

Build exercise

Practice tool selection on a real repo

~35 min
  1. 1
    Use Grep to find every caller of a chosen function and every place a specific error string is emitted.

    Why: Cements Grep as the content-search tool for callers and error messages.

    You should see: You get file+line hits inside files, not just file names.

  2. 2
    Use Glob **/*.test.tsx (and a second pattern of your choice) to list files purely by name/extension.

    Why: Cements Glob as the path/name-pattern tool independent of file contents.

  3. 3
    Make a precise change with Edit using a unique surrounding anchor, then deliberately target a snippet that appears many times and watch Edit fail.

    You should see: Edit succeeds on the unique anchor and errors on the non-unique one.

  4. 4
    Recover from the failed Edit by using Read to load the file, transforming the text, and Write to save it back.

    Why: Read + Write is the standard fallback when no unique anchor exists.

  5. 5
    Trace one feature's usage by enumerating a module's exported names and Grepping each across the repo, following imports as you go.

    Why: Incremental, import-following exploration finds indirect callers without reading the whole tree.

Sources

Drill Tool Design & MCP Integration

Practice only this domain’s questions, untimed, with instant explanations.