Skip to content
6 changes: 6 additions & 0 deletions changes/stable-hosted-documentation-followups.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"githits": patch
"@githits/mcp": patch
---

- **Keep hosted documentation follow-ups stable across publications** - Automatic search follow-ups now reuse mutable hosted page URLs or exact emitted fragments without stale search line bounds, while repository-document snapshots and explicit read ranges remain unchanged.
10 changes: 5 additions & 5 deletions docs/implementation/cli-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ Unified search spans indexed dependency and repository code, docs, and explicit

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 atomic across runnable target/source pairs: callers must explicitly opt into a serveable subset, while any unflagged interim evidence still covers every runnable pair and carries its `searchRef` and freshness signals.

**Output.** CLI human output and MCP `text-v1` use one shared outcome-first formatter. The headline combines result count/type breakdown, active or terminal lifecycle, aggregate readiness, and pagination when applicable. Ordinary completed current results collapse to one `Sources: <target> - <sources>` row: code and symbols use compact lane names, while documentation contributors retain canonical `site:<host[/path]>` and `github:<owner>/<repo>@<revision>` locators. A source identical to its standalone target is written once; a sole pinned repository source replaces its less-specific ref-less repository target, while an already-pinned target remains beside its resolved commit. Compact repository provenance requires both the repository URL and commit. Documentation without concrete provenance stays in detailed target-state form. Any stale, provisional, coverage, constraint, alternative, suggestion, terminal, or other trust fact keeps every requested target in the same detailed list. Each target identity is followed by deterministic `using`, `searched`, `indexing`, terminal/unavailable, `available`, `indexed`, and constraint segments as applicable. Detailed lanes are `code`, `symbols`, `repository docs`, concrete site docs, and docs. Hits remain a separate numbered ranked evidence list with their follow-up locators: `[1] npm:express@5.2.1 History.md:169-179 [repo doc] - 5.0.0-alpha.4 / 2017-03-01` or `[2] https://expressjs.com/en/4x/api/router/#routerroute [docs page] npm:express - router.route()`. Proven repository snippets use their matched range in the header; path-only hits show an actionable file header without arbitrary chunk coordinates. Explicit symbol hits preserve qualified identity, signature detail, kind, and any differing definition range. A differing indexed range without a definition is labelled as a chunk; equal ranges are printed once. Documentation headers promote an exact indexed `docsReadTarget#fragment` as the read target, otherwise prefer the emitted `docsReadTarget`, and fall back to stable `pageId` only when discovery omits the target; formatter-authored punctuation is ASCII and Unicode in backend payloads passes through unchanged. Executable read command lines and qualified internal IDs stay omitted from default text. Active empty output is `No results yet | indexing | 0/1 ready`; no-snapshot output is `No result snapshot yet | indexing | 0/1 ready`, with the corresponding lower-case lifecycle for other active states. Terminal no-snapshot output is `No result snapshot | failed | 0/1 ready`, and completed output omits lifecycle/readiness. Query-wide warnings appear once after target rows and before hits. There is no separate session row: at most one `Next:` line follows the hit list, and an active `searchRef` appears exactly once there. CLI uses `Next: githits search-status <ref> --wait <seconds>`; MCP uses its own `search_status` syntax. Active follow-up waits use the largest indexing upper bound plus ten seconds, rounded upward to ten seconds and capped at 120. Missing ranges use the 30-second default as a floor; no ranges keep 30 seconds. The examples below show that default. Completed evidence-status retrieval keeps the default. These are advisory total indexing durations, not remaining time or a search ETA; JSON progress preserves the full `indexingEstimates` evidence. See [discovery indexing estimates](tools.md) for the contract and production backend rollout prerequisite. CLI enables ANSI emphasis when supported, but removing ANSI leaves the same hierarchy and wording apart from surface-native actions; line breaks can differ because CLI uses terminal width while MCP defaults to 80 columns. `--json` emits the shared stable success/error envelope used by MCP `search`, including the full initial `query` echo and exact result-bearing `partialResults` Boolean. Repository hit locators preserve legacy target-relative evidence coordinates plus `commitSha`, `repositoryFilePath`, `evidenceRange`, `indexedRange`, and relation-aware `symbolContext`; the preferred `followUp` uses semantic `preferredRead` when available, otherwise the proven definition or focused evidence at the exact served repository snapshot. JSON remains lossless while text is optimized for agent decisions.
**Output.** CLI human output and MCP `text-v1` use one shared outcome-first formatter. The headline combines result count/type breakdown, active or terminal lifecycle, aggregate readiness, and pagination when applicable. Ordinary completed current results collapse to one `Sources: <target> - <sources>` row: code and symbols use compact lane names, while documentation contributors retain canonical `site:<host[/path]>` and `github:<owner>/<repo>@<revision>` locators. A source identical to its standalone target is written once; a sole pinned repository source replaces its less-specific ref-less repository target, while an already-pinned target remains beside its resolved commit. Compact repository provenance requires both the repository URL and commit. Documentation without concrete provenance stays in detailed target-state form. Any stale, provisional, coverage, constraint, alternative, suggestion, terminal, or other trust fact keeps every requested target in the same detailed list. Each target identity is followed by deterministic `using`, `searched`, `indexing`, terminal/unavailable, `available`, `indexed`, and constraint segments as applicable. Detailed lanes are `code`, `symbols`, `repository docs`, concrete site docs, and docs. Hits remain a separate numbered ranked evidence list with their follow-up locators: `[1] npm:express@5.2.1 History.md:169-179 [repo doc] - 5.0.0-alpha.4 / 2017-03-01` or `[2] https://expressjs.com/en/4x/api/router/#routerroute [docs page] npm:express - router.route()`. Proven repository snippets use their matched range in the header; path-only hits show an actionable file header without arbitrary chunk coordinates. Explicit symbol hits preserve qualified identity, signature detail, kind, and any differing definition range. A differing indexed range without a definition is labelled as a chunk; equal ranges are printed once. Documentation headers promote an exact indexed `docsReadTarget#fragment` as the read target, otherwise prefer the emitted `docsReadTarget`, and fall back to compatible `pageId` only when discovery omits the target; formatter-authored punctuation is ASCII and Unicode in backend payloads passes through unchanged. Executable read command lines and qualified internal IDs stay omitted from default text. Active empty output is `No results yet | indexing | 0/1 ready`; no-snapshot output is `No result snapshot yet | indexing | 0/1 ready`, with the corresponding lower-case lifecycle for other active states. Terminal no-snapshot output is `No result snapshot | failed | 0/1 ready`, and completed output omits lifecycle/readiness. Query-wide warnings appear once after target rows and before hits. There is no separate session row: at most one `Next:` line follows the hit list, and an active `searchRef` appears exactly once there. CLI uses `Next: githits search-status <ref> --wait <seconds>`; MCP uses its own `search_status` syntax. Active follow-up waits use the largest indexing upper bound plus ten seconds, rounded upward to ten seconds and capped at 120. Missing ranges use the 30-second default as a floor; no ranges keep 30 seconds. The examples below show that default. Completed evidence-status retrieval keeps the default. These are advisory total indexing durations, not remaining time or a search ETA; JSON progress preserves the full `indexingEstimates` evidence. See [discovery indexing estimates](tools.md) for the contract and production backend rollout prerequisite. CLI enables ANSI emphasis when supported, but removing ANSI leaves the same hierarchy and wording apart from surface-native actions; line breaks can differ because CLI uses terminal width while MCP defaults to 80 columns. `--json` emits the shared stable success/error envelope used by MCP `search`, including the full initial `query` echo and exact result-bearing `partialResults` Boolean. Repository hit locators preserve legacy target-relative evidence coordinates plus `commitSha`, `repositoryFilePath`, `evidenceRange`, `indexedRange`, and relation-aware `symbolContext`; the preferred `followUp` uses semantic `preferredRead` when available, otherwise the proven definition or focused evidence at the exact served repository snapshot. JSON remains lossless while text is optimized for agent decisions.

