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
41 changes: 40 additions & 1 deletion docs/implementation/cli-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The CLI exposes three primary commands (`search`, `languages`, `feedback`) that
| `feedback <solution_id>` | `--accept` or `--reject` | `-m, --message <text>`, `--json` | Submit feedback on a search result |
| `code search <package> [query]` | package spec | `--keywords`, `--keyword`, `--match-mode`, `--category`, `--kind`, `--file`, `--intent`, `--limit`, `--wait`, `--json` | Search indexed dependency source code |
| `pkg info <spec>` | package spec | `--verbose`, `--json` | Show a package overview (latest version, downloads, license, vulnerabilities) |
| `pkg vulns <spec>` | package spec (optional `@version`) | `--severity`, `--include-withdrawn`, `--verbose`, `--json` | List known vulnerabilities for a package (npm/pypi/hex/crates) |

### `githits init`

Expand Down Expand Up @@ -100,7 +101,7 @@ Shows a concise overview for a single package: latest version, license, descript

**Package spec.** `<registry>:<name>`. Registries: `npm`, `pypi`, `hex`, `crates`, `nuget`, `maven`, `zig`, `vcpkg`, `packagist`. Scoped npm names (`npm:@types/node`) are supported.

**Always latest.** `pkg info` returns the latest published version regardless of input. Passing `<spec>@<version>` is rejected with `INVALID_ARGUMENT` and a clear message — the tool never silently swaps to latest. Use `pkg vulns` (future) or `pkg deps` (future) for version-pinned queries.
**Always latest.** `pkg info` returns the latest published version regardless of input. Passing `<spec>@<version>` is rejected with `INVALID_ARGUMENT` and a clear message — the tool never silently swaps to latest. Use `pkg vulns` (supports `@version`) or `pkg deps` (future) for version-pinned queries.

**`--verbose` + `--json`.** `--verbose` has no effect under `--json` — the JSON envelope always carries every field the verbose terminal view exposes (and more). The flag only affects human-readable output.

Expand All @@ -110,6 +111,44 @@ Shows a concise overview for a single package: latest version, license, descript

**Troubleshooting.** `GITHITS_DEBUG=pkg-intel` emits PII-safe classified-error diagnostics (area, event, code, error class, detail keys). `GITHITS_DEBUG=pkg-graphql` emits transport-failure diagnostics from inside the POST helper. Use `GITHITS_DEBUG=*` to enable both.

### `githits pkg vulns`

```
githits pkg vulns npm:express
githits pkg vulns npm:express@4.17.0
githits pkg vulns pypi:requests --severity high
githits pkg vulns crates:serde --json
githits pkg vulns npm:minimatch --include-withdrawn --verbose
```

Lists known CVE / OSV advisories for a package: severity, affected version ranges, fix versions, and upgrade targets. Malicious-package advisories (supply-chain attacks flagged by OSV) surface in a separate `MALWARE` bucket that sorts above all CVE advisories.

**Package spec.** `<registry>:<name>[@<version>]`. Unlike `pkg info`, `pkg vulns` supports `@<version>` because the backend query accepts a concrete version (useful for checking an older pinned release). Only `npm`, `pypi`, `hex`, and `crates` support vulnerability data; other registries are rejected client-side with `pkg vulns only supports npm, pypi, hex, and crates. Got: ${registry}.`

**Filtering.** `--severity low|medium|high|critical` maps to a CVSS float threshold (`low=0.1, medium=4, high=7, critical=9`) and goes server-side. The backend's returned `vulnerabilityCount` reflects the filtered set — no client-side filtering, no dual-summary block. Callers wanting the full picture omit the flag. `--include-withdrawn` sends `includeWithdrawn: true` to the backend; withdrawn advisories bucket below active ones in the terminal list.

**Zero-vulns hot path.** The common case (clean package) renders as header + one-line summary body (`No known vulnerabilities.`) — no breakdown, no advisory list, no footer. Agents checking "am I safe?" pay minimal token cost on the happy path.

**Version validation.** `pkg vulns` expects canonical package versions. Tag-style inputs such as `@v4.18.0` are rejected client-side with `INVALID_ARGUMENT` and an actionable message telling the caller to drop the leading `v`, instead of forwarding the request to the backend and surfacing its current generic failure.

