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
6 changes: 6 additions & 0 deletions docs/implementation/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ The two methods exist because OAuth provides full access but requires a browser,

> **The container resolves auth at startup.** The `createContainer()` function checks for `GITHITS_API_TOKEN` first — if set, it takes precedence even when OAuth tokens are stored. If not set, it loads stored OAuth tokens and attempts auto-refresh if expired. See `src/container.ts` for the resolution logic.

For the code navigation PoC, the CLI also inspects the current bearer token for Supabase `feature_flags` claims. This is a local capability hint only: the backend still enforces access. The CLI decodes JWT payloads without signature verification, treating opaque or malformed tokens as `unknown` capability.

## OAuth PKCE Flow

The login command (`src/commands/login.ts`) orchestrates a 9-step OAuth flow (matching the `// Step N:` comments in the code):
Expand All @@ -42,6 +44,7 @@ Tokens are JWTs with a configurable expiration (typically 1 hour). The CLI handl
- **Proactive refresh** — When 90% of the token lifetime has elapsed (e.g., at ~54 minutes for a 1-hour token), the `TokenManager` refreshes before expiry. This avoids a stale-token window.
- **Reactive refresh** — If the token is already expired, refresh is attempted immediately.
- **401 retry** — The `RefreshingGitHitsService` decorator wraps `GitHitsServiceImpl` and retries once on `AuthenticationError`, calling `forceRefresh()` to handle clock skew or server-side revocation.
- **Shared retry helper** — GitHits REST calls and code navigation GraphQL calls both use the same token-refresh/retry flow, so auth drift is handled consistently across both service families.
- **Concurrent coalescing** — Multiple concurrent refresh requests share a single in-flight Promise.
- **At login** (`src/commands/login.ts`) — Checks if existing token is still valid before starting the OAuth flow. Respects `--force` flag to re-authenticate regardless.
- **At auth status** (`src/commands/auth-status.ts`) — Attempts refresh before reporting "Token expired".
Expand Down Expand Up @@ -158,6 +161,9 @@ The `hasValidToken` flag is checked by `requireAuth()` in `src/commands/mcp.ts`
| `src/container.ts` | Dependency wiring, keychain probe with fallback |
| `src/services/token-manager.ts` | `TokenProvider` interface, `TokenManager` (proactive refresh, coalescing) |
| `src/services/refreshing-githits-service.ts` | `GitHitsService` decorator with token refresh and 401 retry |
| `src/services/execute-with-token-refresh.ts` | Shared helper for token-authenticated retry-on-refresh flows |
| `src/services/code-navigation-capability.ts` | Local JWT capability decoding for `code_navigation` |
| `src/services/code-navigation-service.ts` | GraphQL code navigation client using the shared refresh helper |
| `src/services/auth-service.ts` | OAuth operations (DCR, PKCE, token exchange, callback server) |
| `src/services/auth-storage.ts` | `AuthStorage` interface and file-based implementation |
| `src/services/keyring-service.ts` | `KeyringService` interface wrapping `@napi-rs/keyring` |
Expand Down
34 changes: 31 additions & 3 deletions docs/implementation/cli-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Purpose

The CLI exposes three commands (`search`, `languages`, `feedback`) that mirror the MCP tools for direct human and agent use. These commands share business logic with the MCP tools through the same `GitHitsService` and shared utilities, but format output for terminal consumption instead of MCP tool results.
The CLI exposes three primary commands (`search`, `languages`, `feedback`) that mirror the public MCP tools for direct human and agent use. It also has a capability-gated `code search` command that searches indexed dependency source via the code-navigation backend. These commands share business logic with the MCP tools through the same service interfaces and shared utilities, but format output for terminal consumption instead of MCP tool results.

## Commands

