From 3159af0fefcbc0c4dba196cc4eddcb9e118db47d Mon Sep 17 00:00:00 2001 From: Juha Litola Date: Fri, 24 Apr 2026 16:03:42 +0300 Subject: [PATCH 1/2] fix: align gated search surfaces and docs Hide indexed search and package tools behind the explicit code_navigation capability while preserving direct CLI refresh paths for expired stored auth. Update product docs and plugin instructions to reflect the example-search rename and current MCP surface. --- GEMINI.md | 18 ++++-- README.md | 20 ++++--- commands/example.md | 23 ++++++++ commands/help.md | 11 +++- commands/search.md | 10 ++-- docs/implementation/cli-commands.md | 12 ++-- docs/implementation/config.md | 15 ++--- docs/implementation/tools.md | 9 ++- plugins/claude/commands/example.md | 23 ++++++++ plugins/claude/commands/help.md | 11 +++- plugins/claude/commands/search.md | 10 ++-- plugins/claude/skills/search/SKILL.md | 5 +- skills/search/SKILL.md | 5 +- src/cli.ts | 71 +++++++++++++++++++----- src/commands/code/index.test.ts | 7 +-- src/commands/code/index.ts | 12 ++-- src/commands/mcp-instructions.test.ts | 4 +- src/commands/mcp-instructions.ts | 11 +--- src/commands/mcp.test.ts | 10 ++-- src/commands/pkg/index.test.ts | 7 +-- src/commands/pkg/index.ts | 15 ++--- src/commands/search-registration.test.ts | 11 ++-- src/commands/search.ts | 7 +-- src/container.ts | 2 +- src/services/config.ts | 5 +- 25 files changed, 216 insertions(+), 118 deletions(-) create mode 100644 commands/example.md create mode 100644 plugins/claude/commands/example.md diff --git a/GEMINI.md b/GEMINI.md index 572b68fc..30e6c02c 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -4,7 +4,7 @@ Code examples from global open source for developers and AI assistants. ## Available Tools -### search +### get_example Find code examples from open source repositories. @@ -16,7 +16,7 @@ Find code examples from open source repositories. ### search_language -Look up supported programming language names. Use this before calling `search` to get the correct language identifier. +Look up supported programming language names. Use this before calling `get_example` to get the correct language identifier. **Parameters:** @@ -34,14 +34,14 @@ Rate a search result. Submit feedback after each search to improve future result ## When to Use -Use `search` when: +Use `get_example` when: - You are stuck or blocked on an implementation problem - You need up-to-date examples for an API, library, or framework - The user mentions GitHits or asks you to search for code examples - You encounter an error you cannot resolve from your training data -Do not use `search` for: +Do not use `get_example` for: - General knowledge questions that do not require code examples - Problems you can already solve confidently @@ -60,12 +60,20 @@ Do not use `search` for: ## How to Search Well -- Call `search_language` first to confirm the correct language name before calling `search` +- Call `search_language` first to confirm the correct language name before calling `get_example` - Formulate queries as natural language questions (e.g., "How to stream responses with the Vercel AI SDK in Next.js") - Include specific error messages, library names, or API names when relevant - Keep queries focused: 3-4 technical terms maximum - Submit `feedback` after every search result you use or discard +## Feature-Gated Tools + +When the authenticated token carries the `code_navigation` feature flag, GitHits +also exposes indexed dependency/package tools such as `search`, +`search_status`, `package_summary`, `package_vulnerabilities`, +`package_dependencies`, `package_changelog`, `list_files`, `read_file`, and +`grep_repo`. + ## License Filtering Results respect license filtering by default. Three modes: diff --git a/README.md b/README.md index 6d40f795..d8ed89af 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ 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 GitHits search tools, and on accounts with package/source access enabled it also gets dependency inspection tools. +That's it. Your assistant now has GitHits example-search tools, and on accounts with package/source access enabled it also gets indexed dependency/package inspection tools. ## How It Works @@ -92,7 +92,7 @@ Core tools available in every authenticated session: | Tool | Purpose | |---|---| -| `search` | Find code examples by describing what you need in natural language | +| `get_example` | Find canonical code examples by describing what you need in natural language | | `search_language` | Look up supported programming language names | | `feedback` | Rate search results to improve future quality | @@ -102,16 +102,17 @@ When package/source access is enabled for the current token, GitHits also expose | Tool | Purpose | |---|---| +| `search` | Unified indexed search across dependency/repository code, docs, and symbols | +| `search_status` | Follow up a prior indexed `search` by `searchRef` | | `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_repo` | Deterministic text grep across indexed dependency or repo files | -These advanced tools remain feature-gated. The MCP server advertises them only when the authenticated token is entitled to package/source access. +These advanced tools remain feature-gated. The MCP server advertises them only when the authenticated token explicitly carries the `code_navigation` feature flag. ### License Filtering @@ -156,11 +157,16 @@ githits logout Remove stored credentials githits mcp Show setup instructions in a terminal; starts MCP server when piped githits mcp start Always start MCP server (for use in MCP config files) githits auth status Show current authentication status +githits example Get canonical code examples from global open source +githits languages List or filter supported language names +githits feedback Send feedback on a returned example ``` -When package/source access is enabled for the current token, two extra command groups are also available: +When the current token explicitly carries `code_navigation`, these extra commands are also available: ``` +githits search ... Unified indexed dependency/repository search +githits search-status Follow up a prior indexed search githits pkg ... Package metadata: overview, advisories, deps, changelog githits code ... Dependency source inspection: search, files, read, grep ``` @@ -173,14 +179,14 @@ githits code ... Dependency source inspection: search, files, read, grep | `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 | — | +| `GITHITS_CODE_NAVIGATION` | Expose hidden `search` / `pkg` / `code` CLI surfaces locally for development | — | | `GITHITS_TELEMETRY` | Emit end-of-run timing spans to stderr for local profiling | — | ## 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. +The same MCP server command exposes both the core example-search tools and, when your token carries `code_navigation`, the indexed 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): diff --git a/commands/example.md b/commands/example.md new file mode 100644 index 00000000..50ef562d --- /dev/null +++ b/commands/example.md @@ -0,0 +1,23 @@ +--- +description: Search for canonical code examples from open source via GitHits +--- + +# Example + +Search for code examples using GitHits for the query: "$ARGUMENTS" + +Use the GitHits MCP `get_example` tool with the user's query. The tool requires two +parameters: + +- **query**: The user's search query, formulated in natural language. +- **language**: The programming language. If the language is unclear from + context, use the `search_language` tool first to find the correct language + name. + +Optional parameter: + +- **license_mode**: `"strict"` (default, excludes copyleft), `"yolo"` (all + licenses), or `"custom"` (user's blocklist). + +Present the results clearly. After the user has reviewed the result, use the +`feedback` tool to report whether the example was helpful. diff --git a/commands/help.md b/commands/help.md index 18ba741d..0c1fd1ee 100644 --- a/commands/help.md +++ b/commands/help.md @@ -16,7 +16,8 @@ context summary: ## Slash Commands -- `/githits:search ` — Search for code examples from open source. +- `/githits:example ` — Search for canonical code examples from open source. +- `/githits:search ` — Legacy alias for `/githits:example`. - `/githits:login` — Authenticate with your GitHits account. - `/githits:status` — Show your current authentication status. - `/githits:logout` — Remove stored credentials. @@ -24,14 +25,18 @@ context summary: ## MCP Tools -This plugin connects to the GitHits MCP server and exposes three tools: +This plugin connects to the GitHits MCP server and always exposes three core tools: -- **search** — Find code examples by describing what you need in natural +- **get_example** — Find code examples by describing what you need in natural language. Requires `query` and `language` parameters. - **search_language** — Look up supported programming language names before searching. - **feedback** — Rate a search result to improve future quality. +Additional indexed dependency/package tools such as `search`, `package_summary`, +`list_files`, and `grep_repo` stay hidden unless the authenticated token carries +the `code_navigation` feature flag. + ## Authentication Run `npx -y githits login` to authenticate via browser, or set the diff --git a/commands/search.md b/commands/search.md index 833acb7f..0e16f7b7 100644 --- a/commands/search.md +++ b/commands/search.md @@ -1,12 +1,14 @@ --- -description: Search for code examples from open source via GitHits +description: Legacy alias for GitHits example search --- -# Search +# Search (Legacy Alias) -Search for code examples using GitHits for the query: "$ARGUMENTS" +Use GitHits example search for the query: "$ARGUMENTS" -Use the GitHits MCP `search` tool with the user's query. The tool requires two +This slash command is the older alias for `/githits:example`. + +Use the GitHits MCP `get_example` tool with the user's query. The tool requires two parameters: - **query**: The user's search query, formulated in natural language. diff --git a/docs/implementation/cli-commands.md b/docs/implementation/cli-commands.md index ea4b6aee..7a6b30ca 100644 --- a/docs/implementation/cli-commands.md +++ b/docs/implementation/cli-commands.md @@ -2,7 +2,7 @@ ## Purpose -The CLI exposes four primary top-level commands: `example`, `languages`, and `feedback` are always available, while `search` is capability-gated and surfaces unified dependency/repository search when package/source access is open for the current session. It also has capability-gated `code` and `pkg` command groups for lower-level 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. +The CLI exposes three always-on top-level commands: `example`, `languages`, and `feedback`. Indexed dependency/package surfaces are capability-gated: top-level `search` / `search-status` plus the `code` and `pkg` command groups are shown only when the startup token explicitly carries `code_navigation`, or when `GITHITS_CODE_NAVIGATION=1` is set locally for development. 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 @@ -130,7 +130,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 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. +**Capability gate.** The `code` group is registered only when the startup token explicitly carries `code_navigation`, or when `GITHITS_CODE_NAVIGATION=1` is set for local override. **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. @@ -346,8 +346,8 @@ This is still the standard `grep(1)` contract even though the output includes fi CLI command (src/commands/search.ts) └─ searchAction(query, options, deps) ├─ requireAuth(deps) - └─ deps.githitsService.search(params) - └─ GitHitsServiceImpl makes REST API call + └─ deps.codeNavigationService.search(params) + └─ CodeNavigationServiceImpl makes package/source API call ``` Each command follows this pattern: @@ -361,9 +361,9 @@ Each command follows this pattern: | Shared Module | Used By | |---|---| | `GitHitsService` (via container) | `example`, `languages`, `feedback`, and always-on MCP tools | -| `CodeNavigationService` (via container) | top-level unified `search` / `search-status`, capability-gated `search` MCP tools, and `githits code search` | +| `CodeNavigationService` (via container) | top-level unified `search` / `search-status`, capability-gated MCP indexed-search tools, and `githits code search` | | `filterLanguages()` from `src/shared/language-filter.ts` | `search_language` MCP tool + `languages` CLI command | -| `requireAuth()` from `src/shared/require-auth.ts` | MCP server startup + all CLI commands | +| `requireAuth()` from `src/shared/require-auth.ts` | all CLI commands and auth-required MCP tool handlers | ## Adding a New CLI Command diff --git a/docs/implementation/config.md b/docs/implementation/config.md index b949226b..d793f855 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`) | -| **Package/source URL** | configured per environment | `GITHITS_CODE_NAV_URL` | Package/source service endpoint used by the hidden `pkg` / `code` tooling | +| **Package/source URL** | `https://pkgseer.dev` in the default production environment; otherwise unset | `GITHITS_CODE_NAV_URL` | Package/source service endpoint used by hidden indexed `search` / `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. @@ -28,7 +28,7 @@ The container (`src/container.ts`) resolves authentication in priority order: 2. **Stored OAuth JWT** — Loaded from `~/.githits/auth.json`. If expired, the container automatically attempts a refresh using the stored refresh token. If refresh fails, auth is cleared silently. -3. **Unauthenticated** — No token available. The MCP server refuses to start (`requireAuth()` in `src/commands/mcp.ts`). CLI commands like `auth status` still work to help the user diagnose the issue. +3. **Unauthenticated** — No token available. Auth-required CLI commands fail on use, and the MCP server can start but every authenticated tool call will fail. Commands like `auth status` still work to help the user diagnose the issue. ### Auth Mode Capabilities @@ -40,9 +40,10 @@ The container (`src/container.ts`) resolves authentication in priority order: Package/source access is different from the REST endpoints above: -- 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 +- the CLI resolves the package/source service URL from `GITHITS_CODE_NAV_URL`; in the default production environment it falls back to `https://pkgseer.dev`, but custom GitHits environments must set this explicitly +- MCP registration for `search`, `search_status`, `package_*`, `list_files`, `read_file`, and `grep_repo` happens only when the current token explicitly carries `code_navigation` +- CLI registration for top-level `search` / `search-status` plus the hidden `githits code` / `githits pkg` groups uses the same capability check, with one local-development escape hatch: `GITHITS_CODE_NAVIGATION=1` +- if the capability is absent or unknown, those indexed tools and command groups are omitted from the surfaced interface ## Environment Variables @@ -52,7 +53,7 @@ Package/source access is different from the REST endpoints above: | `GITHITS_API_URL` | Override REST API URL | `http://localhost:8000` | | `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 hidden `code` / `pkg` CLI groups locally | `1` | +| `GITHITS_CODE_NAVIGATION` | Override capability gate and expose hidden indexed `search` / `code` / `pkg` CLI surfaces locally | `1` | | `GITHITS_TELEMETRY` | Emit end-of-run timing spans to stderr for local profiling | `1` | ## Local Storage @@ -98,4 +99,4 @@ Commands receive the full `Dependencies` object. Services receive only what they | `src/services/auth-storage.ts` | File-based token storage with secure permissions | | `src/services/filesystem-service.ts` | File system abstraction for testable storage | | `src/commands/auth-status.ts` | Diagnosing current auth state (reached via `githits auth status`) | -| `src/commands/mcp.ts` | Auth gate (see `requireAuth`) before MCP server startup | +| `src/commands/mcp.ts` | MCP tool registration and deferred-auth startup behavior | diff --git a/docs/implementation/tools.md b/docs/implementation/tools.md index 9ed73ab7..93cd1f9a 100644 --- a/docs/implementation/tools.md +++ b/docs/implementation/tools.md @@ -24,7 +24,6 @@ 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` | `query`, `target?`, `targets?`, `sources?`, `category?`, `kind?`, `path_prefix?`, `file_intent?`, `public_only?`, `name?`, `language?`, `allow_partial_results?`, `limit?`, `offset?`, `wait_timeout_ms?` | Unified indexed dependency/repository discovery search across code, docs, and symbols. Defaults `file_intent` to production for AUTO/code/symbol searches when omitted; explicit docs-only searches do not get a file-intent default. Prefer `sources:["symbol"]` for symbol-shaped unified search. Complete-by-default; set `allow_partial_results: true` to receive available partial hits while indexing continues. | | `search_status` | `search_ref` | Check progress, fetch partial hits when the original request used `allow_partial_results: true`, or fetch final results for a prior unified search. | -| `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. | | `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`. | @@ -33,15 +32,15 @@ Both expose the same tools with identical names, parameters, and descriptions. T | `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_repo` | `target`, `pattern`, `path?`, `path_prefix?`, `globs?`, `extensions?`, `pattern_type?`, `case_sensitive?`, `exclude_doc_files?`, `exclude_test_files?`, `context_lines?`, `context_lines_before?`, `context_lines_after?`, `max_matches?`, `max_matches_per_file?`, `cursor?`, `symbol_fields?`, `wait_timeout_ms?` | Deterministic text grep over indexed dependency or repository source. Defaults to literal, ASCII case-insensitive matching across the whole target; non-ASCII letters match case-sensitively. Narrow with `path`, `path_prefix`, `globs`, or `extensions`. `pattern_type: "regex"` uses RE2 syntax; whole-target regexes must include at least one literal substring for index pre-filtering. Returns matches plus pagination and scan counters; `symbol_fields` hydrates enclosing symbol metadata on each match. | -`search`, `search_status`, `search_symbols`, `package_summary`, `package_vulnerabilities`, `package_dependencies`, `package_changelog`, `list_files`, `read_file`, and `grep_repo` 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`, `search_status`, `package_summary`, `package_vulnerabilities`, `package_dependencies`, `package_changelog`, `list_files`, `read_file`, and `grep_repo` are only registered when the current token explicitly carries the `code_navigation` feature flag. The package/source service URL defaults to `https://pkgseer.dev` in the default production environment and 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. +`search_symbols` is no longer registered by the local MCP server. It remains in the codebase as the legacy parity surface for the CLI `githits code search` command. 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. **Unified `search` query syntax.** The `search.query` field is the backend discovery query syntax, not a raw pass-through to a per-source search engine. It supports implicit `AND`, uppercase `OR`, parentheses, unary `-`, quoted phrases, semantic qualifiers (`kind:`, `category:`, `path:`, `lang:`, `name:`, `intent:`), and routing qualifiers (`registry:`, `package:`, `version:`, `repo:`). The backend parses the query once and compiles it per source. Structured `name` and `language` inputs are compiled into `name:` / `lang:` qualifiers and AND-ed with the query before sending. Per-source support, ignored features, and incompatibilities are reported in `sourceStatus`. -**Response shape.** `search_symbols` always requests `mode: DETAILED` and always selects the `code`, `resolution`, `kind`, and `category` fields. Responses include each match's full source code, precise symbol kind (from the unified symbol taxonomy), broad symbol category, and line range. The tool does not expose `mode` or `verbose` inputs — the service layer makes the choice once so both surfaces get the richest response without callers juggling the knobs. The legacy `chunkType` field is no longer selected or surfaced client-side; `kind` is the single source of truth for taxonomy. The text payload is always valid JSON (whether the result is success or error), so MCP clients can parse `content[0].text` without branching on `isError`. +**Legacy `search_symbols` response shape.** `search_symbols` always requests `mode: DETAILED` and always selects the `code`, `resolution`, `kind`, and `category` fields. Responses include each match's full source code, precise symbol kind (from the unified symbol taxonomy), broad symbol category, and line range. The tool does not expose `mode` or `verbose` inputs — the service layer makes the choice once so both surfaces get the richest response without callers juggling the knobs. The legacy `chunkType` field is no longer selected or surfaced client-side; `kind` is the single source of truth for taxonomy. The text payload is always valid JSON (whether the result is success or error), so MCP clients can parse `content[0].text` without branching on `isError`. -**Filter parameters.** `category` is the preferred surface for filtering (`callable`, `type`, `module`, `data`, `documentation`); `kind` is for the "I want this specific construct" case (27-value taxonomy). Both filters may be combined; both route through the shared `buildSearchSymbolsParams` helper. +**Legacy `search_symbols` filter parameters.** `category` is the preferred surface for filtering (`callable`, `type`, `module`, `data`, `documentation`); `kind` is for the "I want this specific construct" case (27-value taxonomy). Both filters may be combined; both route through the shared `buildSearchSymbolsParams` helper. ### `package_summary` response shape diff --git a/plugins/claude/commands/example.md b/plugins/claude/commands/example.md new file mode 100644 index 00000000..50ef562d --- /dev/null +++ b/plugins/claude/commands/example.md @@ -0,0 +1,23 @@ +--- +description: Search for canonical code examples from open source via GitHits +--- + +# Example + +Search for code examples using GitHits for the query: "$ARGUMENTS" + +Use the GitHits MCP `get_example` tool with the user's query. The tool requires two +parameters: + +- **query**: The user's search query, formulated in natural language. +- **language**: The programming language. If the language is unclear from + context, use the `search_language` tool first to find the correct language + name. + +Optional parameter: + +- **license_mode**: `"strict"` (default, excludes copyleft), `"yolo"` (all + licenses), or `"custom"` (user's blocklist). + +Present the results clearly. After the user has reviewed the result, use the +`feedback` tool to report whether the example was helpful. diff --git a/plugins/claude/commands/help.md b/plugins/claude/commands/help.md index 18ba741d..0c1fd1ee 100644 --- a/plugins/claude/commands/help.md +++ b/plugins/claude/commands/help.md @@ -16,7 +16,8 @@ context summary: ## Slash Commands -- `/githits:search ` — Search for code examples from open source. +- `/githits:example ` — Search for canonical code examples from open source. +- `/githits:search ` — Legacy alias for `/githits:example`. - `/githits:login` — Authenticate with your GitHits account. - `/githits:status` — Show your current authentication status. - `/githits:logout` — Remove stored credentials. @@ -24,14 +25,18 @@ context summary: ## MCP Tools -This plugin connects to the GitHits MCP server and exposes three tools: +This plugin connects to the GitHits MCP server and always exposes three core tools: -- **search** — Find code examples by describing what you need in natural +- **get_example** — Find code examples by describing what you need in natural language. Requires `query` and `language` parameters. - **search_language** — Look up supported programming language names before searching. - **feedback** — Rate a search result to improve future quality. +Additional indexed dependency/package tools such as `search`, `package_summary`, +`list_files`, and `grep_repo` stay hidden unless the authenticated token carries +the `code_navigation` feature flag. + ## Authentication Run `npx -y githits login` to authenticate via browser, or set the diff --git a/plugins/claude/commands/search.md b/plugins/claude/commands/search.md index 833acb7f..0e16f7b7 100644 --- a/plugins/claude/commands/search.md +++ b/plugins/claude/commands/search.md @@ -1,12 +1,14 @@ --- -description: Search for code examples from open source via GitHits +description: Legacy alias for GitHits example search --- -# Search +# Search (Legacy Alias) -Search for code examples using GitHits for the query: "$ARGUMENTS" +Use GitHits example search for the query: "$ARGUMENTS" -Use the GitHits MCP `search` tool with the user's query. The tool requires two +This slash command is the older alias for `/githits:example`. + +Use the GitHits MCP `get_example` tool with the user's query. The tool requires two parameters: - **query**: The user's search query, formulated in natural language. diff --git a/plugins/claude/skills/search/SKILL.md b/plugins/claude/skills/search/SKILL.md index 53b28d1b..b4e1c3ee 100644 --- a/plugins/claude/skills/search/SKILL.md +++ b/plugins/claude/skills/search/SKILL.md @@ -29,7 +29,10 @@ Guidelines: - Prefer existing search context if it already answers the problem. - Use `search_language` first when language naming is uncertain. -- Use `search` for one focused question at a time. +- Use `get_example` for one focused example-search question at a time. +- When the task is about indexed dependency or repository internals and the + capability-gated tools are available, prefer unified `search` instead of + `get_example`. - After using results, send `feedback` with helpful/unhelpful outcome. Tool argument details and rich query guidance are provided directly in the MCP diff --git a/skills/search/SKILL.md b/skills/search/SKILL.md index 53b28d1b..b4e1c3ee 100644 --- a/skills/search/SKILL.md +++ b/skills/search/SKILL.md @@ -29,7 +29,10 @@ Guidelines: - Prefer existing search context if it already answers the problem. - Use `search_language` first when language naming is uncertain. -- Use `search` for one focused question at a time. +- Use `get_example` for one focused example-search question at a time. +- When the task is about indexed dependency or repository internals and the + capability-gated tools are available, prefer unified `search` instead of + `get_example`. - After using results, send `feedback` with helpful/unhelpful outcome. Tool argument details and rich query guidance are provided directly in the MCP diff --git a/src/cli.ts b/src/cli.ts index 89b5dc9f..83b93772 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -60,7 +60,6 @@ Getting started: githits init Set up MCP for your coding agents githits login Authenticate with your GitHits account githits mcp Start MCP server for your AI assistant - githits search "router middleware" --in npm:express Search dependency code/docs githits example "query" --lang python Get code examples Learn more at https://githits.com @@ -84,12 +83,9 @@ registerLanguagesCommand(program); registerFeedbackCommand(program); const argv = process.argv.slice(2); const helpInvocation = isHelpInvocation(argv); -const helpRegistrationOptions = helpInvocation - ? { - capability: "enabled" as const, - expiredStoredAuth: false, - envTokenPresent: false, - } +const shouldLoadGatedHelpRegistration = needsGatedHelpRegistration(argv); +const helpRegistrationOptions = shouldLoadGatedHelpRegistration + ? await loadHelpRegistrationOptions() : undefined; if (shouldEagerLoadSearchCommands(argv)) { @@ -131,10 +127,7 @@ function shouldEagerLoadGatedCommandGroup( ): boolean { const [firstArg] = args; return ( - firstArg === groupName || - firstArg === "help" || - firstArg === "--help" || - firstArg === "-h" + firstArg === groupName || (firstArg === "help" && args[1] === groupName) ); } @@ -143,10 +136,7 @@ function shouldEagerLoadSearchCommands(args: string[]): boolean { return ( firstArg === "search" || firstArg === "search-status" || - firstArg === "help" || - firstArg === "--help" || - firstArg === "-h" || - args.length === 0 + (firstArg === "help" && isSearchHelpTarget(args[1])) ); } @@ -159,6 +149,57 @@ function isHelpInvocation(args: string[]): boolean { ); } +function needsGatedHelpRegistration(args: string[]): boolean { + if (!isHelpInvocation(args)) { + return false; + } + + const [firstArg, secondArg] = args; + if (firstArg === "help") { + return ( + isSearchHelpTarget(secondArg) || + secondArg === "code" || + secondArg === "pkg" + ); + } + + return ( + isSearchHelpTarget(firstArg) || firstArg === "code" || firstArg === "pkg" + ); +} + +function isSearchHelpTarget(value: string | undefined): boolean { + return value === "search" || value === "search-status"; +} + +async function loadHelpRegistrationOptions() { + const { resolveStartupCodeNavigationRegistrationState } = await import( + "./container.js" + ); + const registrationState = + await resolveStartupCodeNavigationRegistrationState(); + return { + capability: registrationState.capability, + expiredStoredAuth: shouldUseExpiredStoredAuthFallbackForHelp(argv) + ? registrationState.expiredStoredAuth + : false, + }; +} + +function shouldUseExpiredStoredAuthFallbackForHelp(args: string[]): boolean { + const [firstArg, secondArg] = args; + return ( + firstArg === "search" || + firstArg === "search-status" || + firstArg === "code" || + firstArg === "pkg" || + (firstArg === "help" && + (isSearchHelpTarget(secondArg) || + secondArg === "code" || + secondArg === "pkg")) + ); +} + function getTelemetryCommandName(command: Command): string { const names: string[] = []; let current: Command | null = command; diff --git a/src/commands/code/index.test.ts b/src/commands/code/index.test.ts index 199be7b3..8ed6c905 100644 --- a/src/commands/code/index.test.ts +++ b/src/commands/code/index.test.ts @@ -50,21 +50,20 @@ describe("registerCodeCommandGroup", () => { ).toBe(true); }); - it("registers the code command group for opaque env tokens", async () => { + it("does not register the code command group for opaque env tokens without the capability claim", async () => { const program = new Command(); await registerCodeCommandGroup(program, { codeNavigationUrl: "https://nav.example.com", overrideEnabled: false, capability: "unknown", - envTokenPresent: true, }); expect(program.commands.some((command) => command.name() === "code")).toBe( - true, + false, ); }); - it("registers the code command group for expired stored auth", async () => { + it("registers the code command group for expired stored auth so direct invocation can refresh", async () => { const program = new Command(); await registerCodeCommandGroup(program, { codeNavigationUrl: "https://nav.example.com", diff --git a/src/commands/code/index.ts b/src/commands/code/index.ts index 802e2848..7d06c602 100644 --- a/src/commands/code/index.ts +++ b/src/commands/code/index.ts @@ -3,7 +3,6 @@ import { resolveStartupCodeNavigationRegistrationState } from "../../container.j import { type CodeNavigationCapability, getCodeNavigationUrl, - getEnvApiToken, isCodeNavigationCliOverrideEnabled, } from "../../services/index.js"; import { registerCodeFilesCommand } from "./files.js"; @@ -15,15 +14,15 @@ export interface CodeCommandGroupOptions { codeNavigationUrl?: string; overrideEnabled?: boolean; capability?: CodeNavigationCapability; - envTokenPresent?: boolean; expiredStoredAuth?: boolean; } /** * Registers the capability-gated code-navigation command group. * Only exposed when the token advertises `code_navigation`, the - * user sets `GITHITS_CODE_NAVIGATION=1`, an opaque env token is - * present, or stored auth has expired. + * user sets `GITHITS_CODE_NAVIGATION=1` for local development, or + * stored auth has expired and the direct command path needs a chance + * to refresh before the CLI can re-evaluate capability. */ export async function registerCodeCommandGroup( program: Command, @@ -43,13 +42,10 @@ export async function registerCodeCommandGroup( expiredStoredAuth: options.expiredStoredAuth ?? false, } : await resolveStartupCodeNavigationRegistrationState(); - const capability = registrationState.capability; - const envTokenPresent = options.envTokenPresent ?? Boolean(getEnvApiToken()); if ( !overrideEnabled && - capability !== "enabled" && - !envTokenPresent && + registrationState.capability !== "enabled" && !registrationState.expiredStoredAuth ) { return; diff --git a/src/commands/mcp-instructions.test.ts b/src/commands/mcp-instructions.test.ts index dfc4e725..e22fe9b1 100644 --- a/src/commands/mcp-instructions.test.ts +++ b/src/commands/mcp-instructions.test.ts @@ -82,12 +82,12 @@ describe("isPackageToolsCapabilityOpen", () => { expect(isPackageToolsCapabilityOpen(deps)).toBe(false); }); - it("is true when capability unknown but env token provides opaque grant", () => { + it("is false when capability unknown even if an env token is present", () => { const deps = createTestDeps({ codeNavigationCapability: "unknown", envApiToken: "ghi-opaque-token", }); - expect(isPackageToolsCapabilityOpen(deps)).toBe(true); + expect(isPackageToolsCapabilityOpen(deps)).toBe(false); }); it("is false when capability unknown and no env token", () => { diff --git a/src/commands/mcp-instructions.ts b/src/commands/mcp-instructions.ts index cf3937d1..a0ec72bb 100644 --- a/src/commands/mcp-instructions.ts +++ b/src/commands/mcp-instructions.ts @@ -56,9 +56,8 @@ const SEARCH_VS_SYMBOLS_TIP = /** * Whether the MCP session should register and describe package tools. * - * Narrower than the CLI gate by design: agents must not see tools - * or guidance that would silently fail, so a bare `unknown` - * capability (no env token to probe further) keeps the gate closed. + * Agents must not see tools or guidance that would silently fail, so + * only an explicit `code_navigation` capability enables the surface. * * Single source of truth — tool registration in `mcp.ts` and the * package-tools fragment in `buildMcpInstructions` must share this @@ -66,11 +65,7 @@ const SEARCH_VS_SYMBOLS_TIP = * documented. */ export function isPackageToolsCapabilityOpen(deps: Dependencies): boolean { - return ( - deps.codeNavigationCapability === "enabled" || - (deps.codeNavigationCapability === "unknown" && - deps.envApiToken !== undefined) - ); + return deps.codeNavigationCapability === "enabled"; } /** diff --git a/src/commands/mcp.test.ts b/src/commands/mcp.test.ts index e4a991e7..8049630d 100644 --- a/src/commands/mcp.test.ts +++ b/src/commands/mcp.test.ts @@ -85,7 +85,7 @@ describe("createMcpServer", () => { ]); }); - it("adds unified search tools for opaque env tokens", () => { + it("omits unified search tools for opaque env tokens without an explicit capability claim", () => { const deps = createTestDeps({ envApiToken: "ghi-opaque-token", codeNavigationCapability: "unknown", @@ -94,8 +94,8 @@ describe("createMcpServer", () => { }); const tools = getMcpToolDefinitions(deps); - expect(tools.some((tool) => tool.name === "search")).toBe(true); - expect(tools.some((tool) => tool.name === "search_status")).toBe(true); + expect(tools.some((tool) => tool.name === "search")).toBe(false); + expect(tools.some((tool) => tool.name === "search_status")).toBe(false); }); it("adds package_summary when capability is enabled and service wired", () => { @@ -132,7 +132,7 @@ describe("createMcpServer", () => { expect(tools.map((tool) => tool.name)).not.toContain("package_summary"); }); - it("adds package_summary for opaque env tokens (capability unknown + env token)", () => { + it("omits package_summary for opaque env tokens without an explicit capability claim", () => { const deps = createTestDeps({ envApiToken: "ghi-opaque-token", codeNavigationCapability: "unknown", @@ -141,7 +141,7 @@ describe("createMcpServer", () => { }); const tools = getMcpToolDefinitions(deps); - expect(tools.some((tool) => tool.name === "package_summary")).toBe(true); + expect(tools.some((tool) => tool.name === "package_summary")).toBe(false); }); it("preserves half-open invariant: whenever package_summary is advertised, unified search is too (enabled path)", () => { diff --git a/src/commands/pkg/index.test.ts b/src/commands/pkg/index.test.ts index 92b3e487..6dbfeed5 100644 --- a/src/commands/pkg/index.test.ts +++ b/src/commands/pkg/index.test.ts @@ -56,21 +56,20 @@ describe("registerPkgCommandGroup", () => { ).toBe(true); }); - it("registers the pkg command group for opaque env tokens", async () => { + it("does not register the pkg command group for opaque env tokens without the capability claim", async () => { const program = new Command(); await registerPkgCommandGroup(program, { codeNavigationUrl: "https://pkgseer.dev", overrideEnabled: false, capability: "unknown", - envTokenPresent: true, }); expect(program.commands.some((command) => command.name() === "pkg")).toBe( - true, + false, ); }); - it("registers the pkg command group for expired stored auth", async () => { + it("registers the pkg command group for expired stored auth so direct invocation can refresh", async () => { const program = new Command(); await registerPkgCommandGroup(program, { codeNavigationUrl: "https://pkgseer.dev", diff --git a/src/commands/pkg/index.ts b/src/commands/pkg/index.ts index 195862ae..f6fee498 100644 --- a/src/commands/pkg/index.ts +++ b/src/commands/pkg/index.ts @@ -3,7 +3,6 @@ import { resolveStartupCodeNavigationRegistrationState } from "../../container.j import { type CodeNavigationCapability, getCodeNavigationUrl, - getEnvApiToken, isCodeNavigationCliOverrideEnabled, } from "../../services/index.js"; import { registerPkgChangelogCommand } from "./changelog.js"; @@ -15,7 +14,6 @@ export interface PkgCommandGroupOptions { codeNavigationUrl?: string; overrideEnabled?: boolean; capability?: CodeNavigationCapability; - envTokenPresent?: boolean; expiredStoredAuth?: boolean; } @@ -27,10 +25,10 @@ export interface PkgCommandGroupOptions { * `GITHITS_CODE_NAV_URL`, no sensible default), skip registration * entirely. * 2. Capability gate — register only when the token advertises - * `code_navigation`, `GITHITS_CODE_NAVIGATION=1` is set, an env - * API token is present, or the stored auth has expired (we can't - * inspect the caps inside an expired JWT, so show the command and - * let the caller refresh). + * `code_navigation`, or when `GITHITS_CODE_NAVIGATION=1` is set + * for local development. Direct command invocation also keeps the + * expired-stored-auth fallback so a refreshable session can reach + * the token refresh path before capability is re-evaluated. * * The capability check is intentionally duplicated with * `registerCodeCommandGroup` rather than factored out — two tiny @@ -54,13 +52,10 @@ export async function registerPkgCommandGroup( expiredStoredAuth: options.expiredStoredAuth ?? false, } : await resolveStartupCodeNavigationRegistrationState(); - const capability = registrationState.capability; - const envTokenPresent = options.envTokenPresent ?? Boolean(getEnvApiToken()); if ( !overrideEnabled && - capability !== "enabled" && - !envTokenPresent && + registrationState.capability !== "enabled" && !registrationState.expiredStoredAuth ) { return; diff --git a/src/commands/search-registration.test.ts b/src/commands/search-registration.test.ts index 08875622..182db6f8 100644 --- a/src/commands/search-registration.test.ts +++ b/src/commands/search-registration.test.ts @@ -19,14 +19,12 @@ describe("registerUnifiedSearchCommands", () => { ).toBe(false); }); - it("does not register search commands without override, capability, env token, or expired auth", async () => { + it("does not register search commands without override or explicit capability", async () => { const program = new Command(); await registerUnifiedSearchCommands(program, { codeNavigationUrl: "https://nav.example.com", overrideEnabled: false, capability: "disabled", - envTokenPresent: false, - expiredStoredAuth: false, }); expect( @@ -63,21 +61,20 @@ describe("registerUnifiedSearchCommands", () => { ).toBe(true); }); - it("registers search commands for opaque env tokens", async () => { + it("does not register search commands for opaque env tokens without the capability claim", async () => { const program = new Command(); await registerUnifiedSearchCommands(program, { codeNavigationUrl: "https://nav.example.com", overrideEnabled: false, capability: "unknown", - envTokenPresent: true, }); expect( program.commands.some((command) => command.name() === "search"), - ).toBe(true); + ).toBe(false); }); - it("registers search commands for expired stored auth", async () => { + it("registers search commands for expired stored auth so direct invocation can refresh", async () => { const program = new Command(); await registerUnifiedSearchCommands(program, { codeNavigationUrl: "https://nav.example.com", diff --git a/src/commands/search.ts b/src/commands/search.ts index ac462588..7190067d 100644 --- a/src/commands/search.ts +++ b/src/commands/search.ts @@ -6,7 +6,6 @@ import type { } from "../services/code-navigation-service.js"; import { getCodeNavigationUrl, - getEnvApiToken, isCodeNavigationCliOverrideEnabled, } from "../services/config.js"; import { @@ -55,7 +54,6 @@ export interface SearchCommandRegistrationOptions { codeNavigationUrl?: string; overrideEnabled?: boolean; capability?: CodeNavigationCapability; - envTokenPresent?: boolean; expiredStoredAuth?: boolean; } @@ -293,13 +291,10 @@ export async function registerUnifiedSearchCommands( expiredStoredAuth: options.expiredStoredAuth ?? false, } : await loadStartupCodeNavigationRegistrationState(); - const capability = registrationState.capability; - const envTokenPresent = options.envTokenPresent ?? Boolean(getEnvApiToken()); if ( !overrideEnabled && - capability !== "enabled" && - !envTokenPresent && + registrationState.capability !== "enabled" && !registrationState.expiredStoredAuth ) { return; diff --git a/src/container.ts b/src/container.ts index 1369c65e..73fbe12d 100644 --- a/src/container.ts +++ b/src/container.ts @@ -79,7 +79,7 @@ export interface Dependencies { envApiToken: string | undefined; /** Code navigation capability derived from the startup token snapshot */ codeNavigationCapability: CodeNavigationCapability; - /** Whether GITHITS_CODE_NAVIGATION is set to force-expose the code CLI commands locally */ + /** Whether GITHITS_CODE_NAVIGATION is set to force-expose gated search/code/pkg CLI surfaces locally */ codeNavigationCliOverrideEnabled: boolean; /** Code navigation backend URL when configured */ codeNavigationUrl: string | undefined; diff --git a/src/services/config.ts b/src/services/config.ts index a4eeeb17..a5969c35 100644 --- a/src/services/config.ts +++ b/src/services/config.ts @@ -53,8 +53,9 @@ export function getEnvApiToken(): string | undefined { /** * Whether `GITHITS_CODE_NAVIGATION` forces the capability-gated - * `code` CLI commands to be exposed locally regardless of the - * startup token's `code_navigation` claim. + * code-navigation CLI surfaces (`search`, `code`, `pkg`) to be + * exposed locally regardless of the startup token's + * `code_navigation` claim. */ export function isCodeNavigationCliOverrideEnabled(): boolean { const raw = process.env.GITHITS_CODE_NAVIGATION; From 9917fd7cf5d3808377ee15852e7645e9b11bfd6f Mon Sep 17 00:00:00 2001 From: Juha Litola Date: Fri, 24 Apr 2026 16:04:27 +0300 Subject: [PATCH 2/2] chore(release): bump version to 0.2.0 Cut the next minor release for the renamed example-search surface and the tightened code_navigation-gated indexed tooling. --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- .plugin/plugin.json | 2 +- gemini-extension.json | 2 +- package.json | 2 +- plugins/claude/.claude-plugin/plugin.json | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index eb8a414d..db05d56c 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.11" + "version": "0.2.0" }, "plugins": [ { diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 650a2e58..e7a7f2e9 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "githits", - "version": "0.1.11", + "version": "0.2.0", "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 650a2e58..e7a7f2e9 100644 --- a/.plugin/plugin.json +++ b/.plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "githits", - "version": "0.1.11", + "version": "0.2.0", "description": "Code examples from global open source for developers and AI assistants", "author": { "name": "GitHits" diff --git a/gemini-extension.json b/gemini-extension.json index 4c3f91d9..db5c2d55 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "githits", - "version": "0.1.11", + "version": "0.2.0", "description": "Code examples from global open source for developers and AI assistants.", "mcpServers": { "githits": { diff --git a/package.json b/package.json index 84bce8b9..94b93f2d 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.11", + "version": "0.2.0", "type": "module", "files": [ "dist", diff --git a/plugins/claude/.claude-plugin/plugin.json b/plugins/claude/.claude-plugin/plugin.json index 650a2e58..e7a7f2e9 100644 --- a/plugins/claude/.claude-plugin/plugin.json +++ b/plugins/claude/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "githits", - "version": "0.1.11", + "version": "0.2.0", "description": "Code examples from global open source for developers and AI assistants", "author": { "name": "GitHits"