Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions docs/implementation/cli-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ The CLI exposes three primary commands (`search`, `languages`, `feedback`) that
| `pkg vulns <spec>` | package spec (optional `@version`) | `--severity`, `--include-withdrawn`, `--verbose`, `--json` | List known vulnerabilities for a package (npm/pypi/hex/crates) |
| `pkg deps <spec>` | package spec (optional `@version`) | `--groups`, `--lifecycle`, `--transitive`, `--depth`, `--verbose`, `--json` | Analyse dependencies: direct runtime deps, structured groups, optional transitive graph (npm/pypi/hex/crates/vcpkg/zig) |
| `pkg changelog [spec]` | package spec OR `--repo-url` | `--from`, `--to`, `--limit`, `--git-ref`, `--no-body`, `--verbose`, `--json` | Release notes / changelog entries for a package or GitHub repo (GitHub Releases, CHANGELOG.md, or HexDocs). Default shows each entry with a 10-line body preview; `--verbose` uncaps, `--no-body` drops. |
| `code files [spec] [path-prefix]` | package spec OR `--repo-url` + `--git-ref`; optional `[path-prefix]` | `--limit`, `--wait`, `--verbose`, `--json` | List files in an indexed dependency. `[path-prefix]` is a literal directory prefix (not a glob). Plain output is one path per line; `--verbose` adds language / type / size annotations. Indexing-retry via `--wait` or the `availableVersions` hint in the error envelope. |
| `code read <spec?> <path>` | package spec OR `--repo-url` + `--git-ref`; plus `<path>` | `--lines`, `--start`, `--end`, `--wait`, `--verbose`, `--json` | Read a file's contents. Plain output is the raw file bytes (pipe-friendly); `--verbose` adds a header and a line-number gutter. `--lines 10-40` concise form; `--start`/`--end` equivalent. Binary files show a sentinel line. |
| `code grep <spec?> <pattern> <path>` | package spec OR `--repo-url` + `--git-ref`; plus `<pattern>` and `<path>` | `--context`, `--limit`, `--wait`, `--verbose`, `--json` | Search within a single file for a case-insensitive substring (not regex). Plain output is matching lines only (pipe-friendly, `grep`-style); `--verbose` adds a header and a line-number gutter with `>` markers on match lines. `--context <n>` adds surrounding lines (default 0, up to 10); overlapping blocks merge without duplicates. Max 200-char pattern; up to 200 matches. |

### `githits init`

Expand Down Expand Up @@ -224,6 +227,78 @@ Fetches release notes or changelog entries for a package or GitHub repository. O

**Troubleshooting.** Same debug areas (`GITHITS_DEBUG=pkg-intel` for classified errors; `GITHITS_DEBUG=pkg-graphql` for transport failures).

### `githits code files`

```
githits code files npm:express
githits code files npm:express lib # scope by prefix
githits code files npm:express lib --verbose # + language / type / size
githits code files --repo-url https://github.com/expressjs/express --git-ref main lib
githits code files npm:express --json
```

Lists files in an indexed dependency. `[spec] [path-prefix]` positionals mirror `code read` / `code grep` so the three commands chain without friction. `[path-prefix]` is a literal directory prefix; glob / extension filtering is not supported by the backend today (tracked as an upstream ask).

**Plain output (default).** One bare path per line on stdout — pipe-friendly. No header, no annotations. `code files npm:express lib | xargs -I{} …` works cleanly.

**`--verbose`.** Adds a contextual header (`<identity> · <count>`), the resolution line (`indexed at <ref> · commit <sha>`), and per-row language / file-type / byte-size annotations.

**`stdout` vs `stderr` routing (plain mode).** Truncation warnings (`More files available — pass --limit higher …`) and empty-result hints go to **stderr**, not stdout, so they stay visible to humans without polluting pipes. In `--verbose` the same text renders inline.

**Addressing ambiguity guard.** In `--repo-url` mode, a positional that matches a known registry prefix (`npm:`, `pypi:`, `hex:`, `crates:`, `nuget:`, `maven:`, `zig:`, `vcpkg:`, `packagist:`) is rejected with a "looks like a package spec" error — catches `code files npm:express --repo-url …` typos that would otherwise silently interpret the spec as a path prefix.

**Exit codes.** `0` on success (including empty results — absence of files is not an error). `1` on error (authentication, indexing, invalid arguments, backend failures).

