Skip to content

build(SDK-970): create agents and skills for tsdoc#1987

Merged
mariechatfield merged 13 commits into
mainfrom
marie/SDK-970-skill-for-tsdoc
Jun 2, 2026
Merged

build(SDK-970): create agents and skills for tsdoc#1987
mariechatfield merged 13 commits into
mainfrom
marie/SDK-970-skill-for-tsdoc

Conversation

@mariechatfield
Copy link
Copy Markdown
Contributor

@mariechatfield mariechatfield commented Jun 1, 2026

Summary

Set up claude agents and skills that can orchestrate them to backfill API documentation with intention.

Changes

Generate api-report md as the source of truth for what is exported

Add @mircosoft/api-extractor as a dev dependency and include a command to build the API report with full warnings

  • This is not included in any CI or build requirements, because for now we are only using it as a source of truth for documentation quality
  • Long term I would love to move this and the endpoints-inventory:derive scripts into a CI build step so they are autogenerated on every branch that would change them
// Warning: (ae-forgotten-export) The symbol "FederalTaxesProps" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "FederalTaxes" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
function FederalTaxes(props: FederalTaxesProps & BaseComponentInterface): JSX_2.Element;

Create build/tsdoc-stub.ts to build initial comments based on AST

npx tsx build/tsdoc-stub.ts \
  --file src/components/Company/FederalTaxes/FederalTaxes.tsx \
  --all-exports

Each export then gets emitted with context for the LLM about what the symbol's name is, where to insert the new comment, and the complete text of the declaration so we can skip expensive file reads.

SYMBOL: FederalTaxes
LINE:32
DECLARATION:
export function FederalTaxes(props: FederalTaxesProps & BaseComponentInterface) {
  return (
    <BaseComponent {...props}>
      <Root {...props}>{props.children}</Root>
    </BaseComponent>
  )
}
---
EVENTS:
COMPANY_FEDERAL_TAXES_DONE company/federalTaxes/done
COMPANY_FEDERAL_TAXES_UPDATED company/federalTaxes/updated
---
/**
 *
 * @param props -
 * @returns
 * @public
 */

Create /tsdoc-file skill that writes quality prose based on script

Allow the LLM to autocomplete the partial documentation then edit the files in place.

 /tsdoc-file src/components/Company/FederalTaxes/FederalTaxes.tsx 

The skill, without any additional context, emits this:

/**
 * Collects and updates a company's federal tax information.
 *
 * @remarks
 * Renders the form with default sub-components ({@link Head}, {@link Form}, {@link Actions}).
 * To customize the layout, pass children — the form context is provided so
 * children can compose their own layout from those sub-components.
 *
 * | Event | Description | Data |
 * | ----- | ----------- | ---- |
 * | `company/federalTaxes/updated` | Federal tax details were saved | {@link FederalTaxDetails} |
 * | `company/federalTaxes/done` | User completed the step | — |
 *
 * @param props - Company ID and optional default field values; server data takes precedence over `defaultValues` when the company already has values on file.
 * @returns The federal taxes form wrapped in a `BaseComponent` error boundary.
 * @public
 */     

Create tsdoc-api-documenter agent that uses the /tsdoc-file file skill plus context

The agent is responsible for gathering source material related to a file (whether from existing docs or MCP tool calls) to include in the context, along with running any generated comments based on that context through the eslint rules until they are resolved.

Running the agent with just FederalTaxes, it looked up docs/hooks/useFederalTaxesForm.md (employee version, disregarded) and docs/workflows-overview/company-onboarding.md. The comment it ended up generated looked more like this:

