Merge remote-tracking branch 'origin/master' into codex/trim-ai-prose

# Conflicts:
#	docs/config-catalog.md
#	scripts/gen-persistence-catalog.ts
#	scripts/jsdoc.ts
#	scripts/verify-md-wrap.ts
#	scripts/verify-package-paths.ts
This commit is contained in:
Tianyi Cui
2026-07-14 00:47:30 +08:00
21 changed files with 316 additions and 399 deletions

View File

@@ -1,10 +1,6 @@
/**
* Shared JSDoc parsing and completeness-check helpers for the documentation gates: the cordis
* catalog generator (`scripts/gen-cordis-catalog.ts` — the events + `ctx.<key>` service
* surface), the plugin config catalog generator (`scripts/gen-config-catalog.ts`, which
* renders the parsed prose), and the export-surface gate (`scripts/verify-export-jsdoc.ts` —
* every module-level export). This is the single definition of description,
* parameter, return, and stale-tag completeness across those surfaces.
* Shared JSDoc parsing and completeness checks for the Cordis, persistence,
* and config catalogs and the export-surface gate.
*/
import ts from 'typescript'
@@ -30,15 +26,17 @@ export type Mode = 'emit' | 'waterfall' | 'parallel' | 'serial'
* ends at the first block tag, paragraphs collapse to one line, bullet items
* remain separate lines, and `{@link X}` renders as `X`.
* @param raw - the raw comment text including the JSDoc delimiters.
* @returns the collapsed description prose plus the parsed `@mode` (or null).
* @returns the collapsed description prose, parsed valid `@mode` (or null),
* and whether any `@mode` tag was present.
*/
export function parseJsDoc(raw: string): { doc: string; mode: Mode | null } {
export function parseJsDoc(raw: string): { doc: string; mode: Mode | null; hasMode: boolean } {
const inner = raw
.replace(/^\/\*\*/, '')
.replace(/\*\/$/, '')
.split('\n')
.map(l => l.replace(/^\s*\*?\s?/, '').replace(/\s+$/, ''))
let mode: Mode | null = null
let hasMode = false
let inTags = false
const blocks: string[] = []
let para: string[] = []
@@ -60,9 +58,11 @@ export function parseJsDoc(raw: string): { doc: string; mode: Mode | null } {
para = []
}
for (const line of inner) {
const m = /^@mode\s+(emit|waterfall|parallel|serial)\s*$/.exec(line)
if (m) { mode = m[1] as Mode; flushPara(); inTags = true; continue }
if (line.startsWith('@')) { flushPara(); inTags = true; continue }
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 }
if (inTags) continue // block-tag territory: continuations are never prose
if (line.trim() === '') { flushPara(); continue }
if (/^-\s+/.test(line)) {
@@ -78,7 +78,7 @@ export function parseJsDoc(raw: string): { doc: string; mode: Mode | null } {
}
flushPara()
const doc = blocks.join('\n\n').replace(/\{@link\s+([^}]+)\}/g, '$1').trim()
return { doc, mode }
return { doc, mode, hasMode }
}
/**