### `githits code read`

```
githits code read npm:express lib/express.js
githits code read npm:express lib/express.js --lines 1-40
githits code read npm:express lib/express.js --verbose # + header + gutter
githits code read --repo-url https://github.com/expressjs/express --git-ref main lib/express.js
githits code read npm:express lib/express.js --json
```

Reads a file from an indexed dependency. `<path>` is package-relative in spec mode, repo-relative in `--repo-url` mode.

**Plain output (default).** Raw file bytes, verbatim (preserves the backend's trailing newline). Piping `code read … | grep …` or `code read … > file` round-trips cleanly.

**`--verbose`.** Adds the `<path> · <language> · lines <N-M> of <total>` header and a right-aligned line-number gutter. No stderr routing — `read` has no truncation path.

**Line ranges.** `--lines 10-40` (concise form), `--lines 10-` (open end), `--lines -40` (open start). `--start <n>` / `--end <n>` are the verbose equivalents. Combining `--lines` with `--start` / `--end` is rejected.

**Binary files.** Plain mode writes `Binary file — cannot display as text.` to stdout (consistent with `grep`'s binary-file convention). `--verbose` adds the header above the sentinel. `--json` exposes the classification via `isBinary: true` with `content` omitted — agents branch on the flag, not a null check.

**Exit codes.** `0` on success. `1` on error — `FILE_NOT_FOUND` (path doesn't resolve) carries a "Use `code files` to list available paths" hint in terminal output.

### `githits code grep`

```
githits code grep npm:express express lib/express.js
githits code grep npm:express express lib/express.js --context 2 # merged blocks
githits code grep npm:express express lib/express.js --verbose # + header + gutter + `>` marker
githits code grep --repo-url https://github.com/expressjs/express --git-ref main export lib/express.js
githits code grep npm:express express lib/express.js --json
```

Case-insensitive **substring** search inside a single file — not regex. Max pattern 200 chars; up to 200 matches with up to 10 context lines each. For symbol-shaped searches use `githits code search` (backed by `search_symbols`).

**Plain output (default).** Matching lines only, one per line on stdout — mirrors `grep`'s default. `--context <n>` (0–10, default **0**) adds surrounding lines; nearby matches whose contexts touch or overlap merge into a single block with no duplicated lines. Distinct blocks are separated by `--` on its own line, matching `grep -C` / `rg -C` convention.

**`--verbose`.** Adds header, right-aligned line-number gutter, and a `>` marker on match lines so they're distinguishable from context at a glance.

**`stdout` vs `stderr` routing (plain mode).** "More matches available" truncation warning goes to **stderr**. When a zero-match pattern looks like a regex attempt (`\bfoo\b`, `^start`, character classes, etc.) a one-line nudge — "Note: pattern matched literally — this tool does case-insensitive substring search, not regex." — also goes to stderr. Pipes stay clean; humans still see the hints.

**Exit codes (grep-compatible).**

- `0` — at least one match.
- `1` — zero matches. Fires in both plain and `--json` modes so scripting (`if code grep X file; then …`) behaves consistently across surfaces.
- `2` — error (missing file, indexing, invalid arguments, backend failure). Distinguished from "no match" so scripts can branch correctly.

This is the standard `grep(1)` contract; the tool adopts it deliberately because its output shape mirrors grep's.

**Regex pattern note.** The `GREP_PATTERN_SEMANTICS_NOTE` string ("Case-insensitive substring matching. NOT regex — …") is shared verbatim across the CLI help text, the MCP tool description, and the MCP `pattern` argument's `describe` so the three surfaces never disagree about pattern semantics.

## Architecture

```
Expand Down
68 changes: 66 additions & 2 deletions docs/implementation/mcp-cli-parity.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,23 +154,41 @@ When a new tool lands with both MCP and CLI surfaces:
| `src/shared/package-dependencies-response.ts` | Lean JSON envelope builder for `package_dependencies` (shared); terminal formatter (CLI-only). |
| `src/shared/package-changelog-request.ts` | Shared request builder for `package_changelog`; owns spec-XOR-repo-URL validation, `<spec>@<version>` rejection, `--from` / `--limit` mutex, tag-style version rejection, and the `explicitFilterFields` tracker. |
| `src/shared/package-changelog-response.ts` | JSON envelope builder for `package_changelog` (shared); terminal formatter (CLI-only). |
| `src/shared/list-files-request.ts` | Shared request builder for `list_files`; applies the shared `DEFAULT_WAIT_TIMEOUT_MS`, enforces limit bounds, tracks explicit-filter fields. |
| `src/shared/list-files-response.ts` | JSON envelope builder for `list_files` (shared); terminal formatter (CLI-only). Resolves the `hasMore` → `N+` header behaviour. |
| `src/shared/read-file-request.ts` | Shared request builder for `read_file`; trims filePath, validates start/end line positive-integer rules, rejects reversed ranges. |
| `src/shared/read-file-response.ts` | JSON envelope builder for `read_file` (shared); terminal formatter (CLI-only). Normalises the envelope key to `path` (not `filePath`) so `list_files` → `read_file` chains without renames. |
| `src/shared/grep-file-request.ts` | Shared request builder for `grep_file`; exports `GREP_PATTERN_SEMANTICS_NOTE` referenced by MCP description, MCP `pattern` describe, and CLI help. Also exports `looksLikeRegexAttempt` heuristic. |
| `src/shared/grep-file-response.ts` | JSON envelope builder for `grep_file` (shared); terminal formatter (CLI-only) owns the regex-char empty-result nudge. |
| `src/shared/code-navigation-error-map.ts` | `mapCodeNavigationError` classifier. Owns the `INDEXING` / `FILE_NOT_FOUND` / `NOT_FOUND` codes shared across all four code-nav tools. |
| `src/shared/code-navigation-defaults.ts` | `DEFAULT_WAIT_TIMEOUT_MS = 20_000` + `MAX_WAIT_TIMEOUT_MS = 60_000`. Both CLI and MCP request builders import these so defaults never diverge. |
| `src/tools/code-navigation-shared.ts` | `codeTargetSchema` + `resolveCodeTarget` — the single addressing primitive used by `search_symbols`, `list_files`, `read_file`, `grep_file`. |
| `src/shared/package-intelligence-error-map.ts` | `mapPackageIntelligenceError` classifier (reuses `MappedError` from the code-nav map). |
| `src/services/promote-version-not-found.ts` | Shared helper that promotes generic backend errors with "no matching version" messages into typed `VERSION_NOT_FOUND`. Used by `packageVulnerabilities`, `packageDependencies`, and `packageChangelog` executors. Extended in P4 to recognise `fromVersion` / `toVersion` (and to skip the `details.package` synthesis when registry/name aren't available, i.e. repo-URL mode). |
| `src/services/promote-version-not-found.ts` | Shared helper that promotes generic backend errors with "no matching version" messages into typed `VERSION_NOT_FOUND`. Used by the `packageVulnerabilities`, `packageDependencies`, and `packageChangelog` executors. Handles both `version` (single-version queries) and `fromVersion` / `toVersion` (range queries), and skips `details.package` synthesis when registry/name aren't available (repo-URL mode). |
| `src/tools/search-symbols.ts` | MCP tool definition for `search_symbols`. |
| `src/tools/package-summary.ts` | MCP tool definition for `package_summary`. |
| `src/tools/package-vulnerabilities.ts` | MCP tool definition for `package_vulnerabilities`. |
| `src/tools/package-dependencies.ts` | MCP tool definition for `package_dependencies`. |
| `src/tools/package-changelog.ts` | MCP tool definition for `package_changelog`. |
| `src/tools/list-files.ts` | MCP tool definition for `list_files`. |
| `src/tools/read-file.ts` | MCP tool definition for `read_file`. |
| `src/tools/grep-file.ts` | MCP tool definition for `grep_file`. |
| `src/commands/code/search-symbols.ts` | CLI command. |
| `src/commands/pkg/info.ts` | CLI command for `pkg info`. |
| `src/commands/pkg/vulns.ts` | CLI command for `pkg vulns`. |
| `src/commands/pkg/deps.ts` | CLI command for `pkg deps`. |
| `src/commands/pkg/changelog.ts` | CLI command for `pkg changelog`. |
| `src/commands/code/files.ts` | CLI command for `code files`. |
| `src/commands/code/read.ts` | CLI command for `code read`. |
| `src/commands/code/grep.ts` | CLI command for `code grep`. |
| `src/tools/search-symbols-parity.test.ts` | Parity tests (cite rule IDs). |
| `src/tools/package-summary-parity.test.ts` | Parity tests for `package_summary` (cite rule IDs). |
| `src/tools/package-vulnerabilities-parity.test.ts` | Parity tests for `package_vulnerabilities` (cite rule IDs). |
| `src/tools/package-dependencies-parity.test.ts` | Parity tests for `package_dependencies` (cite rule IDs). |
| `src/tools/package-changelog-parity.test.ts` | Parity tests for `package_changelog` (cite rule IDs). |
| `src/tools/list-files-parity.test.ts` | Parity tests for `list_files` (cite rule IDs). |
| `src/tools/read-file-parity.test.ts` | Parity tests for `read_file` (cite rule IDs). |
| `src/tools/grep-file-parity.test.ts` | Parity tests for `grep_file` (cite rule IDs). |

## Per-tool notes

Expand Down Expand Up @@ -305,7 +323,8 @@ When a new tool lands with both MCP and CLI surfaces:
`packageChangelog` is intrinsically repo-level on the backend (its
sources are GitHub Releases, CHANGELOG.md, HexDocs); repo-URL
isn't a bolt-on, it's a peer addressing mode on the GraphQL
signature. P1 / P2 / P3 omit it because their backend queries are
signature. `package_summary` / `package_vulnerabilities` /
`package_dependencies` omit it because their backend queries are
registry-metadata APIs without repo-URL alternatives. Future
pkg-intel tool authors should not cargo-cult the asymmetry.
- **`<spec>@<version>` rejected.** Other `pkg` commands give
Expand Down Expand Up @@ -369,3 +388,48 @@ When a new tool lands with both MCP and CLI surfaces:
`BACKEND_ERROR`.
- `toMatchObject` for builder-sourced `INVALID_ARGUMENT` cases:
`<spec>@<version>` rejection, `--from` + `--limit` mutex.

### `list_files` / `read_file` / `grep_file` (file-exploration bundle)

All three reuse `codeTargetSchema` + `resolveCodeTarget` from
`src/tools/code-navigation-shared.ts`. The indexing lifecycle is
shared (see `tools.md` "Indexing lifecycle" section). Parity
tests cover dual addressing, default + explicit filter echoes,
INDEXING error envelope, NOT_FOUND envelope, and INVALID_ARGUMENT
with full envelope shape (`{error, code, retryable}`) — the
partial-match policy is deliberately *not* used on INVALID_ARGUMENT
so envelope-drift surfaces in the test rather than at an agent.

- **`list_files`**: `filter.path_prefix` / `filter.limit` echo
only when explicit. Default `limit: 200` never round-trips.
Backend returns `total` capped at returned count when
`hasMore: true`; terminal formatter renders `N+` to avoid
misleading users.
- **`read_file`**: envelope uses `path` (not `filePath`) to
match `list_files.files[].path`, so agent chains mechanically.
Binary files: `isBinary: true` + `content` omitted (not
`null`). Parity fixture locks this in. `fetchCodeContext`
on the backend doesn't return `availableVersions` on
INDEXING responses, so its `details` block carries only
`indexingRef` — MCP description calls this out explicitly.
- **`grep_file`**: `GREP_PATTERN_SEMANTICS_NOTE` constant
(exported from `grep-file-request.ts`) ensures the
substring-only disclosure is identical in the MCP
description, MCP `pattern` field describe, and CLI help text.
Regex-char heuristic in the terminal formatter nudges users
who typed clearly-regex patterns; the JSON envelope never
carries this hint. Triggered signals cover `\b\B\w\W\d\D\s\S`,
escaped metacharacters, character classes, non-capturing /
lookaround / named groups / inline flags, and brace
quantifiers. Deliberately excludes bare `.`, `*`, `+`, `?`,
`^`, `$`, `|`, `(`, `)` — too common in ordinary code.

- **Parity assertion policy** (coded in the three parity
tests):
- `toEqual` across service-sourced fixtures: happy (package
and repo-URL addressing), filter echoes, INDEXING, NOT_FOUND,
and (for `read_file`) the binary fixture; (for
`read_file`) FILE_NOT_FOUND and line range.
- `toMatchObject` with explicit `retryable: false` assertion
for builder-sourced `INVALID_ARGUMENT` — both surfaces
must emit the same envelope keys so drift is loud.
Loading
Loading