diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index e47e07f8..db4e79ac 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.2.3" + "version": "0.3.0" }, "plugins": [ { diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 06441cc7..0623dd16 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "githits", - "version": "0.2.3", + "version": "0.3.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 06441cc7..0623dd16 100644 --- a/.plugin/plugin.json +++ b/.plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "githits", - "version": "0.2.3", + "version": "0.3.0", "description": "Code examples from global open source for developers and AI assistants", "author": { "name": "GitHits" diff --git a/README.md b/README.md index 120d5f46..bd9f27dd 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ npx githits init `init` authenticates with your [GitHits](https://githits.com) account, then auto-detects your installed coding tools and configures each one with GitHits MCP. -Supported tools: Claude Code, Cursor, Windsurf, VS Code / Copilot, Cline, Claude Desktop, Codex CLI, Gemini CLI, and Google Antigravity. +Supported tools: Claude Code, Cursor, Windsurf, VS Code / Copilot, Cline, Claude Desktop, Codex CLI, Gemini CLI, Google Antigravity, and OpenCode. If you are using a tool that is not listed above, use the manual MCP setup instructions near the end of this README. diff --git a/commands/help.md b/commands/help.md index 97ec0e3b..7a97b077 100644 --- a/commands/help.md +++ b/commands/help.md @@ -25,7 +25,7 @@ context summary: ## MCP Tools -This plugin connects to the GitHits MCP server and always exposes three core tools: +This plugin connects to the GitHits MCP server and always exposes these core tools: - **get_example** — Find code examples by describing what you need in natural language. Requires `query`; `language` is optional and inferred when omitted. @@ -33,8 +33,9 @@ This plugin connects to the GitHits MCP server and always exposes three core too need to force a specific language. - **feedback** — Rate a search result to improve future quality. -Additional indexed dependency/package tools such as `search`, `pkg_info`, -`code_files`, and `code_grep` are available by default. +Additional indexed dependency/package tools are available by default: +`search`, `search_status`, `docs_list`, `docs_read`, `pkg_info`, `pkg_vulns`, +`pkg_deps`, `pkg_changelog`, `code_files`, `code_read`, and `code_grep`. ## Authentication diff --git a/docs/implementation/cli-commands.md b/docs/implementation/cli-commands.md index 35cf19c8..36618e25 100644 --- a/docs/implementation/cli-commands.md +++ b/docs/implementation/cli-commands.md @@ -10,7 +10,7 @@ The CLI exposes `example`, `languages`, `feedback`, top-level indexed `search` / |---|---|---|---| | `init` | — | `-y, --yes`, `--skip-login` | Authenticate and set up MCP server for coding agents | | `example ` | `` | `-l, --lang `, `--license `, `--explain`, `--json` | Search for code examples | -| `search ` | `--in ` | `--source `, `--kind `, `--category `, `--path-prefix `, `--intent `, `--public`, `--name `, `--lang `, `--allow-partial`, `--limit `, `--offset `, `--wait `, `--json` | Unified indexed search across dependency/repository code, docs, and symbols | +| `search ` | `--in ` | `--source `, `--kind `, `--category `, `--path-prefix `, `--intent `, `--public`, `--name `, `--lang `, `--allow-partial`, `--limit `, `--offset `, `--wait `, `--json` | Unified indexed search across dependency/repository code, docs, and symbols. Defaults to 10 results. | | `search-status ` | `` | `--json` | Check progress, fetch partial hits, or fetch final results for a prior unified search | | `languages [query]` | — | `--json` | List or filter supported languages | | `feedback ` | `--accept` or `--reject` | `-m, --message `, `--json` | Submit feedback on a search result | @@ -34,7 +34,7 @@ githits init --skip-login # Skip authentication, configure tools only Authenticates with GitHits (via OAuth in the browser), then scans for available coding agents, checks which are already configured, and sets up unconfigured ones with your confirmation. All agents are pre-checked before any setup begins, so the status display is fully resolved. CLI agents are considered available only when their executable is on `PATH`; related dot-directories alone do not count. Config-file agents remain filesystem-detected using their known app/config directories. If already authenticated, the login step is skipped automatically. If login fails, the user is prompted to continue with tool setup anyway. If all detected agents are already configured, exits early with a summary. -Supports Claude Code, Cursor, Windsurf, VS Code / Copilot, Cline, Claude Desktop, Codex CLI, Gemini CLI, and Google Antigravity. Uses plugin install (Claude Code), CLI commands (Codex, Gemini CLI), and atomic config file writes (Cursor, Windsurf, VS Code, Cline, Claude Desktop, Google Antigravity). CLI agents use read-only check commands (e.g., `claude plugin list`) to determine configuration status before prompting. +Supports Claude Code, Cursor, Windsurf, VS Code / Copilot, Cline, Claude Desktop, Codex CLI, Gemini CLI, Google Antigravity, and OpenCode. Uses plugin install (Claude Code), CLI commands (Codex, Gemini CLI), and atomic config file writes (Cursor, Windsurf, VS Code, Cline, Claude Desktop, Google Antigravity, OpenCode). CLI agents use read-only check commands (e.g., `claude plugin list`) to determine configuration status before prompting. The command uses `createContainer()` lazily for the login step. Tool detection and configuration use lightweight dependencies that don't require auth. @@ -72,7 +72,7 @@ Unified search spans indexed dependency and repository code, docs, and explicit **Intent filter.** When `--intent` is omitted, unified search sends no file-intent filter. Pass `--intent production` or another specific intent only when you want to narrow the result set. Some sources can still ignore `fileIntent`; when they do, the JSON `sourceStatus` block and terminal notes report that explicitly. -**Complete-by-default results.** The CLI sends `allowPartialResults: false` unless `--allow-partial` is passed. If indexing does not complete within the wait window, the default behavior returns a `searchRef` and progress summary instead of partial hits. With `--allow-partial`, available hits are included while remaining sources continue indexing. `--wait` is in seconds (0-60, default 20). +**Complete-by-default results.** The CLI sends `allowPartialResults: false` unless `--allow-partial` is passed. If indexing does not complete within the wait window, the default behavior returns a `searchRef` and progress summary instead of partial hits. With `--allow-partial`, available hits are included while remaining sources continue indexing. `--limit` defaults to 10 results. `--wait` is in seconds (0-60, default 20). The original unified-search plan envisaged hiding partial mode entirely in v1 to make results trustworthy by default. We kept the flag exposed because some agent and CLI flows benefit from "show me what you have so far"; the trust contract is preserved by keeping the *default* false. Users and agents must explicitly opt in, so a vanilla `search` call still cannot return incomplete results by surprise. diff --git a/docs/implementation/mcp-cli-parity.md b/docs/implementation/mcp-cli-parity.md index 2d0a224b..88932561 100644 --- a/docs/implementation/mcp-cli-parity.md +++ b/docs/implementation/mcp-cli-parity.md @@ -253,8 +253,8 @@ When a new tool lands with both MCP and CLI surfaces: values never round-trip as caller intent. - **`entries: { count, items }` shape.** Mirrors `runtime: {count, items}` from `pkg_deps`. -- **`source: null` promoted to `NOT_FOUND`.** The service layer - promotes the null-source case to a typed +- **Missing source promoted to `NOT_FOUND`.** The service layer + promotes null or empty `source` to a typed `PackageIntelligenceChangelogSourceNotFoundError` with a message naming the sources tried (GitHub Releases, CHANGELOG.md, HexDocs). - **`--verbose` / `--no-body` / `--json` interaction.** Default diff --git a/docs/implementation/tools.md b/docs/implementation/tools.md index e3375fe0..8b7c297f 100644 --- a/docs/implementation/tools.md +++ b/docs/implementation/tools.md @@ -8,10 +8,10 @@ The CLI exposes MCP tools that mirror the backend's MCP server. This document ex GitHits has two MCP server implementations: -- **Backend** (`githits-backend`) — Python/FastMCP, runs as a hosted service at `mcp.githits.com`. Surfaces only the example-search workflow (`get_example`, `search_language`, `feedback`). -- **CLI** (`githits-cli`) — TypeScript/MCP SDK, runs locally via `githits mcp start`. Surfaces the full set, including unified `search`, package intelligence (`pkg_*`), and code navigation (`code_*`). +- **Backend** (`githits-backend` / PkgSeer MCP) — Python/FastMCP, runs as hosted MCP services. Production exposes both the core example-search workflow (`get_example`, `search_language`, `feedback`) and indexed package/source tooling. +- **CLI** (`githits-cli`) — TypeScript/MCP SDK, runs locally via `githits mcp start`. Surfaces the same public tool families, including unified `search`, package intelligence (`pkg_*`), docs (`docs_*`), and code navigation (`code_*`). -The two surfaces overlap on the example-search workflow only; for the overlapping tools the parameters and descriptions stay aligned. Tools that exist only on the CLI surface (code navigation, package intelligence, unified `search`) are not constrained by backend parity. +The CLI mirrors the production MCP tool contract where equivalent tools exist. Core example-search tool descriptions are kept aligned with GitHits backend wording; indexed package/source tool descriptions are kept aligned with the PkgSeer/GitHits indexed-service contract. ## Current Tools @@ -20,7 +20,7 @@ The two surfaces overlap on the example-search workflow only; for the overlappin | `get_example` | `query`, `language?`, `license_mode?` | Search for canonical code examples. Returns markdown-formatted results. If `language` is omitted, the backend infers it from the query. | | `search_language` | `query` | Find supported programming language names before searching. | | `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?`, `format?` | Unified indexed dependency/repository discovery search across code, docs, and symbols. Omit `file_intent` to search across all intents; set it only when you want to narrow results, and note that some sources may ignore it and report that in `sourceStatus`. Prefer `sources:["symbol"]` for symbol-shaped unified search. Complete-by-default; set `allow_partial_results: true` to receive available partial hits while indexing continues. `format` defaults to `text-v1` for compact agent output; pass `format: "json"` for the structured envelope. | +| `search` | `query`, `target?`, `targets?`, `sources?`, `category?`, `kind?`, `path_prefix?`, `file_intent?`, `public_only?`, `name?`, `language?`, `allow_partial_results?`, `limit?`, `offset?`, `wait_timeout_ms?`, `format?` | Unified indexed dependency/repository discovery search across code, docs, and symbols. Omit `file_intent` to search across all intents; set it only when you want to narrow results, and note that some sources may ignore it and report that in `sourceStatus`. Prefer `sources:["symbol"]` for symbol-shaped unified search. Complete-by-default; `limit` defaults to 10 to keep agent output compact. Set `allow_partial_results: true` to receive available partial hits while indexing continues. `format` defaults to `text-v1` for compact agent output; pass `format: "json"` for the structured envelope. | | `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. | | `docs_list` | `registry`, `package_name`, `version?`, `limit?`, `after?` | List hosted/crawled and repository-backed documentation pages for a package. Entries include stable page IDs for `docs_read`; repo-backed entries include exact source metadata for `code_read` follow-up. | | `docs_read` | `page_id`, `start_line?`, `end_line?` | Read a documentation page by page ID. Supports bounded line ranges for long pages; repo-backed pages include exact file follow-up metadata. | @@ -104,7 +104,7 @@ The two surfaces overlap on the example-search workflow only; for the overlappin **Version validation.** Same rule as `pkg_vulns` / `pkg_deps`: tag-style `v`-prefixed inputs on `from_version` / `to_version` are rejected client-side with `INVALID_ARGUMENT`. `@` is also rejected — the `pkg changelog` family has no single-version query, and silently remapping to `to_version` would be a client-invented semantic shift. Hint text redirects callers to `--to` / `to_version`. -**NOT_FOUND semantics.** Backend `source === null` is promoted to a typed `PackageIntelligenceChangelogSourceNotFoundError` at the service boundary, which the shared classifier routes to the `NOT_FOUND` envelope with a message naming the sources that were tried. Empty `entries.items: []` with a valid `source` is a success, not an error — "no entries in this range" is a legitimate neutral outcome. +**NOT_FOUND semantics.** Backend `source === null` or `source === ""` is promoted to a typed `PackageIntelligenceChangelogSourceNotFoundError` at the service boundary, which the shared classifier routes to the `NOT_FOUND` envelope with a message naming the sources that were tried. Empty `entries.items: []` with a valid `source` is a success, not an error — "no entries in this range" is a legitimate neutral outcome. **Overlap with `pkg_info`.** `pkg_info` already surfaces a short-form `recentChanges` block (from the backend's `latestChangelogs` field on `PackageSummaryResult`). For a quick "what shipped recently" glance embedded in a package overview, use `pkg_info`. For the full range-capable, body-rich, `include_bodies`-toggleable changelog with `--no-body` timeline mode and repo-URL addressing, use `pkg_changelog`. diff --git a/gemini-extension.json b/gemini-extension.json index 3646628a..e271a3ab 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "githits", - "version": "0.2.3", + "version": "0.3.0", "description": "Code examples from global open source for developers and AI assistants.", "mcpServers": { "githits": { diff --git a/package.json b/package.json index d3d2e455..7e0136ac 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.2.3", + "version": "0.3.0", "type": "module", "files": [ "dist", diff --git a/plugins/claude/.claude-plugin/plugin.json b/plugins/claude/.claude-plugin/plugin.json index 06441cc7..0623dd16 100644 --- a/plugins/claude/.claude-plugin/plugin.json +++ b/plugins/claude/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "githits", - "version": "0.2.3", + "version": "0.3.0", "description": "Code examples from global open source for developers and AI assistants", "author": { "name": "GitHits" diff --git a/plugins/claude/commands/help.md b/plugins/claude/commands/help.md index 97ec0e3b..7a97b077 100644 --- a/plugins/claude/commands/help.md +++ b/plugins/claude/commands/help.md @@ -25,7 +25,7 @@ context summary: ## MCP Tools -This plugin connects to the GitHits MCP server and always exposes three core tools: +This plugin connects to the GitHits MCP server and always exposes these core tools: - **get_example** — Find code examples by describing what you need in natural language. Requires `query`; `language` is optional and inferred when omitted. @@ -33,8 +33,9 @@ This plugin connects to the GitHits MCP server and always exposes three core too need to force a specific language. - **feedback** — Rate a search result to improve future quality. -Additional indexed dependency/package tools such as `search`, `pkg_info`, -`code_files`, and `code_grep` are available by default. +Additional indexed dependency/package tools are available by default: +`search`, `search_status`, `docs_list`, `docs_read`, `pkg_info`, `pkg_vulns`, +`pkg_deps`, `pkg_changelog`, `code_files`, `code_read`, and `code_grep`. ## Authentication diff --git a/src/commands/mcp-instructions.ts b/src/commands/mcp-instructions.ts index 30a6471e..700f310c 100644 --- a/src/commands/mcp-instructions.ts +++ b/src/commands/mcp-instructions.ts @@ -70,7 +70,7 @@ const MULTI_TURN_TIP = * Mirrors `getMcpToolDefinitions` so the instructions stay aligned * with the registered tool surface. */ -export function buildMcpInstructions(deps: Dependencies): string { +export function buildMcpInstructions(_deps: Dependencies): string { const sections = [CORE_BLOCK]; const bullets: string[] = []; diff --git a/src/commands/search.test.ts b/src/commands/search.test.ts index efd45e0a..ebbab108 100644 --- a/src/commands/search.test.ts +++ b/src/commands/search.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it, mock, spyOn } from "bun:test"; import type { UnifiedSearchIncomplete, UnifiedSearchOutcome, + UnifiedSearchParams, UnifiedSearchProgress, UnifiedSearchSessionStatus, } from "../services/code-navigation-service.js"; @@ -108,6 +109,29 @@ describe("searchAction", () => { consoleSpy.mockRestore(); }); + it("uses the shared search default limit and preserves explicit limits", async () => { + const search = mock((_: UnifiedSearchParams) => + Promise.resolve(defaultUnifiedSearchOutcome), + ); + const deps = createDeps({ + codeNavigationService: createMockCodeNavigationService({ search }), + }); + const consoleSpy = spyOn(console, "log").mockImplementation(() => {}); + + await searchAction("router middleware", { in: ["npm:express"] }, deps); + expect(search.mock.calls[0]?.[0]?.limit).toBe(10); + + search.mockClear(); + await searchAction( + "router middleware", + { in: ["npm:express"], limit: "25" }, + deps, + ); + expect(search.mock.calls[0]?.[0]?.limit).toBe(25); + + consoleSpy.mockRestore(); + }); + it("does not send a file-intent filter unless the caller explicitly set one", async () => { const search = mock< ( diff --git a/src/commands/search.ts b/src/commands/search.ts index 4792c0c3..c9812abd 100644 --- a/src/commands/search.ts +++ b/src/commands/search.ts @@ -225,7 +225,7 @@ export function registerSearchCommand(program: Command) { "--allow-partial", "Include hits already available while indexing continues; a searchRef is still returned so search-status can fetch the rest", ) - .option("--limit ", "Max results (1-100)") + .option("--limit ", "Max results (1-100, default: 10)") .option("--offset ", "Result offset") .option( "--wait ", diff --git a/src/services/package-intelligence-service.test.ts b/src/services/package-intelligence-service.test.ts index 618c711e..4f28f2c6 100644 --- a/src/services/package-intelligence-service.test.ts +++ b/src/services/package-intelligence-service.test.ts @@ -5,6 +5,7 @@ import { MalformedPackageIntelligenceResponseError, PackageIntelligenceAccessError, PackageIntelligenceBackendError, + PackageIntelligenceChangelogSourceNotFoundError, PackageIntelligenceFeatureFlagRequiredError, PackageIntelligenceNetworkError, PackageIntelligenceServiceImpl, @@ -1291,6 +1292,35 @@ describe("PackageIntelligenceServiceImpl.packageVulnerabilities", () => { }); }); +describe("PackageIntelligenceServiceImpl — packageChangelog", () => { + const ENDPOINT = "https://pkgseer.dev"; + + it("treats an empty source as no changelog data", async () => { + const fetchFn = mock(() => + Promise.resolve( + jsonResponse({ + data: { + packageChangelog: { + package: { name: "express", registry: "NPM" }, + source: "", + entries: [], + }, + }, + }), + ), + ); + const service = new PackageIntelligenceServiceImpl( + ENDPOINT, + createMockTokenProvider(), + asFetchFn(fetchFn), + ); + + await expect( + service.packageChangelog({ registry: "NPM", packageName: "express" }), + ).rejects.toBeInstanceOf(PackageIntelligenceChangelogSourceNotFoundError); + }); +}); + describe("PackageIntelligenceServiceImpl — packageDependencies", () => { const ENDPOINT = "https://pkgseer.dev"; diff --git a/src/services/package-intelligence-service.ts b/src/services/package-intelligence-service.ts index 30c5a3b9..827cc807 100644 --- a/src/services/package-intelligence-service.ts +++ b/src/services/package-intelligence-service.ts @@ -358,7 +358,7 @@ export interface ChangelogEntryDetail { export interface ChangelogReport { /** Echo of addressing + filter as the backend saw it. */ package?: ChangelogPackageInfo; - /** `"releases"` | `"changelog_file"` | `"hexdocs"` when resolved; null otherwise. */ + /** `"releases"` | `"changelog_file"` | `"hexdocs"` when resolved; null/empty otherwise. */ source?: string; /** Entries, newest-first. Empty array = resolved source but nothing in range. */ entries: ChangelogEntryDetail[]; @@ -2023,12 +2023,11 @@ export class PackageIntelligenceServiceImpl data: z.infer, params: PackageChangelogParams, ): ChangelogReport { - // Backend returns `source: null` when no changelog source could - // be resolved for the package/repo. Distinct from `entries: []` - // which means "source resolved but produced no entries in this - // range". Promote the null-source case to a typed error at the - // service boundary so the envelope builder never has to think - // about it. + // Backend returns a null or empty source when no changelog data could + // be resolved for the package/repo. Distinct from `entries: []` with + // a valid source, which means "source resolved but produced no entries + // in this range". Promote the no-source case to a typed error at the + // service boundary so the envelope builder never has to think about it. const source = data.source ?? undefined; if (!source) { const target = diff --git a/src/services/test-helpers.ts b/src/services/test-helpers.ts index 08191e2b..dec8778d 100644 --- a/src/services/test-helpers.ts +++ b/src/services/test-helpers.ts @@ -140,7 +140,7 @@ export const defaultUnifiedSearchOutcome: UnifiedSearchOutcome = { ], page: { offset: 0, - limit: 20, + limit: 10, returned: 1, hasMore: false, }, diff --git a/src/shared/package-changelog-response.ts b/src/shared/package-changelog-response.ts index 6d97805c..79835c50 100644 --- a/src/shared/package-changelog-response.ts +++ b/src/shared/package-changelog-response.ts @@ -10,7 +10,7 @@ * the backend returned and what the caller asked for, not by * additional caller flags. `entries` is `{count, items}` whenever * the backend resolved a `source` (even `{count: 0, items: []}` - * for an empty range); `source === null` is promoted to a + * for an empty range); null/empty source is promoted to a * `NOT_FOUND` error at the service boundary and never reaches the * envelope builder. * - **Mode derived from request.** `mode: "range"` iff `fromVersion` @@ -88,8 +88,8 @@ export interface LeanChangelogEnvelope { name?: string; /** Present for repo-URL addressing. */ repoUrl?: string; - /** `"releases"` | `"changelog_file"` | `"hexdocs"`. Never null here - * (null is promoted to NOT_FOUND at the service boundary). */ + /** `"releases"` | `"changelog_file"` | `"hexdocs"`. Never null/empty here + * (no-source responses are promoted to NOT_FOUND at the service boundary). */ source: string; /** Derived from request params. */ mode: ChangelogMode; diff --git a/src/shared/unified-search-request.test.ts b/src/shared/unified-search-request.test.ts index 08cef82b..2103531a 100644 --- a/src/shared/unified-search-request.test.ts +++ b/src/shared/unified-search-request.test.ts @@ -11,7 +11,7 @@ describe("buildUnifiedSearchParams", () => { expect(built.rawQuery).toBe("router middleware"); expect(built.compiledQuery).toBe("router middleware"); expect(built.params.filters).toBeUndefined(); - expect(built.params.limit).toBe(20); + expect(built.params.limit).toBe(10); expect(built.params.offset).toBe(0); expect(built.params.waitTimeoutMs).toBe(20_000); }); diff --git a/src/shared/unified-search-request.ts b/src/shared/unified-search-request.ts index 13b5d0d9..5fdd3bee 100644 --- a/src/shared/unified-search-request.ts +++ b/src/shared/unified-search-request.ts @@ -10,6 +10,8 @@ import type { import { DEFAULT_WAIT_TIMEOUT_MS } from "./code-navigation-defaults.js"; import { InvalidArgumentError } from "./package-spec.js"; +export const DEFAULT_UNIFIED_SEARCH_LIMIT = 10; + export interface UnifiedSearchRequestInput { target?: CodeNavigationTarget; targets?: CodeNavigationTarget[]; @@ -40,7 +42,7 @@ export function buildUnifiedSearchParams( const targets = resolveTargets(input.target, input.targets); const rawQuery = normaliseRequiredQuery(input.query); - const limit = input.limit ?? 20; + const limit = input.limit ?? DEFAULT_UNIFIED_SEARCH_LIMIT; const offset = input.offset ?? 0; const waitTimeoutMs = input.waitTimeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS; diff --git a/src/shared/unified-search-response.test.ts b/src/shared/unified-search-response.test.ts index fa16eb66..850d558d 100644 --- a/src/shared/unified-search-response.test.ts +++ b/src/shared/unified-search-response.test.ts @@ -15,7 +15,7 @@ describe("buildUnifiedSearchSuccessPayload", () => { const params: UnifiedSearchParams = { targets: [{ registry: "NPM", packageName: "express" }], query: "router middleware", - limit: 20, + limit: 10, offset: 0, waitTimeoutMs: 20_000, }; @@ -228,7 +228,7 @@ describe("buildUnifiedSearchSuccessPayload — sourceStatus warnings on complete targets: [{ registry: "NPM", packageName: "zod" }], query: "parse kind:function", sources: ["DOCS"], - limit: 20, + limit: 10, offset: 0, waitTimeoutMs: 20_000, }; diff --git a/src/shared/unified-search-response.ts b/src/shared/unified-search-response.ts index db8ad07e..42053136 100644 --- a/src/shared/unified-search-response.ts +++ b/src/shared/unified-search-response.ts @@ -9,6 +9,7 @@ import type { import { MalformedCodeNavigationResponseError } from "../services/index.js"; import { DEFAULT_WAIT_TIMEOUT_MS } from "./code-navigation-defaults.js"; import { mapCodeNavigationError } from "./code-navigation-error-map.js"; +import { DEFAULT_UNIFIED_SEARCH_LIMIT } from "./unified-search-request.js"; /** * Default values folded out of the JSON envelope when the caller did @@ -16,7 +17,7 @@ import { mapCodeNavigationError } from "./code-navigation-error-map.js"; * back wastes tokens. The defaults must stay aligned with * `buildUnifiedSearchParams`. */ -const DEFAULT_LIMIT = 20; +const DEFAULT_LIMIT = DEFAULT_UNIFIED_SEARCH_LIMIT; const DEFAULT_OFFSET = 0; export interface UnifiedSearchQueryEcho { diff --git a/src/tools/search.test.ts b/src/tools/search.test.ts index 39d45d2b..430d6108 100644 --- a/src/tools/search.test.ts +++ b/src/tools/search.test.ts @@ -44,6 +44,7 @@ describe("searchTool", () => { expect.objectContaining({ query: "(handler) AND (lang:typescript)", allowPartialResults: true, + limit: 10, filters: expect.objectContaining({ kind: "FUNCTION" }), }), ); diff --git a/src/tools/search.ts b/src/tools/search.ts index 7a0ede98..fa4aef2b 100644 --- a/src/tools/search.ts +++ b/src/tools/search.ts @@ -150,7 +150,13 @@ const schema = { .describe( "Default false waits for all sources; if the wait window expires, returns only searchRef/progress. When true, includes hits from sources that finished so far and still returns searchRef for continuation. Partial payloads support normal pagination via nextOffset.", ), - limit: z.coerce.number().int().min(1).max(100).optional(), + limit: z.coerce + .number() + .int() + .min(1) + .max(100) + .optional() + .describe("Maximum results to return (default 10, max 100)."), offset: z.coerce.number().int().min(0).optional(), wait_timeout_ms: z.coerce.number().int().min(0).max(60000).optional(), format: z