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
+6
View File
@@ -40,6 +40,10 @@ export type FsDiffMeta = { diffs: FileDiff[] }
* (a pure insertion) reports `oldText: null` (nothing to diff against), mirroring
* the call-time card's new-file convention. The unified-diff "\ No newline at end
* of file" markers are dropped — they annotate the patch, not file content.
* @param path - the path stamped on every produced diff (the model-facing `file_path`; the bridge relativizes it).
* @param before - the file text before the change (the backend's LF-normalized diff basis).
* @param after - the file text after the change, on the same basis.
* @returns one diff per applied hunk, in file order; empty when the texts are identical.
*/
export function computeHunkDiffs(path: string, before: string, after: string): FileDiff[] {
const patch = structuredPatch('', '', before, after, undefined, undefined, { context: DIFF_CONTEXT })
@@ -83,6 +87,8 @@ function isFileDiff(value: unknown): value is FileDiff {
* it validates defensively rather than trusting the payload — a bad `meta` yields
* `undefined`, and the caller decides the fallback (edit → the generic result
* rendering; write → an args-derived whole-file diff), never a thrown presenter.
* @param meta - the opaque `tool/result` meta payload (live or replayed from the session log).
* @returns the validated non-empty hunk list, or undefined for an absent/empty/malformed payload.
*/
export function diffsFromMeta(meta: unknown): FileDiff[] | undefined {
if (typeof meta !== 'object' || meta === null || Array.isArray(meta)) return undefined
+17 -3
View File
@@ -29,7 +29,13 @@ interface EditInput {
replaceAll: boolean
}
/** Validate value constraints the schema DSL can't express. */
/**
* Validate value constraints the schema DSL can't express: a non-blank
* `file_path`, a non-empty `old_string`, and `old_string !== new_string`
* (an equal pair would be a guaranteed no-op edit).
* @param args - the schema-validated raw tool arguments.
* @returns the camelCased input with `replace_all` defaulted to false.
*/
export function parseEditArgs(args: { file_path: string; old_string: string; new_string: string; replace_all?: boolean }): EditInput {
if (args.file_path.trim().length === 0) throw new Error('file_path must be a non-empty string')
if (args.old_string.length === 0) throw new Error('old_string must be a non-empty string')
@@ -42,14 +48,22 @@ export function parseEditArgs(args: { file_path: string; old_string: string; new
}
}
/** Format an edit success (single-match or replace-all) as a Claude-style model-facing message. */
/**
* Format an edit success (single-match or replace-all) as a Claude-style model-facing message.
* @param displayPath - the backend-resolved path shown to the model.
* @param replaceAll - selects the all-occurrences wording over the single-replacement one.
* @returns the confirmation sentence the model sees as the tool result.
*/
export function formatEditOutput(displayPath: string, replaceAll: boolean): string {
return replaceAll
? `The file ${displayPath} has been updated. All occurrences were successfully replaced.`
: `The file ${displayPath} has been updated successfully.`
}
/** Register the `edit` tool and its system-prompt guidance. */
/**
* Register the `edit` tool and its system-prompt guidance.
* @param ctx - the plugin context; registrations are effects scoped to it, and execution uses its `fs` service.
*/
export function applyEditTool(ctx: Context): void {
ctx.systemPrompt.section({
name: 'tool:edit',
+10 -1
View File
@@ -119,6 +119,10 @@ function finish(acc: WindowAccumulator, request: ReadWindow, displayPath: string
* path serves both. Scans for newlines with a capped line buffer (a newline-free
* giant line is truncated, never buffered past `request.maxLineLength`),
* enforces the byte cap, and throws `FS_NOT_FOUND` for an offset past EOF.
* @param chunks - decoded text chunks in file order; chunk boundaries carry no meaning.
* @param request - the resolved window; the caller has already applied its defaults and caps.
* @param displayPath - the caller-facing path used in the offset-out-of-range error.
* @returns the numbered window lines, the total line count seen, and the byte-cap truncation flag.
*/
export async function buildWindow(
chunks: AsyncIterable<string> | Iterable<string>,
@@ -156,7 +160,12 @@ export async function buildWindow(
return finish(acc, request, displayPath)
}
/** Format a read outcome as one OpenCode-style line-numbered text block body. */
/**
* Format a read outcome as one OpenCode-style line-numbered text block body.
* @param displayPath - the backend-resolved path rendered in the envelope's `<path>` element.
* @param outcome - the windowed read to render.
* @returns the model-facing envelope: numbered lines plus a continuation or end-of-file footer.
*/
export function formatReadOutput(displayPath: string, outcome: FileReadOutcome): string {
const endLine = outcome.lines.at(-1)?.number ?? Math.max(0, outcome.offset - 1)
let footer: string
+11 -2
View File
@@ -58,7 +58,12 @@ function parsePositiveInteger(value: number, name: string): number {
return value
}
/** Validate value constraints the schema DSL can't express. `maxLimit` is the deployment's line cap. */
/**
* Validate value constraints the schema DSL can't express. `maxLimit` is the deployment's line cap.
* @param args - the schema-validated raw tool arguments; `offset`/`limit` must be positive integers when given.
* @param maxLimit - the configured line cap: both the default `limit` and the largest one accepted.
* @returns the validated input with `offset` defaulted to 1 and `limit` to `maxLimit`.
*/
export function parseReadArgs(args: { file_path: string; offset?: number; limit?: number }, maxLimit: number): ReadInput {
if (args.file_path.trim().length === 0) throw new Error('file_path must be a non-empty string')
const offset = args.offset === undefined ? 1 : parsePositiveInteger(args.offset, 'offset')
@@ -67,7 +72,11 @@ export function parseReadArgs(args: { file_path: string; offset?: number; limit?
return { filePath: args.file_path, offset, limit }
}
/** Register the `read` tool and its system-prompt guidance. */
/**
* Register the `read` tool and its system-prompt guidance.
* @param ctx - the plugin context; registrations are effects scoped to it, and execution uses its `fs` service.
* @param caps - the deployment's resolved read caps (plugin config after defaulting).
*/
export function applyReadTool(ctx: Context, caps: ReadToolCaps): void {
ctx.systemPrompt.section({
name: 'tool:read',
+5 -1
View File
@@ -18,7 +18,11 @@
import type { ToolExecution } from '@deepseek-ai/dsh-tools'
/** The session workspace cwd for this call, or `undefined` when none applies. */
/**
* The session workspace cwd for this call, or `undefined` when none applies.
* @param exec - the tool-execution context; only its optional `agent` is read.
* @returns the calling agent's session cwd, or undefined for a non-agent caller (the backend then applies its own default).
*/
export function sessionCwd(exec: ToolExecution): string | undefined {
return exec.agent?.session.header.cwd
}
+16 -3
View File
@@ -21,13 +21,23 @@ import type {} from '@deepseek-ai/dsh-system-prompt'
import { computeHunkDiffs, diffsFromMeta, type FsDiffMeta } from './diff.ts'
import { sessionCwd } from './session-cwd.ts'
/** Validate value constraints the schema DSL can't express. */
/**
* Validate value constraints the schema DSL can't express: only a non-blank
* `file_path` — an empty `content` is legitimate (it writes an empty file).
* @param args - the schema-validated raw tool arguments.
* @returns the camelCased input; `content` passes through untouched.
*/
export function parseWriteArgs(args: { file_path: string; content: string }): { filePath: string; content: string } {
if (args.file_path.trim().length === 0) throw new Error('file_path must be a non-empty string')
return { filePath: args.file_path, content: args.content }
}
/** Format a write outcome as one model-facing text block body. */
/**
* Format a write outcome as one model-facing text block body.
* @param displayPath - the backend-resolved path rendered in the envelope's `<path>` element.
* @param outcome - the write outcome; its `operation` selects the Created/Updated wording.
* @returns the model-facing confirmation envelope (no file content is echoed back).
*/
export function formatWriteOutput(displayPath: string, outcome: FsWriteOutcome): string {
const verb = outcome.operation === 'create' ? 'Created' : 'Updated'
return `<path>${displayPath}</path>
@@ -37,7 +47,10 @@ ${verb} file
</content>`
}
/** Register the `write` tool and its system-prompt guidance. */
/**
* Register the `write` tool and its system-prompt guidance.
* @param ctx - the plugin context; registrations are effects scoped to it, and execution uses its `fs` service.
*/
export function applyWriteTool(ctx: Context): void {
ctx.systemPrompt.section({
name: 'tool:write',