Skip to content

feat(cli): doctor integration validate checks each doc against its published authoring type - #7673

Merged
josephfarina merged 5 commits into
mainfrom
josephfarina/doctor-doc-types-validate
Oct 19, 2026
Merged

josephfarina merged 5 commits into
mainfrom
josephfarina/doctor-doc-types-validate

Conversation

@josephfarina

@josephfarina josephfarina commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

User need

#7664 adds a check that reads each doc against the type @astryxdesign/cli/authoring publishes for its kind, the way tsc would. Nothing runs it yet, so an integration still learns about an unknown field, a missing displayName, or a malformed usage entry only when someone runs tsc on its docs, if ever.

Problem and solution fit

astryx doctor integration validate now finds every doc an integration's roots hold (components, templates, docs topics and their overlays, themes, each file given to the deepest root that holds it) and runs #7664's check on them. Each finding is a warning naming the doc, the line, the field path, the problem (unknown_field, missing_field or wrong_type), TypeScript's message, and a fix. Without TypeScript it prints one line saying the docs weren't type-checked.

Core gets the same check as a repository test, packages/cli/test/core-doc-types.test.mjs. Core's component, hook, template, theme and topic docs all pass; the 307 mismatches it has today are all in translations, listed in core-doc-types.known-drift.json, a list that only shrinks.

Current specification

Implements architecture:cli-surface INV34, "Every descriptor satisfies its published authoring type" (AST-039 FR13, which now points to it; DEC-5), with #7664. Its evidence row names the doc-types, doctor-integration and core-doc-types tests, which this PR's tests satisfy.

Public delta and caller burden

  • astryx doctor integration validate: after its summary, a "Checking N doc(s) against the authoring types (TypeScript x.y)" section with the findings, each with every field it carries (doc, line, export, type, field, problem, message, fix), then [ok] Every doc matches its authoring type (N checked). (or, after another check failed, [info] No doc field differs from its authoring type (N checked).); or one [skip] Docs not type-checked: … line; or, with no doc files, [info] No doc files to type-check. Exit codes are unchanged: findings are warnings.
  • JSON: one additive field, present when the manifest loads, data.docTypes = {status: 'checked' | 'skipped', reason?, typescript?, docs, findings[], unchecked[]}. Each finding is {doc, line, export, type, field, problem, severity: 'warning', message, fix}.
  • No new command, flag, option or environment variable.

Example: an Acme component doc with keyword: and a block template without displayName or aspectRatio. Both load today, and validate reports no issues:

Checking 2 doc(s) against the authoring types (TypeScript 6.0.3)

severity: [warn]
doc:      components/AcmeButton.doc.mjs
line:     7
export:   default
type:     ComponentDoc
field:    keyword
problem:  unknown_field
message:  Object literal may only specify known properties, but 'keyword' does not exist in type 'SingleComponentDoc'. Did you mean to write 'keywords'?
fix:      Rename `keyword` to `keywords`.

severity: [warn]
doc:      templates/acme-card.doc.mjs
line:     1
export:   default
type:     IntegrationTemplateDoc
field:    aspectRatio
problem:  missing_field
message:  Type '{ type: "block"; name: string; description: string; }' is missing the following properties from type 'IntegrationBlockTemplateDoc': displayName, aspectRatio
fix:      Add `aspectRatio`: IntegrationBlockTemplateDoc requires it.

