Skip to content

feat: migration nudges, inspect --bundler, and dev-watch of bin/lib (RFC #50 Phase 2, framework side) - #60

Merged
ScriptedAlchemy merged 3 commits into
mainfrom
feat/rfc50-phase2-framework
Aug 31, 2026
Merged

feat: migration nudges, inspect --bundler, and dev-watch of bin/lib (RFC #50 Phase 2, framework side)#60
ScriptedAlchemy merged 3 commits into
mainfrom
feat/rfc50-phase2-framework

Conversation

@ScriptedAlchemy

Copy link
Copy Markdown
Owner

Implements the framework-side Phase 2 items of RFC #50 (Phase 1 shipped in #52).

Summary

Migration nudge diagnostics (AB4730–AB4733, severity info)

Validate-time nudges for the pre-Phase-1 patterns the entry conventions now replace. Never errors — migrations stay optional per the RFC's additive-first principle. Codes follow the existing AB47xx package-build block (AB470x bin, AB471x lib, AB472x tools → AB473x nudges):

  • AB4730 — a local stdio MCP entry (explicit entry: or conventional) whose module has no default export is self-connecting, so the build cannot wrap it in the framework lifecycle shell. Detection reuses scanEntryExportsSource, the exact static scan the build uses to decide the wrap, so nudge and build can never disagree.
  • AB4731src/cli.ts exists but explicit bin config never references it (shadowed convention). bin: false stays silent (deliberate opt-out).
  • AB4732src/index.ts exists but explicit lib config points elsewhere. lib: false stays silent.
  • AB4733src/mcp/<server-id>.ts exists but the declared server's explicit entry/command/url doesn't resolve to it.

Each nudge carries an "Optional: …" recovery describing both the adopt path and the silence path. Human validate output now prints non-error diagnostics (they were previously invisible outside --json). All five examples validate clean — the nudges fire only on genuinely pre-convention projects.

agent-bundle inspect --bundler (RFC §3.4)

A third inspect focus (mutually exclusive with --hooks/--skills, same result envelope: selected.bundler). Dumps the synthesized bundler configuration for every output the build composes — per-target artifact scripts, MCP entries, hook wrappers, the per-target MCP Apps Rsbuild config, and the dist/ package build — post-tools-hatch merge with the framework invariant hook visible last, exactly as lowered: the composition was extracted into composeEntryLibConfig (rslib) and composeMcpAppsRsbuildConfig (rsbuild) and both the build and the inspection call the same functions, so the dump cannot drift.

Output shape: { entries: [{ bundler, kind, name, target?, source, outputPath, generatedEntry?, config }] }, sorted by (target, kind, name). config is the composed config JSON-rendered with functions as [function <name>] (the consumer's tools.rspack mutators and the invariant hook stay visible in position). generatedEntry carries the framework-provided wrapper module source for wrapped entries. Nothing is redacted; two per-build temp values are replaced with stable tokens for determinism: <output>/<target> (artifact output root) and <generated-dts-tsconfig>. Tested: a consumer with a tools.rspack hatch sees tools.rspack: [<hatch>, "[function enforceInvariants]"] and a tools.rsbuild fragment merged over the profile, plus byte-identical repeated runs.

Dev-watch of bin/lib outputs (RFC §3.5)

agent-bundle dev now rebuilds the framework-owned package build inside the same debounced, serialized rebuild pass that publishes artifact epochs (new DevPackageBuildService, wired into DevCoordinator after epoch commit). Incremental discipline is provenance-based: after a successful package build the service keeps the source inputs of every emitted file (recorded from bundler stats), and skips the next rebuild unless an invalidated path was one of those inputs, package.json/tsconfig.json changed, the normalized bin/lib declaration changed, the invalidation was manual/initial, or the previous package build failed.

Boundary, stated honestly: a package build failure never invalidates the artifact epoch that already committed — it surfaces as one AB7103 warning on the succeeded attempt and retries on the next invalidation; and a brand-new file that changes module resolution without touching a tracked input is only picked up on the next tracked change. No fake watch: an end-to-end integration test drives a real coordinator through initial build → untracked-change skip (dist mtime unchanged) → entry-change rebuild (new dist content).

Docs

  • New docs/diagnostics.md — diagnostics reference (severity model, code families, detailed AB473x + AB7103).
  • docs/entry-conventions.md — migration nudges, inspect --bundler contract and tokens, the dev-watch section; the "dev never writes dist/" stance updated.
  • Both READMEs (CLI reference rows/bullets).
  • Changeset: agent-bundle minor.

Test plan

  • pnpm build — green
  • pnpm typecheck (root + workbench) — green
  • pnpm lint — 0 errors, 0 warnings
  • pnpm test:unit — 1730 passed (includes new dev-package-build-service, inspect-bundler, and nudge suites)
  • Targeted integration: cli, api, mcp, hooks, public-api, build, package-build, artifact-validator, dev-artifact-service, dev-workbench, dev-package-build (new, registered in the integration list), integration-matrix — all green post-rebase
  • public-api-packed — green (declaration graph now reaches @rslib/core, a runtime dependency; the source-tree consumer fixture gained the same symlink @rsbuild/@rspack already had)
  • agent-bundle validate --json on all five examples — zero diagnostics (no stray nudges)
  • Artifact shapes unchanged (no emit/manifest/provenance changes), so the full packed suite beyond public-api-packed was not rerun.

Refs #50.

@changeset-bot

changeset-bot Bot commented Aug 31, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 04c5084

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 Minor

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 Aug 31, 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-08-31T03:02:21.840832Z cb0cffa 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 Aug 31, 2026

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

commit: 04c5084

@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: cb0cffab7d

ℹ️ 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 +62 to +64
if (model?.packageBuild === undefined) {
this.#last = undefined;
return outcome('absent');

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 Remove stale outputs when the package build disappears

In agent-bundle dev, if the last package entry is removed—for example, src/cli.ts is deleted or configuration changes to bin: false with no library entry—the prepared model has no packageBuild, so this path merely clears in-memory state. The previously published dist/bin/... output remains on disk indefinitely, leaving consumers able to run a package executable that no longer exists in the current project configuration. Remove the outputs previously owned by the package build when transitioning to the absent state.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in d1d526a: when the prepared model no longer declares a package build (entries removed or bin: false/lib: false), the service now removes exactly the outputs this session previously published (tracked per file from the last successful build) and prunes then-empty directories, so dist/ never keeps serving executables the configuration no longer declares. Files the session never published are untouched, and cross-session staleness intentionally matches agent-bundle build (documented in docs/entry-conventions.md). Covered by new unit tests (removal, foreign-file preservation, root pruning) and an end-to-end coordinator step that deletes src/cli.ts and observes the bin output disappear.

Comment on lines +66 to +68
const identity = digest(model.packageBuild);
if (!this.#shouldRebuild(identity, invalidation)) {
return outcome('skipped');

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 Include bundler tools in the rebuild identity

When a project uses conventional src/cli.ts or src/index.ts entries, changing tools.rsbuild or tools.rspack in agent-bundle.config.ts does not change model.packageBuild, and that config path is not among the conventional entries' recorded provenance inputs. Consequently the source-change invalidation is skipped even though the package build consumes prepared.tools below, leaving dist compiled with the old bundler configuration until another tracked input changes or dev restarts. Include the effective tools/config input in the identity or otherwise force a rebuild for the prepared config path.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in d1d526a: the rebuild identity now digests the tools hatch alongside the normalized bin/lib declaration (hatch functions compared by source text, which stays current because every preparation re-evaluates the config), and the configuration file path joined package.json/tsconfig.json as an always-rebuild input — so a hatch edit rebuilds dist/ even for conventional entries whose provenance never references the config. Covered by new unit tests: identical hatch source skips, edited mutator body rebuilds, rsbuild-fragment change rebuilds, config-path change rebuilds.

@ScriptedAlchemy
ScriptedAlchemy merged commit 66a7961 into main Aug 31, 2026
9 checks passed
@ScriptedAlchemy
ScriptedAlchemy deleted the feat/rfc50-phase2-framework branch August 31, 2026 03:44
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.

1 participant