Build your own Claude Code
Reading the world·18 min

view (Read)

0 / 1
# grade this stage (from your course directory)
npx theinference test --stage view --watch

view is the first tool the model reaches for and the one it leans on most — you can't edit a file you haven't read. It does two jobs in one: hand it a file and it returns the contents with 1-indexed line numbers; hand it a directory and it lists the entries. The line numbers aren't decoration — they're how the model later tells str_replace which region to touch, and they're why a botched offset is a real bug. This step also plants the seed for stale-write detection in Chapter 4: every view of a file records that file's read-version.

The output format the grader checks
view("src/a.ts")            →  1: line1
                               2: line2
                               3: line3

view("src/a.ts", [2, -1])   →  2: line2
                               3: line3        (-1 means "to end of file")

view("src")                 →  a.ts
                               b.js            (sorted directory entries)

What to build

Your task

Implement Tools.view

Implement Tools.view(path, range?) in src/tools.ts:

  • Resolve path through the sandbox (a later chapter; for now resolve relative to the task dir). If it doesn't exist, return an error result.
  • Directory → return the entries, sorted, one per line.
  • File → return each line prefixed ${n}: where n is the 1-indexed line number. With range: [from, to], emit only that slice; to === -1 means "through the last line". Clamp to the file's length.
  • Record the file's read-version (a content hash) in a per-instance map — str_replace will check it later to reject stale edits.

The program.ts view subcommand builds a sandbox from case.files, then prints tools.view(case.path, case.range).

src/tools.ts
view(path: string, range?: [number, number]): ToolOutput {
  // resolve path -> error if missing
  // directory  -> sorted entries, one per line
  // file       -> "${n}: ${line}" for the (clamped) range; record read-version
}
Line numbers are 1-indexed; the array is 0-indexed

The classic off-by-one: you split on \n into a 0-indexed array, but the model speaks in 1-indexed lines. Line n is lines[n - 1], and a range of [1, lines.length] is the whole file. to === -1 resolves to lines.length before the loop, and the upper bound is clamped so a range past EOF doesn't crash or emit blanks.

Record the read-version even though nothing checks it yet

On every successful file view, store hash(text) keyed by the resolved path. It looks like dead code in this chapter — but it's the anchor for Chapter 4's read-before-edit and stale-write guards. str_replace will refuse to touch a file it has no read-version for, and refuse again if the on-disk hash no longer matches. Wiring it now means you don't retrofit it later.

How it's graded

Process mode. The harness runs npx tsx program.ts view fixtures/case.json, which materializes case.files into a fresh sandbox and prints JSON.stringify(tools.view(...)) to stdout. The grader compares that line to fixtures/expected.json exactly — so {"content":"1: line1\n2: line2\n3: line3"} must match byte-for-byte, including the : separator and the newline joins. The fixtures cover a file read, a range slice, and a directory listing.