**Malware marker.** Advisories with `isMalicious: true` render with a red/bold `MALWARE` column (optionally combined as `MALWARE · crit` when both flags exist). Count surfaces in the summary breakdown line as `N MALWARE · N crit · …`. Buckets partition every returned advisory: `MALWARE + crit + high + medium + low + unrated = advisories.length`, which equals `summary.total` when the backend keeps its count and list consistent. Non-malicious advisories without a CVSS score bucket under `unrated` so the breakdown reconciles with the header total (common for PyPI / Rust advisories where CVSS may be absent).

**Affected-range truncation (terminal-width aware).** The `affected` detail row under each advisory caps at 4 ranges on narrow terminals (≤119 cols), 6 on standard-wide (120–159 cols), and 8 on ultrawide (≥160 cols). The remainder collapses into a dim `… (+N more; use -v)` hint. Verbose mode (`-v`) shows every range. JSON output is never truncated — machine consumers get the full list.

**Unrated severity column.** Advisories with no CVSS score (common on RUSTSEC / PYSEC upstreams) render with a dim `unrated` label in the severity column rather than an empty gutter, matching the header-breakdown vocabulary. They sort below banded advisories within the active bucket.

**Placeholder summary stripping.** When the upstream advisory feed returns the literal string `No summary available` (an OSV convention), both the JSON envelope and the terminal row drop the field entirely — absence of `summary` is the signal, and the advisory row is shorter as a result.

**Upgrade-path ordering.** `upgradePaths` are de-duplicated and sorted ascending by semver-ish comparison (pre-release suffixes rank below the matching base release), so the footer presents the minimum-churn upgrade first: `Upgrade options: 3.11.0, 4.0.0-rc1, 4.5.0, 4.19.2, …` rather than the backend's advisory-iteration order.

**Output envelope.** `{registry, name, version, requestedVersion?, summary: {total, affected?, bySeverity?}, advisories?, upgradePaths?}`. Each advisory: `{id?, aliases?, summary?, severity?, severityLabel?, affectedRanges?, fixedIn?, publishedAt?, modifiedAt?, withdrawnAt?, isMalicious?}`. `modifiedAt` included only when it differs from `publishedAt`. `isMalicious` included only when `true`.

**Exit codes.** 0 on success including zero-vulns; 1 on any error. Under `--json`, the error envelope is written to **stderr**.

**Capability gate.** Same as `pkg info` (inherits from the `code_navigation` token capability).

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

## Architecture

```
Expand Down
53 changes: 53 additions & 0 deletions docs/implementation/mcp-cli-parity.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,18 @@ When a new tool lands with both MCP and CLI surfaces:
| `src/shared/search-symbols-response.ts` | Shared JSON envelope builders for `search_symbols`. |
| `src/shared/package-summary-request.ts` | Shared request builder for `package_summary`. |
| `src/shared/package-summary-response.ts` | Lean JSON envelope builder and terminal formatter for `package_summary`. |
| `src/shared/package-vulnerabilities-request.ts` | Shared request builder for `package_vulnerabilities`; owns the tool-local `supportsVulnerabilitiesRegistry` predicate and the severity-label → CVSS float map. |
| `src/shared/package-vulnerabilities-response.ts` | Lean JSON envelope builder for `package_vulnerabilities` (shared); terminal formatter (CLI-only). |
| `src/shared/package-intelligence-error-map.ts` | `mapPackageIntelligenceError` classifier (reuses `MappedError` from the code-nav map). |
| `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/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/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). |

## Per-tool notes

Expand All @@ -177,3 +182,51 @@ When a new tool lands with both MCP and CLI surfaces:
- **`@version` rejection.** CLI-only. The MCP tool has no `version`
input. The CLI's `pkg info` throws `InvalidPackageSpecError` on
any non-null parsed version — never silently swaps to latest.

### `package_vulnerabilities`

