New doc-sync gate verify-export-jsdoc walks every module-level exported
name under packages/*/*/src and requires description prose everywhere,
plus @param per parameter and @returns on non-void annotated returns for
function-like exports, public class methods, properties, and accessors.
The parsing + check helpers move out of gen-cordis-catalog.ts into a
shared scripts/jsdoc.ts so 'documented' means one thing on both gated
surfaces.
Deliberate exemptions (documented in the RFC): heritage-declared class
members (the seam declaration is the doc's one home — the one checker
query in an otherwise pure-AST walk), cordis plugin-protocol slots
(name/inject/reusable/Config/apply, top-level and static), constructors,
overload implementations, declare-module augmentation bodies, and
re-export statements (checked at the defining module).
The 203 under-documented exports the gate found at adoption are filled
in this change, so the gate lands green; generated catalogs/graphs are
regenerated for the shifted line pointers.
RFC: docs/rfc/implemented/process/2026-07-06-export-surface-jsdoc-gate.md
The Exa and Perplexity providers hard-coded request parameters that
deployments should control while defaults are still unsettled. Exa gains
searchType, numResults, and highlightsPerResult; Perplexity gains
maxTokens (it previously sent none) and an optional searchRecency. Each
follows the deepseek provider's shape: a defaulted Config field, a
DEFAULT_* constant, and a positive-integer status() check for numeric
limits. The call-level maxResults still flows through WebSearchRequest
and wins over the configured default, keeping the seam layering intact.
Addresses tianyicui's "make everything configurable" review comment.
The `if (error instanceof WebError) throw error` guard is dead code in the
exa and perplexity providers: their mappers (mapExaResponse /
mapPerplexityResponse) never throw a WebError — a wrong-shape body throws a
TypeError, which the catch correctly translates to WEB_PROVIDER_ERROR. The
guard was added for symmetry with the deepseek provider, whose mapper DOES
throw a WebError in strict mode (no web_search_tool_result block), so it
keeps the rethrow. The unreachable lines tripped the per-file 100% coverage
gate.
- search providers (exa/perplexity/deepseek): map the parsed response
INSIDE the parse try, so a well-formed body of the wrong shape surfaces
as WEB_PROVIDER_ERROR instead of escaping as a raw TypeError; a WebError
the mapper throws on purpose is re-thrown untouched
- web-fetch-local: validate numeric limits at plugin construction (positive
finite caps; non-negative integer maxRedirects) rather than constructing a
provider with nonsensical values
- web-fetch-local: enforce the redirect budget BEFORE resolving each hop, so
maxRedirects:N follows exactly N redirects and an over-limit hop reports
"exceeded the maximum" rather than misdiagnosing a cross-origin block
- drop the stale dsh-tool-web/search and /fetch path aliases (the package no
longer declares those subpath exports)
- strip trailing EOF blank lines flagged by git diff --check
Each fix carries a regression test.
- Preserve abort errors while parsing search responses: when the caller's
AbortSignal fires after headers but during response.json() (both the success
and HTTP-error body parses), surface WEB_ABORTED instead of wrapping it as
WEB_PROVIDER_ERROR, so agent cancel/dispose is not misreported as a provider
failure. Applied to both the Exa and Perplexity providers.
- Report a malformed baseURL as misconfigured in status() (URL.canParse), so
selection diagnostics and execution agree (configured-unavailable up front
rather than a late WEB_PROVIDER_ERROR). WebProviderStatus already had the
reason.
Introduce web access as a first-class capability seam so the model-facing
web tools stay stable while backends change. dsh-web owns ctx.web as a
provider registry with registration-order-independent selection and the
WebError taxonomy; dsh-web-search-exa, dsh-web-search-perplexity, and
dsh-web-fetch-local register capabilities into it; dsh-tool-web is the sole
owner of the model-facing web_search/web_fetch schemas, prompt sections, and
HTML-to-markdown presentation. Search and fetch are deliberately one seam.
Providers ship as namespace plugins that register into ctx.web (like an
LlmAdapter into ctx.llm), not key-owning services, since multiple search
providers cannot each own the key. Tool registration follows product
enablement, not backend availability, so load order/credentials never enter
the model contract; the seam resolves the provider at execution time and
surfaces a structured WebError otherwise.
Moves the RFC to implemented/ amended to match what shipped. Example/app
configs are intentionally not wired yet (RFC migration step 6).