> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
> Use this file to discover all available pages before exploring further.

# TSDoc/JSDoc Coverage Standard

> This standard defines how Encore OS enforces documentation comment quality and coverage for public TypeScript APIs.

**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:

```bash theme={null}
npm run lint:debt:tsdoc
```

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

```bash theme={null}
# Full audit (includes minimum coverage checks)
npm run docs:comments:audit

# Changed-files gate (blocks undocumented new/changed exports)
npm run docs:comments:audit:changed

# Baseline report generation (non-blocking snapshot)
npm run docs:comments:baseline

# TSDoc syntax debt inventory (non-blocking)
npm run lint:debt:tsdoc
```

### 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.

| Point                  | Check                                                                                                                                                                                     | Applies to                    |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
| **1**                  | Has a leading `/** ... */` block (today's bar)                                                                                                                                            | all                           |
| **1**                  | **Substantive summary** — body (excluding tags) is ≥ 30 chars and not filler                                                                                                              | all                           |
| **1**                  | **Spec linkage** — `@spec <SPEC-ID>` resolves to a real `specs/{core}/**` file, OR `@spec none — <one-line reason>` is explicit                                                           | all                           |
| **1**                  | **Inputs documented** — `@param` for each non-obvious parameter (≥ 2 params, OR any param is an object/union/branded type). N/A for zero-param decls; that point is awarded automatically | functions, methods, hooks     |
| **1**                  | **Outputs documented** — `@returns` present when return type is non-trivial. N/A awarded for trivial returns                                                                              | functions, methods, hooks     |
| **+ bonus substitute** | For interfaces / types / components with no params/returns to document: `@example` OR `@see` to a consumer site counts toward the missing point                                           | interfaces, types, components |

### 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                                  | Duration             | Ships                                                                                                                                                        | Gate                                                 |
| -------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------- |
| **0 — Pre-flight**                     | 1 wk                 | Standard v1.3.0; `tsdoc.json`; codemod; debt \< 100                                                                                                          | None                                                 |
| **1A — Rubric infra (observe)**        | 1 wk                 | `rubric.ts`, `tags.ts`, `spec-index.ts`, `filler.ts`; `--rubric` flag; first quality baseline                                                                | None                                                 |
| **1B — Quality gate on changed files** | 2 wks                | `--rubric` default on; `--min-score 3` in `--changed-only`; weekly backfill PRs                                                                              | PRs modifying in-scope decls fail if any score \< 3  |
| **1C — AI-context artifacts ship**     | concurrent w/ 1B     | `generate-api-surface.ts`; `docs/api/{llms-full.txt,*.md,index.json}` committed; `docs:api:check` in validate                                                | PRs touching in-scope code without regenerating fail |
| **Syntax-debt promotion**              | end of 1B            | `tsdoc/syntax` warn → error                                                                                                                                  | `lint` fails on syntax                               |
| **2 — Expand: interfaces + types**     | ✅ shipped 2026-05-23 | Parser adds interface/type; per-kind rubric variant; baseline → backfill → gate. Plan: `docs/archive/superpowers-plans-2026-05/2026-05-23-tsdoc-phase-2.md`. | After backfill: gate applies                         |
| **3 — Expand: hooks + components**     | ✅ shipped 2026-05-23 | Parser adds `use*` hooks + JSX-returning components; component variant; baseline → backfill → gate                                                           | After backfill: gate applies                         |
| **4 — Stretch (future spec)**          | —                    | MCP server; per-spec back-ref injection; optional embeddings                                                                                                 | n/a                                                  |

### 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:

* **Interfaces** — `export interface Foo { ... }`. Scored under the
  interface/type variant of the rubric.
* **Type aliases** — `export 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 Foo` → `kind: '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

| Path                                                                            | Format                                              | Audience                                                    | Refresh |
| ------------------------------------------------------------------------------- | --------------------------------------------------- | ----------------------------------------------------------- | ------- |
| `docs/api/llms-full.txt`                                                        | flat plain text, one record per symbol              | LLMs ingesting whole-codebase context                       | CI      |
| `docs/api/{core}.md` (one per core + `pf.md`, `shared.md`, `edge-functions.md`) | Markdown w/ stable anchors                          | humans, GitHub search, agents fetching narrow context       | CI      |
| `docs/api/index.json`                                                           | machine-readable, doubly indexed by symbol and spec | tooling (search box, MCP server, IDE extension, future RAG) | CI      |

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)

```text theme={null}
## function calcMeasureRate
- file: src/cores/pm/utils/vbpMeasureCalc.ts:42
- kind: function
- core: pm
- spec: PM-25
- summary: Calculates measure rate from numerator and denominator. Returns null
  if denominator is zero or inputs are missing.
- params:
  - numerator (number | null | undefined) — measure numerator
  - denominator (number | null | undefined) — measure denominator
- returns: number | null — calculated rate, or null when undefined
- example: |
    const rate = calcMeasureRate(48, 80); // 0.6
- see:
  - specs/pm/specs/PM-25-vbp-measures.md
- score: 5
```

Same fields in `index.json`, plus top-level reverse maps:

```json theme={null}
{
  "bySymbol": { "calcMeasureRate": ["src/cores/pm/utils/vbpMeasureCalc.ts:42:calcMeasureRate"] },
  "bySpec":   { "PM-25": ["src/cores/pm/utils/vbpMeasureCalc.ts:42:calcMeasureRate"] }
}
```

`bySpec` gives every implementer of a spec in O(1).

***

## CI Policy

| Gate                         | Where                        | Pass condition                                              | Phase      |
| ---------------------------- | ---------------------------- | ----------------------------------------------------------- | ---------- |
| Coverage (existing)          | `validate` + PR              | `--changed-only --fail-on-missing`                          | already on |
| Quality (changed files)      | `validate` + PR              | `--rubric --changed-only --min-score 3 --enforce-no-filler` | 1B+        |
| AI-surface freshness         | `validate` + `test:baseline` | `docs:api:check` exits 0                                    | 1C+        |
| TSDoc syntax (error)         | `lint:ci`                    | `tsdoc/syntax` reports 0                                    | end of 1B  |
| Full-repo quality (baseline) | nightly + release            | `--rubric --enforce-area-minimums` against baseline         | 1B+        |

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)

| Kind      | Documented / Total | Coverage |
| --------- | -----------------: | -------: |
| function  |      2,281 / 2,438 |    93.6% |
| class     |            20 / 31 |    64.5% |
| variable  |            30 / 35 |    85.7% |
| hook      |      4,058 / 4,282 |    94.8% |
| component |      4,332 / 4,742 |    91.4% |
| interface |        303 / 2,182 |    13.9% |
| type      |           87 / 562 |    15.5% |

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)

| Score | Count | Meaning                                                                                                                     |
| ----: | ----: | --------------------------------------------------------------------------------------------------------------------------- |
|     0 | 3,161 | No doc-block.                                                                                                               |
|     1 | 5,135 | Presence only — filler summary, no spec linkage.                                                                            |
|     2 | 1,548 | Presence + one quality dimension (commonly: substantive summary on a component or interface, missing `@example` / `@spec`). |
|     3 |     0 | (Unreachable in practice — see "Rubric histogram quirk" below.)                                                             |
|     4 | 4,255 | Substantive summary + N/A awards, no `@spec`. Passing.                                                                      |
|     5 |   173 | Full rubric — substantive summary + `@spec` + structured tags (including `@example` for the interface/type variant).        |

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

| Area           | Mean | Scored |
| -------------- | ---: | -----: |
| edge-functions | 2.17 |    658 |
| shared         | 2.42 |    242 |
| platform       | 1.99 |  3,459 |
| cores          | 1.74 |  9,913 |

### 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)