- **Permissive MCP schema + in-handler validation.** Same pattern as
`package_summary`. `buildPackageVulnerabilitiesParams` is the
single validator used by both surfaces; raw Zod errors never
surface in the envelope.
- **Filter-aware summary.** `minSeverity` + `includeWithdrawn` go
straight to the GraphQL query; the backend's `vulnerabilityCount`
reflects the filtered set. No client-side filtering, no
`summary.filtered` dual-block.
- **Partitioning bySeverity buckets.** `summary.bySeverity` carries
a `malware` key for `isMalicious === true` advisories; severity
bands for non-malicious advisories with a positive CVSS score;
and `unrated` for non-malicious advisories with no score. Every
returned advisory lands in exactly one bucket — client-side
guarantee `MALWARE + crit + high + medium + low + unrated =
advisories.length`. The sum also equals `summary.total` whenever
the backend keeps `vulnerabilityCount` and `vulnerabilities[]`
consistent. Malware advisories sort first in the advisory list
regardless of severity score; `unrated` advisories sort last
within the active bucket.
- **Scope of the shared helper.** `buildPackageVulnerabilitiesSuccessPayload`
is shared between CLI `--json` and MCP `content[0].text` — that's
what enforces envelope parity. The terminal formatter
`formatPackageVulnerabilitiesTerminal` is CLI-only (MCP always
emits JSON). The parity doc's default rule (CLI-local rendering)
still applies to the formatter; the envelope builder is the
explicit shared-helper exception.
- **Parity assertion policy** (coded in
`src/tools/package-vulnerabilities-parity.test.ts`):
- `toEqual` for the service-sourced fixtures: happy, zero-vulns,
filtered-success, versioned-match (no `requestedVersion`),
versioned-real-diff (`requestedVersion` present), `NOT_FOUND`,
`VERSION_NOT_FOUND` (with structured details), `BACKEND_ERROR`.
- `toMatchObject` for builder-sourced `INVALID_ARGUMENT` cases
such as unsupported registry (`vcpkg`) and tag-style version
input (`v4.18.0`).
- **Typed `VERSION_NOT_FOUND`.** Mirrors the code-nav precedent:
`PackageIntelligenceVersionNotFoundError` carries structured
fields sourced from GraphQL `extensions` (`packageName`,
`requestedVersion`, `availableVersions`). The classifier emits
structured `details` in the error envelope.
- **Client-side `v`-prefix rejection.** `package_vulnerabilities`
validates version strings before the service call. Tag-style
inputs like `v4.18.0` are rejected as `INVALID_ARGUMENT` with an
actionable message instead of relying on the current production
backend, which returns a generic error for that input.
19 changes: 18 additions & 1 deletion docs/implementation/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ Both expose the same tools with identical names, parameters, and descriptions. T
| `feedback` | `solution_id`, `accepted`, `feedback_text?` | Submit feedback on a search result to improve quality. |
| `search_symbols` | `target`, `query?`, `keywords?`, `match_mode?`, `category?`, `kind?`, `file_path?`, `limit?`, `file_intent?`, `wait_timeout_ms?` | Capability-gated code navigation search over indexed dependency source. |
| `package_summary` | `registry`, `package_name` | Package overview: latest version, license, description, repository, downloads, GitHub metadata, install command, and known vulnerabilities. Always returns the latest published version. |
| `package_vulnerabilities` | `registry`, `package_name`, `version?`, `min_severity?`, `include_withdrawn?` | Known vulnerabilities for a package on npm, PyPI, Hex, or Crates. Count summary, per-advisory OSV ID + severity + affected/fix ranges, and upgrade paths. Malware is surfaced in a disjoint bucket. |

`search_symbols` and `package_summary` are only registered when the startup token advertises `code_navigation` capability. The backend endpoint can be overridden via `GITHITS_CODE_NAV_URL` for local development. Capability gating keeps the tools hidden from public/default flows while the feature is still rolling out.
`search_symbols`, `package_summary`, and `package_vulnerabilities` are only registered when the startup token advertises `code_navigation` capability. The backend endpoint can be overridden via `GITHITS_CODE_NAV_URL` for local development. Capability gating keeps the tools hidden from public/default flows while the feature is still rolling out.

`search_symbols` shares request-construction, error classification, and JSON-payload shape with the CLI `githits code search` command via shared helpers under `src/shared/`. The parity rules are codified in [`mcp-cli-parity.md`](./mcp-cli-parity.md); the parity test (`src/tools/search-symbols-parity.test.ts`) asserts that both surfaces emit identical JSON for equivalent inputs.

Expand All @@ -43,6 +44,22 @@ Both expose the same tools with identical names, parameters, and descriptions. T

