diff --git a/packages/coding-agent/src/prompts/tools/read.md b/packages/coding-agent/src/prompts/tools/read.md index 8712ff14a..92c8e0806 100644 --- a/packages/coding-agent/src/prompts/tools/read.md +++ b/packages/coding-agent/src/prompts/tools/read.md @@ -15,7 +15,7 @@ Read files, directories, archives, SQLite databases, images, documents, internal Append `:` to `path`. The bare path falls back to the default mode. - _(none)_ — parseable code → structural summary (signatures kept, bodies elided); other files → read from the start (up to {{DEFAULT_LIMIT}} lines). -- `:50` — read from line 50 onward. +- `:50` / `:50-` — read from line 50 onward. - `:50-200` — lines 50–200 inclusive. - `:50+150` — 150 lines starting at line 50. - `:20+1` — exactly one line. diff --git a/packages/coding-agent/src/tools/path-utils.ts b/packages/coding-agent/src/tools/path-utils.ts index dad41bb69..be280eab5 100644 --- a/packages/coding-agent/src/tools/path-utils.ts +++ b/packages/coding-agent/src/tools/path-utils.ts @@ -7,8 +7,8 @@ import { InternalUrlRouter } from "../internal-urls"; import { ToolError } from "./tool-errors"; const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g; -const FILE_LINE_RANGE_RE = /^(?:L?\d+(?:[-+]L?\d+)?(?:,L?\d+(?:[-+]L?\d+)?)*|raw|conflicts)$/i; -const FILE_LINE_RANGE_ONLY_RE = /^L?\d+(?:[-+]L?\d+)?(?:,L?\d+(?:[-+]L?\d+)?)*$/i; +const FILE_LINE_RANGE_RE = /^(?:L?\d+(?:[-+]L?\d+|-)?(?:,L?\d+(?:[-+]L?\d+|-)?)*|raw|conflicts)$/i; +const FILE_LINE_RANGE_ONLY_RE = /^L?\d+(?:[-+]L?\d+|-)?(?:,L?\d+(?:[-+]L?\d+|-)?)*$/i; const FILE_RAW_ONLY_RE = /^raw$/i; const NARROW_NO_BREAK_SPACE = "\u202F"; const TOP_LEVEL_INTERNAL_URL_PREFIXES = [ diff --git a/packages/coding-agent/src/tools/read.ts b/packages/coding-agent/src/tools/read.ts index 15a081c39..00a127cd1 100644 --- a/packages/coding-agent/src/tools/read.ts +++ b/packages/coding-agent/src/tools/read.ts @@ -513,7 +513,7 @@ type ParsedSelector = | { kind: "conflicts" } | { kind: "lines"; ranges: [LineRange, ...LineRange[]]; raw?: boolean }; -const LINE_RANGE_RE = /^L?(\d+)(?:([-+])L?(\d+))?$/i; +const LINE_RANGE_RE = /^L?(\d+)(?:([-+])L?(\d+)?)?$/i; /** Returns true when the selector requested verbatim/raw output (alone or combined with a range). */ function isRawSelector(parsed: ParsedSelector): boolean { @@ -541,10 +541,13 @@ function parseLineRangeChunk(sel: string): LineRange | null { } rawEnd = rawStart + rhs - 1; } else if (sep === "-") { - if (rhs === undefined || rhs < rawStart) { - throw new ToolError(`Invalid range ${rawStart}-${rhs ?? 0}: end must be >= start.`); + // `301-` is shorthand for "from 301 onward" — equivalent to bare `301`. + if (rhs !== undefined) { + if (rhs < rawStart) { + throw new ToolError(`Invalid range ${rawStart}-${rhs}: end must be >= start.`); + } + rawEnd = rhs; } - rawEnd = rhs; } return { startLine: rawStart, endLine: rawEnd }; }