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

@@ -75,6 +75,12 @@ function permissionDecisionOf(value: string | undefined): HookOutput['decision']
* (`decision`/`reason`/`continue`/`stopReason`/`systemMessage`)
* are unaffected. Omit `expectedEventName` (or pass a matching one) to apply the
* block as-is — a caller that doesn't key by event opts out of the check.
*
* @param exitCode - the process exit code; `undefined` when the hook could not be spawned at all.
* @param stdout - the captured stdout stream; consulted for structured JSON only on a 0 exit.
* @param stderr - the captured stderr stream; becomes the blocking `reason` on exit 2.
* @param expectedEventName - the event the hook is firing for; omit to apply a `hookSpecificOutput` block as-is.
* @returns the dialect-neutral decoded outcome.
*/
export function parseHookOutput(exitCode: number | undefined, stdout: string, stderr: string, expectedEventName?: string): HookOutput {
const trimmedErr = stderr.trim()

View File

@@ -66,6 +66,9 @@ export const DEFAULT_STDERR_SUMMARY_MAX_CHARS = 500
* `undefined` when empty, cut at `maxChars` with an ellipsis when over. The
* bound is a parameter — like `runHook`'s `defaultTimeoutMs`, each bridge owns
* the config default and passes it in.
* @param stderr - the hook's raw captured stderr.
* @param maxChars - the character cap for the summary (the bridge's config value).
* @returns the trimmed, capped summary, or `undefined` when stderr is blank.
*/
export function summarizeStderr(stderr: string, maxChars: number): string | undefined {
const t = stderr.trim()
@@ -73,7 +76,11 @@ export function summarizeStderr(stderr: string, maxChars: number): string | unde
return t.length > maxChars ? t.slice(0, maxChars) + '…' : t
}
/** Append a `hook/invoked` provenance event to `session`. */
/**
* Append a `hook/invoked` provenance event to `session`.
* @param session - the session whose open turn records the event.
* @param invocation - the invocation identity; an absent `matcher` is omitted from the payload.
*/
export function appendHookInvoked(session: Session, invocation: HookInvocation): void {
session.append('hook/invoked', {
turn: invocation.turn,
@@ -91,6 +98,8 @@ export function appendHookInvoked(session: Session, invocation: HookInvocation):
* else `'pass'`; `stderrSummary` is the trimmed stderr truncated to
* `record.stderrSummaryMaxChars` characters (omitted when empty); `exitCode`
* is omitted when the hook never ran.
* @param session - the session whose open turn records the event.
* @param record - the outcome to record: the decoded output plus the summary cap and duration.
*/
export function appendHookResult(session: Session, record: HookResultRecord): void {
const { output } = record

View File

@@ -34,6 +34,10 @@ const CLAUDE_LITERAL = /^[A-Za-z0-9_|]+$/
* pattern exact-matches the query (splitting `|` into alternatives); every other
* `claude` pattern and ALL `codex` patterns are tested as an unanchored regex.
* An invalid regex matches nothing (never throws).
* @param matcher - the configured pattern; absent/empty/`'*'` are the match-all sentinels.
* @param query - the candidate value (a tool name, a session source, …).
* @param mode - the dialect deciding literal-vs-regex interpretation of the pattern.
* @returns `true` when the pattern selects the query; `false` on a non-match or an invalid regex.
*/
export function matchesMatcher(matcher: string | undefined, query: string, mode: MatcherMode): boolean {
if (isMatchAll(matcher)) return true

View File

@@ -71,6 +71,8 @@ function decisionForRank(maxRank: number): MergedDecision {
* into one {@link MergedHookOutcome} by the precedence rules above. An empty list
* yields a neutral outcome (`decision: 'none'`, no stop, empty context) — the
* caller treats that as "no hook had anything to say".
* @param outputs - every matched hook's decoded output, in hook order.
* @returns the single folded outcome the bridge maps onto its seam.
*/
export function mergeHookOutputs(outputs: HookOutput[]): MergedHookOutcome {
let maxRank = 0

View File

@@ -70,6 +70,11 @@ export interface RunHookResult {
* `exitCode: undefined`, so the caller's merge logic treats it as a
* non-blocking error rather than crashing the turn. `now` is injected for
* testable durations.
* @param bash - the executor seam the command runs through.
* @param hook - the configured command; its `timeoutSec` (wire unit: seconds) overrides the default timeout.
* @param options - the invocation's payload, env, cwd, signal, stdin framing, and default timeout.
* @param now - millisecond clock used for the reported duration.
* @returns the decoded output plus the run's wall-clock duration.
*/
export async function runHook(
bash: BashExecutor,