Repository code/docs display proven numbered `matchedSource` and enclosing scope
metadata, without routine authority captions. A file-path-only hit without matched
Expand Down Expand Up @@ -773,7 +773,7 @@ githits docs list npm:express --limit 20
githits docs list npm:express --json
```

Lists hosted/crawled and repository-backed documentation pages for a package. Each row includes the stable page ID, a source badge, any distinct provenance, and a shell-quoted `read` command using the emitted `docsReadTarget`. Active crawled pages therefore use their publisher HTTP(S) URL, while retired crawled and snapshot-pinned repository pages use stable IDs. JSON retains all three locator roles and includes repo URL / git ref / file path for repository-backed docs so callers can follow up with `code read` when source context is needed.
Lists hosted/crawled and repository-backed documentation pages for a package. Each row includes the compatible page ID, a source badge, any distinct provenance, and a shell-quoted `read` command using the emitted `docsReadTarget`. Hosted/crawled HTTP(S) targets address mutable current content; no docpack or page-row identifier is a stable snapshot address. Repository targets remain snapshot-addressed. JSON retains all three locator roles and includes repo URL / git ref / file path for repository-backed docs so callers can follow up with `code read` when source context is needed.

The response also retains the backend's exact `codeIndexState`. `PENDING` and `INDEXING` empty results are rendered as preparation still in progress with a replayable `docs list` action, never as “No documentation pages found.” `PROVISIONAL` results keep and render every available page while clearly marking that indexing continues. CLI `--json` and MCP `format: "json"` share the same lifecycle-bearing envelope.

Expand All @@ -796,11 +796,11 @@ githits docs read <docs-read-target> --verbose
githits docs read <docs-read-target> --json
```

