Skip to content

feat: make the bundled docs agent-readable - #8781

Merged
keepview merged 19 commits into
mainfrom
feat/agents-md-only-flag
Aug 6, 2026
Merged

feat: make the bundled docs agent-readable#8781
keepview merged 19 commits into
mainfrom
feat/agents-md-only-flag

Conversation

@keepview

@keepview keepview commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

What

Two follow-ups to #8774, both about how agents actually consume the bundled docs:

  • Bundle the rendered docs instead of the MDX sources. The MDX sources are the docs site's input, not something an agent can read — components aren't rendered and shared fragments aren't inlined, so a quarter of the pages arrived without their actual content. We now copy the docs site's build output, where every page is self-contained. llms.txt ships alongside as a local index, and the bundle no longer trims sections (it's about half the size of the old one).
  • Replace the create agents-md subcommand with a --agents-md-only flag. The subcommand reserved agents-md as a project name and changed meaning depending on argument position. A flag has neither problem.

Test

  • create unit tests pass (14).
  • node scripts/check-doc-bundle.mjs passes: the tarball has the expected page count plus llms.txt, within the size limit.
  • A real pnpm pack --ignore-scripts confirms the docs and llms.txt are in the tarball.

@changeset-bot

changeset-bot Bot commented Jul 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 913679d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 119 packages
Name Type
@modern-js/app-tools Minor
@modern-js/create Minor
@modern-js/adapter-rstest Minor
@modern-js/plugin-bff Minor
@modern-js/plugin-ssg Minor
@modern-js/plugin-styled-components Minor
@modern-js/plugin-i18n Minor
@modern-js/image Minor
@modern-js/runtime Minor
@modern-js/plugin-polyfill Minor
@integration-test/alias-set Patch
app-document Patch
async-entry-test Patch
tmp Patch
bff-api-app Patch
bff-client-app Patch
bff-indep-client-app Patch
bff-hono Patch
integration-clean-dist-path Patch
integration-compatibility Patch
integration-custom-dist-path Patch
custom-file-system-entry Patch
integration-custom-template Patch
deploy Patch
deploy-server Patch
dev-server Patch
integration-disable-html Patch
entries-app-builder Patch
app-custom-entries Patch
app-custom-routes-runtime Patch
app-custom Patch
app-entry Patch
app-route Patch
app-entry-server Patch
i18n-app-csr-html-lang Patch
i18n-app Patch
i18n-app-ssr-html-lang Patch
i18n-app-ssr Patch
i18n-custom-i18n-wrapper Patch
i18n-mf-app-provider Patch
i18n-mf-component-provider Patch
i18n-mf-consumer Patch
i18n-routes Patch
i18n-routes-ssr Patch
@integration-test/image-component Patch
main-entry-name Patch
nonce Patch
pure-esm-project Patch
react-compiler-test Patch
routes-match Patch
routes Patch
app-rsbuild-hooks Patch
rsc-csr-app Patch
rsc-csr-routes Patch
rsc-ssr-app Patch
rsc-ssr-routes Patch
basic-app-rstest-browser Patch
basic-app-rstest Patch
runtime-custom-plugin Patch
runtime-custom-config-plugin Patch
select-mul-entry-test Patch
select-one-entry-test Patch
server-config Patch
server-esm-tsx Patch
server-json-script Patch
server-monitors Patch
server-prod Patch
server-routes Patch
@source-code-build/app Patch
ssg-fixtures-mega-list-routes Patch
ssg-fixtures-nested-routes Patch
ssg-fixtures-simple Patch
ssg-fixtures-web-server Patch
ssr-base-async-entry-test Patch
ssr-base-async-pre-entry-test Patch
ssr-base-json-test Patch
ssr-base-test Patch
ssr-base-fallback-test Patch
init Patch
ssr-base-loadable Patch
ssr-partial-test Patch
rsc-closing-tags-test Patch
ssr-script-loading Patch
ssr-useid-test Patch
ssr-streaming-inline-test Patch
ssr-streaming-lazy-test Patch
ssr-streaming-test Patch
styled-components-stream Patch
styled-components-string Patch
integration-tailwindcss-v2 Patch
integration-tailwindcss-v3 Patch
integration-tailwindcss-v4-tools Patch
integration-tailwindcss-v4 Patch
tmp-dir Patch
write-to-dist Patch
@modern-js/bundle-diff-benchmark Minor
@modern-js/sandpack-react Minor
@modern-js/main-doc Minor
@modern-js/tsconfig Minor
@modern-js/builder Minor
@modern-js/plugin-data-loader Minor
@modern-js/render Minor
@modern-js/bff-core Minor
@modern-js/bff-runtime Minor
@modern-js/server-core Minor
@modern-js/create-request Minor
@modern-js/prod-server Minor
@modern-js/server-runtime Minor
@modern-js/server Minor
@modern-js/server-utils Minor
@modern-js/i18n-utils Minor
@modern-js/plugin Minor
@modern-js/runtime-utils Minor
@modern-js/types Minor
@modern-js/utils Minor
@modern-js/rslib Minor
@scripts/prebundle Patch
@scripts/rstest-config Patch
@scripts/release-node Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@netlify

netlify Bot commented Jul 27, 2026

Copy link
Copy Markdown

Deploy Preview for modernjs-byted ready!

Name Link
🔨 Latest commit 913679d
🔍 Latest deploy log https://app.netlify.com/projects/modernjs-byted/deploys/6a733e6b213f1f00080cdd0c
😎 Deploy Preview https://deploy-preview-8781--modernjs-byted.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 99 (no change from production)
Accessibility: 100 (no change from production)
Best Practices: 100 (no change from production)
SEO: 100 (no change from production)
PWA: -
View the detailed breakdown and full score reports
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions

Copy link
Copy Markdown
Contributor

Rsdoctor Bundle Diff Analysis

📊 Quick Summary
Project Total Size Change
bundle-diff 471.8 KB 0

Generated by Rsdoctor GitHub Action

@keepview keepview changed the title feat(create): use --agents-md-only flag instead of the agents-md subcommand feat: make the bundled docs agent-readable Jul 28, 2026
@keepview
keepview force-pushed the feat/agents-md-only-flag branch from 1d87450 to dcf6c54 Compare July 30, 2026 14:10
zllkjc
zllkjc previously approved these changes Aug 5, 2026
@keepview
keepview enabled auto-merge (squash) August 5, 2026 13:34
keepview added 17 commits August 5, 2026 21:39
…ommand

The `create agents-md` subcommand overloaded the project-name positional: it
reserved "agents-md" as a name and behaved differently depending on argument
position. Replace it with an `--agents-md-only` flag, which can never collide
with a project name and is position-independent. It updates the agent files in
the current directory only, and is mutually exclusive with a project name and
with `--no-agents-md` (guarded with a clear error).

Updates the docs and adds tests for the guards and for "agents-md" now being a
valid project name again.

Co-Authored-By: Riff
The bundle was copied from the MDX sources, which are the docs site's input
rather than something an agent can read: components are not rendered and shared
fragments are not inlined. A quarter of the pages got their actual content from
components whose data never ships with the package — configure/app/tools/
postcss.mdx, for example, was 38 lines with the type and defaults left to a
component, so an agent reading it learned nothing.

Copy from the docs site build output (packages/document/doc_build) instead.
Those pages are self-contained: components are rendered and fragments inlined,
so agents no longer need to follow cross-file references. Also ship llms.txt as
a local index, and stop trimming sections — the full bundle is 906KB, roughly
half of what the MDX bundle cost.

Order is guaranteed via an nx dependsOn so the docs site builds first; when its
output is absent the copy step warns and skips rather than blocking everyday
builds, and check-doc-bundle.mjs is the gate that keeps a release from shipping
without the docs.

Co-Authored-By: Riff
EdenX and PIA need the same capability — version-matched bundled docs plus an
idempotently generated AGENTS.md — but both pieces were private to Modern.js:
the copy script had its paths hardcoded, and the agent-file writer lived inside
@modern-js/create with the marker name, block content and target baked in.
Reimplementing them downstream would fork the idempotency rules.

Make both reusable instead:

- Rename copy-main-doc.mjs to copy-docs.mjs (the source is no longer the
  main-doc package, so "main" meant nothing) and parameterize it as
  `bundleDocs({source, target, indexFile, excludedDirs})`, exposed via a
  `modern-bundle-docs` bin. Modern.js's own call keeps the previous defaults.
- Extract the AGENTS.md/CLAUDE.md writer into agent-files.mjs, parameterized by
  marker name, managed block and target directory, exported as
  `@modern-js/create/agent-files`. The create command now calls it too, so the
  rules stay in one place.

Co-Authored-By: Riff
…kage

Review found four ways the new APIs work in the repo but break once published:

- `modern-bundle-docs` could not run. The script had no shebang, so the shell
  read `import` as a command; and the direct-invocation check compared the
  module path against argv[1], which is a symlink under `.bin`, so even with a
  shebang it would exit silently. Add the shebang and compare realpaths.
- `bundleDocs()` was exported but unreachable: nothing in the exports map
  pointed at it, so importing from a real tarball raised
  ERR_PACKAGE_PATH_NOT_EXPORTED. Add a `./bundle-docs` subpath.
- Docs could be published stale. `@modern-js/main-doc:build` neither hashed
  `docs/**` nor declared `doc_build` as an output, so editing a page hit the
  cache and the bundle silently kept the old content. Declare both, and run
  check-doc-bundle from `release` so a bad bundle fails the publish instead of
  shipping.
- `@modern-js/create/agent-files` had no types, degrading the public API to
  `any` for strict consumers. Ship a `.d.mts` and add `types` conditions.

Verified against the failure modes: invoking through a `.bin` symlink now
works, the subpath imports, editing a doc rebuilds instead of hitting cache,
deleting doc_build restores it from cache, and the release gate fails when the
docs site has not been built. create tests still 14/14.

Co-Authored-By: Riff
…ered components

Two more issues the previous round's checks could not see:

- The `.d.mts` files were unreachable for consumers on `moduleResolution:
  node`, which is what this repo itself uses — classic resolution ignores the
  exports map, so both subpaths raised TS2307. Add `typesVersions` entries;
  verified by typechecking a consumer under both node and nodenext.
- `<ReleaseNote />` and `<PackageManagerTabs />` in upgrade.mdx were indented
  inside a list item, so MDX treated them as text and the release notes and
  install commands never reached the bundled docs. Un-indent them, matching how
  the same components are used elsewhere, and extend check-doc-bundle to flag
  component tags that survive the build — the existing import check could not,
  since the build strips imports either way.

The component check matches only self-closing or attributed tags: a bare
`<Name>` also shows up in type signatures such as `Promise<RsbuildConfig>`,
which is prose rather than an unrendered component.

Verified: the install commands now appear as real code blocks in the bundle,
check-doc-bundle passes, create tests 14/14.

Co-Authored-By: Riff
…ists

The bundle gate had two blind spots that let non-self-contained pages through:
the import check only accepted single quotes, while the docs use both (e.g.
`import EnableBFFCaution from "@site-docs-en/..."`), and the component check
enumerated five names, so anything outside that list — EnableBFFCaution, Badge
— passed silently.

Match both structurally: accept either quote style, and treat any capitalized
self-closing tag as an unrendered component rather than maintaining a list.
Fixing the detection surfaced a third bug in the code-stripping: fences
indented inside a list item were not stripped, so JSX in sample code read as a
leftover component.

Move the rules into scripts/doc-bundle-rules.mjs so they can be tested without
triggering a docs build, add negative tests for each blind spot, and run them
in CI — the previous rules would have passed a test suite that only used the
cases they already handled.

Co-Authored-By: Riff
The gate tried to infer whether a page had rendered completely by scanning its
text for leftover imports and component tags. Each round of review found
another case it got wrong — quote styles, component names, tilde fences,
doubled backticks, paired tags — and each fix only moved the blind spot. Doing
it properly means parsing the full MDX surface, which is the docs site's job,
not a downstream packaging check's.

Keep what can be verified deterministically: the tarball has the pages, the
page count matches the docs site output, llms.txt ships, and the bundle stays
under the size limit. Stop claiming "all pages self-contained" in the success
log, since that is no longer checked.

Pages that fail to render are fixed at the source instead — the upgrade.mdx
indentation fix earlier in this branch is one such case.

Co-Authored-By: Riff
AGENTS.md asserted `node_modules/@modern-js/app-tools/docs/` unconditionally.
No released version ships that directory yet, and versions before 3.8.0 never
will, so `--agents-md-only` on any existing project wrote a path that does not
resolve — while printing that agents would read the bundled docs. An agent
following it wastes a turn and concludes it has consulted the documentation.

Resolve the location from the version instead, and write one address:

  major <= 2                -> https://modernjs.dev/v2/llms.txt
  >= 3.8.0                  -> node_modules/@modern-js/app-tools/docs/
  current major, older      -> https://modernjs.dev/llms.txt
  canary / workspace links  -> bundled (trunk builds always carry them)

The version comes from node_modules when installed and from the declared range
otherwise, so it works in the two entry points where nothing is installed yet.
Deciding here rather than leaving the agent an "if the directory exists" rule
is deliberate: the file is written once and read on every turn.

Also refuses to write into a directory that declares no @modern-js dependency
— it would point that project's agent at a framework it does not use — and the
completion message now names the location it actually wrote.

Drops the v3 essentials list. It duplicated what the docs say, in the block
that costs context on every turn.
ut-windows failed on the check that keeps template/AGENTS.md in step with the
generated block. The repo has no .gitattributes rule for text, so the template
checks out with CRLF on the Windows runner while the block is always joined
with \n — the assertion compared line endings, which was never its point.
Four entries described one capability from four angles, which reads as four
features in the release notes. One entry, one paragraph per language.

Drops the note about re-exporting `build` and `closeServer` — that change was
reverted; feat/export-modern-app-tools-dev exposes the same API properly.

Also corrects the pending entry from #8774: it still tells existing projects to
run `create agents-md`, the subcommand this PR replaced with a flag. Both ship
in the same release, so the notes would have contradicted each other.
The hand-written .mjs + .d.mts pair existed for one consumer: EdenX's
generator imported it through the `./agent-files` export. That generator now
ships its own copy so the two release independently, which leaves this package
as the only user — so the module moves into src as TypeScript, the export, the
typesVersions mapping and the hand-maintained declaration file all go, and the
types come from the compiler.
copy-docs.mjs was hand-written JS with a hand-maintained .d.mts beside it —
the declaration existed only because the module was not TypeScript. It now
lives in src/bundleDocs.ts and comes out of the normal rslib build, so the
types are generated and the pair of loose files under scripts/ goes away.

The three consumers keep their contracts:

- the `modern-bundle-docs` bin becomes a two-line CommonJS shim that dynamic-
  imports the compiled ESM and passes the package root in — the compiled
  module cannot know that path portably across output formats, and the shim
  keeps the shebang without needing a loader on user machines
- the `./bundle-docs` export points at the compiled ESM with generated types
- the package's own build runs the shim after rslib, same as before

The former direct-invocation realpath check is gone with the file: the CLI
only runs when the shim calls it, so import-vs-execute no longer needs
detecting.

The root gate keeps calling the bin; scripts/ at the repo root stays plain
node — that is its convention (check-dependencies.js, skipCI.js,
sync-skills.mjs), unlike EdenX's script workspaces.
check-doc-bundle sat at the top of scripts/ as a loose .mjs, next to four
script workspaces that all run TypeScript through tsx — check-changeset,
lint-package-json, release-note, release-version. Nothing about this one
justified the exception; it now has the same shape, and the root script enters
it the same way the others are entered.

Unlike its siblings it also declares typescript, so its `build` script — the
type check — actually runs; theirs reference tsc without depending on it.
Multi-paragraph headers shrink to what a reader needs, and references to
downstream consumers by name go — which frameworks reuse these entry points is
their business, not this repo's. No behavior change; the full suite and the
release gate pass unchanged.
The version decides everything. At or past 3.8.0 — and on trunk builds — the
managed block names the bundled docs, exactly as before. Below that, the
command now creates and modifies nothing: it prints where the online index
lives and how to move forward, instead of writing a block that points there.

Writing the online index ourselves is retired because it produced a file the
tool half-manages on versions it cannot serve — one that would need migrating
again right after the upgrade it is nudging the user toward. The superseded-
major index table goes with it: its only purpose was choosing which online
address to write.

Scaffolding is unaffected — a new project pins the version of the create
package itself, which ships the docs.
The rebase onto main merged main's lockfile over the entry for the new script
workspace, so CI's frozen install refused it.
@keepview
keepview merged commit a305126 into main Aug 6, 2026
10 checks passed
@keepview
keepview deleted the feat/agents-md-only-flag branch August 6, 2026 04:08
This was referenced Aug 6, 2026
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