Skip to main content
Version: 1.5.1
Last Updated: 2026-05-24
Status: Active Owning Core/Module: PF-75

Purpose

This standard defines how Encore OS enforces documentation comment quality and coverage for public TypeScript APIs. The goal is to improve maintainability and API clarity while enforcing durable standards for all in-scope exported APIs.

Scope

In Scope (initial)

  • src/platform/**
  • src/shared/**
  • src/cores/**
  • supabase/functions/**

Declaration types currently enforced for coverage

  • export function ...
  • export class ...
  • exported const initialized with function expressions / arrow functions
  • export const/function whose name matches /^use[A-Z]/ (hooks, Phase 3)
  • export const/function whose direct return is JSX (components, Phase 3)
  • export interface ... (Phase 2)
  • export type ... (Phase 2)

Initial exclusions

  • src/routes/** route/lazy-map files
  • src/integrations/** generated integration files
  • test/spec/story files
  • pure re-export barrels (export { ... } from ...)
  • type-heavy mapping files:
    • **/types.ts
    • **/types/**/*.ts
    • **/index.types.ts

TSDoc Syntax Rule

As of Phase 1B.7 (v1.5.1), tsdoc/syntax is an ESLint error for src/**/*.{ts,tsx}. Malformed TSDoc fails npm run lint:ci immediately, so syntax debt can no longer accrue. The dedicated inventory command remains available for ad-hoc audits:
Coverage enforcement (minimum doc-block presence and rubric quality) remains handled by the audit script; the ESLint rule guards syntax correctness.

Phase 1B.7: tsdoc/syntax promoted to error (shipped 2026-05-24)

tsdoc/syntax moved from off to error. The path to a clean promotion:
  1. Codemod (scripts/codemods/fix-tsdoc-syntax.ts) — extended to escape { } < > and stray @ in doc-comment prose (it already handled }/>), plus escaping inside unfenced @example bodies and a single-line /** @tag */ exemption so real block tags are preserved. Every escape uses a (?<!\\) guard (idempotent). A repo-wide comment-only sweep (~700 files), combined with the commit-hook Biome reformat (which normalized whitespace-sensitive cases), cleared the bulk.
  2. fix-dotted-param — converted JSDoc @param parent.child dotted names into plain-text bullets under the parent @param.
  3. Tag registration — 12 block/modifier tags registered in tsdoc.json (@packageDocumentation, @experimental, @since, @updated, @owner, @route, @permission, @accessibility, @todo, @generated, @note, @size).
  4. Residual cleanup — the long tail (unbalanced code spans, JSDoc @param {type}, package refs like `@tanstack/react-table`) fixed by hand, comment-only.