(The same template's displayName finding follows; the command still exits 0.)

Design and alternatives

  • In the docs subject's adapter. The check reads the package's docs and loads the project's TypeScript, so it runs from api/docs/_adapter.mjs (cli-surface INV21). validate-integration.mjs only attaches the report. Finding the files is its own module, foundation/discovery/integration-doc-files.mjs.
  • Warnings first. Packages already published have these mismatches, and an error would break them on upgrade. A build that wants to fail on them opts in with astryx integration check --strict (feat(cli): integration check runs every source check for a package's build, with --strict #7665).
  • Rejected: a flag or environment variable to turn it on.

Evidence

  • foundation/discovery/integration-doc-files.test.mjs (each root's docs and overlays, a nested root's files given to that root, skipped folders), doctor-integration.test.mjs (text with each finding's export and type and the docs count, JSON, the skip line, the line for a package with no doc files, and a test that compares the JSON with the text field by field for each problem kind and each doc not checked, via test-utils/text-projection.mjs), and the Core ratchet test above.
  • Core: 1,502 doc values in 1,111 files checked in about 3.5 s; 307 findings, all in translations, exactly the list.

Since the first automated review

  • The text shows each finding's export and type, and the section header says how many docs were checked, so every JSON field shows in the text.
  • A package with no doc files gets [info] No doc files to type-check., not an empty section or a request to install TypeScript.
  • The template guide's sentence that validate warns about a template without displayName or aspectRatio lands here, where it becomes true.
  • A test compares validate's JSON docTypes with its text, field by field, so every field stays in the text view. The no-TypeScript test looks for the current section heading, so it can fail.
  • The changeset says the skip line names a TypeScript the check cannot use, and that a package with no doc files gets a line saying so.
  • When another check fails and every doc matches, the section ended at its heading. It says [info] No doc field differs from its authoring type (N checked). now, without an [ok] after the failure.
  • An integration's templates are checked against feat(cli): check each authored doc against its published authoring type #7664's new IntegrationTemplateDoc, so the example's template findings name it. Its section in astryx docs authoring says validate checks them against it. The Core test holds Core's own templates to TemplateDoc, which is unchanged.

After the latest review

  • The check skipped docs the CLI serves, without listing them: component and topic docs in __fixtures__, in dot-folders or behind symlinks, and templates under __tests__. A package could get [ok] while those docs drifted. The check now takes each root's files from what the reader serves, as described above, and nothing else: an audit found a second component doc of one name checked though astryx component serves the first, which no longer happens. A themes root the reader cannot read is listed under unchecked, so nothing is skipped silently. Tests cover a __fixtures__ doc, a dot-folder doc and a symlinked doc, from the file set through validate's JSON, and a hidden doc, a second doc of one name, a template without its source and an overlay without its topic, none of them checked.
  • integration add template writes a template doc that declares IntegrationTemplateDoc, and the page, block and replace-a-Core-template guides' samples do too.
  • In a scoped package, namespace docs and placed guides were looked up under the wrong folder (docs/widgets/… for @acme/widgets), so they weren't checked, and unchecked listed files that don't exist. Their paths come from the reader now: a guide's ref.topicFile, and a namespace's source without its package name. A placed guide's zh and dense overlays, which astryx docs <route> --zh applies, are checked too. Tests cover a scoped package's namespace doc, placed guide and guide overlays.

Which docs the check covers

Every doc the package ships under its roots. Each root's reader names the docs it serves, with the kind it reads each as:

  • components: the doc component discovery records for each component, so a doc in __fixtures__, in a dot-folder or behind a symlinked file is checked;
  • templates: the doc of each template that template discovery serves, so a template under __tests__ is checked too;
  • docs: the file of each topic, namespace and placed guide that docs discovery serves, where it is (in a scoped package too), and each topic's and guide's zh and dense overlay that a docs read applies;
  • themes: each theme's descriptor, as theme discovery resolves it. A themes root that discovery cannot read serves no theme; it is listed under unchecked with the reason.

A doc no reader serves still ships, and an agent can still open it, so it is checked with its root's kind: a hidden doc, a second doc for one name, a template doc without its source, an overlay without its topic. A test fixture doesn't ship as a doc: a doc under __tests__ in the components or docs root, which those readers skip. It isn't checked, and validate says how many once ([info] N test fixture doc(s) not checked., docTypes.fixtures in JSON). The template reader reads __tests__ and __fixtures__, so a template doc there is checked like any other, as validate's own template check sees it. A doc that doesn't load is also reported by its reader's own check in validate. One doc is checked that no route serves: a placed guide whose parent namespace doesn't exist. doctor integration docs reports it as an error, and its fields are checked so that the fix needs no second pass.

After the review that came next

  • A components, templates or docs root that validate couldn't read, or a folder inside one, made validate stop with a raw EACCES (ERR_UNKNOWN in JSON) instead of main's unreadable_root or invalid_* issue. A root validate already reports is now left to that issue. Each reader runs in its own guard, so one that fails inside a root lists the root under unchecked with the error.
  • Tests cover an unreadable root and an unreadable folder, at the reader and through validate, whose issue codes match main's. CLI runs give main's exact issue codes for an unreadable components, docs, templates or themes root, and for an unreadable folder inside components, docs or templates.

Authoring docs

  • "Check your docs" gains a section on this check: what it reports (unknown_field, missing_field, wrong_type), real output, which type each root's docs are read against, where the findings are in JSON, and what it prints without TypeScript.

Rebased

After the review of INV34's coverage

  • The check took only the docs each reader serves, so a hidden doc, a second doc for a name and a doc under __tests__ were neither checked nor listed, and a package with a type error in each still got [ok]. INV34's "each descriptor an integration's roots hold" means every descriptor the package ships. The check now covers every doc file each readable root holds, as described above, and counts the test fixtures it leaves out: the docs under __tests__ that the components and docs readers skip. [ok] Every doc matches its authoring type prints only when nothing was left unchecked.
  • Tests cover each kind at the reader, plus a command test where a type error in a hidden doc and in a second doc for one name is reported and [ok] isn't printed. Both failed before.
  • It inherits feat(cli): check each authored doc against its published authoring type #7664's handling of a doc's own wrong type.

After the review of the fixture count

  • A fixture is judged by its path inside its root now. Before, a package that sat anywhere under a folder named __tests__ had its hidden and second docs counted as fixtures, unchecked, and could print [ok].
  • When fixtures are a package's only docs, the text keeps the count under [info] No doc files to type-check., as the JSON does.
  • docTypes.fixtures is documented in validateIntegration's docs and in the integration.validate response type, with the README row regenerated. The command docs, the API docs and the changeset now say that every shipped doc is checked and fixtures are counted, as the guide does.
  • Each fix has a test that failed first.

Merge order

The order is required:

  1. feat(cli): read a package's TypeScript project the way tsc reads it #7681 (reading the package's TypeScript project), then feat(cli): compare documented component props with the TypeScript source #7663 (the prop comparison, stacked on feat(cli): read a package's TypeScript project the way tsc reads it #7681), then feat(cli): doctor integration components compares documented props with the code #7676 (props in doctor integration components, stacked on feat(cli): compare documented component props with the TypeScript source #7663), then feat(cli): read two written TypeScript types as one when they differ only in how they are written #7684 (reading two written types as one, stacked on feat(cli): doctor integration components compares documented props with the code #7676), then feat(cli): doctor integration components judges documented prop types against the code #7667 (prop types, stacked on feat(cli): read two written TypeScript types as one when they differ only in how they are written #7684).
  2. feat(cli): check each authored doc against its published authoring type #7664 (the doc type check), after feat(cli): read a package's TypeScript project the way tsc reads it #7681: once feat(cli): read a package's TypeScript project the way tsc reads it #7681 merges, feat(cli): check each authored doc against its published authoring type #7664 is rebased onto main, which takes out the 65 lines the two share and brings it to 1,960, and reviewed again. Then feat(cli): doctor integration validate checks each doc against its published authoring type #7673 (doc types in validate, stacked on feat(cli): check each authored doc against its published authoring type #7664).
  3. fix(cli): doc links to a component, page, or block resolve #7668 (doc links to components and templates) any time before feat(cli): integration check runs every source check for a package's build, with --strict #7665.
  4. feat(cli): integration check runs every source check for a package's build, with --strict #7665 (integration check) last, after a rebase that adds these PRs' fields and codes to its failures list. Merged earlier, it breaks main.

This PR must ship in the same release as integration check. If it slips past that release, it can ship only in a minor, because its findings fail integration check --strict.

Overlap with open PRs

Size

1,623 changed lines. 309 of them are packages/cli/test/core-doc-types.known-drift.json, a data file the Core test reads, one line per mismatch Core's translations have today. Authored lines: 1,063.

Scope

  • One capability is added.
  • Unrelated fixes, cleanup, and policy changes were removed or split.
  • Consumer docs, focused tests, and migration evidence ship with the feature.
  • Public text and artifacts contain no internal Meta context.

Testing

The CI test jobs run only for pull requests into main, so this PR's tests run in CI once it is retargeted after #7664 merges. On main 91cfb53 with #7664 and this branch: copyright, sync-exports, the published ./api type check, readme:check, the four CLI typechecks (strict, json-api, authoring-contract, template-docs), check:changesets, check:cli-structure, check:repo, eslint on the changed files, prettier on the new files, and the full pnpm vitest run --project node suite: 10,318 tests pass. The 21 failures are in the npm pack, packed real-world package, theme resolution and CSS graph tests, which fail the same way on current main on the same machine (they need a package registry it doesn't reach), plus one browser accessibility test that is flaky there. With all nine PRs merged into main, the four doctor integration subcommands were run in text and JSON on 64 package shapes, against main: templates output is byte-identical everywhere; validate and components only add their new section and JSON field; docs differs only where the doc-links PR fixes a link warning. integration check exits 1 exactly when one of the four would, each of its sections is byte-identical to that subcommand's output, and only integration check's three list tests fail until its planned rebase.

@vercel

vercel Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 19, 2026 11:27pm UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🔄 In progress (for maintainers only) Oct 19, 2026, 11:22 PM UTC

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

Previews ready: Storybook and Sandbox were built with this PR's docsite. CI evidence will be added when available.

View Storybook for this PR · View Sandbox for this PR

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This PR is on hold for a maintainer decision: the doc type check it adds to astryx doctor integration validate (with the new data.docTypes field and the Core known-drift test) has no current spec yet, because the requirement it implements, AST-039 FR13, exists only in draft spec PR #7658 and AST-039 itself is still a draft. Approval needs a maintainer to approve that requirement, or point to another current record that covers it, including the warnings-only severity, whether findings belong in a separate report rather than the existing issues list, and whether Core's docs belong under the same check. Separately, two small fixes are needed:

  • Print each finding's export and type (and the number of docs checked) in the text output, since the CLI's response contract requires every JSON field in the text view.
  • When an integration has no doc files, say so instead of printing an empty "Checking docs…" section or asking the author to install TypeScript.

[Automated review]

const files = new Set(report.findings.map(f => f.doc)).size;
blocks.push(
records(report.findings, {
fields: [

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The JSON findings also carry export and type, but these text records drop them, so findings for the same field in default, docsZh and docsDense are told apart only by line. AST-017 FR13 asks for every field in the text projection, and cli-surface INV5 keeps every field greppable. Suggest adding 'export' and 'type' here, and the checked count (report.docs) to the summary line below.

records(report.unchecked, {fields: ['doc', 'reason']}),
);
}
if (report.findings.length === 0 && !failed && report.docs > 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

When no root holds a doc file (a codemods-only package, say), docs is 0, so nothing is printed after the section heading; and without TypeScript the [skip] line asks the author to install TypeScript although there is nothing to check. Suggest returning early from integrationDocTypes() when integrationDocFiles() finds nothing (no TypeScript lookup) and printing one outcome line for docs: 0 here.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This PR is on hold for a maintainer decision: the doc type check it adds to astryx doctor integration validate and validateIntegration() (with the new data.docTypes field and the Core known-drift test) has no current spec, because the requirement it implements, AST-039 FR13, exists only in draft spec PR #7658 and AST-039 itself is still a draft. Approval needs a maintainer to approve that requirement, or point to another current record that covers it, including the warnings-only severity, reporting findings in docTypes rather than in issues, which doc files the check covers, and whether Core's docs belong under the same check. The text output now shows every finding field and handles packages without docs, so no other change is required; as an optional cleanup, the no-TypeScript test still asserts against the old Checking docs against the authoring types heading, so that assertion can no longer fail.

[Automated review]

expect(output).toContain(
'[skip] Docs not type-checked: TypeScript is not installed in this project',
);
expect(output).not.toContain('Checking docs against the authoring types');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Optional: the heading is now Checking N doc(s) against the authoring types (…), so this string can never appear and the assertion can't fail. expect(output).not.toMatch(/Checking \d+ doc\(s\)/) keeps the intent; a bare 'against the authoring types' would trip on the [skip] reason itself.

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.

Fixed at ccf0bd6: the assertion looks for the current heading (/Checking \d+ doc\(s\) against the authoring types/), so it can fail.

records(report.unchecked, {fields: ['doc', 'reason']}),
);
}
if (report.findings.length === 0 && !failed && report.docs > 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Optional: when validate also reports an error-severity issue and every doc matches, the section ends at its heading with no outcome line, although the JSON shows the docs were checked with no findings. Saying so here too, or leaving out the heading in that state, would avoid an empty section.

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.

Fixed at ccf0bd6: when another check fails and every doc matches, the section says [info] No doc field differs from its authoring type (N checked). clients/cli/commands/doctor-integration.test.mjs › "validate says the docs matched even when another check failed" pins it.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This PR is on hold for a maintainer decision. The doc type check it adds to astryx doctor integration validate and validateIntegration(), with the new data.docTypes field and the Core known-drift test, has no current spec: AST-039 is still a draft on main, and the FR13 it implements exists only in draft spec PR #7658. Approval needs a maintainer to approve that requirement, or point to another current record that covers it, and to settle four points: the warnings-only severity; reporting findings in docTypes rather than in issues; which doc files the check covers (it skips docs in __fixtures__, dot-folders and symlinked files that component discovery still reads); and whether Core's own docs belong under the same check. Both earlier cleanups are in, and no other change is needed unless that decision changes the file set or leaves out the Core test.

[Automated review]

const DOC_FILE = /\.doc\.(?:ts|mjs|js)$/;
const TEMPLATE_FILE = /\.(?:doc|template)\.(?:ts|mjs|js)$/;
const OVERLAY_FILE = /\.doc\.(?:zh|dense)\.mjs$/;
const SKIP_DIRS = new Set(['node_modules', '__tests__', '__fixtures__']);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

For the scope decision on this check: this walk skips __fixtures__ and dot entries (here and at line 99) and takes only regular files (line 102). Integration component discovery skips only node_modules and __tests__, and it reads symlinked doc files. So a served doc in one of those places is neither checked nor listed under unchecked, and the [ok] Every doc matches… line can overstate coverage. If the check should cover every doc the CLI reads, as draft FR13 words it, matching the readers' walk or listing skipped docs as unchecked would close the gap.

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.

The PR body now sets this out under "Which docs the check covers (for the FR13 decision)": what each walk skips, what that leaves unchecked, and my view that the two sets should match (check exactly the doc files discovery returns). The scope is the owner's call, so this head leaves the walk as it is.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This PR is on hold for a maintainer decision: the doc type check it adds to astryx doctor integration validate and validateIntegration(), with the new data.docTypes field and the Core known-drift test, has no current spec, because AST-039 is still a draft on main and the FR13 it implements exists only in draft spec PR #7658. Approval needs a maintainer to approve that requirement, or point to another current record that covers it, and to settle four points: the warnings-only severity, reporting findings in docTypes rather than in issues, which doc files the check covers, and whether Core's own docs belong under the same check. On the file set, the integration readers also serve docs this walk skips without listing them as unchecked: component docs in __fixtures__, dot-folders and symlinked files, and templates under __tests__. No other change is needed unless that decision changes the file set or leaves out the Core test.

[Automated review]

return out;
}
for (const entry of entries) {
if (entry.name.startsWith('.') || SKIP_DIRS.has(entry.name)) continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

For the file-set decision, the template reader differs more than the PR body's comparison says: integration template discovery (findIntegrationDocFiles() in foundation/discovery/template-adapter.mjs) walks the templates root with no skip list, so a template under __tests__ (or node_modules) is served, while this walk skips it without listing it under unchecked. On a fixture, templates/__tests__/acme-tested.doc.mjs lacked displayName and aspectRatio, and validate reported nothing for it. Also, the hooks and utils skips the PR body attributes to component discovery belong to Core's reader; integration component discovery skips only node_modules and __tests__. Taking each root's files from its reader, as the PR body proposes, would cover both.

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.

Corrected in the body's section on which docs the check covers. Integration template discovery walks the templates root with no skip list. Integration component discovery skips only node_modules and __tests__. The hooks and utils skips are Core's reader only. The view is unchanged: take each root's files from the reader that serves them. That decision waits on the spec owner.

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.

Done in 67852a2. The check now takes each root's files from the reader that serves them: component, template and docs discovery, each topic's overlays that a docs read applies, and theme descriptors as theme discovery resolves them. A themes root the reader cannot read is listed under unchecked. Tests cover a __fixtures__ doc, a dot-folder doc and a symlinked doc, through the file set and through validate's JSON.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This PR is on hold for a maintainer decision: the doc type check it adds to astryx doctor integration validate and validateIntegration(), with the new data.docTypes field and the Core known-drift test, has no current spec, because AST-039 is still a draft on main and the FR13 it implements exists only in draft spec PR #7658. Approval needs a maintainer to approve that requirement, or point to another current record that covers it, and to settle four points: the warnings-only severity, reporting findings in docTypes rather than in issues, which doc files the check covers, and whether Core's own docs belong under the same check. On the file set, the CLI's readers still serve docs this walk skips without listing them as unchecked (component and docs-topic files in __fixtures__, dot-folders or symlinks, and templates under __tests__), so a package can get [ok] Every doc matches its authoring type while those docs drift: if FR13 is approved as worded, take each root's files from its reader or list the skipped docs under unchecked, and if the current set is kept, say in the docs which docs the check leaves out. Nothing else needs to change.

[Automated review]

@josephfarina
josephfarina force-pushed the josephfarina/doctor-doc-types branch from bd4443b to 257cec7 Compare October 18, 2026 09:31
@josephfarina
josephfarina force-pushed the josephfarina/doctor-doc-types-validate branch from dcbfdf0 to 67852a2 Compare October 18, 2026 09:31

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This PR is on hold for a maintainer decision: the doc type check it adds to astryx doctor integration validate and validateIntegration(), with the new data.docTypes field and the Core known-drift test, has no current spec, because the AST-039 FR13 it implements exists only in draft spec PR #7658 and AST-039 is still a draft on main. Approval needs a maintainer to approve that requirement, or point to another current record that covers it, and to settle the warnings-only severity, reporting findings in docTypes rather than in issues, the reader-based file set (the draft FR13 wording covers every descriptor a root holds), and whether Core's own docs belong under the same check. The unreadable-root fix works: an unreadable root, or an unreadable folder or doc file inside components, templates or docs, now gets the same unreadable_root and invalid_* issues as before, so no code change is needed unless that decision changes the file set or leaves out the Core test.

[Automated review]

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The doc type check now has current authority in architecture:cli-surface INV34, and the change matches it: warnings only, unchanged exit codes, a visible skip without TypeScript, and an additive data.docTypes field, each covered by the PR's tests. Approval waits on one maintainer ruling: whether INV34's "each descriptor an integration's roots hold" includes component docs that component discovery does not serve (a hidden: true doc, a second doc for one name, a doc under __tests__), which this PR neither checks nor lists under unchecked, so validate can print [ok] Every doc matches its authoring type while one of them is wrong. If INV34 covers them, please check them; if it means reader-served docs only, please narrow the new guide sentence that says validate reads "every doc your package ships", and the PR is otherwise ready once #7664 lands.

[Automated review]

await take(components, 'components', async () => [
[
'components',
discoverIntegrationComponents(integration).map(c => c.docPath),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This takes only the docs discoverIntegrationComponents() serves, so a hidden: true doc, a second doc for a name an earlier doc takes, and a doc under __tests__ are neither checked nor listed under unchecked; a package with a type error in each still gets [ok] Every doc matches its authoring type. INV34 says "each descriptor an integration's roots hold", and whether that covers these docs is waiting on a maintainer ruling. If it does, forEachIntegrationDoc() already visits hidden and shadowed docs (but not __tests__), so the components root could take its files from there; if it does not, this line can stay.

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.

Fixed in 87fdabd. Every descriptor the package ships under its roots is checked now, served or not: a hidden: true doc, a second doc for a name an earlier doc takes, a template doc without its source, and an overlay without its topic. A test fixture doesn't ship as a doc: a doc under __tests__ in the components or docs root, which those readers skip. It isn't checked, and validate says how many once: [info] N test fixture doc(s) not checked., and docTypes.fixtures in JSON. The template reader reads __tests__ and __fixtures__, so a template doc there is checked like any other. The new tests cover each kind, and the command test shows a package with a type error in a hidden doc and in a second doc for one name, now reported, with no [ok]. Both failed before.

content: [
{
type: 'prose',
text: "`doctor integration validate` reads every doc your package ships against the type `@astryxdesign/cli/authoring` publishes for its kind, the way `tsc` would. It reports a field the type does not have (`unknown_field`), a missing required field (`missing_field`), and a value outside the field's type (`wrong_type`).",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If the ruling is that INV34 means reader-served docs only, this sentence overstates the coverage: a hidden: true component doc ships with the package but is not checked. The CommandDoc's wording fits: "each doc the CLI's readers take from the package".

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.

With every shipped doc now checked, the sentence holds. It now also says that a hidden doc and a second doc for one name are checked, and that test fixtures under __tests__ in the components or docs root are counted, not checked.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The doc type check matches architecture:cli-surface INV34, including the wider file set (every shipped doc, with __tests__ fixtures counted), but four small fixes are needed before approval:

  • Document docTypes.fixtures in the validateIntegration FunctionDoc and in the integration.validate response-types entry (regenerating the README row), as AST-017 FR13 requires for every response field.
  • Bring the CommandDoc, the FunctionDoc and the changeset in line with the guide, since they still say only the docs the CLI's readers take are checked.
  • Make isFixture() test the path relative to its root, because today a package anywhere under a __tests__ folder has its hidden or shadowed docs counted as fixtures and still prints [ok].
  • Keep the fixture count in the text when no other doc exists, which now prints only [info] No doc files to type-check. while the JSON reports fixtures.

[Automated review]

return (
root !== 'templates' &&
root !== 'themes' &&
file.split(path.sep).includes('__tests__')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This splits the absolute path, so a package that sits anywhere under a folder named __tests__ has every unserved doc in its components or docs root counted as a fixture instead of checked. A hidden: true doc with an unknown field in such a package gives [info] 1 test fixture doc(s) not checked. and then [ok] Every doc matches its authoring type (1 checked). Testing the path relative to the root that holds it keeps the intent:

function isFixture(file, owner) {
  return (
    owner.root !== 'templates' &&
    owner.root !== 'themes' &&
    path.relative(owner.dir, file).split(path.sep).includes('__tests__')
  );
}

with the call site passing owner, plus a test whose package lives under a __tests__ folder.

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.

Fixed in 15254b0. isFixture() now reads the path relative to its root, so a package that sits under a folder named __tests__ has its hidden and second docs checked, not counted. The new test puts a package under __tests__/ with a hidden doc, now checked, and a real fixture, still counted. It failed before.

report.findings.length === 0 &&
report.unchecked.length === 0
) {
return [text('[info] No doc files to type-check.')];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

When a package's only doc files are __tests__ fixtures, the JSON carries docs: 0 and fixtures: 1, but this early return prints only [info] No doc files to type-check., so the text loses fixtures. Adding !report.fixtures to the condition above, or printing the fixture line here as well, keeps the two views aligned.

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.

Fixed in 15254b0. With fixtures as the only docs, the text prints [info] No doc files to type-check. and then [info] N test fixture doc(s) not checked., matching docTypes.fixtures in the JSON. The new command test covers it and failed before.

type: 'integration.validate',
description:
'The result envelope: `data.validated`, false when nothing was validated — with no `package.json` at all, `data.issues` carries one `no_package` error-severity issue so the command exits 1; with a `package.json` but no integration manifest, `data.issues` is empty, which means "not looked at", not "healthy"; `data.name` and `data.version` of the validated package (both null when `data.validated` is false; `data.name` is `(local package)` when the local package.json has no readable name), plus `data.issues`, an AstryxIntegrationIssue[] of {code, severity: `warning` | `error`, message}.',
'The result envelope: `data.validated`, false when nothing was validated — with no `package.json` at all, `data.issues` carries one `no_package` error-severity issue so the command exits 1; with a `package.json` but no integration manifest, `data.issues` is empty, which means "not looked at", not "healthy"; `data.name` and `data.version` of the validated package (both null when `data.validated` is false; `data.name` is `(local package)` when the local package.json has no readable name), plus `data.issues`, an AstryxIntegrationIssue[] of {code, severity: `warning` | `error`, message}. When the manifest loads, `data.docTypes` holds the doc type check: `status` (`checked` | `skipped`), `reason` when skipped, `typescript`, `docs` (doc values checked), `findings` ({doc, line, export, type, field, problem: `unknown_field` | `missing_field` | `wrong_type`, severity: `warning`, message, fix}) and `unchecked` ({doc, reason}: a doc a reader takes that could not be checked, or a root its reader cannot read).',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

data.docTypes.fixtures is missing here, and from the integration.validate entry in foundation/response/response-types.doc.mjs that generates the README row; AST-017 FR13 asks for every response field in the consumer docs. Since the check now covers docs no reader serves, unchecked can also hold a doc no reader takes (a second doc for one name that does not parse, say), so “a doc a reader takes” is narrower than the code.

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.

Done in 15254b0. data.docTypes.fixtures is documented in the FunctionDoc's returns and in the integration.validate response type, and the README row is regenerated. unchecked now reads "a doc that could not be fully checked, or a root its reader cannot read", with no reader wording.

'installed package resolved by name. It schema-checks the manifest, verifies ' +
'every declared contribution root, and reports every finding. Safe as a CI gate.',
'every declared contribution root, and reports every finding. Safe as a CI gate. ' +
"It then checks each doc the CLI's readers take from the package against the " +

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The check now also covers docs no reader serves (a hidden doc, a second doc for one name, a template doc without its source, an overlay without its topic) and counts __tests__ fixtures, so “each doc the CLI's readers take” understates it. The FunctionDoc (validateIntegration.doc.mjs line 27) and the changeset say the same; the wording of the new guide section in check-your-docs.doc.mjs fits all three.

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.

Done in 15254b0. The CommandDoc, the FunctionDoc and the changeset now say that every doc the package ships under its roots is checked, served or not, and that test fixtures under __tests__ in the components or docs root are counted, not checked, as the guide does.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Recommended for approval: the doc type check matches architecture:cli-surface INV34, and the four fixes from the last review (documenting docTypes.fixtures, aligning the command docs, API docs and changeset with the guide, judging __tests__ fixtures by their path inside the root, and keeping the fixture count in the text) are in and work as described. It still has to wait for #7664 to land, and it should ship no later than the release that first ships integration check. One optional cleanup: the hidden-doc command test writes hidden: true on the export default line, which component discovery does not treat as hidden, so moving it onto its own line would make that test cover an unserved hidden doc.

[Automated review]

[
'components/AcmeHidden.doc.mjs',
'AcmeHidden',
', hidden: true, keyword: []',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Optional: component discovery reads hidden with a line-anchored text match (HIDDEN_RE in foundation/discovery/component-discovery.mjs), so this inline hidden: true is not seen and discovery still serves AcmeHidden; the hidden half of this test would pass even if only reader-served docs were checked. Putting hidden: true on its own line, as integration-doc-files.test.mjs does, makes it an unserved hidden doc:

export default {
  type: 'component',
  name: 'AcmeHidden',
  hidden: true,
  // …
};

…blished authoring type

`astryx doctor integration validate` now finds every doc an integration
ships (components, templates, docs topics, themes, and their translations)
and checks each against the type `@astryxdesign/cli/authoring` publishes for
its kind, with the project's own TypeScript, the way `tsc` checks a value
written with that type. Each unknown field, missing required field, or value
outside its field's type is a warning naming the doc, line, field, and a
fix. The exit code is unchanged; JSON adds `data.docTypes`, present when the
manifest loads. Without TypeScript it prints one `[skip]` line.

A repository test checks Core's own docs with a shrink-only list of the
current mismatches, all in translations.
…ocs matched after a failed check

A test compares validate's JSON docTypes with its text for each problem
kind and each doc not checked. When another check fails and every doc
matches, the section now says so in an [info] line instead of ending at its
heading. The no-TypeScript test looks for the current section heading, so
it can fail. The changeset names the version of a TypeScript the check
cannot use, and the no-docs line.
…tegration templates declare IntegrationTemplateDoc

The doc type check takes each root's files from the reader that serves
them: the doc component discovery records for each component (so a doc in
`__fixtures__`, in a dot-folder, or behind a symlinked file is checked,
while a `hidden` doc or a second doc for one name is not), the doc of each
template template discovery serves (a template under `__tests__` too), the
file of each topic, namespace and placed guide docs discovery serves (where
it is, in a scoped package too) with each topic's and guide's `zh` and
`dense` overlay, and each theme's descriptor as theme discovery resolves
it. A root that `validate` already reports (missing, a file, or one it
cannot read) is left to that issue, and a root whose reader fails inside it
is listed under `unchecked` with the error, so nothing a reader serves is
skipped silently.

`integration add template` and the template guides' samples declare
`IntegrationTemplateDoc`, the type the check holds integration templates
to.
The "Check your docs" guide gains a section on the doc-type check: what it
reports (`unknown_field`, `missing_field`, `wrong_type`), sample output,
which type each root's docs are read against, where the findings are in
JSON, and what it prints without TypeScript.
…fixtures

The doc type check read only the docs each root's reader serves, so a
`hidden` doc, a second doc for one name, a template doc without its
source and an overlay without its topic were never checked, yet an agent
can still open them. Now every doc file a root holds is checked, with
the kind its root reads. A test fixture, a doc under `__tests__` in the
components or docs root (which those readers skip), does not ship as a
doc: it is not checked, and validate says how many once
("N test fixture doc(s) not checked"), as `docTypes.fixtures` in JSON. The
template reader reads `__tests__` and `__fixtures__`, so a template doc
there is checked like any other.
`[ok] Every doc matches its authoring type` still prints only when
nothing was left unchecked, and with only fixtures the text still counts
them. A fixture is judged by its path inside its root. The "Check your
docs" guide, the validate command and API docs, the response types (with
the README row) and the changeset say so.
@josephfarina
josephfarina force-pushed the josephfarina/doctor-doc-types-validate branch from def4748 to b75fc8c Compare October 19, 2026 23:22
@josephfarina
josephfarina changed the base branch from josephfarina/doctor-doc-types to main October 19, 2026 23:22
@josephfarina
josephfarina merged commit c2e0a59 into main Oct 19, 2026
47 checks passed
josephfarina added a commit that referenced this pull request Oct 19, 2026
…ion-guides-cleanup

links.doc.mjs takes #7668's component, page and block kinds beside this branch's schema and enum, and #7668's sentence on naming a component or template replaces "does not resolve". The check-your-docs header keeps this branch's route and adds #7673's "each doc's fields".
@github-actions
github-actions Bot deleted the josephfarina/doctor-doc-types-validate branch October 20, 2026 06:22

This branch was successfully deployed

1 active deployment
Preview — b75fc8cb Deployed Oct 19, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant