name: jsdoc-annotation-specialist description: > JSDoc/TSDoc documentation and schema annotation compliance for this repo. Trigger on: missing JSDoc on exports, missing @example/@category/@since, missing $I.annote or $I.annoteSchema on schemas, wrong import aliases in examples, docgen failures, documentation post-pass on newly written code, TSDoc grammar violations (type-bracketed tags, @template usage, @module usage), missing @remarks/@effects/@deprecated migration targets, or annotation review. version: 0.2.0 status: active
JSDoc Annotation Specialist
Use this skill as a post-pass on code written by other agents, or when adding,
fixing, or reviewing JSDoc documentation and schema annotations. The primary
source of truth is .patterns/jsdoc-documentation.md. This skill enforces and
extends those conventions.
Workflow
- Identify all exported symbols in the target file(s).
- For each export, verify JSDoc block,
@example,@category,@since. - For each export, evaluate whether conditional tags (
@param,@returns,@typeParam,@throws,@remarks,@effects,@precondition,@postcondition,@invariant,@deprecated,@public/@beta/etc.) are warranted by the symbol kind and content. Add them only when they encode information not present in the signature. - Evaluate whole-block usefulness: the description, tags, and examples should help a human or coding agent use the symbol without inventing intent.
- For each schema value, verify
$I.annoteor$I.annoteSchema. - Add or fix any missing documentation.
- Verify TSDoc grammar — no
{type}blobs in tags, no@template, no@module, no hyphen on@returns. - Run
bun run docgento verify every example compiles. - Run
bun run beep docgen quality -p <package>when touching a package and use the report as advisory remediation input. - Fix compilation failures in examples until docgen passes.
JSDoc Block Structure
Every exported symbol MUST have this minimum structure:
/**
* Brief one-line description.
*
* @example
* ```ts
* import { Effect } from "effect"
* import * as S from "effect/Schema"
*
* const result = MyModule.myFunction(args)
* console.log(result)
* ```
*
* @category constructors
* @since 0.0.0
*/
Tag order within the block:
- Description
@remarks(when semantics are non-obvious)@example(one or more)@typeParam(when constrained or non-obvious)@param(when prose adds beyond name + type)@returns(when prose adds beyond type)@throws(synchronous throws / defects only)@effects(custom — side effects)@precondition/@postcondition/@invariant(custom — contracts)@see@deprecated(with{@link}migration target)@public/@beta/@alpha/@internal/@experimental@category(required, canonical kebab-case slug)@since(required,0.0.0)
Quality Rubric
The report-only beep docgen quality command scores the whole JSDoc block, not
just whether tags exist. Treat @example as universal for exported symbols; for
error classes, type-only helpers, constants, and schemas, choose a handling,
narrowing, construction, or import example that fits the symbol.
Re-export declarations are graph edges, not symbol-quality subjects. Document the exported symbol at its owning declaration instead of inventing a fake barrel example.
A useful example is fenced TypeScript and shows an observable result:
assertion, returned value, decoded value, Effect execution, visible output, or
type-level evidence. For type-only exports, useful evidence includes named
aliases, assignability or satisfies checks, Equal/Expect-style assertions,
or comments that show inferred types. const result = ...; void result is a
compile trick, not documentation.
TSDoc Grammar Hard Rules
The following are violations the post-pass MUST catch and fix:
{type}blobs in@param,@returns,@throws— drop the braces. The TS signature is authoritative.@param x {string} - descbecomes@param x - desc.@template— replace with@typeParam.- Hyphen after
@returns— drop it. The hyphen separator is@param-only.@returns - The countbecomes@returns The count. @module— replace with@packageDocumentationfor package entry-point files.@deprecatedwithout{@link}migration target — every deprecation must point at its replacement.
Conditional Tag Rules
These tags appear only when they encode information not present in the TS signature:
| Tag | Add when | Skip when |
|---|---|---|
@param |
Parameter has units, constraints, ordering, or interactions beyond name + type | Parameter is self-explanatory from name: Type |
@returns |
Return value has ordering, filtering, ownership, or semantic interpretation | Return type is Effect<A, E, R> and channels speak for themselves |
@typeParam |
Constraint or semantic role isn't obvious from the parameter name | Trivial generic like <A> |
@throws |
Synchronous throw or defect not in typed error channel | All errors are in the E channel of an Effect |
@remarks |
Non-obvious semantics, ordering guarantees, idempotency, or complexity | Behavior is fully obvious from name + signature |
@effects |
Function performs writes, publishes, cache mutations, or other side effects | Function is pure |
The default for conditional tags is omit. Add prose-padding @param/@returns
that just restates the signature is a bug, not thoroughness.
Import Aliases in Examples
Mandatory aliases inside every @example code fence:
| Module | Alias | Correct | Forbidden |
|---|---|---|---|
effect/Schema |
S |
import * as S from "effect/Schema" |
import { Schema } |
effect/Array |
A |
import * as A from "effect/Array" |
import { Array } |
effect/Option |
O |
import * as O from "effect/Option" |
import { Option } |
effect/Predicate |
P |
import * as P from "effect/Predicate" |
import { Predicate } |
effect/Record |
R |
import * as R from "effect/Record" |
import { Record } |
Core combinators use named imports: import { Effect, Console, Layer } from "effect".
Never import from the deprecated @effect/schema package.
Schema Annotation Requirements
Every schema definition must carry identity annotations via the file-local $I
composer created from the package identity.
Class schemas (S.Class, Model.Class, TaggedErrorClass)
The third argument to the class factory receives $I.annote:
class MyEntity extends S.Class<MyEntity>($I`MyEntity`)(
{ /* fields */ },
$I.annote("MyEntity", {
description: "A meaningful description reflected in JSDoc for the schema."
})
) {}
TaggedErrorClass
Same pattern, but imported from @beep/schema:
class MyError extends TaggedErrorClass<MyError>($I`MyError`)(
"MyError",
{ message: S.String, cause: S.Defect({ includeStack: true }) },
$I.annote("MyError", {
description: "Describes when and why this error occurs."
})
) {}
Non-class schemas
Use $I.annoteSchema via .pipe(...):
const MySchema = S.String.pipe(
S.pattern(/^[a-z]+$/),
$I.annoteSchema("MySchema", {
description: "A meaningful description."
})
)
LiteralKit schemas
Use .annotate($I.annote(...)):
const Status = LiteralKit(["active", "inactive"]).annotate(
$I.annote("Status", {
description: "Entity lifecycle status."
})
)
Union, TemplateLiteral, and composed schemas
Use $I.annoteSchema(...) inside .pipe(...):
const MyUnion = S.Union([SchemaA, SchemaB]).pipe(
$I.annoteSchema("MyUnion", {
description: "Discriminated union of A and B."
})
)
Category Conventions
Use canonical kebab-case slugs. Choose the exported symbol's semantic role, not
its package location. The code source of truth is
packages/tooling/tool/cli/src/commands/Shared/JSDocCategories.ts.
Canonical groups:
- Core API roles:
models,schemas,type-level,constructors,factories,destructors,combinators,predicates,guards,refinements,assertions,getters,setters,mapping,filtering,folding,sequencing,concurrency,resource-management,error-handling,utilities,layers - Domain roles:
aggregates,entities,value-objects,domain-events,policies,specifications,identifiers,entity-ids,type-ids,symbols,errors - Application and ports:
use-cases,commands,queries,events,workflows,processes,schedulers,protocols,ports,services,handlers,endpoints,clients,adapters,repositories,projections,read-models,tables - Data boundaries:
validation,parsing,encoding,decoding,serialization,codecs,formatting,normalization,dtos,mappers - UI and client state:
components,hooks,providers,themes,tokens,forms,atoms - Tooling and cross-cutting:
tools,tool-schemas,cli-commands,configuration,constants,observability,diagnostics,fixtures,testing,streams,resources,interop
Legacy values such as DomainModel, Utility, UseCase, PortContract, and
ToolSchemas are migration aliases only. New or touched JSDoc should use the
canonical slug.
Type Alias Convention
Every non-class schema that is exported must also export a same-name runtime type alias immediately after it:
export const MySchema = S.String.pipe(
$I.annoteSchema("MySchema", { description: "..." })
)
/**
* Type for {@link MySchema}. {@inheritDoc MySchema}
*
* @category models
* @since 0.0.0
*/
export type MySchema = typeof MySchema.Type
The type alias JSDoc uses {@link} and {@inheritDoc} to avoid duplicating
the description.
Forbidden Patterns in Examples
anytypes — never.- Type assertions (
as,as unknown as) — never. declarestatements — never.- Non-compiling code — every example must pass
bun run docgen. import { Schema } from "effect/Schema"— use theSalias.from "@effect/schema"— the package is deprecated.- Removing examples to fix compilation — always fix the example instead.
import { Array }/import { Option }etc. — use namespace aliases.- Empty
Effect.genbodies — examples must be complete and demonstrate real usage.Effect.gen(function* () {})with no body is forbidden. @templateinstead of@typeParam— replace.{type}blobs in tags — drop the braces.@moduleinstead of@packageDocumentation— replace.
Agent Context Lifting Rules
When a downstream agent loads a symbol's documentation as context for code generation, surface tags selectively based on what the agent is doing:
When generating a call site for a symbol
Lift these into the prompt:
- Always:
@deprecated(with migration target) — block use of deprecated APIs - Always: The TS signature
@effects— so the agent reasons about side effects in surrounding code@precondition— so the agent verifies preconditions at the call site@invariant— so the agent knows what state is preserved@throws— so the agent handles defects@remarks— for non-obvious semantics
Skip:
@param/@returnswhen they restate the signature (the agent can read the signature)@example(the agent generates its own usage)@since/@category(irrelevant to call-site generation)
When generating an implementation for a symbol
Lift:
- The TS signature
@postcondition— what the implementation must guarantee@invariant— what the implementation must preserve@remarks— describing intent and complexity@throws— defects the implementation may produce@effects— side effects the implementation must perform
When choosing between candidate symbols
Lift:
@public/@beta/@alpha/@experimental— prefer stable APIs@deprecated— never pick deprecated symbols when alternatives exist@remarks— to disambiguate similar APIs
This is the most direct lever for using documentation to improve agent output quality. Generated tags that don't end up in agent context are write-only information.
Post-Pass Checklist
Run this checklist against every file before finishing:
Required tag presence
- Every
export const,export function,export class,export interface,export typehas a JSDoc block. - Every JSDoc block contains at least one
@examplewith a code fence. - Every JSDoc block contains
@category(canonical kebab-case slug from the standard list). - Every JSDoc block contains
@since 0.0.0.
Whole-block quality
- Description explains purpose rather than restating the symbol name.
@exampleshows meaningful input and an observable result, or type-level evidence for type-only exports.- Error, type-only, schema, and constant symbols still have examples suited to their shape; re-export declarations point at owning symbol docs.
Conditional tag correctness
@param/@returns/@typeParam/@throwsare present only when they add information beyond the signature; absent when they would just restate it.@remarksis present on combinators with non-obvious semantics, ordering guarantees, or idempotency claims.@effectsis present on functions that write, publish, or mutate state beyond what the type signature reveals.@deprecatedincludes a{@link}migration target.
TSDoc grammar correctness
- No
{type}blobs appear in any@param,@returns, or@throwstag. - No
@templatetags appear; all type parameters use@typeParam. - No
@returnshas a hyphen separator. - No
@moduletag appears; package entry points use@packageDocumentation.
Schema annotation requirements
- Every
S.Class/Model.Classcall has$I.annote(...)as the third arg. - Every
TaggedErrorClasscall has$I.annote(...)as the third arg. - Every non-class schema value has
$I.annoteSchema(...)in its pipe. - Every
LiteralKitvalue has.annotate($I.annote(...)). - Every non-class schema export has a same-name
export typealias.
Import and pattern correctness
- All
@examplecode fences use correct import aliases. - No empty
Effect.gen(function* () {})bodies in examples. - No forbidden patterns (
any, type assertions,declare, deprecated package imports).
Custom tag registration
- If
@effects,@precondition,@postcondition, or@invariantappear in any file,tsdoc.jsonregisters them as block tags (verify once per workspace, not per file).
Final compilation
bun run docgenpasses with zero errors.bun run beep docgen quality -p <package>produces a reviewable report.
Grep Verification Commands
Use these to audit a file or directory for compliance gaps:
Required-tag presence
# Exports missing JSDoc (heuristic — check lines above each match)
rg "^export (const|function|class|interface|type)" --type ts
# Files with @example but missing @since
rg "@example" --type ts -l | xargs rg -L "@since"
# Files with @example but missing @category
rg "@example" --type ts -l | xargs rg -L "@category"
TSDoc grammar violations
# Type braces in @param / @returns / @throws (TSDoc violation)
rg '@(param|returns|throws)\s+\{' --type ts
# @template instead of @typeParam
rg '@template\b' --type ts
# @returns with hyphen separator
rg '@returns\s+-\s' --type ts
# @module instead of @packageDocumentation
rg '@module\b' --type ts | rg -v '@packageDocumentation'
Conditional tag quality
# @deprecated without {@link} migration target
rg -B0 -A2 '@deprecated' --type ts | rg -v '\{@link'
# Empty Effect.gen bodies in examples (heuristic)
rg -A1 'Effect\.gen\(function\*\s*\(\)\s*\{' --type ts | rg -B1 '^\s*\}\)'
Import alias compliance
# Wrong Schema import alias
rg 'import \{ Schema \}' --type ts
rg 'from "@effect/schema"' --type ts
# Wrong Array / Option / Predicate / Record imports
rg 'import \{ (Array|Option|Predicate|Record) \}' --type ts
Schema annotation gaps
# S.Class without $I.annote
rg "extends S\.Class" --type ts -l | xargs rg -L "annote"
# TaggedErrorClass without $I.annote
rg "extends TaggedErrorClass" --type ts -l | xargs rg -L "annote"
Forbidden patterns
# Uses any type in examples or signatures
rg ': any' --type ts
# Type assertions
rg ' as unknown as ' --type ts
rg ' as [A-Z]\w+' --type ts
Internal symbol leakage
# @internal symbols re-exported from package index (potential leak)
rg -l '@internal' --type ts | while read f; do
base=$(basename "$f" .ts)
if grep -q "$base" "$(dirname "$f")/../index.ts" 2>/dev/null; then
echo "Potential @internal leak: $f"
fi
done
Escalation
- Use
schema-first-developmentwhen the task is schema modeling beyond annotation work. - Use
effect-first-developmentwhen the task is broader than documentation. - Use
effect-error-handlingwhen defining new TaggedErrorClass hierarchies.
Source References
.patterns/jsdoc-documentation.md— primary JSDoc/TSDoc standardtsdoc.json(workspace root) — custom tag registrations for@effects,@precondition,@postcondition,@invariantpackages/common/schema/src/SemanticVersion.ts— TemplateLiteral + annoteSchemapackages/tooling/tool/cli/src/commands/Quality/Tasks.ts— TaggedErrorClass + annotepackages/common/schema/src/Duration.ts— S.Class + annote + LiteralKit + annotate