From 8662a9f7322825b8d0149c93d18d1682e4905277 Mon Sep 17 00:00:00 2001 From: Juha Litola Date: Tue, 21 Apr 2026 16:56:40 +0300 Subject: [PATCH] chore: bump package to 0.1.9 Prepare the hidden package and code tools for a silent rollout by tightening MCP guidance, CLI help text, and release metadata while keeping public docs free of backend-specific details. --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- .plugin/plugin.json | 2 +- README.md | 32 ++++++++++++++++-- docs/implementation/auth.md | 8 ++--- docs/implementation/cli-commands.md | 40 +++++++++++------------ docs/implementation/config.md | 15 ++++----- docs/implementation/mcp-cli-parity.md | 37 ++++++++++----------- docs/implementation/tools.md | 26 +++++++-------- gemini-extension.json | 2 +- package.json | 2 +- plugins/claude/.claude-plugin/plugin.json | 2 +- src/commands/code/files.ts | 3 +- src/commands/code/index.ts | 2 +- src/commands/code/read.ts | 3 +- src/commands/code/search-symbols.ts | 5 ++- src/commands/mcp-instructions.ts | 16 ++++----- src/commands/pkg/index.ts | 2 +- src/tools/list-files.ts | 7 ++-- src/tools/package-summary.ts | 11 ++++--- src/tools/package-vulnerabilities.ts | 9 ++--- src/tools/read-file.ts | 3 +- 22 files changed, 133 insertions(+), 98 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index ad9bd292..7362d378 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -6,7 +6,7 @@ }, "metadata": { "description": "GitHits plugins for Claude Code - code examples from global open source", - "version": "0.1.8" + "version": "0.1.9" }, "plugins": [ { diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 0a456fb2..b195fe71 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "githits", - "version": "0.1.8", + "version": "0.1.9", "description": "Code examples from global open source for developers and AI assistants", "author": { "name": "GitHits" diff --git a/.plugin/plugin.json b/.plugin/plugin.json index 0a456fb2..b195fe71 100644 --- a/.plugin/plugin.json +++ b/.plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "githits", - "version": "0.1.8", + "version": "0.1.9", "description": "Code examples from global open source for developers and AI assistants", "author": { "name": "GitHits" diff --git a/README.md b/README.md index 83a34605..6b239c94 100644 --- a/README.md +++ b/README.md @@ -82,11 +82,13 @@ For plugin-based hosts, install from npm/GitHub using your agent's plugin workfl - **GitHub Copilot**: supports Open Plugin components - **Gemini CLI**: supports `gemini-extension.json` and `GEMINI.md` -That's it. Your assistant now has a `search` tool it will use automatically when it needs code examples. +That's it. Your assistant now has GitHits search tools, and on accounts with package/source access enabled it also gets dependency inspection tools. ## How It Works -GitHits runs as an [MCP server](https://modelcontextprotocol.io/) that your AI assistant connects to over stdio. The assistant gets three tools: +GitHits runs as an [MCP server](https://modelcontextprotocol.io/) that your AI assistant connects to over stdio. + +Core tools available in every authenticated session: | Tool | Purpose | |---|---| @@ -96,6 +98,21 @@ GitHits runs as an [MCP server](https://modelcontextprotocol.io/) that your AI a The assistant decides when to call these tools on its own — typically when it's stuck, needs a working example for an unfamiliar API, or encounters an error it can't resolve from its training data alone. +When package/source access is enabled for the current token, GitHits also exposes these capability-gated tools: + +| Tool | Purpose | +|---|---| +| `package_summary` | Quick package overview: version, license, downloads, quickstart, advisories | +| `package_vulnerabilities` | CVE / OSV advisories for a package or specific version | +| `package_dependencies` | Direct dependencies, dependency groups, and optional transitive graph | +| `package_changelog` | Release notes / changelog entries for a package or GitHub repo | +| `search_symbols` | Exact-token search inside indexed dependency source | +| `list_files` | Discover what files a dependency or repo contains | +| `read_file` | Read a dependency file by path | +| `grep_file` | Search for a case-insensitive substring within one file | + +These advanced tools remain feature-gated. The MCP server advertises them only when the authenticated token is entitled to package/source access. + ### License Filtering Search results respect license filtering by default, excluding copyleft-licensed code. Three modes are available: @@ -141,6 +158,13 @@ githits mcp start Always start MCP server (for use in MCP config files) githits auth status Show current authentication status ``` +When package/source access is enabled for the current token, two extra command groups are also available: + +``` +githits pkg ... Package metadata: overview, advisories, deps, changelog +githits code ... Dependency source inspection: search, files, read, grep +``` + ## Environment Variables | Variable | Purpose | Default | @@ -148,11 +172,15 @@ githits auth status Show current authentication status | `GITHITS_API_TOKEN` | API token for authentication | — | | `GITHITS_MCP_URL` | Override MCP server URL | `https://mcp.githits.com` | | `GITHITS_API_URL` | Override REST API URL | `https://api.githits.com` | +| `GITHITS_CODE_NAV_URL` | Override package/source service URL | environment-specific | +| `GITHITS_CODE_NAVIGATION` | Expose hidden `pkg` / `code` command groups locally | — | ## Manual Setup If your tool is not in the supported `githits init` list, configure GitHits manually. +The same MCP server command exposes both the core search tools and, when your token is entitled, the package/source inspection tools. No separate install is required. + Use this MCP server command in your tool's MCP config (the host/agent runs this command): ```sh diff --git a/docs/implementation/auth.md b/docs/implementation/auth.md index 4a095f04..37068fa6 100644 --- a/docs/implementation/auth.md +++ b/docs/implementation/auth.md @@ -19,7 +19,7 @@ The two methods exist because OAuth provides full access but requires a browser, > **The container resolves auth at startup.** The `createContainer()` function checks for `GITHITS_API_TOKEN` first — if set, it takes precedence even when OAuth tokens are stored. If not set, it loads stored OAuth tokens and attempts auto-refresh if expired. See `src/container.ts` for the resolution logic. -For the code navigation PoC, the CLI also inspects the current bearer token for Supabase `feature_flags` claims. This is a local capability hint only: the backend still enforces access. The CLI decodes JWT payloads without signature verification, treating opaque or malformed tokens as `unknown` capability. +Some hidden package/source tooling is conditionally exposed based on the active auth context. ## OAuth PKCE Flow @@ -44,7 +44,7 @@ Tokens are JWTs with a configurable expiration (typically 1 hour). The CLI handl - **Proactive refresh** — When 90% of the token lifetime has elapsed (e.g., at ~54 minutes for a 1-hour token), the `TokenManager` refreshes before expiry. This avoids a stale-token window. - **Reactive refresh** — If the token is already expired, refresh is attempted immediately. - **401 retry** — The `RefreshingGitHitsService` decorator wraps `GitHitsServiceImpl` and retries once on `AuthenticationError`, calling `forceRefresh()` to handle clock skew or server-side revocation. -- **Shared retry helper** — GitHits REST calls and code navigation GraphQL calls both use the same token-refresh/retry flow, so auth drift is handled consistently across both service families. +- **Shared retry helper** — GitHits REST calls and package/source service calls both use the same token-refresh/retry flow, so auth drift is handled consistently across both service families. - **Concurrent coalescing** — Multiple concurrent refresh requests share a single in-flight Promise. - **At login** (`src/commands/login.ts`) — Checks if existing token is still valid before starting the OAuth flow. Respects `--force` flag to re-authenticate regardless. - **At auth status** (`src/commands/auth-status.ts`) — Attempts refresh before reporting "Token expired". @@ -162,8 +162,8 @@ The `hasValidToken` flag is checked by `requireAuth()` in `src/commands/mcp.ts` | `src/services/token-manager.ts` | `TokenProvider` interface, `TokenManager` (proactive refresh, coalescing) | | `src/services/refreshing-githits-service.ts` | `GitHitsService` decorator with token refresh and 401 retry | | `src/services/execute-with-token-refresh.ts` | Shared helper for token-authenticated retry-on-refresh flows | -| `src/services/code-navigation-capability.ts` | Local JWT capability decoding for `code_navigation` | -| `src/services/code-navigation-service.ts` | GraphQL code navigation client using the shared refresh helper | +| `src/services/code-navigation-capability.ts` | Local package/source access gating helpers | +| `src/services/code-navigation-service.ts` | Package/source service client using the shared refresh helper | | `src/services/auth-service.ts` | OAuth operations (DCR, PKCE, token exchange, callback server) | | `src/services/auth-storage.ts` | `AuthStorage` interface and file-based implementation | | `src/services/keyring-service.ts` | `KeyringService` interface wrapping `@napi-rs/keyring` | diff --git a/docs/implementation/cli-commands.md b/docs/implementation/cli-commands.md index 0cbcb350..c9c69fe2 100644 --- a/docs/implementation/cli-commands.md +++ b/docs/implementation/cli-commands.md @@ -2,7 +2,7 @@ ## Purpose -The CLI exposes three primary commands (`search`, `languages`, `feedback`) that mirror the public MCP tools for direct human and agent use. It also has a capability-gated `code search` command that searches indexed dependency source via the code-navigation backend. These commands share business logic with the MCP tools through the same service interfaces and shared utilities, but format output for terminal consumption instead of MCP tool results. +The CLI exposes three core commands (`search`, `languages`, `feedback`) that mirror the always-on MCP tools for direct human and agent use. It also has capability-gated `code` and `pkg` command groups for dependency source inspection and package intelligence. All of these commands share business logic with the MCP tools through the same service interfaces and shared utilities, but format output for terminal consumption instead of MCP tool results. ## Commands @@ -89,7 +89,7 @@ Finds functions, classes, modules, and doc sections inside an indexed dependency **Output.** Default terminal output leads each entry with `path:startLine-endLine [kind]`, followed by the symbol name and a 3-line dedented snippet. `--json` emits the shared success/error envelope also produced by the MCP `search_symbols` tool — see [`mcp-cli-parity.md`](./mcp-cli-parity.md) for the wire contract. The command is registered as `code search` with `code search-symbols` as a Commander alias. -**Capability gate.** The `code` group is registered only when the startup token advertises `code_navigation`, when `GITHITS_CODE_NAVIGATION=1` is set for local override, when `GITHITS_API_TOKEN` is present (opaque env token), or when stored auth is expired. +**Capability gate.** The `code` group is registered only when package/source access is available for the current session, when `GITHITS_CODE_NAVIGATION=1` is set for local override, or when stored auth is expired and the CLI cannot reliably pre-classify access. **Troubleshooting.** Set `GITHITS_DEBUG=code-nav` to emit single-line JSON diagnostics to stderr on error paths. Include the output when filing an issue. Debug payloads never contain query text, tokens, or response bodies. @@ -106,15 +106,15 @@ Shows a concise overview for a single package: latest version, license, descript **Package spec.** `:`. 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 `@` 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. +**Always latest.** `pkg info` returns the latest published version regardless of input. Passing `@` is rejected with `INVALID_ARGUMENT` and a clear message — the tool never silently swaps to latest. Use `pkg vulns` or `pkg deps` 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. **Output envelope.** Success payload is hand-crafted for agent token efficiency: `{registry, name, version, description?, license?, homepage?, repository?, publishedAt?, downloads?, github?, install?, usage?, vulnerabilities?, recentChanges?}`. Omitted fields reflect backend nulls, not dropped data. Error envelope: `{error, code, retryable, details?}` — same shape as `search_symbols`, same classifier family. Under `--json` the error envelope is written to **stderr** so stdout stays clean for `jq`. -**Capability gate.** Same as `code`: `code_navigation` capability on the token, `GITHITS_CODE_NAVIGATION=1` override, `GITHITS_API_TOKEN` env token, or expired stored auth. +**Capability gate.** Same as `code`. -**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. +**Troubleshooting.** `GITHITS_DEBUG=pkg-intel` emits PII-safe classified-error diagnostics (area, event, code, error class, detail keys). Use `GITHITS_DEBUG=*` to enable all package/source diagnostics. ### `githits pkg vulns` @@ -128,15 +128,15 @@ 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.** `:[@]`. Unlike `pkg info`, `pkg vulns` supports `@` 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}.` +**Package spec.** `:[@]`. Unlike `pkg info`, `pkg vulns` supports `@` so callers can inspect older pinned releases. 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. +**Filtering.** `--severity low|medium|high|critical` maps to a CVSS float threshold (`low=0.1, medium=4, high=7, critical=9`) and is applied by the service. The returned `vulnerabilityCount` reflects the filtered set — no client-side filtering, no dual-summary block. Callers wanting the full picture omit the flag. `--include-withdrawn` includes retracted advisories; 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. +**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 and surfacing an opaque upstream 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). +**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 upstream count and list stay 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. @@ -150,9 +150,9 @@ Lists known CVE / OSV advisories for a package: severity, affected version range **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). +**Capability gate.** Same as `pkg info`. -**Troubleshooting.** Same debug areas as `pkg info` (`GITHITS_DEBUG=pkg-intel` for classified errors; `GITHITS_DEBUG=pkg-graphql` for transport failures). +**Troubleshooting.** Same debug areas as `pkg info`. ### `githits pkg deps` @@ -172,11 +172,11 @@ Analyses dependencies for a package on npm, PyPI, Hex, Crates, vcpkg, or Zig. De **Two views.** The default runtime view collapses to a single-column list from `dependencies.direct` — the flat answer to "what does this pull in?". The structured groups view (`--groups`, or implicitly via `--lifecycle`) iterates `dependencyGroups.groups` and preserves registry-specific condition metadata (PyPI extras, Crates features). Dev / peer / build / optional deps live only in the groups view — the wire's `direct[]` is always runtime-only. -**Lifecycle filter.** `-l, --lifecycle ` accepts a comma-separated list of canonical lowercase tokens (`runtime`, `development`, `build`, `peer`, `optional`). Uppercase and whitespace are tolerated. Filters server-side via the backend's `lifecycle: [String!]` input, which only affects `dependencyGroups`; `direct[]` and `transitive[]` are returned regardless. Unknown tokens are rejected with `INVALID_ARGUMENT` and the canonical list. +**Lifecycle filter.** `-l, --lifecycle ` accepts a comma-separated list of canonical lowercase tokens (`runtime`, `development`, `build`, `peer`, `optional`). Uppercase and whitespace are tolerated. The filter only affects `dependencyGroups`; `direct[]` and `transitive[]` are returned regardless. Unknown tokens are rejected with `INVALID_ARGUMENT` and the canonical list. **Groups view (`--groups` or any `--lifecycle`).** Headings collapse to `name` when `conditionType === "always"` (e.g. `runtime`, `development`). Feature / TFM groups render `name (lifecycle, conditionType[: conditionValue])` — `conditionValue` is omitted when it equals `name` (the common case on Crates features and PyPI extras). Within each group, entries sort alphabetically. Duplicate `{name, constraint}` tuples inside a group collapse in the terminal for scannability; the JSON envelope preserves every duplicate the backend emitted. -**Transitive view (`--transitive`).** Replaces the direct-deps list with the full unique transitive closure (alphabetical, `name@version`, one per line). Summary row carries the aggregate counts + conflict / cycle counts, and `(max depth N)` only when `--depth` was applied — otherwise the backend's full-graph traversal is shown. `--depth ` (1–10) caps traversal; there is **no client-side default cap** (matches `npm ls` / `cargo tree` ergonomics). +**Transitive view (`--transitive`).** Replaces the direct-deps list with the full unique transitive closure (alphabetical, `name@version`, one per line). Summary row carries the aggregate counts + conflict / cycle counts, and `(max depth N)` only when `--depth` was applied — otherwise the full traversal is shown. `--depth ` (1–10) caps traversal; there is **no client-side default cap** (matches `npm ls` / `cargo tree` ergonomics). **Verbose (`--verbose`).** In both plain and transitive modes, each dep expands to a multi-line block: the first line is `name@version`, followed by indented `- required by @, …` bullets. Importers that share a constraint are collapsed onto one bullet with a comma-separated list. In plain mode each direct dep has exactly one importer (the root package itself); in transitive mode a popular leaf may list many importers grouped by constraint. Conflicts expand into a `Conflicts (N):` table (`name: range1, range2, …`, one row per package); circular dependencies expand into a `Circular dependencies (N):` list (`a → b → a` arrow chain). @@ -186,9 +186,9 @@ Analyses dependencies for a package on npm, PyPI, Hex, Crates, vcpkg, or Zig. De **Exit codes.** 0 on success including zero-dep packages; 1 on any error. Under `--json`, the error envelope is written to **stderr**. -**Capability gate.** Same as `pkg info` / `pkg vulns` (inherits from the `code_navigation` token capability). +**Capability gate.** Same as `pkg info` / `pkg vulns`. -**Troubleshooting.** Same debug areas as `pkg info` / `pkg vulns` (`GITHITS_DEBUG=pkg-intel` for classified errors; `GITHITS_DEBUG=pkg-graphql` for transport failures). +**Troubleshooting.** Same debug areas as `pkg info` / `pkg vulns`. ### `githits pkg changelog` @@ -203,13 +203,13 @@ githits pkg changelog pypi:requests --no-body --json # lean timeline Fetches release notes or changelog entries for a package or GitHub repository. Output is a newest-first list with a summary header identifying the source (GitHub Releases, CHANGELOG.md, or HexDocs). -**Addressing.** `` (`registry:name`, same parser as `pkg info` / `pkg vulns` / `pkg deps`) **or** `--repo-url `, mutually exclusive. Unlike the other `pkg` commands, `pkg changelog` is intrinsically repo-level on the backend — its sources live on the repo, not the registry — so repo-URL addressing is a first-class peer mode. +**Addressing.** `` (`registry:name`, same parser as `pkg info` / `pkg vulns` / `pkg deps`) **or** `--repo-url `, mutually exclusive. Unlike the other `pkg` commands, `pkg changelog` is intrinsically repo-level, so repo-URL addressing is a first-class peer mode. **`@` rejected.** `pkg vulns` and `pkg deps` both treat `@version` as "for this exact version", but `pkg changelog` has no single-version query: all entries live on a timeline. Remapping `@version` to `--to` would be a silent semantic shift. CLI rejects with `INVALID_ARGUMENT` and a hint pointing to `--to ` (or `--from ` for range mode). -**Two modes.** Latest mode is the default; `--limit ` (1–50, backend default 10) caps entry count. `--from ` switches to range mode — returns every entry between `--from` and `--to` (or latest) with no count cap. `--to ` works in either mode. `--from` + `--limit` together is rejected client-side with a hint. +**Two modes.** Latest mode is the default; `--limit ` (1–50, default 10) caps entry count. `--from ` switches to range mode — returns every entry between `--from` and `--to` (or latest) with no count cap. `--to ` works in either mode. `--from` + `--limit` together is rejected client-side with a hint. -**Pre-release versions.** Backend-normalised versions flow through unchanged (`5.0.0-rc.1`, `2.32.0.dev0`, `1.7.0-rc.5` round-trip cleanly on `--from` / `--to`). Tag-style `v`-prefixed inputs are rejected on any version flag, consistent with `pkg vulns` / `pkg deps`. +**Pre-release versions.** Normalised versions flow through unchanged (`5.0.0-rc.1`, `2.32.0.dev0`, `1.7.0-rc.5` round-trip cleanly on `--from` / `--to`). Tag-style `v`-prefixed inputs are rejected on any version flag, consistent with `pkg vulns` / `pkg deps`. **Default terminal output.** Summary header (`name · registry · source · mode · entry count`) followed by each entry's `version date url` header plus the first 10 lines of its markdown body, indented and dimmed. Bodies longer than the cap show a footer `… (+N more lines — use --verbose for the full body)`. Missing dates render as `—`; missing versions render as `(unversioned)`. The version column is padded to the longest entry in the current response (no fixed width). @@ -223,9 +223,9 @@ Fetches release notes or changelog entries for a package or GitHub repository. O **Errors.** `NOT_FOUND` covers both the backend's "package not found" case and the distinct "package exists but no changelog source resolved" case (typed `PackageIntelligenceChangelogSourceNotFoundError`; message names the sources that were tried). `VERSION_NOT_FOUND` enriches with structured `package` / `requested` / `available` detail lines from the shared `promoteGenericVersionNotFound` helper — which was extended in this PR to recognise `--from` and `--to` as promotable version inputs. -**Capability gate.** Same as the rest of the `pkg` family (inherits from the `code_navigation` token capability). +**Capability gate.** Same as the rest of the `pkg` family. -**Troubleshooting.** Same debug areas (`GITHITS_DEBUG=pkg-intel` for classified errors; `GITHITS_DEBUG=pkg-graphql` for transport failures). +**Troubleshooting.** Same debug areas as the rest of the `pkg` family. ### `githits code files` diff --git a/docs/implementation/config.md b/docs/implementation/config.md index 1a039f18..7d795de5 100644 --- a/docs/implementation/config.md +++ b/docs/implementation/config.md @@ -14,7 +14,7 @@ GitHits separates its MCP server (which handles OAuth discovery and the MCP prot |---|---|---|---| | **MCP URL** | `https://mcp.githits.com` | `GITHITS_MCP_URL` | OAuth discovery (`.well-known`), DCR registration, auth flow | | **API URL** | `https://api.githits.com` | `GITHITS_API_URL` | REST endpoints (`/search`, `/languages`, `/feedbacks`) | -| **Code navigation URL** | configured per environment | `GITHITS_CODE_NAV_URL` | GraphQL code-navigation endpoint used by `search_symbols` / `githits code search` | +| **Package/source URL** | configured per environment | `GITHITS_CODE_NAV_URL` | Package/source service endpoint used by the hidden `pkg` / `code` tooling | > **These are different services.** Setting only one won't work for custom environments. Both must be overridden together when pointing to a non-production backend. @@ -38,12 +38,11 @@ The container (`src/container.ts`) resolves authentication in priority order: | `/languages` | Full access | Full access | Blocked | | `/feedbacks` | Full access | Full access | Blocked | -Code navigation is different from the REST endpoints above: +Package/source access is different from the REST endpoints above: -- the CLI resolves the code-navigation endpoint from `GITHITS_CODE_NAV_URL`; custom GitHits environments must set this explicitly (no default inference) -- MCP registration requires a startup token whose JWT advertises `code_navigation`; opaque env tokens are treated optimistically and left to backend enforcement -- the `githits code search` CLI command is shown when the startup token advertises `code_navigation`, when an opaque `GITHITS_API_TOKEN` is present, or when `GITHITS_CODE_NAVIGATION=1` is set locally -- the backend remains authoritative and can still deny access even if the CLI exposes the local command +- the CLI resolves the package/source service URL from `GITHITS_CODE_NAV_URL`; custom GitHits environments must set this explicitly (no default inference) +- MCP registration and the hidden `githits code` / `githits pkg` CLI groups are only exposed when package/source access is available for the current session, or when `GITHITS_CODE_NAVIGATION=1` is set locally for development +- if access is unavailable, those tools and command groups are omitted from the surfaced interface ## Environment Variables @@ -51,9 +50,9 @@ Code navigation is different from the REST endpoints above: |---|---|---| | `GITHITS_MCP_URL` | Override MCP server URL | `http://localhost:7071/mcp` | | `GITHITS_API_URL` | Override REST API URL | `http://localhost:8000` | -| `GITHITS_CODE_NAV_URL` | Override code navigation GraphQL URL | `http://localhost:4000` | +| `GITHITS_CODE_NAV_URL` | Override package/source service URL | `http://localhost:4000` | | `GITHITS_API_TOKEN` | API token for authentication | `ghi-abc123...` | -| `GITHITS_CODE_NAVIGATION` | Override capability gate and expose `code` CLI commands locally | `1` | +| `GITHITS_CODE_NAVIGATION` | Override capability gate and expose hidden `code` / `pkg` CLI groups locally | `1` | ## Local Storage diff --git a/docs/implementation/mcp-cli-parity.md b/docs/implementation/mcp-cli-parity.md index fd5e5214..740c5b34 100644 --- a/docs/implementation/mcp-cli-parity.md +++ b/docs/implementation/mcp-cli-parity.md @@ -2,11 +2,12 @@ ## Purpose -Both the MCP tool (`search_symbols`) and the CLI command (`githits code -search`) expose the code-navigation feature. Users and agents should be -able to cross the surface boundary without learning a new shape — -parameter names differ per surface convention, but defaults, error -behaviour, and the serialised payload do not. +This document started with `search_symbols` ↔ `githits code search`, +then expanded into the parity pattern used by the rest of the hidden +package/code tooling. Users and agents should be able to cross the +surface boundary without learning a new payload shape — parameter names +can differ per surface convention, but defaults, error behaviour, and +the serialised envelopes do not. This document is **the pattern and checklist derived from `search_symbols`**, not a permanent contract for every future @@ -27,8 +28,8 @@ the test suite anchors the doc. - **CLI flags** use `--kebab-case`. They are the user-facing surface. - **Public enum values** are lowercase strings on both surfaces (`production`, `test`, `summary`, `all`). -- **Backend coercion** from lowercase enum values to the uppercase - GraphQL enum variants lives in `src/shared/code-navigation.ts` +- **Service coercion** from lowercase enum values to the internal + request enums lives in `src/shared/code-navigation.ts` (`toSearchSymbolsFileIntent`, `toSearchSymbolsKind`, `toSearchSymbolsMatchMode`). @@ -74,9 +75,8 @@ the test suite anchors the doc. - `fileIntent` is echoed as a lowercase enum value, or the literal `"all"` when the caller chose the all-intents sentinel. - `returnedCount` is an explicit echo of `results.length`. - `totalMatches` is the backend-provided total (equal to - `returnedCount` today; see backend request B2 for the future - upgrade). +- `totalMatches` is the service-provided total (equal to + `returnedCount` today). ### `PARITY-ERROR-ENVELOPE` @@ -116,9 +116,8 @@ When a new tool lands with both MCP and CLI surfaces: - [ ] Parity test at `src/tools/-parity.test.ts` that cites the rule IDs it enforces (in a file header comment). Covers at minimum: successful search, zero-result, two error codes. -- [ ] MCP tool description mirrored in the backend MCP server before - public release (coordination point — the CLI may lead; the backend - PR URL is recorded in the plan doc before the frontend PR merges). +- [ ] MCP tool description mirrored across every shipped MCP surface + before public release. ## Non-goals @@ -142,7 +141,7 @@ When a new tool lands with both MCP and CLI surfaces: |---|---| | `src/shared/code-navigation-defaults.ts` | Canonical defaults and sentinels. | | `src/shared/code-navigation-error-map.ts` | `mapCodeNavigationError` classifier and `MappedError` union. | -| `src/shared/pkgseer-graphql.ts` | Low-level authenticated POST helper shared by every service that talks to the upstream endpoint. | +| `src/shared/pkgseer-graphql.ts` | Low-level authenticated package/source POST helper shared by the service clients. | | `src/shared/pkgseer-registry.ts` | Registry taxonomy (`PkgseerRegistry` union + lowercase↔uppercase converters). | | `src/shared/search-symbols-request.ts` | Shared request builder for `search_symbols`. | | `src/shared/search-symbols-response.ts` | Shared JSON envelope builders for `search_symbols`. | @@ -219,7 +218,7 @@ When a new tool lands with both MCP and CLI surfaces: 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` + straight to the service; the returned `vulnerabilityCount` reflects the filtered set. No client-side filtering, no `summary.filtered` dual-block. - **Partitioning bySeverity buckets.** `summary.bySeverity` carries @@ -251,7 +250,7 @@ When a new tool lands with both MCP and CLI surfaces: input (`v4.18.0`). - **Typed `VERSION_NOT_FOUND`.** Mirrors the code-nav precedent: `PackageIntelligenceVersionNotFoundError` carries structured - fields sourced from GraphQL `extensions` (`packageName`, + fields sourced from the service response (`packageName`, `requestedVersion`, `availableVersions`). The classifier emits structured `details` in the error envelope. - **Client-side `v`-prefix rejection.** `package_vulnerabilities` @@ -320,11 +319,11 @@ When a new tool lands with both MCP and CLI surfaces: - **Dual addressing — the only pkg-intel tool with it.** `registry` + `package_name` XOR `repo_url` on both surfaces. Justified because - `packageChangelog` is intrinsically repo-level on the backend (its + `packageChangelog` is intrinsically repo-level (its sources are GitHub Releases, CHANGELOG.md, HexDocs); repo-URL - isn't a bolt-on, it's a peer addressing mode on the GraphQL + isn't a bolt-on, it's a peer addressing mode on the service signature. `package_summary` / `package_vulnerabilities` / - `package_dependencies` omit it because their backend queries are + `package_dependencies` omit it because their queries are registry-metadata APIs without repo-URL alternatives. Future pkg-intel tool authors should not cargo-cult the asymmetry. - **`@` rejected.** Other `pkg` commands give diff --git a/docs/implementation/tools.md b/docs/implementation/tools.md index 63c74ad2..9b788ebb 100644 --- a/docs/implementation/tools.md +++ b/docs/implementation/tools.md @@ -28,10 +28,10 @@ Both expose the same tools with identical names, parameters, and descriptions. T | `package_dependencies` | `registry`, `package_name`, `version?`, `lifecycle?`, `include_transitive?`, `include_importers?`, `max_depth?` | Direct runtime dependency list (each `{name, version, constraint}` — the backend resolves each constraint to a concrete version) plus, when the backend has them, structured groups for dev / peer / build / optional with registry-specific condition metadata (PyPI extras, Crates features). Optional transitive block with aggregate edge counts, the preprocessed install footprint as `{name, version}`, typed conflicts and circular-dependency cycles; opt into per-package importer provenance with `include_importers`. | | `package_changelog` | `registry?`, `package_name?`, `repo_url?`, `from_version?`, `to_version?`, `limit?`, `git_ref?`, `include_bodies?` | Release notes or changelog entries for a package or GitHub repo. Default latest mode returns the ten most recent entries; `from_version` switches to range mode (no count cap). Dual addressing (spec vs repo URL) mutually exclusive. Response always includes `source` (`"releases"` / `"changelog_file"` / `"hexdocs"`), `mode` (`"latest"` / `"range"`), and `entries: { count, items }` with full markdown bodies by default; set `include_bodies: false` for a lean version / date / URL timeline. | | `list_files` | `target`, `path_prefix?`, `limit?`, `wait_timeout_ms?` | List files in an indexed dependency. Returns `{total, hasMore, files: [{path, name, language, fileType, byteSize}], resolution, indexedVersion}`. Dual addressing via `target.registry + target.package_name` (spec) or `target.repo_url + target.git_ref` (repo). `path_prefix` is a literal directory prefix — NOT a glob (`*.ts` won't match); glob / pattern filtering is an upstream enhancement. Emits an `INDEXING` error envelope when the dependency is being indexed on-demand — retry with a longer `wait_timeout_ms` or pick a version from `details.availableVersions`. | -| `read_file` | `target`, `file_path`, `start_line?`, `end_line?`, `wait_timeout_ms?` | Read a file from an indexed dependency. Default full file; use `start_line` / `end_line` for a bounded range. Binary files set `isBinary: true` and omit `content` — agents branch on the flag. On `NOT_FOUND` / `FILE_NOT_FOUND` call `list_files` to discover the actual path. | +| `read_file` | `target`, `path`, `start_line?`, `end_line?`, `wait_timeout_ms?` | Read a file from an indexed dependency. Default full file; use `start_line` / `end_line` for a bounded range. Binary files set `isBinary: true` and omit `content` — agents branch on the flag. On `NOT_FOUND` / `FILE_NOT_FOUND` call `list_files` to discover the actual path. | | `grep_file` | `target`, `path`, `pattern`, `context_lines?`, `max_matches?`, `wait_timeout_ms?` | Search within a single file for a case-insensitive substring (not regex). Returns matches — `context_lines` defaults to 0 (matches only, token-efficient); pass explicitly for surrounding lines (0–10). Max pattern 200 chars; up to 200 matches. For symbol-shaped searches use `search_symbols`. | -`search_symbols`, `package_summary`, `package_vulnerabilities`, `package_dependencies`, `package_changelog`, `list_files`, `read_file`, and `grep_file` 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. +`search_symbols`, `package_summary`, `package_vulnerabilities`, `package_dependencies`, `package_changelog`, `list_files`, `read_file`, and `grep_file` are only registered when package/source access is open for the current session. The package/source service URL can be overridden via `GITHITS_CODE_NAV_URL` for local development. `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. @@ -41,7 +41,7 @@ Both expose the same tools with identical names, parameters, and descriptions. T ### `package_summary` response shape -**Hand-crafted JSON envelope.** `package_summary` returns a lean JSON payload designed for agent token efficiency. Every GraphQL field dropped is deliberate — backend metadata (`schemaVersion`, `downloadsRefreshedAt`, `versionCount`, duplicate GitHub identifiers) does not reach the envelope. Null scalars are omitted; blocks (`github`, `vulnerabilities`, `downloads`, `recentChanges`) are omitted entirely when they carry no actionable data. `vulnerabilities` is omitted when `total === 0` or missing; when present, severity values include a CVSS-banded `severityLabel` (`critical` ≥9, `high` ≥7, `medium` ≥4, else `low`) for agent convenience. +**Hand-crafted JSON envelope.** `package_summary` returns a lean JSON payload designed for agent token efficiency. Fields that do not add caller value are deliberately omitted. Null scalars are omitted; blocks (`github`, `vulnerabilities`, `downloads`, `recentChanges`) are omitted entirely when they carry no actionable data. `vulnerabilities` is omitted when `total === 0` or missing; when present, severity values include a CVSS-banded `severityLabel` (`critical` ≥9, `high` ≥7, `medium` ≥4, else `low`) for agent convenience. **Validation.** The MCP schema is permissive (`registry: z.string()`, `package_name: z.string()`) — validation happens in-handler via `buildPackageSummaryParams`, producing the same structured `{error, code, retryable}` envelope as CLI. Matches the `search_symbols` pattern of never surfacing raw Zod errors to agents. @@ -51,13 +51,13 @@ Both expose the same tools with identical names, parameters, and descriptions. T ### `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. +**Filter-aware summary.** `min_severity` and `include_withdrawn` are passed straight through to the service. The returned `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. +**Typed `VERSION_NOT_FOUND`.** Mirrors the code-nav precedent: a dedicated `PackageIntelligenceVersionNotFoundError` carries structured `{ packageName, requestedVersion, availableVersions? }` fields. Classifier routes it to `VERSION_NOT_FOUND` with a structured `details` block. When the service only gets a generic "no matching version" error, it promotes that into the typed error so CLI / MCP surfaces still render an actionable envelope. `availableVersions` remains undefined in the fallback path unless the service supplied 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`. @@ -69,9 +69,9 @@ Both expose the same tools with identical names, parameters, and descriptions. T **Data-first envelope.** `runtime`, `groups`, and `transitive` are three independent blocks emitted based on what the backend returned and what the caller asked for, not on additional caller flags. Agents branch on the envelope's shape rather than inferring from inputs. -- `runtime` block: emitted whenever the backend returned `dependencies.direct` (including `{count: 0, items: []}` for zero-dep packages). `runtime.count` is computed client-side from `runtime.items.length` — the backend's `summary.directCount` is deliberately not selected so the invariant cannot drift. The wire's `direct[]` is always runtime-only: dev / peer / build / optional deps live in the groups block instead. -- `groups` block: emitted whenever the backend returned `dependencyGroups` — including when a lifecycle filter matched nothing (`{items: []}`). Omitted entirely when the backend returned `dependencyGroups: null` (e.g. on zero-dep packages), so agents can tell "backend has no groups concept" apart from "filter excluded everything". Each group carries its members under `items`, matching `runtime.items` so dependency lists share one key throughout the envelope. Duplicate `{name, constraint}` entries inside a group are preserved verbatim; the terminal formatter dedups for scannability but JSON is lossless. -- `transitive` block: emitted only when the caller set `include_transitive: true`. Carries aggregates (`edges`, `uniquePackages`, `depth?`) plus preprocessed arrays: `packages[]` (each `{name, version, importers[]}` with importer name / version / constraint pulled from the backend DAG), `conflicts[]` (typed `{name, requiredVersions}` when decodable), `circularDependencies[]` (typed `{cycle: string[]}` when decodable). Raw backend DAG is not exposed — the preprocessing happens in the envelope builder so agents consume the same signal the terminal `--verbose` renderer reads without re-implementing the decoder. A future dedicated `pkg deps-dag` command will expose the full DAG under a typed contract for graph-visualisation tooling. +- `runtime` block: emitted whenever the service returned `dependencies.direct` (including `{count: 0, items: []}` for zero-dep packages). `runtime.count` is computed client-side from `runtime.items.length`. The source `direct[]` is always runtime-only: dev / peer / build / optional deps live in the groups block instead. +- `groups` block: emitted whenever the service returned `dependencyGroups` — including when a lifecycle filter matched nothing (`{items: []}`). Omitted entirely when the service returned `dependencyGroups: null` (e.g. on zero-dep packages), so agents can tell "groups unavailable" apart from "filter excluded everything". Each group carries its members under `items`, matching `runtime.items` so dependency lists share one key throughout the envelope. Duplicate `{name, constraint}` entries inside a group are preserved verbatim; the terminal formatter dedups for scannability but JSON is lossless. +- `transitive` block: emitted only when the caller set `include_transitive: true`. Carries aggregates (`edges`, `uniquePackages`, `depth?`) plus preprocessed arrays: `packages[]` (each `{name, version, importers[]}` with importer name / version / constraint pulled from the service graph), `conflicts[]` (typed `{name, requiredVersions}` when decodable), `circularDependencies[]` (typed `{cycle: string[]}` when decodable). The raw graph is not exposed — the preprocessing happens in the envelope builder so agents consume the same signal the terminal `--verbose` renderer reads without re-implementing the decoder. A future dedicated `pkg deps-dag` command will expose the full graph under a typed contract for graph-visualisation tooling. **`filter.lifecycles` echo.** Canonicalised lowercase array (deduplicated, sorted in canonical display order: `runtime` → `development` → `build` → `peer` → `optional`). Emitted only when the caller supplied a non-empty lifecycle input. Matches what the backend actually received — the raw CSV string is not echoed. @@ -93,7 +93,7 @@ Both expose the same tools with identical names, parameters, and descriptions. T **Per-entry shape.** `{version, normalizedVersion?, publishedAt?, htmlUrl?, body?}`. `version` is kept in the envelope even when `null` so agents can write `items.map(e => e.version)` without guarding; every other nullable field is stripped when absent. `body` is additionally stripped when the caller set `include_bodies: false`. The backend's opaque per-entry `metadata` GenericJSON is deliberately dropped from the envelope in v1 — revisit via agent feedback. -**Dual addressing (`registry` + `package_name` XOR `repo_url`).** `package_changelog` is the only metadata-side MCP tool with dual addressing. `package_summary` / `package_vulnerabilities` / `package_dependencies` all accept only `registry` + `package_name` because their underlying backend queries are registry-metadata APIs without repo-URL alternatives. `packageChangelog` is intrinsically repo-level — its sources are GitHub Releases, CHANGELOG.md, and HexDocs — so `repoUrl` is a peer addressing mode in the GraphQL signature, not a bolt-on. Future tool authors should not cargo-cult the asymmetry without reading this rationale. +**Dual addressing (`registry` + `package_name` XOR `repo_url`).** `package_changelog` is the only metadata-side MCP tool with dual addressing. `package_summary` / `package_vulnerabilities` / `package_dependencies` all accept only `registry` + `package_name` because they are registry-metadata lookups without repo-URL alternatives. `package_changelog` is intrinsically repo-level — its sources are GitHub Releases, CHANGELOG.md, and HexDocs — so `repoUrl` is a peer addressing mode, not a bolt-on. Future tool authors should not cargo-cult the asymmetry without reading this rationale. **Mode selection.** `from_version` triggers range mode (returns every entry in `[fromVersion, toVersion]` with no cap). Latest mode is the default, capped by `limit` (1–50, backend default 10). `from_version` + `limit` is rejected client-side with `INVALID_ARGUMENT` rather than silently routed to one mode. @@ -113,7 +113,7 @@ Both expose the same tools with identical names, parameters, and descriptions. T These three indexing-gated tools share an addressing and lifecycle contract (documented below) and then each projects its own data-first envelope. All three reuse the shipped `codeTargetSchema` + `resolveCodeTarget` from `src/tools/code-navigation-shared.ts` — no parallel addressing module. -**`list_files` envelope**: `{registry?|repoUrl?+gitRef?, total, hasMore, indexedVersion?, resolution?, files: [{path, name?, language?, fileType?, byteSize?}], hint?, filter?}`. `fileType` values come from the backend verbatim (uppercase: `CONFIG`, `SOURCE`, `DOC`, `TEST`). `total` is capped at returned count when `hasMore: true` — the terminal formatter renders `N+ files` in that case to avoid misleading users. `filter.pathPrefix` / `filter.limit` echo only when the caller supplied them explicitly; default limit (200) never round-trips. +**`list_files` envelope**: `{registry?|repoUrl?+gitRef?, total, hasMore, indexedVersion?, resolution?, files: [{path, name?, language?, fileType?, byteSize?}], hint?, filter?}`. `fileType` values preserve the service vocabulary (`CONFIG`, `SOURCE`, `DOC`, `TEST`). `total` is capped at returned count when `hasMore: true` — the terminal formatter renders `N+ files` in that case to avoid misleading users. `filter.pathPrefix` / `filter.limit` echo only when the caller supplied them explicitly; default limit (200) never round-trips. **`read_file` envelope**: `{registry?|repoUrl?+gitRef?, path, language?, totalLines?, startLine?, endLine?, content?, isBinary?}`. `path` (not `filePath`) so the key matches `list_files.files[].path` and `grep_file`'s `path` input — the `list_files` → `read_file` / `grep_file` chain needs no renames. Binary files set `isBinary: true` and **omit** `content` (not `null`); agents branch on the flag. @@ -121,7 +121,7 @@ These three indexing-gated tools share an addressing and lifecycle contract (doc ### Indexing lifecycle (shared across `search_symbols`, `list_files`, `read_file`, `grep_file`) -All four code-navigation tools share the same indexing-retry contract. The state reaches us via two wire shapes — a GraphQL error (`extensions.code: "PACKAGE_INDEXING"`) and a data-path sentinel (`indexingStatus: "INDEXING"` on a successful response) — and the service layer collapses both to the same typed `CodeNavigationIndexingError` before the envelope builder runs. Agents therefore never see an `indexingStatus` field in a success envelope; they branch on the error path instead. +All four code-navigation tools share the same indexing-retry contract. The state can arrive through either an error response or a success sentinel, and the service layer collapses both to the same typed `CodeNavigationIndexingError` before the envelope builder runs. Agents therefore never see an `indexingStatus` field in a success envelope; they branch on the error path instead. **`INDEXING` error envelope**: ```json @@ -172,7 +172,7 @@ MCP SDK Server (src/commands/mcp.ts) └─ each tool: createXxxTool(service) └─ ToolDefinition { name, description, schema, handler, annotations? } └─ handler calls GitHitsService or CodeNavigationService methods - └─ service implementation makes REST or GraphQL calls + └─ service implementation makes HTTP calls ``` The layering is intentional: @@ -248,7 +248,7 @@ See `docs/guidelines/TESTING.md` for the full testing pattern. | `src/services/test-helpers.ts` | `createMockGitHitsService()` and `createMockCodeNavigationService()` factories | | `src/commands/mcp.ts` | Tool registration, MCP server setup, and TTY detection | | `src/services/githits-service.ts` | REST API client (what tools and CLI commands call) | -| `src/services/code-navigation-service.ts` | GraphQL code navigation client for `search_symbols` | +| `src/services/code-navigation-service.ts` | Package/source service client for `search_symbols` | | `src/shared/language-filter.ts` | Pure `filterLanguages()` function shared between MCP tool and CLI | ## Pending backend follow-ups diff --git a/gemini-extension.json b/gemini-extension.json index d2b7cfd5..8b652dfc 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "githits", - "version": "0.1.8", + "version": "0.1.9", "description": "Code examples from global open source for developers and AI assistants.", "mcpServers": { "githits": { diff --git a/package.json b/package.json index b7616f5c..1b974f06 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "githits", "description": "CLI companion for GitHits - code examples from global open source for developers and AI assistants", - "version": "0.1.8", + "version": "0.1.9", "type": "module", "files": [ "dist", diff --git a/plugins/claude/.claude-plugin/plugin.json b/plugins/claude/.claude-plugin/plugin.json index 0a456fb2..b195fe71 100644 --- a/plugins/claude/.claude-plugin/plugin.json +++ b/plugins/claude/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "githits", - "version": "0.1.8", + "version": "0.1.9", "description": "Code examples from global open source for developers and AI assistants", "author": { "name": "GitHits" diff --git a/src/commands/code/files.ts b/src/commands/code/files.ts index 7890826c..dfe2ab79 100644 --- a/src/commands/code/files.ts +++ b/src/commands/code/files.ts @@ -150,7 +150,8 @@ function resolvePositionals( const PKG_FILES_DESCRIPTION = `List files in an indexed dependency. Default returns up to 200 entries; pass [path-prefix] to scope to a directory and --limit to -fetch more. +fetch more. Returned paths feed directly into \`githits code read\` +and \`githits code grep\`. [path-prefix] is a literal directory prefix (e.g. \`src/\` or \`lib/parser\`), NOT a glob — \`*.ts\` and similar patterns won't diff --git a/src/commands/code/index.ts b/src/commands/code/index.ts index 753be229..802e2848 100644 --- a/src/commands/code/index.ts +++ b/src/commands/code/index.ts @@ -59,7 +59,7 @@ export async function registerCodeCommandGroup( .command("code") .summary("Source-level operations on indexed dependencies") .description( - "Search, list, read, and grep inside indexed dependency source code. Every command accepts either `` (registry:name[@version]) or `--repo-url --git-ref `. For package-level metadata (versions, vulnerabilities, dependencies, changelog) use `githits pkg`.", + "Search exact tokens, list files, read files, and grep substrings inside indexed dependency source. Every command accepts either `` (registry:name[@version]) or `--repo-url --git-ref `. For package-level metadata (versions, vulnerabilities, dependencies, changelog) use `githits pkg`.", ); registerCodeSearchSymbolsCommand(codeCommand); diff --git a/src/commands/code/read.ts b/src/commands/code/read.ts index 402930a2..262f424b 100644 --- a/src/commands/code/read.ts +++ b/src/commands/code/read.ts @@ -252,7 +252,8 @@ Default output is the raw file content — pipe-friendly for downstream tools (\`code read … | grep …\`). Pass --verbose for a header and a line-number gutter. -Use --lines for a bounded range (e.g. \`--lines 10-40\`). +Use --lines for a bounded range (e.g. \`--lines 10-40\`). The \`path\` +comes directly from \`githits code files\`. Addressing: (registry:name[@version]) OR --repo-url --git-ref . is package-relative for spec addressing, diff --git a/src/commands/code/search-symbols.ts b/src/commands/code/search-symbols.ts index bbf08133..d9463453 100644 --- a/src/commands/code/search-symbols.ts +++ b/src/commands/code/search-symbols.ts @@ -113,7 +113,7 @@ export async function searchSymbolsAction( } } -const SEARCH_SYMBOLS_DESCRIPTION = `Find functions, classes, modules, and doc sections inside an indexed dependency by exact-token search. +const SEARCH_SYMBOLS_DESCRIPTION = `Find functions, classes, modules, and doc sections inside an indexed dependency by exact-token search. This is for symbol-shaped inspection, not natural-language example search. Package spec: :[@]. Omit the registry to default to npm. Supported registries: npm, pypi, hex, crates, nuget, maven, zig, vcpkg, @@ -123,6 +123,9 @@ Filter by --category (broad: callable, type, module, data, documentation) or --kind (precise: function, method, class, trait, …). Prefer --category for most use cases; reach for --kind when you need a specific construct. +Default file intent is production source. Pass --intent all to include tests, +examples, benchmarks, generated files, and other non-production code. + Examples: githits code search npm:express middleware githits code search npm:express middleware --intent all diff --git a/src/commands/mcp-instructions.ts b/src/commands/mcp-instructions.ts index 5e9c527e..614833bf 100644 --- a/src/commands/mcp-instructions.ts +++ b/src/commands/mcp-instructions.ts @@ -19,36 +19,36 @@ const CORE_BLOCK = `GitHits surfaces verified, canonical code examples from glob Workflow: call \`search_language\` first if the language name is uncertain → call \`search\` with one focused question → send \`feedback\` on the returned solution_id so quality improves. Each search addresses a single issue; reuse context from prior results before re-searching.`; -const PACKAGE_TOOLS_PREAMBLE = `Package tools work with third-party dependency source and registry metadata. Use them when a stack trace points into a dependency, you need to verify how a library actually works, or you're evaluating a package. +const PACKAGE_TOOLS_PREAMBLE = `Package tools work with third-party dependency source plus registry metadata. Use them when a stack trace points into a dependency, you need to verify how a library actually works, or you're evaluating whether to add or upgrade a package. Package spec: \`registry:name[@version]\`.`; const PACKAGE_SUMMARY_BULLET = - "- `package_summary` — instant package overview."; + "- `package_summary` — instant package overview: latest version, license, downloads, quickstart, and active advisory count."; const PACKAGE_VULNERABILITIES_BULLET = "- `package_vulnerabilities` — known CVE / OSV advisories for npm, PyPI, Hex, or Crates packages (optionally pinned to `@version`). Malicious-package advisories surface in a disjoint `malware` bucket; filter with `min_severity` or include retracted advisories with `include_withdrawn`."; const PACKAGE_DEPENDENCIES_BULLET = - "- `package_dependencies` — direct runtime deps plus, when the backend has them, dev / peer / optional / feature groups. Pass `lifecycle` to filter groups server-side, or `include_transitive` for the full graph, conflict detection, and circular-dependency flags. Supports npm, PyPI, Hex, Crates, vcpkg, and Zig."; + "- `package_dependencies` — direct runtime deps plus, when the backend has them, dev / peer / optional / feature groups. Pass `lifecycle` to filter groups server-side, `include_transitive` for the full graph, and `include_importers` when you also need per-package provenance. Supports npm, PyPI, Hex, Crates, vcpkg, and Zig."; const PACKAGE_CHANGELOG_BULLET = "- `package_changelog` — release notes for a package or GitHub repo, newest-first. Default latest mode returns the 10 most recent entries with full markdown bodies; `from_version` switches to range mode between two versions. Addressable via `registry` + `package_name` or `repo_url`. Set `include_bodies: false` for a version / date / URL timeline when bodies aren't needed."; const SEARCH_SYMBOLS_BULLET = - "- `search_symbols` — text search across a dependency's source. On an INDEXING response, retry with a larger `wait_timeout_ms` (up to 60000)."; + "- `search_symbols` — exact-token search across indexed dependency source. Use it for symbol-shaped lookups inside a known package or repo, not natural-language questions. On an `INDEXING` response, retry with a larger `wait_timeout_ms` (up to 60000)."; const LIST_FILES_BULLET = - "- `list_files` — discover what files a dependency ships. Use `path_prefix` to scope to a subdirectory; the response includes each file's language, type, and byte size. Same indexing-retry rules as `search_symbols`."; + "- `list_files` — discover what files a dependency ships. Use `path_prefix` to scope to a subdirectory; the response includes each file's language, type, and byte size. Returned `path` values feed directly into `read_file` and `grep_file`. Same indexing-retry rules as `search_symbols`."; const READ_FILE_BULLET = - "- `read_file` — fetch a file's contents from a dependency. Default returns the full file; pass `start_line` / `end_line` for a bounded range. Binary files set `isBinary: true` and omit `content` — branch on the flag, not the null. A `FILE_NOT_FOUND` (or `NOT_FOUND`) response is the signal to call `list_files` for the actual path."; + "- `read_file` — fetch a file's contents from a dependency. Pass the same `path` emitted by `list_files`. Default returns the full file; pass `start_line` / `end_line` for a bounded range. Binary files set `isBinary: true` and omit `content` — branch on the flag, not the null. A `FILE_NOT_FOUND` (or `NOT_FOUND`) response is the signal to call `list_files` for the actual path."; const GREP_FILE_BULLET = - "- `grep_file` — find a case-insensitive substring within a single file (not regex). Returns matches with context lines. Max pattern 200 chars, up to 200 matches with up to 10 context lines each. For symbol-shaped searches use `search_symbols`. Same addressing and indexing-retry rules as `list_files`."; + "- `grep_file` — find a case-insensitive substring within a single file (not regex). Pass the same `path` emitted by `list_files`. Returns matches with context lines. Max pattern 200 chars, up to 200 matches with up to 10 context lines each. For symbol-shaped searches use `search_symbols`. Same addressing and indexing-retry rules as `list_files`."; const SEARCH_VS_SYMBOLS_TIP = - "Prefer `search` for natural-language example questions; prefer `search_symbols` for exact-token lookups inside a specific package."; + "Prefer `search` for natural-language example questions; prefer `search_symbols` for exact-token lookups inside a specific package or repo. Use `list_files` → `read_file` / `grep_file` once you know the file you need."; /** * Whether the MCP session should register and describe package tools. diff --git a/src/commands/pkg/index.ts b/src/commands/pkg/index.ts index 807ce6be..195862ae 100644 --- a/src/commands/pkg/index.ts +++ b/src/commands/pkg/index.ts @@ -70,7 +70,7 @@ export async function registerPkgCommandGroup( .command("pkg") .summary("Package metadata: info, vulnerabilities, dependencies, changelog") .description( - "Inspect registry metadata for packages from npm, PyPI, Hex, Crates, NuGet, Maven, Packagist, vcpkg, and Zig. For source-level operations (list files, read file, grep inside a file) use `githits code`.", + "Inspect package metadata from npm, PyPI, Hex, Crates, NuGet, Maven, Packagist, vcpkg, and Zig: overviews, advisories, dependency graphs, and changelogs. For source-level operations inside a dependency, use `githits code`.", ); registerPkgInfoCommand(pkgCommand); diff --git a/src/tools/list-files.ts b/src/tools/list-files.ts index 46ccb5e8..9342a7e1 100644 --- a/src/tools/list-files.ts +++ b/src/tools/list-files.ts @@ -47,9 +47,10 @@ const DESCRIPTION = "`target.package_name` (package scope) or `target.repo_url` + " + "`target.git_ref` (repo scope), mutually exclusive. `path_prefix` " + "is a literal directory prefix — it does NOT accept globs " + - "(`*.ts`) or extension filters. Returns an `INDEXING` error " + - "envelope when the dependency is being indexed on-demand — retry " + - "with a longer `wait_timeout_ms` or use a version from " + + "(`*.ts`) or extension filters. The returned `path` values feed " + + "directly into `read_file` and `grep_file`. Returns an `INDEXING` " + + "error envelope when the dependency is being indexed on-demand — " + + "retry with a longer `wait_timeout_ms` or use a version from " + "`details.availableVersions`."; export function createListFilesTool( diff --git a/src/tools/package-summary.ts b/src/tools/package-summary.ts index 62ac619c..db696293 100644 --- a/src/tools/package-summary.ts +++ b/src/tools/package-summary.ts @@ -30,11 +30,12 @@ const schema = { const DESCRIPTION = "Get a package overview — latest version, license, description, " + - "repository, downloads, GitHub stars, install command, and a count " + - "of known vulnerabilities. Use before recommending a package or to " + - "orient on what a dependency is. Works across npm, PyPI, Hex, " + - "Crates, NuGet, Maven, Packagist, vcpkg, Zig. Always returns data " + - "for the latest published version."; + "repository, downloads, GitHub stars, install command, recent " + + "changes, and a count of known vulnerabilities. Use before " + + "recommending a package or to orient on what a dependency is. " + + "Works across npm, PyPI, Hex, Crates, NuGet, Maven, Packagist, " + + "vcpkg, and Zig. Always returns data for the latest published " + + "version."; export function createPackageSummaryTool( service: PackageIntelligenceService, diff --git a/src/tools/package-vulnerabilities.ts b/src/tools/package-vulnerabilities.ts index 62fbba2f..82db6a8a 100644 --- a/src/tools/package-vulnerabilities.ts +++ b/src/tools/package-vulnerabilities.ts @@ -49,10 +49,11 @@ const DESCRIPTION = "Crates (other registries are not yet supported for vulnerability " + "data). Returns a count summary, each advisory with OSV ID, " + "severity, affected ranges and fix versions, plus suggested " + - "upgrade paths. Pass `version` to inspect a specific release; " + - "otherwise the latest is checked. Use `min_severity` to filter to " + - "a threshold (`low`, `medium`, `high`, `critical`) and " + - "`include_withdrawn` to also see retracted advisories."; + "upgrade paths. Malicious-package advisories surface in a separate " + + "bucket. Pass `version` to inspect a specific release; otherwise " + + "the latest is checked. Use `min_severity` to filter to a threshold " + + "(`low`, `medium`, `high`, `critical`) and `include_withdrawn` to " + + "also see retracted advisories."; export function createPackageVulnerabilitiesTool( service: PackageIntelligenceService, diff --git a/src/tools/read-file.ts b/src/tools/read-file.ts index 1951de9c..53955fe4 100644 --- a/src/tools/read-file.ts +++ b/src/tools/read-file.ts @@ -49,7 +49,8 @@ const DESCRIPTION = "file; use `start_line` / `end_line` for a bounded range. Response: " + "`{path, language, totalLines, startLine, endLine, content, " + "isBinary}`. Binary files set `isBinary: true` and omit `content` — " + - "agents branch on the flag rather than checking null. Address via " + + "agents branch on the flag rather than checking null. Pass the same " + + "`path` emitted by `list_files`. Address via " + "`target.registry` + `target.package_name` (package scope) or " + "`target.repo_url` + `target.git_ref` (repo scope), mutually " + "exclusive. On `INDEXING` retry with a longer `wait_timeout_ms` " +