Expand All @@ -12,6 +12,7 @@ The CLI exposes three commands (`search`, `languages`, `feedback`) that mirror t
| `search <query>` | `-l, --lang <language>` | `--license <mode>`, `--explain`, `--json` | Search for code examples |
| `languages [query]` | — | `--json` | List or filter supported languages |
| `feedback <solution_id>` | `--accept` or `--reject` | `-m, --message <text>`, `--json` | Submit feedback on a search result |
| `code search <package> [query]` | package spec | `--keywords`, `--keyword`, `--match-mode`, `--category`, `--kind`, `--file`, `--intent`, `--limit`, `--wait`, `--json` | Search indexed dependency source code |

### `githits init`

Expand Down Expand Up @@ -60,6 +61,31 @@ githits feedback abc123 --accept --message "Solved my problem" --json

`--accept` and `--reject` are mutually exclusive (enforced by Commander's `.conflicts()` API). At least one must be provided (validated in the action function). JSON output is `{ "success": true, "message": "..." }`.

### `githits code search`

```
githits code search npm:express middleware
githits code search npm:express middleware --intent all
githits code search pypi:requests timeout --category callable --limit 10
githits code search crates:serde Serialize --kind trait --limit 5
githits code search npm:@types/node Buffer --file src/ --json
githits code search npm:express --keywords "router,handler" --match-mode and
```

Finds functions, classes, modules, and doc sections inside an indexed dependency by exact-token matches. Unlike `githits search`, which performs natural-language code example search, `code search` is symbol-oriented and returns source chunks with line ranges.

**Package spec.** `<registry>:<name>[@<version>]`. Omit the registry to default to `npm`. Supported registries: `npm`, `pypi`, `hex`, `crates`, `nuget`, `maven`, `zig`, `vcpkg`, `packagist`. Scoped npm names are supported (`npm:@types/node`).

**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.

**Output.** Default terminal output leads each entry with `path:startLine-endLine [kind]`, followed by the symbol name and a 3-line dedented snippet. `--json` emits the shared success/error envelope also produced by the MCP `search_symbols` tool — see [`mcp-cli-parity.md`](./mcp-cli-parity.md) for the wire contract. The command is registered as `code search` with `code search-symbols` as a Commander alias.

**Capability gate.** The `code` group is registered only when the startup token advertises `code_navigation`, when `GITHITS_CODE_NAVIGATION=1` is set for local override, when `GITHITS_API_TOKEN` is present (opaque env token), or when stored auth is expired.

**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.

## Architecture

```
Expand All @@ -80,7 +106,8 @@ Each command follows this pattern:

| Shared Module | Used By |
|---|---|
| `GitHitsService` (via container) | MCP tools + all CLI commands |
| `GitHitsService` (via container) | Public MCP tools + primary CLI commands |
| `CodeNavigationService` (via container) | Capability-gated `search_symbols` MCP tool + `githits code search` CLI command |
| `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 |

Expand Down Expand Up @@ -130,5 +157,6 @@ All commands support two output modes:

## Related Documentation

- `docs/implementation/tools.md` — MCP tools that share business logic with these commands
- [`tools.md`](./tools.md) — MCP tools that share business logic with these commands
- [`mcp-cli-parity.md`](./mcp-cli-parity.md) — rules for dual-surface tools (CLI ↔ MCP)
- `docs/guidelines/ARCHITECTURAL_GUIDELINES.md` — DI and testing patterns
14 changes: 13 additions & 1 deletion docs/implementation/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ GitHits separates its MCP server (which handles OAuth discovery and the MCP prot
|---|---|---|---|
| **MCP URL** | `https://mcp.githits.com` | `GITHITS_MCP_URL` | OAuth discovery (`.well-known`), DCR registration, auth flow |
| **API URL** | `https://api.githits.com` | `GITHITS_API_URL` | REST endpoints (`/search`, `/languages`, `/feedbacks`) |
| **Code navigation URL** | configured per environment | `GITHITS_CODE_NAV_URL` | GraphQL code-navigation endpoint used by `search_symbols` / `githits code search` |

> **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.

Expand All @@ -37,13 +38,22 @@ The container (`src/container.ts`) resolves authentication in priority order:
| `/languages` | Full access | Full access | Blocked |
| `/feedbacks` | Full access | Full access | Blocked |

Code navigation is different from the REST endpoints above:

- the CLI resolves the code-navigation endpoint from `GITHITS_CODE_NAV_URL`; custom GitHits environments must set this explicitly (no default inference)
- MCP registration requires a startup token whose JWT advertises `code_navigation`; opaque env tokens are treated optimistically and left to backend enforcement
- the `githits code search` CLI command is shown when the startup token advertises `code_navigation`, when an opaque `GITHITS_API_TOKEN` is present, or when `GITHITS_CODE_NAVIGATION=1` is set locally
- the backend remains authoritative and can still deny access even if the CLI exposes the local command

## Environment Variables

| Variable | Purpose | Example |
|---|---|---|
| `GITHITS_MCP_URL` | Override MCP server URL | `http://localhost:7071/mcp` |
| `GITHITS_API_URL` | Override REST API URL | `http://localhost:8000` |
| `GITHITS_CODE_NAV_URL` | Override code navigation GraphQL URL | `http://localhost:4000` |
| `GITHITS_API_TOKEN` | API token for authentication | `ghi-abc123...` |
| `GITHITS_CODE_NAVIGATION` | Override capability gate and expose `code` CLI commands locally | `1` |

## Local Storage

Expand All @@ -65,8 +75,10 @@ Environment variables
└─ src/container.ts (createContainer)
├─ mcpUrl → passed to auth commands, used as storage key
├─ apiUrl → passed to GitHitsServiceImpl constructor
├─ codeNavigationUrl → passed to CodeNavigationServiceImpl when configured
├─ apiToken → resolved from env var or OAuth storage
└─ hasValidToken → gates MCP server startup
├─ hasValidToken → gates authenticated commands
└─ codeNavigationCapability / CLI override → gates code navigation exposure
```

Commands receive the full `Dependencies` object. Services receive only what they need (e.g., `GitHitsServiceImpl` gets `apiUrl` and `token`).
Expand Down
149 changes: 149 additions & 0 deletions docs/implementation/mcp-cli-parity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# MCP ↔ CLI Parity

## Purpose

Both the MCP tool (`search_symbols`) and the CLI command (`githits code
search`) expose the code-navigation feature. Users and agents should be
able to cross the surface boundary without learning a new shape —
parameter names differ per surface convention, but defaults, error
behaviour, and the serialised payload do not.

This document is **the pattern and checklist derived from
`search_symbols`**, not a permanent contract for every future
code-navigation tool. When tool #2 lands with a good reason to break a
rule, extend the rule or add a new one here rather than bending tool
#1's shape to fit.

## Rule IDs

Rule IDs are cited from parity tests (e.g.
`src/tools/search-symbols-parity.test.ts`) in file header comments so
the test suite anchors the doc.

### `PARITY-NAMING`

- **MCP arguments** use `snake_case`. They are the wire contract agents
see; the JSON-schema description is the primary UX.
- **CLI flags** use `--kebab-case`. They are the user-facing surface.
- **Public enum values** are lowercase strings on both surfaces
(`production`, `test`, `summary`, `all`).
- **Backend coercion** from lowercase enum values to the uppercase
GraphQL enum variants lives in `src/shared/code-navigation.ts`
(`toSearchSymbolsFileIntent`, `toSearchSymbolsKind`,
`toSearchSymbolsMatchMode`).

### `PARITY-DEFAULTS`

- Both surfaces import defaults from
`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.
- 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
surfaces apply defaults at the same point and under the same
conditions.

### `PARITY-REQUEST`

- Request construction for a dual-surface tool routes through a single
shared helper (`buildSearchSymbolsParams`). The helper:
1. Fills in defaults,
2. Translates user-facing sentinel values (e.g. `FILE_INTENT_ALL`)
into their wire-level equivalents,
3. Returns a `defaulted` array naming the fields that were
client-applied, which feeds the response `query.defaulted` echo.
- Cross-tool helpers (error classification, target resolution) live in
`src/shared/` without a tool-name prefix. Per-tool request builders
live in `src/shared/<tool>-request.ts`.

### `PARITY-JSON-KEYS`

- The CLI `--json` payload and the MCP tool text payload, parsed as
JSON, must `deepEqual` for equivalent inputs.
- String-equal is explicitly not the contract. Key ordering,
whitespace, and trailing newlines are free.
- **No leading-underscore keys.** `warning`, `hint`, and all other
status fields are plain.
- `query` echoes the resolved request parameters. `query.defaulted` is
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.
- `returnedCount` is an explicit echo of `results.length`.
`totalMatches` is the backend-provided total (equal to
`returnedCount` today; see backend request B2 for the future
upgrade).

### `PARITY-ERROR-ENVELOPE`

- Every error result, on both surfaces, carries
`{ error: string, code: MappedErrorCode, details?: object }`.
- `code` is mandatory. `UNKNOWN` is a last resort — every named error
class in the code-navigation stack maps to a specific code. The
classifier is tested by table in
`src/shared/code-navigation-error-map.test.ts`; that test is the
enforcement mechanism, not a convention.
- MCP error text is always valid JSON. A client that parses
`content[0].text` gets the same envelope whether the result is
success or error.

### `PARITY-NO-SHARED-TERMINAL-FORMATTER`

- Terminal rendering is CLI-local.
- MCP emits JSON text only; there is no equivalent pretty-print.
- Small semantic helpers that happen to be reused (e.g. a zero-result
message template) may move into `src/shared/` once two tools need
them. Default: keep formatting surface-local.

## Checklist for adding a new dual-surface tool

When a new tool lands with both MCP and CLI surfaces:

- [ ] Tool-specific defaults added to
`src/shared/code-navigation-defaults.ts` with a `TOOLNAME_` prefix.
- [ ] Request builder at `src/shared/<tool>-request.ts`. Both surfaces
import it.
- [ ] Error classifier reused (`mapCodeNavigationError`). Add new
`MappedErrorCode` variants only when a genuinely new error class
exists; cover the new branch in the table test.
- [ ] Response builder at `src/shared/<tool>-response.ts` emitting the
shared success and error envelopes. JSON shape matches
`PARITY-JSON-KEYS` rules.
- [ ] Parity test at `src/tools/<tool>-parity.test.ts` that cites the
rule IDs it enforces (in a file header comment). Covers at minimum:
successful search, zero-result, two error codes.
- [ ] MCP tool description mirrored in the backend MCP server before
public release (coordination point — the CLI may lead; the backend
PR URL is recorded in the plan doc before the frontend PR merges).

## Non-goals

- **Shared terminal formatter.** CLI terminal output and MCP JSON are
different products. A shared prose-rendering layer is premature
until at least two tools prove the shape is common.
- **Shared MCP description copy.** Each tool's description targets a
different decision the agent is making. Copy is not reusable.

## When to extend this document

- A new rule ID is added when a future tool exposes a pattern that is
genuinely cross-tool (naming, shared helper location, JSON shape).
- An existing rule is revised when the tool that originally
established it (`search_symbols`) turns out to be the outlier.
- The checklist grows by exactly one bullet per rule. Keep it short.

## Related files

| File | Role |
|---|---|
| `src/shared/code-navigation-defaults.ts` | Canonical defaults and sentinels. |
| `src/shared/code-navigation-error-map.ts` | `mapCodeNavigationError` classifier and `MappedError` union. |
| `src/shared/search-symbols-request.ts` | Shared request builder for `search_symbols`. |
| `src/shared/search-symbols-response.ts` | Shared JSON envelope builders for `search_symbols`. |
| `src/tools/search-symbols.ts` | MCP tool definition. |
| `src/commands/code/search-symbols.ts` | CLI command. |
| `src/tools/search-symbols-parity.test.ts` | Parity tests (cite rule IDs). |
Loading
Loading