Skip to content

fix(routes): follow re-exported default components in the route contract check (AB4810) - #524

Merged
ScriptedAlchemy merged 3 commits into
mainfrom
fix/446-route-contract-reexport
Sep 4, 2026
Merged

fix(routes): follow re-exported default components in the route contract check (AB4810)#524
ScriptedAlchemy merged 3 commits into
mainfrom
fix/446-route-contract-reexport

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Fixes #446.

Root cause

scanRouteModuleExports (packages/agent-bundle/src/routes/contract.ts) only set defaultIdentifier for a local export { X as default } (the statement.moduleSpecifier === undefined guard). A re-exported default (export { default } from '../a/tools/ping.tsx') fell through to named.add('default'), so asyncDefault stayed false and every route contract check (AB4810, the event-route AB4810, AB4830, AB4940, the routed-CLI contract, and the AB4737 bin-shared rendered-script gate, which relied on the named.has('default') accident) reported "default export is not an async function component" for a module whose default is exactly that at run time.

Fix

  • The scan now follows relative re-exports: it resolves the specifier against the scanned module's directory (as written, then .js.ts/.tsx, .mjs.mts, then extensionless + .ts/.tsx), reads the target once per specifier, scans it recursively (cycle-guarded), and judges the default export — or the aliased named binding for export { Page as default } from — where it is declared. RouteModuleExports gains defaultReExport ({ name, specifier, resolution: 'followed' | 'unresolved' }), namedFunctions, and namedAsyncFunctions.
  • A followed re-export whose target is not an async component is still AB4810, and the message now names the target module. A default re-exported from a bare specifier / unreadable target / cycle is unresolved and accepted; the worker verifies it at load time (the same leniency AB4737 already documented).
  • Callers pass the module's absolute path ({ source }), so the validators' signatures are unchanged.
  • docs/diagnostics.md AB4810 row and the MCP authoring guide (en + zh) document the two-placement pattern.

Tests

  • route-graph.test.ts: new fixture with one tool placed on two servers via export { default, inputSchema, resultSchema } from, a Page as default alias through src/pages/, a chain with a .js specifier for a .tsx source, a bare-specifier re-export (accepted), a sync component behind a re-export (still AB4810, names the target), and a type-only default re-export (still AB4810); plus a unit test of the scan surface (followed / aliased / sourceless / cyclic / missing).
  • Manual: packed the built package into a two-server repro (src/mcp/a/tools/ping.tsx + src/mcp/b/tools/ping.tsx re-exporting) — validate clean, build passes, and server b answers tools/list with its own config and tools/call pingpong over stdio.
  • pnpm test:unit: 3235 passed; the 3 failures in framework-plugin-registration.test.ts and the pnpm typecheck errors in that same file are pre-existing on main (fixed by test(build): pass the project root to the composed configs in the plugin-registry test #523). pnpm lint clean.

Consumer follow-up

movie-library can delete the thin default wrappers in src/pages/*.tsx (e.g. src/pages/search.tsx:14-19) and have each second placement (src/mcp/movie-library-library/tools/{search,add_to_library,download,delete}.tsx) export { default, inputSchema, resultSchema } from '../../movie-library-public/tools/<tool>.tsx' beside its own config.

Review status

  • Codex review of 2fb3e00 opened one P2 thread (contract.ts:102, re-export candidate order / missing .cjs.cts and index cases). Addressed in b4415d0d0: the resolver is now the extractor's moduleCandidates, extracted to routes/module-candidates.ts and shared by both static scans; the scan-surface test covers .js-beside-.tsx (source first), index.tsx, and .cjs.cts. No reply was posted on the thread by request; this note is the response.
  • Rebased onto main after test(build): pass the project root to the composed configs in the plugin-registry test #523 (fixes the pre-existing framework-plugin-registration typecheck/test failures noted above).
  • Last Codex-reviewed head: 2fb3e008a (pre-rebase). Unreviewed heads: 2ec593cc8, 7ecf7e026, b4415d0d0 (the rebase plus the resolver refactor above). No @codex review was requested by instruction (no PR comments); merged on green CI per the reviewer-fallback rule.

@changeset-bot

changeset-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b4415d0

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

This PR includes changesets to release 1 package
Name Type
agent-bundle 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

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-04T05:26:59.538190Z 2fb3e00 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@pkg-pr-new

pkg-pr-new Bot commented Sep 4, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle@524
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@524
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@524

commit: b4415d0

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2fb3e008a0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +99 to +102
return emitted[1] === 'mjs' ? [written, `${stem}.mts`] : [written, `${stem}.ts`, `${stem}.tsx`];
}
if (/\.[cm]?[jt]sx?$/u.test(written)) return [written];
return [written, `${written}.ts`, `${written}.tsx`];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use TypeScript resolution order for re-export candidates

When a re-export uses an emitted .js specifier and both page.ts/page.tsx and page.js exist, these candidates probe the JavaScript file first even though TypeScript resolves the source file first, so the contract check can reject a valid async component or accept the wrong implementation. This resolver also omits supported .cjs.cts and extensionless index.ts/index.tsx cases, which are classified as unresolved and therefore bypass AB4810. Reuse or extract the complete moduleCandidates logic already present in routes/config-extract.ts instead of maintaining a divergent resolver.

AGENTS.md reference: AGENTS.md:L17-L24

Useful? React with 👍 / 👎.

…act check (AB4810)

A second placement of one tool on another generated server carries its own
config and re-exports the component and schemas from the first route. The
static contract scan only recognised a locally declared default, so
`export { default } from` was AB4810 with a misleading message. The scan now
follows relative re-exports (and chains, `.js` specifiers for TS sources)
and judges the default where it is declared; a target it cannot read is
accepted and verified at load time. Applies to the MCP route, event route,
layout, provider, routed-CLI and AB4737 rendered-script checks.

Fixes #446
…een the config extractor and the contract scan

Review: the re-export follower probed the emitted .js before its .ts/.tsx
source and skipped .cjs->.cts and index modules. Both static scans now use
one moduleCandidates() in routes/module-candidates.ts.
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.

Route contract (AB4810) rejects a re-exported default component for a second placement of the same tool

1 participant