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
2026-07-06 22:09:30 +08:00
/ * *
2026-07-14 00:47:30 +08:00
* Shared JSDoc parsing and completeness checks for the Cordis , persistence ,
* and config catalogs and the export - surface gate .
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
2026-07-06 22:09:30 +08:00
* /
import ts from 'typescript'
/** Repo-relative source pointer `file:line` for a node's first character. */
export function pointer ( rel : string , sf : ts.SourceFile , node : ts.Node ) : string {
const { line } = sf . getLineAndCharacterOfPosition ( node . getStart ( sf ) )
return ` ${ rel } : ${ line + 1 } `
}
/** The raw `/** … * /` JSDoc block immediately preceding a node, or '' if none. */
export function rawJsDoc ( text : string , node : ts.Node ) : string {
const ranges = ts . getLeadingCommentRanges ( text , node . getFullStart ( ) ) ? ? [ ]
const jsdoc = ranges . filter ( r = > text . slice ( r . pos , r . pos + 3 ) === '/**' ) . at ( - 1 )
return jsdoc ? text . slice ( jsdoc . pos , jsdoc . end ) : ''
}
/** A dispatch mode, rendered as the badge after an event name in the catalog. */
export type Mode = 'emit' | 'waterfall' | 'parallel' | 'serial'
/ * *
2026-07-13 23:27:00 +08:00
* Parse a raw JSDoc block into description prose and an optional ` @mode ` . Prose
* ends at the first block tag , paragraphs collapse to one line , bullet items
* remain separate lines , and ` {@link X} ` renders as ` X ` .
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
2026-07-06 22:09:30 +08:00
* @param raw - the raw comment text including the JSDoc delimiters .
2026-07-14 00:24:04 +08:00
* @returns the collapsed description prose , parsed valid ` @mode ` ( or null ) ,
* and whether any ` @mode ` tag was present .
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
2026-07-06 22:09:30 +08:00
* /
2026-07-14 00:24:04 +08:00
export function parseJsDoc ( raw : string ) : { doc : string ; mode : Mode | null ; hasMode : boolean } {
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
2026-07-06 22:09:30 +08:00
const inner = raw
. replace ( /^\/\*\*/ , '' )
. replace ( /\*\/$/ , '' )
. split ( '\n' )
. map ( l = > l . replace ( /^\s*\*?\s?/ , '' ) . replace ( /\s+$/ , '' ) )
let mode : Mode | null = null
2026-07-14 00:24:04 +08:00
let hasMode = false
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
2026-07-06 22:09:30 +08:00
let inTags = false
const blocks : string [ ] = [ ]
let para : string [ ] = [ ]
let list : string [ ] = [ ]
let item : string [ ] = [ ]
const join = ( parts : string [ ] ) : string = > parts . join ( ' ' ) . replace ( /\s+/g , ' ' ) . trim ( )
const flushItem = ( ) : void = > {
if ( item . length ) list . push ( join ( item ) )
item = [ ]
}
const flushList = ( ) : void = > {
flushItem ( )
if ( list . length ) blocks . push ( list . join ( '\n' ) ) // one block, items on own lines
list = [ ]
}
const flushPara = ( ) : void = > {
flushList ( )
if ( para . length ) blocks . push ( join ( para ) )
para = [ ]
}
for ( const line of inner ) {
2026-07-14 00:24:04 +08:00
const tagLine = line . trimStart ( )
const m = /^@mode\s+(emit|waterfall|parallel|serial)\s*$/ . exec ( tagLine )
if ( m ) { mode = m [ 1 ] as Mode ; hasMode = true ; flushPara ( ) ; inTags = true ; continue }
if ( /^@mode\b/ . test ( tagLine ) ) { hasMode = true ; flushPara ( ) ; inTags = true ; continue }
if ( tagLine . startsWith ( '@' ) ) { flushPara ( ) ; inTags = true ; continue }
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
2026-07-06 22:09:30 +08:00
if ( inTags ) continue // block-tag territory: continuations are never prose
if ( line . trim ( ) === '' ) { flushPara ( ) ; continue }
if ( /^-\s+/ . test ( line ) ) {
// A list item starts: a pending paragraph (e.g. an intro line directly
// above the list, no blank between) flushes FIRST so it renders above.
flushItem ( )
if ( para . length ) { blocks . push ( join ( para ) ) ; para = [ ] }
item . push ( line )
continue
}
if ( item . length ) { item . push ( line ) ; continue } // continuation of current item
para . push ( line )
}
flushPara ( )
const doc = blocks . join ( '\n\n' ) . replace ( /\{@link\s+([^}]+)\}/g , '$1' ) . trim ( )
2026-07-14 00:24:04 +08:00
return { doc , mode , hasMode }
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
2026-07-06 22:09:30 +08:00
}
/ * *
2026-07-13 23:27:00 +08:00
* Parse ` @param ` and ` @returns ` descriptions , including continuation lines .
* Parameter separators are optional and ` [optional] ` names unwrap .
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
2026-07-06 22:09:30 +08:00
* @param raw - the raw comment text including the JSDoc delimiters .
* @returns the ` @param ` name → description map plus the ` @returns ` description
* ( null when the tag is absent , '' when present but empty ) .
* /
export function parseTags ( raw : string ) : { params : Map < string , string > ; returns : string | null } {
const inner = raw
. replace ( /^\/\*\*/ , '' )
. replace ( /\*\/$/ , '' )
. split ( '\n' )
. map ( l = > l . replace ( /^\s*\*?\s?/ , '' ) . replace ( /\s+$/ , '' ) )
const params = new Map < string , string > ( )
let returns : string | null = null
let sink : ( ( text : string ) = > void ) | null = null
for ( const line of inner ) {
const param = /^@param\s+(\[?[\w$]+\]?)\s*(?:[-—–]\s*)?(.*)$/ . exec ( line )
if ( param ) {
const name = ( param [ 1 ] ? ? '' ) . replace ( /^\[|\]$/g , '' )
let acc = param [ 2 ] ? ? ''
params . set ( name , acc )
sink = ( t ) = > { acc = acc ? ` ${ acc } ${ t } ` : t ; params . set ( name , acc ) }
continue
}
const ret = /^@returns?(?:\s+[-—–]?\s*(.*))?$/ . exec ( line )
if ( ret ) {
let acc = ret [ 1 ] ? ? ''
returns = acc
sink = ( t ) = > { acc = acc ? ` ${ acc } ${ t } ` : t ; returns = acc }
continue
}
if ( line . startsWith ( '@' ) || line . trim ( ) === '' ) { sink = null ; continue }
sink ? . ( line . trim ( ) )
}
return { params , returns }
}
/ * *
2026-07-13 23:27:00 +08:00
* Require a non - empty tag for each non - exempt identifier parameter , reject
* binding - pattern parameters , and reject stale tags . Exempt parameters may
* still be documented .
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
2026-07-06 22:09:30 +08:00
* @param where - the offender label violations open with , e . g . ` event 'x' (file:1) ` .
2026-07-13 23:27:00 +08:00
* @param surface - surface noun used in binding - pattern diagnostics .
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
2026-07-06 22:09:30 +08:00
* @param parameters - the declaration ' s parameter list .
* @param tags - the parsed ` @param ` name → description map from parseTags .
2026-07-12 03:36:43 +08:00
* @param sf - source file used to render binding patterns .
2026-07-13 23:27:00 +08:00
* @param isExempt - parameters whose tag is optional , such as ` this ` or waterfall ` next ` .
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
2026-07-06 22:09:30 +08:00
* @param violations - the aggregate list violations append to .
* /
export function checkParams (
where : string ,
surface : string ,
parameters : readonly ts . ParameterDeclaration [ ] ,
tags : Map < string , string > ,
sf : ts.SourceFile ,
isExempt : ( p : ts.ParameterDeclaration ) = > boolean ,
violations : string [ ] ,
) : void {
for ( const p of parameters ) {
if ( ! ts . isIdentifier ( p . name ) ) {
violations . push ( ` ${ where } : parameter ' ${ p . name . getText ( sf ) } ' is a binding pattern; the ${ surface } surface needs simple identifier parameters so @param can name them. ` )
continue
}
if ( isExempt ( p ) ) continue
const desc = tags . get ( p . name . text )
if ( desc === undefined ) violations . push ( ` ${ where } is missing @param ${ p . name . text } . ` )
else if ( ! desc . trim ( ) ) violations . push ( ` ${ where } : @param ${ p . name . text } has an empty description. ` )
}
for ( const tag of tags . keys ( ) ) {
if ( ! parameters . some ( p = > ts . isIdentifier ( p . name ) && p . name . text === tag ) ) {
violations . push ( ` ${ where } : @param ${ tag } does not match any parameter (stale tag?). ` )
}
}
}
/ * *
2026-07-12 03:36:43 +08:00
* Check the ` @returns ` half of the completeness contract : a non - ` void ` / ` Promise<void> `
* return needs a non - empty ` @returns ` , and the return type must be ANNOTATED — a pure - AST
2026-07-13 23:27:00 +08:00
* walk cannot classify an inferred return . Void returns may still carry an
* optional tag , for example to document resolution timing .
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
2026-07-06 22:09:30 +08:00
* @param where - the offender label violations open with .
* @param typeNode - the declared return type annotation , or undefined when inferred .
* @param returns - the parsed ` @returns ` description from parseTags ( null when absent ) .
* @param sf - the source file ( for rendering the annotation ' s text ) .
* @param violations - the aggregate list violations append to .
* /
export function checkReturns (
where : string ,
typeNode : ts.TypeNode | undefined ,
returns : string | null ,
sf : ts.SourceFile ,
violations : string [ ] ,
) : void {
if ( typeNode === undefined ) {
violations . push ( ` ${ where } has no return type annotation; annotate it explicitly so the gate can classify the result. ` )
return
}
const rt = typeNode . getText ( sf ) . replace ( /\s+/g , ' ' )
if ( /^(void|Promise<void>)$/ . test ( rt ) ) return
if ( returns === null ) violations . push ( ` ${ where } is missing @returns (return type: ${ rt } ). ` )
else if ( ! returns . trim ( ) ) violations . push ( ` ${ where } : @returns has an empty description. ` )
}
/ * *
* Throw one aggregate error for every completeness violation a walk collected .
* Aggregation ( vs failing fast ) is deliberate : a remediation pass sees the
* whole list at once instead of replaying the gate once per offender .
* @param gate - the reporting gate ' s name , prefixed to the error message .
* @param violations - the collected violation lines ; no - op when empty .
* /
export function reportViolations ( gate : string , violations : string [ ] ) : void {
if ( violations . length === 0 ) return
throw new Error (
` ${ gate } : ${ violations . length } JSDoc completeness violation(s) (see AGENTS.md): \ n `
+ violations . map ( v = > ` ${ v } ` ) . join ( '\n' ) ,
)
}