`package_summary` shares its envelope builder, terminal formatter, and error classifier with the CLI `githits pkg info` command via `src/shared/package-summary-request.ts`, `src/shared/package-summary-response.ts`, and `src/shared/package-intelligence-error-map.ts`. The parity test (`src/tools/package-summary-parity.test.ts`) asserts `toEqual` between CLI `--json` and MCP `content[0].text` for service-sourced fixtures, and `toMatchObject` for the `INVALID_ARGUMENT` fixture where surface-specific error text is acceptable.

### `package_vulnerabilities` response shape

**Filter-aware summary.** `min_severity` and `include_withdrawn` are passed straight to the GraphQL query. The backend's `vulnerabilityCount` reflects the filtered set — there is no client-side filtering and no `summary.filtered` dual-block. Callers wanting the unfiltered view omit the flag.

**Partitioning buckets.** Advisories with `isMalicious: true` count **only** under `summary.bySeverity.malware`; severity bands (`critical`/`high`/`medium`/`low`) count non-malicious advisories with a positive CVSS score; non-malicious advisories with no score count under `summary.bySeverity.unrated`. Every returned advisory lands in exactly one bucket — the client-side guarantee is `MALWARE + crit + high + medium + low + unrated = advisories.length`. The sum also equals `summary.total` whenever the backend keeps `vulnerabilityCount` and `vulnerabilities[]` consistent (the expected case on all shipped registries). The malware bucket sorts to the top of the advisory list regardless of score. The `unrated` bucket ensures the terminal breakdown line reconciles with the header total on Rust / PyPI packages where a non-trivial fraction of advisories ship without a CVSS score.

**Version validation.** `package_vulnerabilities` accepts canonical package versions only. Tag-style refs with a leading `v` (for example `v4.18.0`) are rejected client-side with `INVALID_ARGUMENT` before the backend call. This avoids the current production backend's unhelpful generic error for that input shape. This is intentionally narrow: proper ecosystem-aware version parsing and typed invalid-version errors belong in the backend, not in ad hoc CLI normalization rules.

**Typed `VERSION_NOT_FOUND`.** Mirrors the code-nav precedent: a dedicated `PackageIntelligenceVersionNotFoundError` carries structured `{ packageName, requestedVersion, availableVersions? }` fields sourced from GraphQL `extensions`. Classifier routes it to `VERSION_NOT_FOUND` with a structured `details` block. When the backend returns a generic backend error whose message matches `/no matching version/i` (current production behaviour — the typed `extensions.code` is not yet emitted on `packageVulnerabilities`), the service promotes the error to `VersionNotFoundError` using the caller's `packageName` + `version` so CLI / MCP surfaces still render an actionable envelope. `availableVersions` remains undefined in the fallback path until the backend ships them.

**Omission rules.** Null scalars omitted; empty arrays dropped; zero-count `bySeverity` keys dropped; the `bySeverity` block itself dropped when `total === 0`. `modifiedAt` included only when it differs from `publishedAt`. `isMalicious` included only when `true`.

**Registry coverage.** Only npm, PyPI, Hex, and Crates have vulnerability data. The CLI + MCP reject the other five registries client-side with a tool-specific message (`pkg vulns only supports npm, pypi, hex, and crates. Got: ${registry}.`) — rejection predicate lives in `src/shared/package-vulnerabilities-request.ts` rather than the shared registry module, since it is a tool-specific capability matrix.

`package_vulnerabilities` shares its envelope builder with the CLI `githits pkg vulns` command via `src/shared/package-vulnerabilities-request.ts` and `src/shared/package-vulnerabilities-response.ts`. The terminal formatter is CLI-only (MCP always emits JSON). The parity test (`src/tools/package-vulnerabilities-parity.test.ts`) asserts `toEqual` across the service-sourced success and typed-error fixtures, and `toMatchObject` for builder-sourced `INVALID_ARGUMENT` fixtures such as unsupported registries and tag-style `v`-prefixed versions.

## Server instructions

The MCP server advertises a short, cross-tool orientation via the protocol's server-level `instructions` field. This is distinct from per-tool `description` text: instructions cover rationale, workflow glue, and decisions that span multiple tools, while per-tool descriptions remain the source of truth for arguments, output shape, and tool-specific constraints.
Expand Down
Loading
Loading