/**
 * Renders a form for capturing a company's federal tax information.
 *
 * Collects the company's EIN, tax payer type, filing form, and legal name, then submits them
 * to update the company's federal tax details. The form pre-populates from any federal tax
 * details already on file and falls back to `defaultValues` for fields the API has not yet
 * returned.
 *
 * @remarks
 * Emits the following events through {@link CommonComponentInterface.onEvent}:
 *
 * | Event | Description | Data |
 * | --- | --- | --- |
 * | `COMPANY_FEDERAL_TAXES_UPDATED` | Federal tax details were successfully updated. | The updated `FederalTaxDetails` entity returned by the API. |
 * | `COMPANY_FEDERAL_TAXES_DONE` | The federal tax update process is complete. | None. |
 *
 * @param props - The component props. `companyId` identifies the company being updated.
 * `defaultValues` pre-fills `legalName`, `taxPayerType`, and `filingForm` when the API has
 * no value for a given field. Also accepts the standard {@link CommonComponentInterface} and
 * {@link BaseComponentInterface} props (`onEvent`, `className`, `dictionary`, `children`).
 * @returns The federal taxes form. When `children` are supplied, the form renders the
 * children inside its `FormProvider` instead of the default `Head` / `Form` / `Actions` layout.
 * @public
 */

Create tsdoc-backfill agent that is responsible for turning on strict linting

This agent doesn't write docs directly. It updates the eslint config to make sure that a directory starts doing stricter linting on comments, then gathers a list of all violations per file.

Calling the agent on FederalTaxes means it will update the eslint config to include that directory in strict linting:

  {
+     files: ['src/components/Company/FederalTaxes/**/*.{ts,tsx}'],
      ignores: LIBRARY_IGNORE_PATHS,
      rules: {
        'tsdoc-coverage/require-comment': 'error',
        'tsdoc-coverage/require-release-tag': 'error',
      },
    },

And the output it emits includes a list of lint violations:

Violations found: 7 across 5 files

       Violation list:
       - src/components/Company/FederalTaxes/Actions.tsx:6 — tsdoc-coverage/require-comment — Actions
       - src/components/Company/FederalTaxes/FederalTaxes.tsx:32 — tsdoc-coverage/require-comment — FederalTaxes
       - src/components/Company/FederalTaxes/Form.tsx:12 — tsdoc-coverage/require-comment — Form
       - src/components/Company/FederalTaxes/Head.tsx:5 — tsdoc-coverage/require-comment — Head
       - src/components/Company/FederalTaxes/useFederalTaxes.ts:10 — tsdoc-coverage/require-comment — FederalTaxFormSchema
       - src/components/Company/FederalTaxes/useFederalTaxes.ts:17 — tsdoc-coverage/require-comment — FederalTaxFormInputs
       - src/components/Company/FederalTaxes/useFederalTaxes.ts:19 — tsdoc-coverage/require-comment — FederalTaxesDefaultValues

Create /tsdoc-directory skill to orchestrate everything

This skill

  • runs the api report so it has a good source of truth to start with
  • uses the tsdoc-backfill agent to upgrade the directory to strict linting and figure out which files need better docs
  • kicks off the tsdoc-api-documenter in the background with that list of files so that it can gather context and start writing documentation; with the strict linting enabled, it knows when each comment finally passes

This skill also knows to rebuild the api report and compare the diff to make sure we aren't introducing new issues (for example, {@link CommonComponentInterface.onEvent} is a broken link since it references an internal, non-exported interface so the skill is able to replace it with onEvent)

Final sample output for FederalTaxes component:

/**
 * Renders a form for capturing a company's federal tax information.
 *
 * Collects the company's EIN, tax payer type, filing form, and legal name, then submits them
 * to update the company's federal tax details. The form pre-populates from any federal tax
 * details already on file and falls back to `defaultValues` for fields the API has not yet
 * returned.
 *
 * @remarks
 * Emits the following events through `onEvent`:
 *
 * | Event | Description | Data |
 * | --- | --- | --- |
 * | `COMPANY_FEDERAL_TAXES_UPDATED` | Federal tax details were successfully updated. | The updated `FederalTaxDetails` entity returned by the API. |
 * | `COMPANY_FEDERAL_TAXES_DONE` | The federal tax update process is complete. | None. |
 *
 * @param props - The component props. `companyId` identifies the company being updated.
 * `defaultValues` pre-fills `legalName`, `taxPayerType`, and `filingForm` when the API has
 * no value for a given field. Also accepts the standard `onEvent`, `className`, `dictionary`,
 * and `children` props from the base component interfaces.
 * @returns The federal taxes form. When `children` are supplied, the form renders the
 * children inside its `FormProvider` instead of the default `Head` / `Form` / `Actions` layout.
 * @public
 */

