Gate JSDoc completeness on every package export

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
This commit is contained in:
Tianyi Cui
2026-07-06 22:09:30 +08:00
parent 1c999804d8
commit cd9737d569
92 changed files with 1802 additions and 289 deletions

View File

@@ -40,6 +40,8 @@ export class BlockAssembler {
/**
* Feed one chunk. Returns the completed block when the chunk closes one
* (an explicit `block-end`), otherwise undefined.
* @param chunk - the next raw chunk, in stream order.
* @returns the authoritative block from the first `block-end` at its index; undefined for every other chunk.
*/
push(chunk: StreamChunk): ContentBlock | undefined {
switch (chunk.type) {
@@ -123,20 +125,29 @@ export class BlockAssembler {
return partial
}
/** Assemble all blocks seen so far, in stream order. */
/**
* Assemble all blocks seen so far, in stream order.
* @returns one block per seen index; an open block assembles from its
* accumulated deltas (an unknown block type never closed by `block-end` throws).
*/
blocks(): ContentBlock[] {
return this.order.map(index => this.assemble(this.mustGet(index), index))
}
/** Usage from the `usage` chunk; undefined until one arrives. */
get usage(): TokenUsage | undefined {
return this._usage
}
/** Finish reason from the `finish` chunk; `{kind: 'stop'}` when the stream ended without one. */
get finish(): FinishReason {
return this._finish ?? { kind: 'stop' }
}
/** The assembled assistant message. */
/**
* The assembled assistant message.
* @returns an assistant-role message over `blocks()` (same open-block assembly rules).
*/
message(): Message {
return { role: 'assistant', content: this.blocks() }
}

View File

@@ -54,6 +54,8 @@ export const APP_IDENTITY: AppIdentity = {
* The standard `User-Agent` value: `product/version (+url)`. The
* parenthesized `+url` comment is the conventional self-identification form
* (RFC 9110 §10.1.5 product + comment syntax).
* @param identity - the identity to render; defaults to {@link APP_IDENTITY}.
* @returns the ready-to-send header value.
*/
export function userAgent(identity: AppIdentity = APP_IDENTITY): string {
return `${identity.product}/${identity.version} (+${identity.url})`
@@ -63,6 +65,8 @@ export function userAgent(identity: AppIdentity = APP_IDENTITY): string {
* Build the attribution headers an adapter must send on every provider
* request. Header names are lowercase (HTTP field names are case-insensitive
* on the wire).
* @param identity - the identity to send; defaults to {@link APP_IDENTITY} — omission cannot suppress attribution.
* @returns headers to merge into the provider request (currently just `user-agent`).
*/
export function attributionHeaders(
identity: AppIdentity = APP_IDENTITY,

View File

@@ -17,7 +17,11 @@ import type { Branded } from '@deepseek-ai/dsh-brand'
*/
export type CallId = Branded<'CallId'>
/** Brand a string as a {@link CallId}. */
/**
* Brand a string as a {@link CallId}.
* @param id - the provider-issued (or synthesized) call id.
* @returns the same string, branded; no validation is performed.
*/
export function CallId(id: string): CallId {
return id as CallId
}

View File

@@ -18,6 +18,7 @@
* `ErrorOptions`. `name` defaults to the subclass constructor name.
*/
export class HarnessError extends Error {
/** Stable machine-routable failure class (e.g. `RATE_LIMIT`); route on this, never by parsing `message`. */
readonly code: string
constructor(message: string, code: string, options?: ErrorOptions) {
@@ -27,7 +28,11 @@ export class HarnessError extends Error {
}
}
/** Narrow an arbitrary thrown value to a HarnessError (for `instanceof` at seams). */
/**
* Narrow an arbitrary thrown value to a HarnessError (for `instanceof` at seams).
* @param value - the caught value (`unknown` in catch clauses).
* @returns true only for real instances; duck-typed or cross-realm errors do not narrow.
*/
export function isHarnessError(value: unknown): value is HarnessError {
return value instanceof HarnessError
}

View File

@@ -73,7 +73,11 @@ export class LlmError extends HarnessError {
* same value to the wire.
*/
export abstract class LlmAdapter {
/** Stream one model call as raw chunks. The only required method. */
/**
* Stream one model call as raw chunks. The only required method.
* @param options - the fully-assembled request; implementations must honor `options.signal`.
* @returns the chunk stream, obeying the adapter contract documented on `StreamChunk`.
*/
abstract stream(options: GenerateOptions): AsyncIterable<StreamChunk>
}

View File

@@ -26,6 +26,9 @@
* variant was added without updating the switch (compile error at the call
* site — the desired outcome) or a value escaped its type (runtime throw
* with diagnostics — the safety net).
* @param value - the impossible value; typed `never` so an unhandled variant fails compilation at the call site.
* @param context - optional label (e.g. the switch site) prefixed into the throw message.
* @returns never — it always throws, with the offending value JSON-rendered in the message.
*/
export function assertNever(value: never, context?: string): never {
// JSON.stringify is typed string but returns undefined for undefined input;

View File

@@ -70,7 +70,9 @@ export interface ContentBlockMap {
'tool-result': ToolResultBlock
}
/** The block `type` tag vocabulary; widens as plugins merge new shapes into {@link ContentBlockMap}. */
export type ContentBlockType = keyof ContentBlockMap
/** Any known content block, derived from {@link ContentBlockMap}; switch on `type` and fall through unknowns (merge-extensible). */
export type ContentBlock = ContentBlockMap[ContentBlockType]
/** A single message in a conversation history. */
@@ -88,6 +90,7 @@ export interface MessageSourceMap {
plugin: { kind: 'plugin'; plugin: string }
}
/** Any known message source, derived from {@link MessageSourceMap}; switch on `kind` and fall through unknowns (merge-extensible). */
export type MessageSource = MessageSourceMap[keyof MessageSourceMap]
/**
@@ -102,6 +105,7 @@ export interface FinishReasonMap {
'error': { kind: 'error'; message: string; code?: string }
}
/** Any known finish reason, derived from {@link FinishReasonMap}; switch on `kind` and fall through unknowns (merge-extensible). */
export type FinishReason = FinishReasonMap[keyof FinishReasonMap]
/**