Reads a documentation page returned by `docs list` or search results. Use a sufficient search snippet directly; otherwise run its generated follow-up. From text, pass the displayed `[docs page]` target unchanged; from `docs list`, pass `docsReadTarget`. Search text promotes an exact indexed fragment to that displayed target. Historical crawled IDs and snapshot-pinned repository IDs remain compatible. URL reads resolve only active existing content and never enqueue crawling. Default output is content-only for easy piping; `--verbose` adds a metadata header.
Reads a documentation page returned by `docs list` or search results. Use a sufficient search snippet directly; otherwise run its generated follow-up. From text, pass the displayed `[docs page]` target unchanged; from `docs list`, pass `docsReadTarget`. Hosted/crawled HTTP(S) targets address mutable current content. Automatic search follow-ups therefore forward the exact emitted page URL or fragment without the search hit's display/evidence line coordinates. Search text promotes only an exact emitted fragment; it never derives an anchor. Snapshot-pinned repository documentation keeps its target and ranges. Historical crawled IDs remain compatible. URL reads resolve only active existing content and never enqueue crawling. Default output is content-only for easy piping; `--verbose` adds a metadata header.

**Fragments and line ranges.** An HTTP(S) fragment needs no explicit range and resolves exactly one indexed section in the backend. `--lines 10-40`, `--lines 10-`, and `--lines -40` are supported and forward only their written bounds; either bound replaces fragment resolution with a page-relative range. The backend defaults omitted bounds, clamps an end beyond EOF, and rejects invalid starts/ranges. The CLI never strips or normalizes opaque IDs and does not infer publisher anchor slugs.
**Fragments and line ranges.** An HTTP(S) fragment needs no explicit range and resolves the heading plus its full subtree through the next equal-or-higher heading. `--lines 10-40`, `--lines 10-`, and `--lines -40` are supported and forward only their written bounds; either caller-supplied bound intentionally replaces fragment resolution with a page-relative range. The backend defaults omitted bounds, clamps an end beyond EOF, and rejects invalid starts/ranges. The CLI never strips or normalizes opaque IDs and does not infer publisher anchor slugs.

**Output envelope.** `{docsReadTarget, pageId, title?, sourceKind?, sourceUrl?, repoUrl?, gitRef?, filePath?, totalLines, startLine?, endLine?, anchor?, content}`. `pageId` remains the stable replay pointer and `sourceUrl` remains provenance. The range is the actual returned absolute page range, `totalLines` is the whole stored page extent including a trailing empty line, and an empty page has no bounds. `anchor` identifies a successfully resolved indexed section. Verbose text prints each distinct locator once plus range/anchor metadata. Repo-backed docs include exact source metadata for `code read` follow-up.
**Output envelope.** `{docsReadTarget, pageId, title?, sourceKind?, sourceUrl?, repoUrl?, gitRef?, filePath?, totalLines, startLine?, endLine?, anchor?, content}`. `pageId` remains a compatible locator and `sourceUrl` remains provenance. Hosted/crawled locators resolve mutable current content rather than a snapshot; repository targets remain snapshot-addressed. The range is the actual returned absolute page range, `totalLines` is the whole stored page extent including a trailing empty line, and an empty page has no bounds. `anchor` identifies a successfully resolved indexed section. Verbose text prints each distinct locator once plus range/anchor metadata. Repo-backed docs include exact source metadata for `code read` follow-up.

`DOCUMENTATION_SECTION_UNRESOLVED` is a non-retryable section error with reason `not_found`, `ambiguous`, `inexact_range`, or `unsupported_format`; page absence remains non-retryable `NOT_FOUND`. This deprecated alias remains on the legacy `getDocPage` root. Compact `githits read <docs-target>` uses `ReadService` and the backend `Query.read` union, which requires both `CodeContextResult` and `GetDocPageResult` branches plus the selected minimum fields, including `contentRange`. Roll out that backend schema first; compact reads have no old-schema fallback.

Expand Down
5 changes: 3 additions & 2 deletions docs/implementation/mcp-cli-parity.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,11 +388,12 @@ MCP text apart from those supplied command dialects; line breaks can differ
because CLI uses the terminal width while MCP uses the 80-column default.

Documentation discovery and list envelopes retain three distinct locator roles:
preferred `docsReadTarget`, stable replay `pageId`, and provenance `sourceUrl`.
preferred `docsReadTarget`, compatible `pageId`, and provenance `sourceUrl`.
Text and generated read follow-ups prefer `docsReadTarget` and fall back to
`pageId` only for discovery results where the target is absent. The compatible
MCP argument is `target`; both MCP and CLI pass URL or ID values through
unchanged and return the same ranged content.
unchanged and return the same ranged content. Hosted HTTP(S) locators address
mutable current content; repository locators remain snapshot-addressed.

CLI `--json` output and MCP `format: "json"` output remain the structured parity
boundary: every
Expand Down
Loading
Loading