Diff in the embedded-react-sdk.api.md report:

  // Warning: (ae-forgotten-export) The symbol "FederalTaxesProps" needs to be exported by the entry point index.d.ts
- // Warning: (ae-missing-release-tag) "FederalTaxes" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
  //
- // @public (undocumented)
+ // @public
  function FederalTaxes(props: FederalTaxesProps & BaseComponentInterface): JSX_2.Element;

Related

Testing

I have been running this locally on a variety of different directories, and I think it's finally ready. You can run this yourself locally with the command /tsdoc-directory <path> but I don't recommend checking in any generated files yet.

mariechatfield and others added 10 commits June 1, 2026 14:38
The write-tsdoc skill documents a single exported symbol given its code
and a prose description. The stub generator (scripts/tsdoc-stub.ts) extracts
type parameters, parameter names, and return type from the TypeScript
signature via ts-morph, producing a pre-filled skeleton so Claude fills
in prose only — not structure.

Workflow:
  npm run tsdoc:stub -- --file <path> --symbol <name>
  → grep .reports/embedded-react-sdk.public.api.md for release tag
  → fill in descriptions from provided prose

Also excludes scripts/ from ESLint (same treatment as build/).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…efinements

- Script now detects existing comments (JSDoc, block, line) and checks
  structural alignment (params, typeParam, returns, release tag) before
  emitting a skeleton — skips if aligned, replaces with pre-filled summary
  if not
- Output includes DECLARATION: block so the skill never needs to read the
  source file; replace case adds DELETE_THROUGH: and OLD_COMMENT: blocks
- --default-release flag controls fallback tag (default: alpha); script
  resolves @public automatically from the api-extractor report
- Skill updated to reflect new output format, write-to-file step, and
  scope guard matching LIBRARY_BASE_PATHS / LIBRARY_IGNORE_PATHS

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… skill

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e, events table rules, and api-report step

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cost

Token-profiler analysis showed the documenter was calling tsdoc-stub once
per symbol, burning a full cache-read turn (~87K tokens) per symbol. Updated
write-tsdoc to lead with --all-exports / --symbols and forbid per-symbol
calls when multiple symbols need documenting in the same file. Updated the
document-directory Phase 2 prompt to drive a file-first loop so the agent
generates all skeletons in one stub call before writing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rename write-tsdoc → tsdoc-file and document-directory → tsdoc-directory.
Add Phase 0 baseline build step to tsdoc-directory so the API report diff
reflects only documentation changes made during the run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@mariechatfield mariechatfield marked this pull request as ready for review June 1, 2026 23:17
@mariechatfield mariechatfield requested a review from a team as a code owner June 1, 2026 23:17
Copy link
Copy Markdown
Member

@serikjensen serikjensen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good overall! i had a few questions here for clarification but i don't think anything blocking

Comment thread .claude/agents/tsdoc-api-documenter.md Outdated

**ComponentsContext**: Note that UI rendering goes through `useComponentContext()` and visual output depends on the configured component set.

**Field Components** (`src/components/Common/Fields/`): Must be used inside a `FormProvider` from react-hook-form.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are interesting codebase patterns to pull out. Are these meant to be codebase patterns that have implications for partners? Just wondering if there's a rhyme or reason to what gets included in this section. If it's just patterns generally, there may be more worth adding. But not sure if being exhaustive is important here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I may delete this section, like you said it's not deeply exhaustive and it's just extra context added to every session that uses this agent

