feat(code-nav): file-exploration bundle — list_files + read_file + grep_file - #20
Merged
Merged
Conversation
jlitola
added a commit
that referenced
this pull request
Apr 21, 2026
…ning, docs Applied plan-reviewer + codex findings on the file-exploration bundle (PR #20). No behaviour changes — reviewers flagged that our UX invariants were only hand-verified, and that the grep MCP description lagged the new default-0 contract. Tests added: - \`code grep\` exit-code contract pinned: 0 on match, 1 on zero matches (plain + --json), 2 on error. Catches any future drift of the grep-style scripting contract. - \`code grep\` plain-mode stderr routing: \`hasMore\` truncation warning and regex-shaped pattern nudge both asserted on stderr (not stdout) so pipe-cleanliness regressions surface in CI. - \`mergeMatchBlocks\` edge cases: unsorted-matches input, match line colliding with another match's context entry (the \`isMatch: true\` override path), and null/empty contextBefore/After — all now have unit coverage. Docs: - New \`### githits code files\` / \`code read\` / \`code grep\` subsections in cli-commands.md matching the \`pkg\` family pattern. Each calls out stdout vs stderr routing, exit codes, and the plain/verbose split — the non-obvious UX commitments we locked in during the pass. - MCP \`grep_file\` description corrected: previously said "Returns matches with surrounding context lines" which contradicted the default-0 behaviour — now says "Returns matches only by default — pass context_lines for surrounding lines (0–10, default 0)."
…ep_file
Ships three capability-gated tools that complete the "which files
does this dependency ship? → read one → search inside it" chain
for agents investigating third-party source. Each is available as
both a CLI subcommand (`githits code files` / `code read` /
`code grep`) and an MCP tool (`list_files` / `read_file` /
`grep_file`). All three sit behind the existing `code_navigation`
capability gate, dual-addressed (`registry`+`package_name` XOR
`repo_url`+`git_ref`), and share the indexing lifecycle used by
the shipped `search_symbols` tool.
## The workflow
```
code files npm:express lib # discover paths
code read npm:express lib/express.js --lines 1-40 # read content
code grep npm:express exports lib/express.js # find a string
```
The `path` field is shared verbatim across the three envelopes
(`list_files.files[].path` → `read_file({path})` /
`grep_file({path})`) so chaining has no rename friction.
## Design
- **Dual addressing** via the shipped `codeTargetSchema` +
`resolveCodeTarget`. XOR enforced; `INVALID_ARGUMENT` envelope
carries `retryable: false`.
- **Indexing lifecycle → typed error.** GraphQL
`PACKAGE_INDEXING` errors and data-path
`indexingStatus: "INDEXING"` sentinels both collapse to
`{code: "INDEXING", retryable: true, details: {indexingRef,
availableVersions}}`. Default wait 20 s (`DEFAULT_WAIT_TIMEOUT_MS`
in `src/shared/code-navigation-defaults.ts`), overridable by
`--wait` / `wait_timeout_ms`.
- **Data-first envelopes.** No indexing metadata on the success
path (the service promotes INDEXING to a typed error before
the envelope builder runs). `filter.*` echoes only
caller-supplied inputs.
- **Shared CLI helpers** (`src/commands/code/code-nav-cli-helpers.ts`)
cover addressing, numeric-option parsing, error rendering,
and the "registry-prefix looks like a spec" ambiguity guard.
- **Binary-file handling** on `read_file`: `isBinary: true` +
`content` omitted (not null). Agents branch on the flag.
- **`grep_file` substring-only** — regex is not supported. The
shared `GREP_PATTERN_SEMANTICS_NOTE` string is referenced
verbatim from the MCP tool description, the MCP `pattern`
argument's `describe`, and the CLI help text so the three
surfaces never disagree.
- **Regex-attempt heuristic** fires a terminal-only nudge on
zero-match results when the pattern contains deliberate
regex signals (`\b`-class escapes, escaped metacharacters,
character classes, lookaround / named groups / inline flags,
brace quantifiers). Deliberately excludes bare `.` / `*` /
`?` / `^` / `$` / `|` / `()` — too common in ordinary code.
## Plain / verbose CLI contract
Each command has a grep-style plain mode and a
human-readable verbose mode:
- `code files` plain = bare paths (one per line, pipe-friendly);
`--verbose` adds header + language / file-type / size
annotations.
- `code read` plain = raw file bytes (verbatim, `cat`-style);
`--verbose` adds the `<path> · <language> · lines <N-M> of
<total>` header and a right-aligned line-number gutter.
- `code grep` plain = matching lines only (`grep`-style);
`--verbose` adds header + gutter + `>` match marker.
`--context <n>` (default 0) adds surrounding lines; nearby
matches whose contexts touch merge into a single block with
no duplicated lines (`grep -C` / `rg -C` convention), blocks
separated by `--`.
Plain-mode truncation warnings (`hasMore`), zero-result hints,
and the regex-attempt nudge all route to **stderr** so pipes
stay uncorrupted. Verbose mode renders the same text inline.
`code grep` adopts grep's exit codes: `0` on match, `1` on zero
matches (including `--json` mode so scripting stays consistent),
`2` on error. The other commands stay on the conventional
`0` success / `1` error split.
## `list_files` `path_prefix` is a literal directory prefix
Not a glob — `*.ts` does not match. Both surfaces call this out
explicitly in their help text / MCP description so agents
reaching for `*.ts` or `**/*.py` aren't silently confused by
zero-result responses.
## Testing
- 1327 tests pass (`bun test`). Full-surface coverage per tool:
service executor, request builder, envelope builder, terminal
formatter, MCP tool, CLI action, CLI↔MCP parity.
- Parity tests assert byte-identical JSON output between CLI
`--json` and MCP text payload; `INVALID_ARGUMENT` fixtures
assert the full envelope shape (including `retryable: false`).
- Plain/verbose split, stderr routing, exit codes, and
block-merging edge cases (unsorted matches, match/context
line collision, null context arrays) all pinned by unit
tests — regressions surface in CI, not in an agent's terminal.
- Typecheck, lint, build clean.
## Live-smoke (production pkgseer)
Chain verified end-to-end against `npm:express`: `code files
npm:express lib` enumerates paths, `code read npm:express
lib/express.js --lines 1-40` reads content verbatim, `code
grep npm:express exports lib/express.js --context 2` returns
merged, deduplicated context blocks. Binary-file sentinel,
NOT_FOUND → code-files hint, INDEXING enrichment, regex-attempt
nudge all exercised.
jlitola
force-pushed
the
feat/code-nav-file-exploration
branch
from
April 21, 2026 12:57
674e3fc to
cbe6cd8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ships three capability-gated tools that complete the "which files does this dependency ship? → read one → search inside it" chain for agents investigating third-party source. Each is available as both a CLI subcommand and an MCP tool:
githits code fileslist_filesgithits code readread_file--lines N-M)githits code grepgrep_fileAll three sit behind the existing
code_navigationcapability gate, dual-addressed (registry+package_nameXORrepo_url+git_ref), and share the indexing lifecycle used by the shippedsearch_symbolstool.The workflow
The
pathfield is shared verbatim across the three envelopes (list_files.files[].path→read_file({path})/grep_file({path})) so chaining has no rename friction.Design
codeTargetSchema+resolveCodeTarget. XOR enforced; theINVALID_ARGUMENTenvelope carriesretryable: false.PACKAGE_INDEXINGerrors and data-pathindexingStatus: \"INDEXING\"sentinels both collapse to{code: \"INDEXING\", retryable: true, details: {indexingRef, availableVersions}}. Default wait 20 s (DEFAULT_WAIT_TIMEOUT_MS), overridable by--wait/wait_timeout_ms.filter.*echoes only caller-supplied inputs; backend defaults do not round-trip.src/commands/code/code-nav-cli-helpers.ts) cover addressing, numeric-option parsing, error rendering, and the "registry-prefix looks like a spec" ambiguity guard.read_file:isBinary: true+contentomitted (notnull). Agents branch on the flag.grep_filesubstring-only — regex is not supported. The sharedGREP_PATTERN_SEMANTICS_NOTEstring is referenced verbatim from the MCP tool description, the MCPpatternargument'sdescribe, and the CLI help text so the three surfaces never disagree.\b-class escapes, escaped metacharacters, character classes, lookaround / named groups / inline flags, brace quantifiers). Deliberately excludes bare./*/?/^/$/|/()— too common in ordinary code.Plain / verbose CLI contract
Each command has a grep-style plain mode and a human-readable verbose mode:
code filesplain = bare paths, one per line;--verboseadds header + language / file-type / size annotations.code readplain = raw file bytes, verbatim (cat-style);--verboseadds the<path> · <language> · lines <N-M> of <total>header and a right-aligned line-number gutter.code grepplain = matching lines only (grep-style);--verboseadds header + gutter +>match marker.--context <n>(default 0) adds surrounding lines; nearby matches whose contexts touch merge into a single block with no duplicated lines (grep -C/rg -Cconvention), distinct blocks separated by--.Plain-mode truncation warnings (
hasMore), zero-result hints, and the regex-attempt nudge all route to stderr so pipes stay uncorrupted. Verbose mode renders the same text inline.code grepexit codes matchgrep(1):--json— scripting stays consistent across surfaces)The other commands stay on the conventional
0success /1error split.list_files.path_prefixis a literal directory prefixNot a glob —
*.tsdoes not match. Both surfaces call this out explicitly in their help text / MCP description so agents reaching for*.tsor**/*.pyaren't silently confused by zero-result responses. A glob /fileTypesenhancement is filed with the backend.Positionals
code files [spec] [path-prefix]code read [spec] <path>code grep [spec] <pattern> <path>All three mirror each other so copy-paste between them works. In
--repo-urlmode the spec positional is replaced by the URL flags; a registry-prefix ambiguity guard oncode filescatchescode files npm:express --repo-url …typos that would otherwise silently interpret the spec as a path prefix.Testing
bun test). Full-surface coverage per tool: service executor, request builder, envelope builder, terminal formatter, MCP tool, CLI action, and CLI↔MCP parity.--jsonand MCP text payload;INVALID_ARGUMENTfixtures assert the full envelope shape (includingretryable: false).Live-smoke (production pkgseer)
Chain verified end-to-end against
npm:express:code files npm:express libenumerates paths,code read npm:express lib/express.js --lines 1-40reads content verbatim,code grep npm:express exports lib/express.js --context 2returns merged, deduplicated context blocks. Binary-file sentinel, NOT_FOUND → code-files hint, INDEXING enrichment, and the regex-attempt nudge have all been exercised.Test plan
bun testpasses (1327 / 0)bun run typecheck/bun run build/bun run lintcleandocs/implementation/cli-commands.md; shared design indocs/implementation/tools.mdandmcp-cli-parity.md🤖 Generated with Claude Code