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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/implementation/cli-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Unified search spans indexed dependency and repository code, docs, and explicit

**Sources and filters.** `--source docs|code|symbol` is repeatable; omitting it delegates source selection to backend AUTO. Use `--source symbol` when you want symbol-shaped search results without dropping down to the older `code search` surface. `--category` is the broad filter (`callable`, `type`, `module`, `data`, `documentation`); `--kind` is the precise taxonomy. `--path-prefix`, `--intent`, and `--public` narrow the result set further. `--name` and `--lang` compile into query qualifiers instead of becoming separate backend fields.

**Production intent by default.** When `--intent` is omitted, unified search defaults to `production` intent for AUTO, code, and symbol searches. This removes test / benchmark / example noise where the backend supports the filter. Explicit docs-only searches do not get a file-intent default because docs do not support that filter. Some sources can still ignore `fileIntent`; when they do, the JSON `sourceStatus` block and terminal notes report that explicitly.
**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).

Expand Down Expand Up @@ -126,13 +126,13 @@ Finds functions, classes, modules, and doc sections inside an indexed dependency

**Filtering by symbol shape.** Prefer `--category` for broad filtering (`callable`, `type`, `module`, `data`, `documentation`) — it works across the full 27-value kind taxonomy without enumerating individual kinds. Reach for `--kind` when you want a specific construct, e.g. `--kind trait` (Rust) or `--kind namespace` (C#/C++/PHP).

**Defaults.** `--intent production` filters to production source by default so top results are not dominated by tests, benchmarks, or examples. Use `--intent all` to include every file intent. `--wait` defaults to 20 seconds (above the p50 indexing time of ~11 s); first-time queries against an unindexed package may need `--wait 60` (the backend ceiling) to block until indexing completes. On an INDEXING error, the response message points out the retry options.
**Defaults.** No file-intent filter is sent by default. Pass `--intent production` or another specific intent to narrow results; `--intent all` remains accepted as an explicit no-filter alias. `--wait` defaults to 20 seconds (above the p50 indexing time of ~11 s); first-time queries against an unindexed package may need `--wait 60` (the backend ceiling) to block until indexing completes. On an INDEXING error, the response message points out the retry options.

**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 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.
**Troubleshooting.** Set `GITHITS_DEBUG=code-nav` to emit single-line JSON diagnostics to stderr without query text, tokens, or response bodies. When you need the exact code-navigation wire payload, `GITHITS_DEBUG=code-nav-wire` logs the GraphQL document plus serialized variables, which includes query text and resolved target values; it is explicit-only and is not enabled by `GITHITS_DEBUG=*`.

### `githits pkg info`

Expand All @@ -155,7 +155,7 @@ Shows a concise overview for a single package: latest version, license, descript

**Capability gate.** Same as `code`.

**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.
**Troubleshooting.** `GITHITS_DEBUG=pkg-intel` emits PII-safe classified-error diagnostics (area, event, code, error class, detail keys). Use `GITHITS_DEBUG=*` to enable all non-sensitive package/source diagnostics.

### `githits pkg vulns`

Expand Down
10 changes: 5 additions & 5 deletions docs/implementation/mcp-cli-parity.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ the test suite anchors the doc.
`src/shared/code-navigation-defaults.ts`. They never diverge
silently.
- Cross-tool defaults (e.g. `DEFAULT_WAIT_TIMEOUT_MS`) live without a
prefix. Tool-specific defaults carry a tool-name prefix
(`SEARCH_SYMBOLS_DEFAULT_FILE_INTENT`) so tool #2 declaring its own
defaults in the same file does not cause naming churn.
prefix. Tool-local sentinels that affect request shaping (for example
`FILE_INTENT_ALL`) also live there so both surfaces translate them the
same way.
- When a surface fills in a default for the caller, that default value
is applied at the shared request builder
(`buildSearchSymbolsParams`) — not at the surface — so both
Expand Down Expand Up @@ -86,7 +86,7 @@ the test suite anchors the doc.
a string array naming the fields whose values the client filled in.
Empty array when every field was caller-set.
- `fileIntent` is echoed as a lowercase enum value, or the literal
`"all"` when the caller chose the all-intents sentinel.
`"all"` when no file-intent filter was applied.
- `returnedCount` is an explicit echo of `results.length`.
- `totalMatches` is the service-provided total (equal to
`returnedCount` today).
Expand Down Expand Up @@ -160,7 +160,7 @@ When a new tool lands with both MCP and CLI surfaces:

| File | Role |
|---|---|
| `src/shared/code-navigation-defaults.ts` | Canonical defaults and sentinels. |
| `src/shared/code-navigation-defaults.ts` | Canonical cross-surface defaults and sentinels. |
| `src/shared/code-navigation-error-map.ts` | `mapCodeNavigationError` classifier and `MappedError` union. |
| `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). |
Expand Down
2 changes: 1 addition & 1 deletion docs/implementation/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Both expose the same tools with identical names, parameters, and descriptions. T
| `get_example` | `query`, `language`, `license_mode?` | Search for canonical code examples. Returns markdown-formatted results. |
| `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?` | 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` | `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. 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. |
| `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. |
| `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. |
Expand Down
39 changes: 33 additions & 6 deletions src/commands/code/search-symbols.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ describe("searchSymbolsAction", () => {
packageName: "express",
version: undefined,
});
expect(payload.query.fileIntent).toBe("production");
expect(payload.query.defaulted).toContain("fileIntent");
expect(payload.query.fileIntent).toBe("all");
expect(payload.query.defaulted).not.toContain("fileIntent");
expect(payload.query.defaulted).toContain("waitTimeoutMs");
expect(payload._warning).toBeUndefined();
consoleSpy.mockRestore();
Expand Down Expand Up @@ -189,7 +189,7 @@ describe("searchSymbolsAction", () => {
exitSpy.mockRestore();
});

it("applies --intent production by default and --intent all to opt out", async () => {
it("sends no file-intent filter by default and preserves explicit intent choices", async () => {
const logSpy = spyOn(console, "log").mockImplementation(() => {});
const searchSymbols = mock<
(
Expand All @@ -203,7 +203,7 @@ describe("searchSymbolsAction", () => {
});

await searchSymbolsAction("npm:express", "middleware", {}, deps);
expect(searchSymbols.mock.calls[0]?.[0]?.fileIntent).toBe("PRODUCTION");
expect(searchSymbols.mock.calls[0]?.[0]?.fileIntent).toBeUndefined();

searchSymbols.mockClear();
await searchSymbolsAction(
Expand Down Expand Up @@ -395,13 +395,13 @@ describe("searchSymbolsAction", () => {
consoleSpy.mockRestore();
});

it("adapts zero-result suggestions to the filters the caller actually used", async () => {
it("suggests --intent all only when the caller explicitly narrowed file intent", async () => {
const consoleSpy = spyOn(console, "log").mockImplementation(() => {});

await searchSymbolsAction(
"npm:express",
"Router",
{ file: "nonexistent/", kind: "function" },
{ file: "nonexistent/", kind: "function", intent: "test" },
createDeps({
codeNavigationService: createMockCodeNavigationService({
searchSymbols: mock(() =>
Expand Down Expand Up @@ -450,6 +450,33 @@ describe("searchSymbolsAction", () => {
consoleSpy.mockRestore();
});

it("omits `try --intent all` from the zero-result suggestion when no intent filter was sent", async () => {
const consoleSpy = spyOn(console, "log").mockImplementation(() => {});

await searchSymbolsAction(
"npm:express",
"Router",
{},
createDeps({
codeNavigationService: createMockCodeNavigationService({
searchSymbols: mock(() =>
Promise.resolve({
results: [],
totalMatches: 0,
hasMore: false,
version: "v5.2.1",
}),
),
}),
}),
);

const output = consoleSpy.mock.calls.map((call) => call[0]).join("\n");
expect(output).toContain("try broader keywords");
expect(output).not.toContain("try --intent all");
consoleSpy.mockRestore();
});

it("accepts --category and passes through to the service and echo", async () => {
const searchSymbols = mock<
(
Expand Down
7 changes: 4 additions & 3 deletions src/commands/code/search-symbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,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.
By default no file-intent filter is sent. Pass --intent production (or another
specific intent) to narrow results; --intent all remains accepted as an explicit
no-filter alias.

Examples:
githits code search npm:express middleware
Expand Down Expand Up @@ -172,7 +173,7 @@ export function registerCodeSearchSymbolsCommand(program: Command) {
.option("--file <prefix>", "Filter to files matching path prefix")
.option(
"--intent <intent>",
"File intent filter (production, test, benchmark, example, generated, fixture, build, vendor, all). Default: production.",
"File intent filter (production, test, benchmark, example, generated, fixture, build, vendor, all). Omit for no filter; `all` is an explicit alias.",
)
.option(
"--wait <seconds>",
Expand Down
2 changes: 1 addition & 1 deletion src/commands/mcp-instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ 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_BULLET =
"- `search` — unified search across indexed dependency code, docs, and explicit symbols. Structured fields are the primary UX; omit `sources` for AUTO. Production file intent is applied by default where supported, but some sources may ignore that filter and report it in `sourceStatus`. Returns only trustworthy complete results by default; opt into partial hits with `allow_partial_results: true`. If indexing is still in progress, the response carries a `searchRef`.";
"- `search` — unified search across indexed dependency code, docs, and explicit symbols. Structured fields are the primary UX; omit `sources` for AUTO. Omit `file_intent` to search across all intents, or set it to narrow results; some sources may ignore that filter and report it in `sourceStatus`. Returns only trustworthy complete results by default; opt into partial hits with `allow_partial_results: true`. If indexing is still in progress, the response carries a `searchRef`.";

const SEARCH_STATUS_BULLET =
"- `search_status` — follow up a prior unified search by `searchRef`. Use it after `search` returns incomplete state to check progress, fetch partial hits when the original request used `allow_partial_results: true`, or fetch final results.";
Expand Down
79 changes: 36 additions & 43 deletions src/commands/search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,42 @@ describe("searchAction", () => {
consoleSpy.mockRestore();
});

it("does not send a file-intent filter unless the caller explicitly set one", async () => {
const search = mock<
(
params: import("../services/code-navigation-service.js").UnifiedSearchParams,
) => Promise<
import("../services/code-navigation-service.js").UnifiedSearchOutcome
>
>(() => 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]?.filters?.fileIntent).toBeUndefined();

search.mockClear();
await searchAction(
"router middleware",
{
in: ["npm:express"],
intent: "test",
},
deps,
);
expect(search.mock.calls[0]?.[0]?.filters?.fileIntent).toBe("TEST");

consoleSpy.mockRestore();
});

it("outputs JSON when --json flag provided", async () => {
const consoleSpy = spyOn(console, "log").mockImplementation(() => {});

Expand Down Expand Up @@ -224,49 +260,6 @@ describe("searchAction", () => {
consoleSpy.mockRestore();
});

it("suppresses ignored-filter notes for auto-defaulted filters", async () => {
const consoleSpy = spyOn(console, "log").mockImplementation(() => {});

if (defaultUnifiedSearchOutcome.state !== "completed") {
throw new Error("expected completed outcome fixture");
}
const completedOutcome = defaultUnifiedSearchOutcome;
const outcomeWithIgnoredFilters: UnifiedSearchOutcome = {
...completedOutcome,
result: {
...completedOutcome.result,
sourceStatus: [
{
source: "DOCS",
targetLabel: "npm:express@4.18.2",
indexingStatus: "INDEXED",
resultCount: 1,
ignoredFilters: ["fileIntent"],
incompatibleFilters: [],
appliedFilters: [],
appliedQueryFeatures: [],
ignoredQueryFeatures: [],
incompatibleQueryFeatures: [],
},
],
},
};

await searchAction(
"router middleware",
{ in: ["npm:express"] },
createDeps({
codeNavigationService: createMockCodeNavigationService({
search: mock(() => Promise.resolve(outcomeWithIgnoredFilters)),
}),
}),
);

const output = String(consoleSpy.mock.calls[0]?.[0]);
expect(output).not.toContain("ignored filters: fileIntent");
consoleSpy.mockRestore();
});

it("renders ignored and incompatible query-feature notes", async () => {
const consoleSpy = spyOn(console, "log").mockImplementation(() => {});

Expand Down
6 changes: 3 additions & 3 deletions src/commands/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@ const SEARCH_DESCRIPTION = `Search code, docs, and symbols across indexed depend

Use repeatable --in targets in package form (npm:express[@version]) or repo
form (https://github.com/org/repo[#ref]). Structured flags are AND-combined with
the discovery query. Unified search defaults to production file intent where the
backend supports that filter. Results are complete-by-default: if indexing is
the discovery query. Omit --intent to search across all file intents; pass it
only when you want to narrow results. Results are complete-by-default: if indexing is
still in progress, search returns a searchRef instead of partial hits unless
--allow-partial is passed.

Expand Down Expand Up @@ -230,7 +230,7 @@ export function registerSearchCommand(program: Command) {
.addOption(
new Option(
"--intent <intent>",
"File intent filter (default: production for AUTO/code/symbol, omitted for docs-only)",
"File intent filter (omit to search across all intents)",
).choices([
"production",
"test",
Expand Down
Loading
Loading