This drove the tsdoc/syntax-specific count to 0 with no eslint-disable. Phase 1B is complete (tag tsdoc-phase-1b-complete).
Measurement note: count tsdoc/syntax in isolation via the JSON reporter filtered on ruleId === "tsdoc/syntax". The default-formatter “N problems” line also includes every other configured rule (no-console, react-hooks/*, …) and will badly overstate the figure.

Coverage Audit Commands

Output reports

  • reports/audits/TSDOC_JSDOC_COVERAGE_REPORT.md
  • reports/audits/TSDOC_JSDOC_COVERAGE_REPORT.json
  • reports/audits/TSDOC_JSDOC_COVERAGE_BASELINE.md
  • reports/audits/TSDOC_JSDOC_COVERAGE_BASELINE.json

Required Comment Quality

At minimum, public declarations should include:
  1. A concise summary line that describes behavior,
  2. @param tags for non-obvious parameters,
  3. @returns when return semantics are non-trivial,
  4. @deprecated with migration guidance and sunset where applicable.
Avoid filler comments that simply restate symbol names.

Quality Rubric

Per-declaration quality score (0–5)

Each in-scope declaration earns up to 5 points. Passing = ≥ 3. Below 3 = audit failure on changed files; backfill is tracked, not blocking.

Filler detection

A doc-block fails the summary point when any of these are true:
  • body < 30 chars after trim
  • body is the symbol name de-camel-cased with throwaway verbs (Hook for X, Helper for X, Utility for X, Function that …)
  • body matches the denylist (TODO, WIP, Documentation pending, Auto-generated, plain dashes)
Per-kind exemption: boolean predicates (e.g., isAdult) and simple getters whose name fully expresses behavior get a lower bar — summary ≥ 15 chars AND adds non-trivial info beyond the type signature earns the point.

Per-kind quality variant

  • Function / class method / hook — full 5-point rubric.
  • React component (functional, exported) — summary + @spec + @example (mount snippet) + @param for props (one entry per prop OR a single block describing the props object) + @returns waived.
  • Interface / type alias — summary + @spec + per-property @remarks for non-obvious members, or @example showing a populated value; @param/@returns waived.
  • Exported const (data / config) — summary + @spec + @example of intended consumption; @param/@returns waived.

Tag Dictionary

Canonical, ESLint-enforced tag set. Required when applicable
  • @spec <SPEC-ID> — owning spec; repeatable. Audit verifies the ID exists under specs/.
  • @param <name> <desc> — non-obvious params.
  • @returns <desc> — non-trivial returns.
  • @throws <Type> <when> — when the throw is part of the contract.
Strongly recommended
  • @example — code block; required for any export marked @public and >0 params.
  • @see <symbol|path|url> — related code, spec, integration doc.
  • @deprecated <since> — <migration> — must include replacement or sunset.
Allowed structural
  • @remarks — additional context.
  • @internal — opts out of public-API quality enforcement (still counted toward presence; lower bar applies).
  • @beta / @alpha — stability markers.
  • @core <CORE> — explicit core override (otherwise inferred from path).
Forbidden / migrated
  • @summary (Storybook): configure tsdoc.json to recognize it as a known tag and confine it to *.stories.tsx. Clears the 853 + 848 tsdoc-undefined-tag warnings.
  • Inline {@link X} with unescaped } and > in generics — fixed by a one-time codemod.

Phasing & Rollout

Phase 3: hooks + components (shipped 2026-05-23)

The audit parser now distinguishes:
  • Hooks — exported function or const whose name matches /^use[A-Z]/. Treated under the full 5-point rubric (identical to functions).
  • Components — exported function or const whose body directly returns JSX (JsxElement / JsxFragment / JsxSelfClosingElement, including conditional and parenthesized JSX). Treated under the component variant: summary + @spec + @example (mount snippet) + per-prop or props-object @param; @returns waived.
Disambiguation rules (parser.ts header doc-block):
  1. Hook name beats JSX return — useFoo returning JSX is still a hook.
  2. JSX return signals a component — regardless of name capitalization.
  3. HOCs (functions whose direct return is another function, not JSX) remain function. An opt-in @component modifier tag is deferred to a future phase.
The AI-context surface (docs/api/{core}.md) now emits ## Hooks and ## Components sections in spec §4.4 order, replacing the previous heuristic (.tsx file + capitalized name) that conflated hooks and components into a single bucket.

Phase 2: interfaces + types (shipped 2026-05-23)

The audit parser now recognizes two additional declaration kinds:
  • Interfacesexport interface Foo { ... }. Scored under the interface/type variant of the rubric.
  • Type aliasesexport type Foo = .... Same variant as interfaces.
Both use the rubric’s interface/type variant: @param and @returns are waived; summary + @spec + @example (or @see) carry the substitute point for the missing call-signature checks. Per-property @remarks count as @example for the “example/see” rubric point. The AI-context surface (docs/api/{core}.md) now emits a ## Types & interfaces section as the first H2 (spec §4.4 ordering: Types & interfaces → Hooks → Components → Functions & utilities → Classes). The section is suppressed when a core has zero documented interfaces/types so we never leave a dangling H2 behind. Volume reality. Phase 2 added +2,744 declarations to the rubric surface (2,182 interfaces + 562 type aliases), bringing the grand total from 11,528 to 14,272. This is smaller than the design spec’s projected +8,786 because the existing config.ts exclusions — **/types.ts, **/types/**, **/index.types.ts — correctly keep interface-barrel modules out of scope. Re-classifying those barrel modules as in-scope (only when they contain ≥ N exported interfaces with non-trivial bodies) is a separate follow-up plan, not a Phase 2 expansion. Hand-authored core-local types.ts files therefore remain audit-invisible; backfill prioritizes the in-scope interfaces/types under platform, hr, fa, edge-functions, ce, cl, fw, and pm (in descending sub-3 count order). Disambiguation rules (parser.ts header doc-block):
  1. export interface Fookind: 'interface' regardless of declaration site (module body, namespace, ambient).
  2. export type Foo = ...kind: 'type'. Type-only alias forms only; export type { Foo } re-exports remain barrels and are excluded.
  3. */types.ts, */types/**, and */index.types.ts files remain excluded by config.ts (Phase 1A behavior, deliberately preserved for Phase 2).

AI-Context Surface

Three artifacts, all committed to git

No separate website. TypeDoc is not introduced; we reuse the audit parser so there is one source of truth.

MCP server (Phase B / Phase 4 Track A)

npm run mcp:serve starts a local HTTP MCP server (scripts/mcp/api-surface-server.ts, default port 3399, registered in .mcp.json as encore-api-surface) that exposes MCP tools over docs/api/index.json: For Cursor IDE usage, add the same endpoint in local .cursor/mcp.json (seed from .cursor/mcp.json.example).
  • lookup_symbol({ name, limit? }) — resolve a symbol name/fragment to its file:line, core, spec links, and summary (exact match first, then case-insensitive substring).
  • symbols_for_spec({ spec_id }) — list the symbols implementing a spec ID.
  • semantic_search({ query, top_k? }) — optional natural-language symbol discovery when embeddings are configured.
The server mtime-watches index.json and hot-reloads after npm run docs:api:generate. Query logic lives in the pure, unit-tested scripts/mcp/index-store.ts. v1 is local/trusted use only (no auth, no deployed instance).

Semantic search (Phase D / Phase 4 Track C)

semantic_search({ query, top_k? }) — a third tool on encore-api-surface — does dense-vector search over the surface for paraphrased / “find the symbol that does X” queries. The index is built by npm run docs:api:embeddings (text-embedding-3-small via the Vercel AI Gateway, full-precision Float32 stored in Vercel Blob; inputs are PHI-pre-scanned before any upstream call) and refreshed by the weekly Refresh API Embeddings workflow. Requires AI_GATEWAY_API_KEY + BLOB_READ_WRITE_TOKEN (read from the environment only); without them the build no-ops and semantic_search returns “unavailable” while lookup_symbol/symbols_for_spec keep working. Pure cores (scripts/mcp/embedding-store.ts k-NN, scripts/mcp/phi-scan.ts) are unit-tested; all provider/Blob calls sit behind injected adapters so CI never hits the network.

Per-symbol record (shared shape)

Same fields in index.json, plus top-level reverse maps:
bySpec gives every implementer of a spec in O(1).

CI Policy

PR comment from the audit script summarizes deltas: +2 decls improved (3→4, 2→5); 0 regressions.

Threshold Policy

Minimum thresholds (for docs:comments:audit):
  • Global minimum: 100%
  • Area minimums:
    • platform: 100%
    • shared: 100%
    • cores: 100%
    • edge-functions: 100%
Any temporary exception must be documented in a tracked spec/errata item with owner and expiry.

Current Coverage Status

As of the Phase 2 re-baseline (2026-05-23, after the parser added interface and type declaration kinds on top of the Phase 3 hook/component split):
  • Files scanned: 9,187
  • Declarations analyzed: 14,272
  • Documented (presence): 11,111 / 14,272 (77.9%)
  • Missing doc-block: 3,161
  • Mean quality score (0–5): 1.83

Coverage by declaration kind (Phase 2 byKind rollup)

Grand total grew from 11,528 to 14,272 — Phase 2 adds 2,182 interfaces + 562 type aliases (+2,744 net) on top of the Phase 3 surface. The interface and type kinds enter with low presence coverage (13.9% and 15.5%), pulling the overall presence rate down from 93% to 77.9% and the mean score from 2.20 to 1.83. This is expected: every TSDoc-less interface contributes a fresh score-0 record until backfill lands. The audit/quality gate continues to fire only on changed files, so this baseline drop does not break CI; it is the new floor for the Phase 2 backfill cadence to climb.

Score histogram (overall)

The “0” bucket grew from 807 to 3,161 because the newly in-scope interface and type aliases overwhelmingly lack a leading TSDoc block. Backfill against the sub-3 worklist (concentrated in platform, hr, fa, edge-functions, ce, cl, fw, pm — see the agent’s per-core priority table) will move these records from 0 directly to 4 or 5 as the interface/type rubric variant awards the @param/@returns checks as N/A.

Per-area means

Rubric histogram quirk

The rubric awards “N/A” automatically when a declaration has no parameters or when its return type is trivial. In practice this means that a declaration with a substantive summary jumps from 2 points (presence + summary) directly to 4 points (presence + summary + params N/A + returns N/A) without passing through 3. The only way to score 3 is to have a non-filler summary plus exactly one of (@spec, @param, @returns) without the N/A backstop — rare in real code. Similarly, 5 requires a non-trivial @spec linkage on top, which is rare until backfill lands. This is a feature, not a bug: the rubric’s pass-threshold --min-score 3 filters in practice to “score 4 or 5,” which is the right pass/fail boundary for the changed-files gate.

Prior coverage figure (now retired)

The previous standard’s “100% (8,201 / 8,201)” figure came from the legacy parser that only counted function/class/variable declarations and used a binary hasDocumentationComment check. The new rubric pipeline (a) captures the raw doc-block text via getDocCommentRaw, (b) finds 813 declarations the legacy parser missed or mis-counted. The 100% figure is retired; the 92.9% figure is the new baseline.

References

  • scripts/audit/audit-doc-comment-coverage.ts
  • specs/pf/specs/PF-75-tsdoc-jsdoc-coverage-program.md
  • docs/superpowers/specs/2026-05-23-tsdoc-coverage-quality-ai-context-design.md
  • AGENTS.md (current: see docs/VERSIONS.md)
  • constitution.md (current: see docs/VERSIONS.md)