Comment thread .claude/agents/tsdoc-api-documenter.md Outdated

**Field Components** (`src/components/Common/Fields/`): Must be used inside a `FormProvider` from react-hook-form.

**Partner hooks**: For hooks returning `errorHandling`, document how the result integrates with `composeErrorHandler`. Reference `composeSubmitHandler` for multi-form screens.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah same kind of feedback here, there's much we could say about hook patterns so this is an interesting one to pull out

Comment thread .claude/agents/tsdoc-api-documenter.md Outdated

Stop and ask the human before continuing if any of these occur:

- **Comment length**: The TSDoc comment you are drafting is more than twice as long as the code it documents.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonder if this merits a stop? I suppose it could document something really non standard, but in my experience sometimes claude is just having a verbose day and maybe instructing to be more succinct? I assume we already have some guidance for claude on comment length and keeping things to the point?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should delete this too, it's not applicable and it's not stopping anyway since the comments for flow components are always massive compared to the function body 😭

Comment thread .claude/skills/tsdoc-file/SKILL.md Outdated
| `event/string/value` | What triggers it | {@link DataType} or — |
```

Use `{@link TypeName}` (importing the type if needed) for the Data column when a type from `@gusto/embedded-api/models/components/` matches. Use `—` when the event carries no data.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this pull in the data type for the rendered docs? or does this link to source somewhere?

Also note there are some events that aren't from embedded-api so we may also need to consider a way of presenting those?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Urgh this was a half-effort on my part that I need to rethink. The best way to do this is:

  • make sure we consistently re-export the models from @gusto/embedded-api-v-2025-11-15 that are included in event bodies. It's not sufficient to say "look at the API docs" because those reference the snake_case params but the actual response is camelCase properties
  • switch to a rolled up .d.ts file. there's an option in our vite config that we can switch to rollup: true which uses api-extractor under the hood, or we can do it ourselves since we already have the api extractor installed for the reporting
    • this means we don't have to import { FederalTaxDetails } from '@gusto/embedded-api-v-....' just to reference it in a comment and have it resolve in an IDE
    • TypeDoc can handle it as long as that model is exported somewhere in the barrel files, it uses the global namespace to resolve links. but IDEs are dependent on what exists in a single d.ts file
    • we may still want to add a lint rule to force ourselves to use the imports so we can have nice linking while writing
  • and then we can tell the documenters to always use the correct model

Right now I think I just want to remove this specific rule and just say to indicate what the response is or link to... the public API page? Or just write something and we can standardize it later?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2026-06-02 at 1 01 23 PM Screenshot 2026-06-02 at 1 01 35 PM

Here's a sample of what kind of markdown is emitted if we re-export the TS client types and link them. If the speakeasy generated types are already documented somewhere, we don't have to re-publish them here; we could just link to the URL of those docs. But the benefit of doing it this way is that the types are right there and we need them anyway if we ever want to add more specific typing to our event payloads 🤷🏻

mariechatfield and others added 3 commits June 2, 2026 13:08
…ofiling

- Remove Codebase-Specific Patterns section from documenter (not exhaustive, adds noise)
- Remove comment-length guardrail (not effective for flow components)
- Add no-internal-leakage rule: public/beta/alpha comments must not mention internal symbols
- Improve ESLint guidance: fix all errors in one pass, confirm with a single re-run
- Backfill: use ESLint output directly for violation list, don't re-read source files
- Backfill Case B: expand existing override block's files array instead of duplicating the block
- Simplify events table Data column: plain text description instead of requiring cross-package links
- Directory skill: batch documenter sessions by subdirectory (≤5 files each), parallel across groups

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@mariechatfield mariechatfield merged commit 0dac010 into main Jun 2, 2026
28 checks passed
@mariechatfield mariechatfield deleted the marie/SDK-970-skill-for-tsdoc branch June 2, 2026 21:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants