view (Read)
# grade this stage (from your course directory)
npx theinference test --stage view --watchview 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.
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
Implement Tools.view
Implement Tools.view(path, range?) in src/tools.ts:
- Resolve
paththrough 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}:wherenis the 1-indexed line number. Withrange: [from, to], emit only that slice;to === -1means "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_replacewill 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).
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
}
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.
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.
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.