Skip to content

Emit one composite plugin artifact; use targets to select the host projections inside it #555

Closed

Description

@ScriptedAlchemy

Decision

Agent Bundle should always emit one composite plugin artifact. plugin should not be a public target. The public targets remain claude, codex, cursor, and portable; selecting them controls which host projections are present inside the composite root.

The same directory should be publishable through Git, npm, or used locally without changing its internal layout. Distribution is a separate axis from host projection selection.

For example:

targets: ['claude', 'codex']

emits one root containing the Claude and Codex conventions plus shared implementation files. It must not emit sibling claude/, codex/, or plugin/ packs.

Adding cursor adds the Cursor projection and the Cursor lifecycle helper. Adding portable adds the Agent Plugins projection inside the same distributable, subject to the compatibility constraint below.

If targets is omitted, keep the current default inclusion policy (portable) unless that default is changed separately. The output shape is still one composite root.

Artifact contract

The publishable root contains the files required by the selected hosts, alongside shared compiled content:

package.json                         # when published to npm
.claude-plugin/plugin.json          # when claude is selected
.claude-plugin/marketplace.json
.codex-plugin/plugin.json           # when codex is selected
.agents/plugins/marketplace.json
.cursor-plugin/plugin.json          # when cursor is selected
.cursor-plugin/marketplace.json
plugin.json                         # portable projection, when compatible
.mcp.json / mcp.json / host variants
hooks/
skills/
scripts/
bin/
mcp/
mcp-apps/
assets/
INSTALL.md
install.mjs                         # only when a selected host needs it
agent-bundle.manifest.json

Shared byte-identical scripts, MCP servers, apps, assets, and skills are emitted once. Host manifests point to those shared files. A host-specific document or wrapper is emitted only when that host requires a different representation.

Target-scoped declarations retain their current meaning. A Claude-only component must not become visible to Codex merely because both projections share one filesystem root. Where a conventionally discovered directory cannot faithfully isolate a host-specific component, the builder must either emit a host-specific view supported by that host or fail with an explicit diagnostic. It must not silently widen the component's target scope.

The artifact manifest records the selected real host projections and their provenance. It does not record plugin as a target or use target names as physical output partitions.

Current implementation

The current code has most of the format-specific emitters, but the planner still treats output format and host selection as the same axis:

  • config/normalize.ts selects registered target names and synthesizes a plugin lowering target.
  • adapters/registry.ts registers pluginAdapter as an ordinary selectable target; portable is the current default.
  • build/build.ts plans each selected adapter independently and stages it at <artifactRoot>/<target.name>.
  • adapters/plugin.ts is an always-Claude-plus-Codex-plus-Cursor adapter. It filters resources through the synthetic plugin target, always invokes those host planners, and has static validation requirements for Claude and Codex.
  • api.ts, package inventory, artifact validation, dev/workbench services, eval harnesses, and test helpers assume that a host is found under <output>/<target>.
  • build/package-build.ts currently creates a sibling npm package whose installer reaches back to a nested artifact. The npm package root is not itself the plugin root.
  • install/install.ts probes the supplied root, then <root>/<host>, then <root>/plugin; install/doctor.ts probes the root and <root>/<host>. The composite root should become canonical in both.

This is not a default-target change. It requires separating two concepts:

  1. Projection selection: claude | codex | cursor | portable.
  2. Artifact assembly and distribution: one composite directory, optionally packed to npm or committed/published through Git.

Required changes

1. Remove plugin from the public target model

  • Reject targets: ['plugin'] and --target plugin as unknown.
  • Remove the synthetic plugin target from normalized components, host documents, manifests, hook indexes, diagnostics, and user-facing target lists.
  • Keep the existing target precedence and default inclusion behavior for real host projections.
  • The current pluginAdapter may remain as an internal composite assembler, but it must not be registered as a public target.

2. Plan selected projections into one stage

  • Replace independent <artifactRoot>/<target> stages with one stage rooted at artifactRoot.
  • Invoke only the planners for selected hosts.
  • Include a declaration when its target set intersects the selected projections, while preserving per-host scoping in emitted manifests.
  • Merge byte-identical entries once. Keep deterministic, fatal collision diagnostics for incompatible same-path bytes.
  • Compute capability and notice-delivery diagnostics over the selected projections rather than a fixed Claude/Codex/Cursor set.
  • Make artifact validation conditional on the projections actually selected.
  • Preserve native hooks for every selected host; the current composite path must not strip them through a hook-free intermediate model.

3. Resolve the portable/Codex/Cursor root collision

The portable adapter requires a root plugin.json and may emit root mcp.json. The current composite adapter intentionally omits root plugin.json because Codex can prefer it over .codex-plugin/plugin.json, which loses Codex hooks/apps and changes MCP lookup. Cursor also uses root MCP conventions.

portable + codex and portable + cursor therefore cannot be implemented as an unchecked file merge. Define and prove one of:

  • a namespaced portable view with install/materialization support while keeping one distributable root;
  • a merged representation proven to preserve all selected host features; or
  • a clear build-time incompatibility diagnostic for combinations that cannot yet be represented faithfully.

The intended end state is additive portable support, but the implementation must not claim that support by degrading another selected host.

4. Make the root distribution-neutral

The exact composite root should work in each of these forms:

  • Git/GitHub: committed at a repository root, artifact branch, release repository, or a documented subdirectory referenced by a root marketplace catalog.
  • npm: packed with package.json, host manifests, generated runtime files, and all referenced assets at the npm package root; never under artifact/<host> or artifact/plugin.
  • local directory: passed directly to host marketplace/install commands or Agent Bundle lifecycle commands.

Git is the common discovery path for both Claude and Codex: users add an owner/repo, Git URL, or local marketplace, then install the named plugin. Both Claude and Codex marketplaces can use an npm package as the plugin source. Codex materializes the package with npm pack --ignore-scripts, requires the standard npm package/ archive root, verifies the package.json name against the requested package, and installs the extracted plugin root without running package scripts. The npm tarballs inspected for @prismer/claude-code-plugin, @ours.network/codex, and @useorgx/codex-plugin put .claude-plugin/ or .codex-plugin/, skills, hooks, and package.json at the same package root; that is the layout to produce.

Supported source and install mapping

npm is a plugin source inside a marketplace catalog, not a replacement for the marketplace itself. The user still adds a catalog and installs a named plugin; the host materializes the package declared by that catalog.

Distribution form Claude Codex Cursor Composite-root requirement
GitHub or Git marketplace claude plugin marketplace add owner/repo, then claude plugin install <plugin>@<marketplace> codex plugin marketplace add owner/repo, then codex plugin add <plugin>@<marketplace> The generated helper can stage a committed local marketplace; Cursor then requires its Customize import/install step Repository catalog resolves the composite root through ./ or a documented relative subdirectory
Local directory Native marketplace add/install commands accept the local catalog/root Native marketplace add/plugin add commands accept the local catalog/root node install.mjs copies the root safely into Cursor's local plugin directory Supplied directory is already the complete composite root
npm package Marketplace entry uses { "source": "npm", "package": "@scope/name", "version": "..." }; Claude installs the package Same source shape; Codex uses npm pack --ignore-scripts, validates the standard package/ archive root and package name, then installs the extracted plugin root No native npm-backed non-interactive install path is assumed; the bundled helper installs from the acquired package root Host manifests and every referenced file are at the npm package root, not under artifact/

The generated catalogs and INSTALL.md must use only source types actually supported by that host. The Git/local/npm delivery choice must not change the plugin's internal bytes or require a post-install Agent Bundle build.

Marketplace catalogs for a repository root and catalogs that resolve an npm package are different distribution metadata, but both must resolve to the same composite plugin root. Do not introduce npm, git, or plugin as host targets. If configuration is needed for distribution wrappers/catalogs, keep it separate from targets.

5. Generate installation surfaces from selected projections

INSTALL.md must contain only the selected hosts and exact commands for the chosen distribution form.

  • Claude and Codex should use their native marketplace/plugin CLIs whenever the source form is supported.
  • Cursor currently has marketplace management but no complete non-interactive install/uninstall verb. When cursor is selected, include the existing self-contained install.mjs and its uninstall path.
  • Preserve the existing Cursor local mode: receipt-owned, idempotent copy into ~/.cursor/plugins/local/<name>, replacement checks, --uninstall, and guarded data purge.
  • Preserve marketplace mode: stage a committed repository, print the required Cursor Customize import step, and report that removal of Cursor-owned cached imports is manual when the host exposes no CLI operation.
  • Emit install.mjs only when at least one selected projection needs it. If a future host CLI provides the complete lifecycle, the generated instructions can use it instead.
  • A requested install for an unselected host must fail with a clear missing-projection diagnostic.

6. Use one marketplace identity

Marketplace naming is currently inconsistent: Claude can configure its name while other adapters and install text hardcode <plugin>-marketplace. Normalize a single marketplace identity, use it in every selected manifest, instruction, receipt, install/uninstall command, and Doctor result, and validate host-reserved names. A reasonable default is the plugin name.

7. Update all composite-root consumers

Update artifact validation, package inventory, prepack, install, uninstall, Doctor, dev host sync, Workbench/playground, MCP services, eval harnesses, and installed-test helpers to resolve the composite root first and select a logical host view within it. Nested <root>/<host> and <root>/plugin probing may remain temporarily as an explicit backward-compatibility path, but new output and documentation use the root.

Generated AGENTS.md must be optional, selected-projection-aware, or placed so it cannot overwrite a repository's authored AGENTS.md when the composite is published at the repository root.

Acceptance tests

  1. targets: ['claude', 'codex'] emits one root with .claude-plugin/ and .codex-plugin/, no claude/, codex/, portable/, or plugin/ output partitions, and no Cursor projection.
  2. Cursor-only emits one root with Cursor files, INSTALL.md, and the self-contained install.mjs --uninstall lifecycle.
  3. plugin is rejected as a config or CLI target.
  4. Omitting targets emits one composite root with the documented default projection set.
  5. Reversing target order produces byte-identical output.
  6. Shared implementation files compile once; each selected host manifest references them correctly; host-scoped components do not leak.
  7. Selected native hook documents and wrappers survive composition; unselected host files are absent.
  8. The artifact manifest and hook index list real projections only.
  9. portable + codex and portable + cursor preserve the native host feature set or fail with the documented incompatibility diagnostic.
  10. npm pack --json shows manifests and plugin content at package/ root, with no artifact/**; installing the packed tarball uses no source checkout and no post-install Agent Bundle build.
  11. The same committed root passes native local/Git marketplace install checks for Claude and Codex, and the supported npm-source install checks.
  12. Install, replacement, uninstall, and Doctor are exercised from the composite root for every selected host. Cursor local and staged-marketplace receipt, purge, and manual-final-step behavior remain covered.
  13. Marketplace name configuration is consistent in all manifests, instructions, receipts, and lifecycle commands.
  14. Authored AGENTS.md preservation and generated-instruction behavior are covered.

References

Version audited

Agent Bundle main at 42539ff5f and issue/code state inspected on 2026-09-04.

Activity

  1. changed the title [-]Make the composite `plugin` bundle the single distributable plugin: one root carrying every host convention, addable as a marketplace by any agent (Claude, Codex, Cursor, Agent Plugins)[/-] [+]Emit one composite plugin artifact; use targets to select the host projections inside it[/+] on Sep 4, 2026
  2. ScriptedAlchemy commented on Sep 4, 2026

    @ScriptedAlchemy
    OwnerAuthor

    Follow-up from the distribution audit: Codex must be called out alongside Claude for npm-backed plugin sources.

    Codex support landed in openai/codex#29375 on 2026-06-26. A marketplace entry may use {"source":"npm","package":"@scope/name","version":"...","registry":"https://..."}. Codex materializes it with npm pack --ignore-scripts, requires the standard npm package/ archive root, verifies that package.json names the requested package, and installs the extracted plugin root without executing package scripts.

    Implication for this issue: both Claude and Codex must be able to consume the same composite package root from npm, Git, or a local directory. Host manifests and every referenced file must live at the package root; no artifact/<host> or artifact/plugin nesting. Cursor keeps the conditional self-contained install/uninstall helper because it lacks the equivalent complete non-interactive lifecycle.

    References:

    The issue body now also contains the Git/local/npm source-and-install matrix and concrete npm package layout examples.

  3. ScriptedAlchemy commented on Sep 4, 2026

    @ScriptedAlchemy
    OwnerAuthor

    Implementation plan for #555 (composite root, projections selected by targets)

    Basis. Issue text as of updatedAt: 2026-09-04T20:16:19Z (includes the "Supported source and install mapping" table) plus the owner's follow-up comment of 20:16:19Z; code read from origin/main at 42539ff5f (the "Version audited" commit). Owner directives folded in: the codebase is unreleased, so every step deletes rather than shims — no <root>/<host> or <root>/plugin probing survives, no per-host partitions, no dual-mode flags, no deprecation warnings. Each PR ships one minor changeset per AGENTS.md's pre-1.0 convention. Steps that touch build/pack-inventory.ts, build/pack-dependencies.ts, api.ts prepack, or install/surface.ts are sequenced after PR #547 (which also allocates AB7014/AB7015, so those codes are taken).

    Owner follow-up (comment, 20:16:19Z) and the new source/install table — each requirement and where it lands:

    Requirement (quoted) Plan item
    "A marketplace entry may use {"source":"npm","package":"@scope/name","version":"...","registry":"https://..."}. Codex materializes it with npm pack --ignore-scripts, requires the standard npm package/ archive root, verifies that package.json names the requested package, and installs the extracted plugin root without executing package scripts." S4: package root = plugin root, no postinstall/prepare dependency, package.json name equals the configured npm package name; S6: generated Claude/Codex catalogs can emit an npm source entry from distribution config; S8a: proof against a local registry (the registry field makes this CI-testable — see F4)
    "both Claude and Codex must be able to consume the same composite package root from npm, Git, or a local directory. Host manifests and every referenced file must live at the package root; no artifact/<host> or artifact/plugin nesting." S2 (one root), S4 (npm root), S8a (11 across all three forms from one root)
    "Cursor keeps the conditional self-contained install/uninstall helper because it lacks the equivalent complete non-interactive lifecycle." / npm row: "the bundled helper installs from the acquired package root" S5: install.mjs only when cursor selected; new test: run install.mjs from an extracted package/ directory
    "npm is a plugin source inside a marketplace catalog, not a replacement for the marketplace itself." S6: distribution config feeds catalog source entries; never a target (change 4)
    "Repository catalog resolves the composite root through ./ or a documented relative subdirectory" S6: distribution.git.subdirectory (default ./) written into every selected catalog's source
    "The generated catalogs and INSTALL.md must use only source types actually supported by that host." S5/S6: source-type support recorded per host in adapters/capabilities/*.json (Claude: git/local/npm; Codex: git/local/npm; Cursor: local + staged git, no npm) and consulted when rendering catalogs and INSTALL.md; unsupported → build diagnostic (AB4106, §2)
    "The Git/local/npm delivery choice must not change the plugin's internal bytes or require a post-install Agent Bundle build." S4 test: digest of the root minus package.json is identical with and without npm packaging; S8a: install from tarball invokes no build

    1. "Current implementation" — claims verified against main

    # Issue claim Verdict Evidence (packages/agent-bundle/src/…)
    C1 config/normalize.ts selects registered target names and synthesizes a plugin lowering target Confirmed config/normalize.ts:84-86 (plugin → skill hosts claude+codex); :1237 adds a plugin skill document only when targetNames.includes('plugin')
    C2 adapters/registry.ts registers pluginAdapter as an ordinary selectable target; portable is default Confirmed adapters/registry.ts:780-786 — .register(portableAdapter, { default: true }) … .register(pluginAdapter)
    C3 build/build.ts plans each selected adapter independently and stages it at <artifactRoot>/<target.name> Confirmed build/build.ts:193 resolve(options.artifactRoot, target.name) inside assertInside
    C4 adapters/plugin.ts is always Claude+Codex+Cursor, filters through the synthetic plugin target, always invokes those planners, static validation requirements for Claude and Codex Confirmed, with one addition plugin.ts:66 pluginName = 'plugin'; :436 isSelected = targets.includes(pluginName); :450-451 always calls planClaudeArtifacts/planCodexArtifacts; :462,:487,:509,:521 always plans Cursor marketplace/MCP/hooks/manifest; :218 and :223 mark .claude-plugin/plugin.json and .codex-plugin/plugin.json required: true. Addition the issue's change 2 relies on: :441 builds hookFreeModel = { ...model, hooks: [], nativeHooks: undefined } and passes it to both host planners — this is the "hook-free intermediate model" that strips native hooks. :97 documents the deliberate omission of root plugin.json (the Codex-precedence claim behind change 3)
    C5 api.ts, package inventory, artifact validation, dev/workbench services, eval harnesses, test helpers assume <output>/<target> Confirmed; the concrete list is longer than the bullet api.ts:768 join(artifact, target), :1260 pluginDirectory: join(output, target), :719-744,1237 a HostValidatedTarget union that includes 'plugin'; build/pack-inventory.ts:201 join(artifactRoot, target.name, path) and :105-110 case 'plugin' expecting three manifests; build/validate-artifact.ts:460-470 per-target prefix scan + resolve(options.artifactRoot, target.name); build/validate-artifact-mcp.ts:133 targetRoot = resolve(artifactRoot, target.name) (and :208 cwd containment against it); dev/epoch-store.ts:787,861 join(root, target); dev/artifacts/artifact-service.ts:94; dev/skill-document-service.ts:363; dev/playground/hook-playground-service.ts:237; dev/playground/lifecycle-replay-service.ts:287 expands plugin→claude,codex; eval/claude-harness.ts:274; eval/codex-harness.ts:246; tests: 23 files join a root with a host literal/target variable and 16 reference artifact/<host> or <host>/.<host>-plugin/… literals (e.g. tests/host-install-proof.test.ts:68,247). Workbench (packages/workbench/src/**) only lists target.name as a label (artifacts/artifacts-model.ts:199, playground/playground-page.tsx:177,749) — no path joins; it inherits the fix from the dev services
    C6 build/package-build.ts creates a sibling npm package whose installer reaches back to a nested artifact; the npm root is not the plugin root Confirmed package-build.ts:196-197 installHosts filtered by target.name === host || target.name === 'plugin'; :206-227 emits a <name>-install bin whose artifactRelativeUrl (:215,:226) points from dist/bin back into artifactRoot
    C7 install/install.ts probes root → <root>/<host> → <root>/plugin; install/doctor.ts probes root → <root>/<host> Confirmed install/install.ts:181-194 (pluginRoot = join(root, 'plugin') at :187, AB7001 message at :189-192); install/doctor.ts:452-462 (no plugin fallback — the two resolvers already disagree, which the deletion removes)

    Also assuming plugin-as-target or <root>/<host>, not named in the issue (all must go in the steps below):

    Location What it does
    adapters/claude.ts:3247, adapters/codex.ts:1258 targetName === 'plugin' ? 'plugin' : 'claude'/'codex' install-surface branching
    install/surface.ts:17,250-251 BuiltInTarget includes 'plugin'; case 'plugin' concatenates all three host instruction blocks
    install/surface.ts:1343 (needsCursorInstaller) decides install.mjs by target name, incl. plugin
    events/projection.ts:496 throws if target === 'plugin' at projection time
    build/entry-shell.ts:1060-1062 allowedEventTargets widened to all three hosts when target === 'plugin'
    adapters/hook-contract.ts:642-647 generated hook wrapper sniffs AGENT_BUNDLE_HOOK_HOST / PLUGIN_ROOT when entry.target === 'plugin' — this runtime host sniff is still needed for a shared wrapper in a Claude+Codex root; it must key off "wrapper serves >1 selected host", not the plugin name
    packages/create-agent-bundle/src/options.ts:7 targetNames includes 'plugin'; template mcp-server/agent-bundle.config.ts:13 selects ['portable','codex','claude']
    examples/host-test/scripts/probe.mjs:235, README.md:79 install <host> --from artifact/<host>
    examples/rsc-agent-runtime/package.json:8, README.md:89-99,164, tests/support/ensure-built.ts:13-14, tests/mcp-transports.integration.test.ts:455, tests/micro-eval.spot.test.ts:35, tests/host-artifacts.test.ts:17 dist/plugins/<host>/… paths
    .github/workflows/ci.yml:232-277 host-install-proofs runs tests/host-install-proof.test.ts, which builds targets: ['claude','codex','cursor','plugin'] and installs from artifact/<host>
    scripts/record-claude-hooks-schema-fixtures.mjs:9,32 placeholder plugin path /bundle/claude (cosmetic; fixture text only)
    Website (en/zh): guide/distribution/installation.mdx (13/9 mentions), index.mdx (6/6), guide/start/quick-start.mdx (6/6), guide/distribution/validation.mdx (5/4), reference/cli.mdx (2/2), guide/start/index.mdx (2/2), guide/distribution/index.mdx (2/1), guide/start/project-structure.mdx (1/1), en/guide/authoring/hooks.mdx (1), reference/targets-artifacts.mdx (lists the plugin target) artifact/<host> trees and the plugin target
    README.md (2), docs/framework-mode.md (8), docs/diagnostics.md same

    Corrections to the issue text: none of the bullets is wrong; C4 understates that the composite path also strips native hooks (plugin.ts:441), and C7 understates that install and doctor already resolve differently. Change 6's claim "Claude can configure its name while other adapters hardcode <plugin>-marketplace" is confirmed: claude.ts:1716 (generatedName, overridable, reserved-name check at :1729,:1784,:1890), codex.ts:1208, install/surface.ts:19 and the inlined install.mjs template at :514,:556,:1050,:1066 all hardcode ${name}-marketplace; receipts in install.ts:599-608 / uninstall.ts:1047-1323 key on ${host}-marketplace registration kinds.


    2. Diagnostic codes to allocate

    docs/diagnostics.md families: AB41xx = "Normalized model invariants (unknown targets, duplicate IDs and outputs)" — currently AB4100 (unknown target, config/validate.ts:2327), AB4101, AB4102 (multiple inputs produce one generated path, :2524). AB70xx install: AB7000–AB7013 on main, AB7014/AB7015 taken by #547.

    Code Severity Meaning Step
    AB4100 (existing) error targets: ['plugin'] / --target plugin is an unknown target — no new code; the message already says Unknown target "plugin" once registration is removed S1
    AB4103 error (fatal) Composite collision: two selected projections emit the same artifact path with different bytes; message names the path, both hosts, and the first source; sorted by path so output is deterministic S2
    AB4104 error (fatal) Incompatible projection combination: portable selected together with codex or cursor cannot yet be represented in one root without degrading a host (root plugin.json / mcp.json); names the exact colliding root files and the reason S2 (change 3, option c)
    AB4105 error (fatal) Host-scoped component cannot be isolated: a component scoped to a subset of the selected hosts lands in a directory another selected host discovers conventionally (Claude skills/, commands/, agents/, hooks/hooks.json, .mcp.json); names the component, its target set, and the leaking host S2
    AB4106 error Distribution config names a source type a selected host does not support (e.g. an npm source with only cursor selected), or the npm package name is missing while an npm source is declared S6
    AB7001 (existing, reworded) error install <host> on a root with no <host> projection: "No <host> projection in <root> (.<host>-plugin/plugin.json missing). Selected projections: …" — reuse, not a new code (recommendation D2) S3

    Change 3 recommendation: ship option (c), AB4104, first, in S2, and make it the only thing standing between the composite and portable. Reasons: it is the only option that satisfies "never degrade a selected host" without new host evidence; the other two need a recorded probe of Codex's root-plugin.json precedence, which the repo asserts (plugin.ts:97) but does not prove (no fixture under host-contracts/ or tests/fixtures/ records it). S9 then either (a) adds a namespaced portable view or (b) drops the diagnostic if the probe shows Codex prefers .codex-plugin/plugin.json — both additive, neither blocks the composite landing.


    3. Steps, grouped into waves

    Sizes: S < 300 changed lines, M 300–800, L > 800. "Deletes" lists code removed, not just moved. Every PR: one minor changeset, en+zh docs listed, pnpm typecheck && pnpm lint && pnpm test:unit.

    Wave 0 — prerequisite

    Wave 1 — serial foundation (one agent, one worktree, S1 then S2)

    S1 — Remove plugin from the public target model; delete adapters/plugin.ts. (size M; satisfies acceptance 3; prerequisite of everything)

    • Changes: adapters/registry.ts:786 (drop registration); config/normalize.ts:84-86,1237 (delete the plugin branches; loweringHosts becomes a pure isSkillHost filter); api.ts:719-744,1237 (HostValidatedTarget = four hosts; claudeValidatedTargets = {'claude'}); install/surface.ts:17,250-251,1343; build/pack-inventory.ts:105-110; build/package-build.ts:197; events/projection.ts:496; build/entry-shell.ts:1060-1062; adapters/hook-contract.ts:642-647 (key the host sniff on entry.targets.length > 1, not the name — it is reused unchanged by S2); adapters/claude.ts:3247, adapters/codex.ts:1258; dev/playground/lifecycle-replay-service.ts:287; packages/create-agent-bundle/src/options.ts:7; cli.ts help text.
    • Deletes: adapters/plugin.ts in full — its only export is pluginAdapter (:1121) and its only importer is registry.ts, so nothing else breaks; the composite-specific constants private to it (codexBundleMcpPath, cursorBundleHookContract) are re-homed in adapters/codex.ts / adapters/cursor.ts by S2 when the composite stage needs them. Also delete plugin-specific fixtures/tests (tests/plugin-adapter*.test.ts if present, the plugin rows of tests/install-surface.test.ts, tests/host-install-proof.test.ts:68,247).
    • Tests: tests/config-validate.test.ts asserts AB4100 for targets: ['plugin'] and tests/cli.test.ts for --target plugin; git grep -n "'plugin'" packages/agent-bundle/src returns only host-CLI argv literals and plugin.json schema names.
    • Docs (en+zh): reference/targets-artifacts.mdx (remove the plugin row), reference/cli.mdx; docs/diagnostics.md (no code change, note AB4100 covers plugin).
    • Note: after S1 and before S2 the tree still emits per-host partitions and has no composite. That window is one PR long and the codebase is unreleased (decision D1).

    S2 — One stage at artifactRoot; compose selected projections. (size L; satisfies 1, 4, 5, 6, 7, 8, 9 (diagnostic form); depends on S1)

    • Changes: build/build.ts:193 (root = artifactRoot; delete the per-target assertInside(... target.name) partition and the per-target loop's directory creation); new build/compose.ts (the only new module; imported by build.ts in the same commit): call planClaudeArtifacts / planCodexArtifacts / planCursorArtifacts / planPortableArtifacts only for selected hosts, with the full model (no hook-free copy — fixes plugin.ts:441's hook stripping so native hooks survive); include a declaration when declaration.targets ∩ selected ≠ ∅; merge entries by path — identical bytes → one entry, differing bytes → AB4103; emit AB4104 when portable ∈ selected and (codex ∈ selected ∨ cursor ∈ selected); emit AB4105 for the scope-leak cases in §5; sort entries by path so target order cannot change output. Capability/notice-delivery diagnostics (plugin.ts §"notice" logic, claude.ts/codex.ts capability checks) computed over the selected set. Artifact manifest targets = selected real hosts only (build/manifest.ts:89,245-261 — already sorted-unique, keep); hook index likewise. build/validate-artifact.ts:460-470 and build/validate-artifact-mcp.ts:133,208 (targetRoot = artifactRoot; delete the ${target.name}/ prefix scan); build/pack-inventory.ts:201 (join(artifactRoot, path)); api.ts:768,1260 (pluginDirectory = artifact; run Claude validation only when claude selected; Cursor AB6026 notice only when cursor selected).
    • Deletes: the per-target staging in build.ts; standardArtifactLayout per-target root documents (adapters/types.ts:436 rootDocuments) if nothing else uses them after AGENTS.md goes (S7).
    • Tests (all new or rewritten in tests/build-compose.test.ts + existing tests/build.test.ts, tests/hooks.test.ts, tests/mcp.test.ts): ['claude','codex'] root has .claude-plugin/, .codex-plugin/, no claude/|codex/|portable/|plugin/ dirs, no .cursor-plugin/ (1); omitted targets → portable-only root (4); ['codex','claude'] byte-identical to ['claude','codex'] (5); one scripts/*.mjs per shared script, referenced from every selected manifest; Claude-only skill absent from a Codex-only build (6); nativeHooks for each selected host present, Cursor hooks absent when unselected (7); manifest and hook index list only selected hosts (8); portable+codex, portable+cursor → AB4104 (9); same-path-different-bytes fixture → AB4103.
    • Docs (en+zh): guide/start/project-structure.mdx (composite tree from the issue's artifact contract), reference/targets-artifacts.mdx ("targets select projections"), guide/start/quick-start.mdx, guide/start/index.mdx, index.mdx, guide/authoring/hooks.mdx (en); docs/diagnostics.md (AB4103–AB4105); README.md, docs/framework-mode.md.

    Wave 2 — parallel after S2 (separate worktrees; disjoint files)

    S3 — Composite-root consumers: install, uninstall, doctor, dev services, eval harnesses, test helpers. (size M; satisfies 12 for install/replace/uninstall/Doctor from the root; depends on S2)

    • Changes: install/install.ts:181-194 and install/doctor.ts:452-462 → one resolveBundleRoot(from) that checks join(root, manifestPath(host)) and otherwise throws AB7001 reworded as the missing-projection diagnostic; move it to a leaf module both import (install/bundle-root.ts) — one implementation, not two; install/uninstall.ts likewise; dev/epoch-store.ts:787,861, dev/artifacts/artifact-service.ts:94, dev/skill-document-service.ts:363, dev/playground/hook-playground-service.ts:237, eval/claude-harness.ts:274, eval/codex-harness.ts:246 → the root; tests/support/* helpers.
    • Deletes: the <root>/<host> and <root>/plugin probes (no back-compat branch); doctor.ts's private copy of the resolver.
    • Tests: tests/install.test.ts / tests/doctor.test.ts — install from a two-host root for each host; install cursor on a ['claude'] root → AB7001 missing-projection; existing epoch-store / hook-playground / eval-harness tests rewired to root paths. Workbench needs no code change (labels only), but pnpm --filter workbench test runs.
    • Docs (en+zh): guide/distribution/installation.mdx (--from <root>), guide/distribution/validation.mdx (Doctor); docs/diagnostics.md (AB7001 text).

    S4 — npm package root = plugin root. (size M; satisfies 10; depends on S2 and #547)

    • Changes: build/package-build.ts:196-227 — delete the <name>-install sibling bin and artifactRelativeUrl; build/pack-inventory.ts — expected files are the composite root's files at package/ root (manifests, hooks/, skills/, scripts/, bin/, mcp/, mcp-apps/, assets/, INSTALL.md, install.mjs when present, agent-bundle.manifest.json); api.ts prepack — the artifact root is the package root (files/main/bin derived from it); config/validate.ts AB4706 ("artifact output overlaps dist") — re-specify: output.distPath and the artifact root are the same directory by construction, so the diagnostic becomes "artifact root must not be the project root" (see §5 flag F1); examples/*/package.json files.
    • Deletes: installEntryRuntimePath/installEntryRuntimeSpecifier alias plumbing (build/entry-shell.ts:129-131, package-build.ts:19-20,218,316-317) and the install-entry runtime module runtimeModulePath('install-entry') resolves to; the artifact/ entry in pack-inventory.ts expectations.
    • Tests: tests/prepack.test.ts — npm pack --json lists package/.claude-plugin/plugin.json etc. and nothing under package/artifact/; extract the tarball into a temp dir and run agent-bundle install claude --from <extracted> with a stub host runner: no source checkout, no build invoked (10); package.json declares no postinstall/prepare/install script and its name equals distribution.npm.package (Codex verifies it); the digest of every root file except package.json is identical between a build with and without npm packaging (follow-up: delivery choice must not change internal bytes).
    • Docs (en+zh): guide/distribution/index.mdx, guide/distribution/validation.mdx (prepack), reference/cli.mdx.

    S5 — INSTALL.md / install.mjs from selected projections and distribution form. (size M; satisfies 2, and the instruction half of 12; depends on S2 and #547)

    • Changes: install/surface.ts — installMarkdown takes the selected projection set and a distribution form (git | npm | local; derived: npm when package.json is emitted, local always, git when the config declares a repository); Claude/Codex sections use native CLIs (claude plugin marketplace add <root|owner/repo> / codex plugin marketplace add … then plugin install|add <name>@<marketplace>), Cursor section keeps the self-contained install.mjs local mode (receipt, idempotent copy to ~/.cursor/plugins/local/<name>, replacement check, --uninstall, guarded purge) and staged-marketplace mode (commit, Customize import step, manual cache removal note); needsCursorInstaller = cursor ∈ selected.
    • Deletes: the per-target case fan-out for unselected hosts; any plugin wording left in the template.
    • Tests: tests/install-surface.test.ts — Cursor-only build emits INSTALL.md with only the Cursor section and install.mjs; ['claude','codex'] emits no install.mjs; install.mjs --uninstall lifecycle against a temp HOME (2); install.mjs run from an extracted npm package/ directory installs the same bytes (follow-up: "the bundled helper installs from the acquired package root").
    • Docs (en+zh): guide/distribution/installation.mdx (already touched by feat(prepack): gate installed dependencies (AB7014/AB7015); host-native INSTALL.md #547 — rebase), guide/distribution/index.mdx.

    S7 — Generated AGENTS.md: delete. (size S; satisfies 14; depends on S1 — the generator lived in plugin.ts:279,589, so after S1 nothing emits it)

    • Changes: config/normalize.ts:360 (drop AGENTS.md from the reserved root-document list if that is its only purpose); adapters/types.ts:436 rootDocuments if now unused.
    • Deletes: the AGENTS.md renderer and its tests. Recommendation D3: do not reintroduce it behind a flag — INSTALL.md is the generated instruction surface, and an authored AGENTS.md at a repository root can then never be overwritten by construction.
    • Tests: build into a directory that already holds an authored AGENTS.md (build must not clean it — see F1) and assert it is byte-identical afterwards; assert no build emits AGENTS.md (14).
    • Docs (en+zh): guide/start/project-structure.mdx (remove AGENTS.md row).

    Wave 3 — parallel after Wave 2

    S6 — One marketplace identity + distribution sources in the catalogs. (size L, or two sequential M PRs by one agent: S6a identity, S6b sources; satisfies 13 and the catalog half of 11; depends on S3 and S5 — same files)

    • Changes (S6a): new leaf config/marketplace-identity.ts exporting marketplaceName(model) (config key marketplace.name, default = plugin name) and the reserved/kebab-case validation moved out of adapters/claude.ts:1716-1892; consumers: adapters/claude.ts, adapters/codex.ts:1208, adapters/cursor.ts, install/surface.ts:19-21,41,68-71,86,110-112,514,556,1050,1066, install/install.ts:599-608, install/uninstall.ts:1047-1323, install/doctor.ts, eval/codex-plugins.ts. Reserved names: Claude's list (already in claude.ts), plus Codex's and Cursor's if the capability JSONs record any.
    • Changes (S6b): new config key distribution (separate from targets, per change 4): { git?: { repository?: 'owner/repo' | url, subdirectory?: './' }, npm?: { package: '@scope/name', version?: string, registry?: url } }; core/types.ts, config/validate.ts (AB4106), adapters/capabilities/{claude,codex,cursor}.json gain a marketplaceSources list (claude: github|git|local|npm, codex: github|git|local|npm, cursor: local|git-staged); Claude and Codex catalog renderers emit the .//subdirectory relative source by default and the npm source entry when distribution.npm is set; install/surface.ts renders the exact marketplace add / install|add commands for the declared forms only.
    • Deletes: every ${name}-marketplace literal; claude.marketplace.name config key (replaced, not aliased); any hardcoded source: './' literal in the adapters.
    • Tests: tests/marketplace-identity.test.ts — one configured name appears in all selected manifests, INSTALL.md, install.mjs, receipts, and the recorded host argv; reserved name → error (13); tests/distribution.test.ts — npm entry appears in Claude and Codex catalogs only, cursor-only + distribution.npm → AB4106; subdirectory catalogs resolve to the root.
    • Docs (en+zh): the config reference page for plugin/marketplace/distribution, guide/distribution/index.mdx, guide/distribution/installation.mdx; docs/diagnostics.md (AB4106; moved codes keep their ids); hosts reference page regenerates from the capability JSONs.

    S8a — Host install proofs from the composite root + CI. (size M; satisfies 11, 12 end-to-end; depends on S3, S4, S5)

    • Changes: tests/host-install-proof.test.ts — build once with ['claude','codex','cursor'], run the Claude and Codex marketplace-add/install/replace/uninstall/Doctor proofs and the Cursor local + staged-marketplace proofs from the same root; add the npm-form proof (npm pack --json the root, extract, run the Codex/Claude proof from the extracted package/); .github/workflows/ci.yml:232-277.
    • Deletes: the per-host join(artifactRoot, host) fixtures.
    • Docs: none.

    S8b — Examples and create-agent-bundle templates. (size M; depends on S3, S4, S5)

    • Changes: examples/host-test/scripts/probe.mjs:235, README.md:79; examples/rsc-agent-runtime/package.json:8, README.md:89-99,164, tests/support/ensure-built.ts, tests/mcp-transports.integration.test.ts:455, tests/micro-eval.spot.test.ts:35, tests/host-artifacts.test.ts; packages/create-agent-bundle/templates/*/agent-bundle.config.ts (targets: ['claude','codex'] or whatever the owner picks as the scaffold default — D8), src/scaffold.ts:28, tests/scaffold.test.ts; tests/examples-contract.test.ts.
    • Deletes: any artifact/<host> path in examples.
    • Docs: example READMEs (user-facing products per AGENTS.md); validate each example at 1440×900 per the Workbench rule where a Workbench route is involved.

    S8c — Website sweep. (size M; depends on S2–S7 text being final)

    • Changes: every page in the table in §1 (en and zh, same PR), reference/index.mdx is rendered from docs/diagnostics.md (do not hand-edit). pnpm docs:site:build is the gate.
    • Deletes: every artifact/<host> tree and the plugin target mention.

    Wave 4 — additive, not blocking

    S9 — Portable alongside Codex/Cursor (change 3, options a/b). (size M–L; depends on S2)

    • First record a host-contract fixture proving whether Codex prefers root plugin.json over .codex-plugin/plugin.json (host-contracts/, pinned Codex CLI per scripts/host-cli-pins.mjs). If Codex prefers .codex-plugin/, delete AB4104 for portable+codex and emit root plugin.json (option b). Otherwise implement a namespaced portable view (hosts/portable/plugin.json + mcp.json, referenced from INSTALL.md's portable section; install's portable materialization copies it to a root) and delete AB4104. Cursor root mcp.json vs portable root mcp.json: same approach.
    • Tests: 9 in its positive form.

    S10 — Host-specific views for scoped components (lifts AB4105). (size M; depends on S2) — emit hosts/<host>/skills|commands|agents/ containing only that host's view and point Codex's/Cursor's explicit manifest paths at it; Claude keeps conventional dirs (its plugin.json schema does not redirect skills/). Turns the §5 F3 failure into output.

    Downstream (outside this repo, after S8): ScriptedAlchemy/cargo-hauler#48 — README install commands and the release workflow publish the composite root (single artifact, .claude-plugin/+.codex-plugin/ at the root) instead of artifact/<host>; the per-host marketplace sources collapse to one.

    Parallelism summary

    Wave Steps Agents Shared files
    1 S1 → S2 1 —
    2 S3 ‖ S4 ‖ S5 ‖ S7 4 S4 and S5 both rebase on #547; otherwise disjoint (install/* vs build/package-build.ts+pack-inventory.ts vs install/surface.ts vs AGENTS.md renderer)
    3 S6 ‖ S8a ‖ S8b ‖ S8c 4 S6 touches install/*, surface.ts, catalog renderers after S3/S5 land; S8a–c touch tests/examples/docs only. S8a's npm-source proof needs S6b's distribution.npm — land the local-registry half of S8a after S6
    4 S9 ‖ S10 2 both extend build/compose.ts; sequence if the same agent pool

    4. Docs, changesets, follow-ups (compressed)

    • One minor changeset per PR (pre-1.0 convention); summaries name the command/config key/diagnostic code, e.g. S2: "Emit one composite plugin root at --output; targets select the host projections inside it; remove the plugin target (AB4100); add AB4103–AB4105 (#PR)".
    • Docs pages per step are listed inline above; every one has an en and zh twin in the same PR; reference/index.mdx and the diagnostics table are generated from docs/diagnostics.md.
    • Follow-ups: cargo-hauler#48 (README + release workflow), S8b templates/examples, S8a CI proofs — all listed as steps, none left implicit.

    5. Spec points the current code cannot satisfy as written, and the smallest reconciliation

    • F1 — "committed at a repository root" vs. the build's ownership of artifactRoot. build.ts treats the output root as build-owned (creates/cleans/validates ownership, AB6014 ownership recovery at validate-artifact.ts:479), and AB4706 forbids the artifact overlapping dist. Building in place at a repository root would clean the repository. Reconciliation: the build never targets a repository root; docs describe publishing by committing the built directory's contents to a release repository root, an artifact branch, or a subdirectory referenced by the root catalog (S4/S8c wording), and the authored-AGENTS.md test in S7 covers the "already has files" case only for non-owned files. Change 4 stays satisfiable; the sentence about "committed at a repository root" describes the published location, not the build output.
    • F2 — Change 3's premise is asserted, not proven. plugin.ts:97 states Codex prefers root plugin.json; no fixture records it. AB4104 (S2) is honest about that ("cannot yet be represented faithfully"); S9 replaces belief with a recorded probe.
    • F3 — "emitted once" and "no scope leak" conflict for Claude. Claude discovers skills/, commands/, agents/, .mcp.json conventionally — the pinned adapters/schemas/claude/plugin*.json exposes only hooks, lspServers, and settings as pointable fields — while Codex and Cursor manifests point at explicit paths. A Codex-only skill in a Claude+Codex root can be isolated (put it in hosts/codex/skills/, point Codex there); a Claude-only skill cannot — anything in skills/ is Claude's, and Codex's single skills path must then point at a directory that duplicates the shared skills. Reconciliation: S2 fails with AB4105; S10 adds the duplicated hosts/<host>/ view as the sanctioned "host-specific representation". The contract's "emitted once" should read "emitted once unless a host needs a scoped view".
    • F4 — Acceptance 11's "supported npm-source install checks" need a registry. Codex's npm source runs npm pack <name> against a registry; CI has no published package. Reconciliation: the source schema quoted in the follow-up carries a registry field, so S8a starts a throwaway local registry (e.g. verdaccio) in the host-install-proofs job, publishes the packed root to it, writes a catalog entry with registry: http://127.0.0.1:…, and runs the real codex plugin marketplace add + plugin add against it. If the pinned Codex CLI rejects loopback registries, fall back to proving the layout (npm pack --json → extract → install from package/) in CI and keep the live check as a skip-gated smoke like ci.yml:304-313.
    • F5 — Cursor mcp.json vs portable mcp.json is the same collision as Codex's and the issue folds it into change 3 — AB4104 covers portable+cursor too (S2), which is why option (c) first is the only combination-complete choice.
    • F6 — "Keep the existing target precedence": precedence today is registration order in registry.ts:780-786. With a sorted, order-independent compose (acceptance 5) precedence only matters for choosing which host's bytes win on a collision — and the spec makes collisions fatal, so precedence has no remaining job. Reconciliation: delete the notion; document that projections are unordered.

    6. Open decisions (recommendation first)

    • D1 S1 deletes adapters/plugin.ts one PR before S2 lands (window without a composite) — recommended; alternative: S1+S2 as one L PR.
    • D2 Missing-projection install diagnostic: reuse AB7001 with a new message (it already means "no manifest for this host at the root"); alternative: allocate AB7016.
    • D3 Generated AGENTS.md: delete outright (S7); alternative: opt-in output.agentsMarkdown: true writing AGENTS.generated.md.
    • D4 Change 3: AB4104 first (S2), evidence-driven S9 later — as reasoned in §2.
    • D5 Scope isolation: fail with AB4105 in S2, add hosts/<host>/ views in S10; alternative: ship the views in S2 (grows S2 past L).
    • D6 Marketplace identity key: top-level marketplace.name, default plugin name, claude.marketplace.name deleted (S6); alternative: keep it under claude and mirror — rejected as a second copy.
    • D7 Publishing at a repo root: build to a directory, publish its contents (F1).
    • D9 Distribution config shape: one top-level distribution: { git?, npm? } key (S6b), consumed only by catalog renderers and INSTALL.md; alternative: infer npm from package.json name alone and git from the repository's remote — rejected because generated catalogs must be reproducible from config, not from the checkout.
    • D8 Scaffold/template default targets: keep the framework default (portable) untouched per the issue; templates select ['claude','codex'] so a scaffolded project installs on both native hosts without hitting AB4104.

    Not established: whether Codex prefers root plugin.json (F2); whether any current example uses host-scoped skills/commands (a grep of examples/*/src/skills for targets: finds none, but the check belongs in S2's PR before merge, since it decides whether AB4105 fires on examples/*).

  4. ScriptedAlchemy commented on Sep 5, 2026

    @ScriptedAlchemy
    OwnerAuthor

    Owner decision (2026-09-05): #569 is the implementation that lands for this issue; #578 (Wave 1 of the plan above) is closed as superseded, and the plan's later waves rebase on main after #569 merges. One directive from the plan was folded into #569 before merge: nothing nested under the built root is probed by install/doctor (no <root>/<host> or <root>/plugin compatibility), since the codebase is unreleased.

  5. ScriptedAlchemy commented on Sep 5, 2026

    @ScriptedAlchemy
    OwnerAuthor

    Owner decision, final (2026-09-05 04:50 UTC): #578 lands for Wave 1; #569 stays closed. This supersedes the 02:23 comment above.

    For the record: two sessions received different answers about the same pair of PRs — 02:1x UTC "keep #569" (which closed #578 at 02:23), then 02:55 UTC "keep #578" (which closed #569 at 03:06) — so both were closed until #578 was reopened at 04:2x. Asked to break the tie, the owner delegated it to this session. #578 is chosen because it follows the implementation plan posted in this issue, it is absorbing #569's AB4106 and consumer/doc changes before it leaves draft, and it is being brought current with main (Rslib 1.0, #580, #582, #585, #586), which #569 is not.

    feat/555-composite-root (#569's branch) is kept until the port lands. Later waves rebase on main after #578 merges.

  6. ScriptedAlchemy commented on Sep 5, 2026

    @ScriptedAlchemy
    OwnerAuthor

    Architecture alignment: #592 is now the umbrella meta-framework direction. This issue remains the concrete composite-artifact/output slice and should stay open until its acceptance criteria are complete.

    Two constraints from #592 should guide the remaining work:

    1. Treat targets strictly as host projection selection. Do not let target names become shared application identity, runtime identity, distribution identity, or another filesystem partitioning concept.
    2. The composite root should ultimately be emitted from common Artifact IR/assembly fed by per-host Projection IR. New composite-root consumers should prefer the compiled manifest/root contract rather than adding more filesystem probing.

    No scope needs to be removed from #555; this comment clarifies how its implementation fits the longer-term compiler architecture.

  7. ScriptedAlchemy commented on Sep 5, 2026

    @ScriptedAlchemy
    OwnerAuthor

    Consumer evidence from cargo-hauler: the old partition model has leaked broadly into application code, not just docs. Current master hard-codes artifact/<host> in agent-bundle.config.ts comments, README/install docs, AGENTS.md, version/packed tests, and src/cli/dashboard.ts path discovery. cargo-hauler#107 tracks deleting all of that after #555.

    This reinforces one acceptance criterion for #555: downstream applications should not need to know a host projection's physical root beyond the composite artifact + manifest pointers. Test helpers and generated install/browser APIs should make hard-coded <root>/<host> paths unnecessary.

  8. ScriptedAlchemy commented on Sep 5, 2026

    @ScriptedAlchemy
    OwnerAuthor

    Wave 1 landed: #578 squash-merged to main as 62b69c068. S1 (remove the plugin target) and S2 (one composite root; targets select host projections) per the plan above, plus the #569 port (AB4106, consumer and docs fixes) and the #592 boundary audit. Details, the port table, and the audit are in the PR body.

    What changed for consumers

    • agent-bundle build writes one directory at the artifact output. There is no artifact/<host> or artifact/plugin partition: host manifests sit in their dotfolders at the root (.claude-plugin/, .codex-plugin/, .cursor-plugin/, plugin.json); Codex/Cursor hook and MCP documents sit beside their manifests (.codex-plugin/{hooks,mcp}.json, .cursor-plugin/{hooks,mcp}.json); skills/, hooks/, mcp/, scripts/, bin/, INSTALL.md are emitted once, install.mjs when cursor or portable is selected.
    • targets accepts claude, codex, cursor, portable (default portable). targets: ['plugin'] / --target plugin fail with AB4100; the generated AGENTS.md is gone; create-agent-bundle --target accepts only the four hosts; TargetAdapter.lowersConfigExtensions, TargetRegistry.lowersConfigExtension(), NormalizationTargetRegistry.lowersConfigExtension() are removed.
    • New diagnostics on the shared root: AB4103 (two selected projections plan one path with different bytes), AB4105 (a command or rule scoped to a subset of the selected hosts that another selected host discovers conventionally), AB4106 (an advanced-registry adapter selected beside any other target). validate, inspect, and build report all three.
    • agent-bundle install <host> --from <root> and doctor --from <root> read the host manifest directly under the root (AB7001 when absent; nothing nested is probed). mcp run, serve-app, dev proxy --target <host> resolve the host's MCP document in the same root; openInstalledHostMcpServer reads the composite root as every host's bundle root; the packed pool reads bin/ and the single mcp/ entry from the root.
    • A hook shared by several selected hosts compiles to hooks/<name>.<host>.mjs, each wrapper baking its host; AGENT_BUNDLE_HOOK_HOST is no longer read. Compiled MCP entries, scripts, bins, and MCP Apps are attributed to the sorted composite identity (claude+codex) in build --json / inspect --bundler only as report metadata; reordering targets yields byte-identical output.
    • The warm event runtime's socket is identified by the artifact alone (epoch + root, never the selection); the invoking host rides on each request. agentBundleBrowserRstest({ target }) now names the host each app mounts as; apps compile once for the whole selection.
    • Downstream (cargo-hauler#107 and the like): every hard-coded <root>/<host> path becomes <root>; nothing else in the layout is host-partitioned.

    #592 constraints carried into the remaining waves

    targets is projection selection only. In W1 the selection identity was removed from the event runtime's endpoint id, the tool-call lineage fallback, the browser-pool mount profile, and the "which hosts does this server host for" list; "built-in host" is judged by adapter identity (TargetRegistry.builtInHost()), not name. What still keys on names, and where it goes:

    • The artifact manifest records target names; consumers that only have the manifest (install, doctor, the packed/installed harnesses) key on them → S6 records the built-in projection identity in the manifest.
    • Host planners still see the selection (selected = model.targets… for wrapper file names and the shield rule) → S3/S10: composite context passed into TargetAdapter.plan explicitly, or wrapper naming and the shadow rule move into assembly.
    • Per-host digests salt the content hash with the host name; host-name gates remain where capability rows exist; compositeNoticeDelivery resolves capabilities during assembly → S9/S10 (details: PR body "Listed, not changed in W1").
    • No new semantics belong in compose.ts: normalization owns application meaning, host adapters own projection, assembly merges bytes.

    Handoff

    Wave 2 (parallel, separate worktrees, rebase on main at 62b69c068):

    • S3 — composite-root consumers: AB7001 rewording (D2), uninstall, dev services' relocation-awareness beyond "root = epoch", the eval Codex home layout, F4 above. New consumers read the compiled manifest/root contract rather than probing the filesystem.
    • S4 — npm package root = plugin root: feat(build): hold the package build's dist bundles to AB6005 #588 ("hold the package build's dist bundles to AB6005") is already on main and merged into this branch; AB6005: also refuse bare createRequire/require/import.meta.resolve loads in compiled modules #591 extends that scanner (bare createRequire/require/import.meta.resolve). package.json name = configured package name, no lifecycle-script dependency, npm pack archive root = plugin root.
    • S5 — INSTALL.md / install.mjs from selected projections and distribution form: W1 composes the existing per-host sections once (by adapter identity); the per-source rendering (npm / Git / local) and F8b are S5.

    Wave 3 (after W2): S6 one marketplace identity + distribution sources in the catalogs (and the manifest identity above); S8a host install proofs from the composite root across npm / Git / local + CI; S8b examples and create-agent-bundle templates; S8c website sweep once S2–S7 text is final.

    Wave 4 (additive): S9 portable alongside Codex/Cursor (change 3, a/b), taking F5/F7/F8a; S10 host-specific views for scoped components (lifts AB4105, acceptance 9).

    Smaller items left from W1 review: Codex host-validation does not follow manifest hooks/mcpServers pointers (Cursor's does); compose.ts sameBytes compares copy entries by source path (conservative AB4103); one reachesSelection helper for the four "targets reach the selection" predicates.

  9. ScriptedAlchemy commented on Sep 5, 2026

    @ScriptedAlchemy
    OwnerAuthor

    Tracker reconciliation (owner audit 2026-09-05 17:11 UTC): composite root (#578 62b69c068) landed. Also landed: #593 (c9546b41e), #594 (0d4a37cef), #595 (ef2abbacc), #596 (efdec6bc0), #564 (512ddaac3). Still open: manifest v2 (#604), npm-root parity S4 (npm dist/bin lacks the web wiring the artifact bin gets — an agent is on it), S5 install instructions/marketplace identity/authored repo instructions, execution-boundary consolidation.

  10. ScriptedAlchemy commented on Sep 5, 2026

    @ScriptedAlchemy
    OwnerAuthor

    Concrete acceptance for the already-assigned S4/npm-root parity follow-up:

    At audited main 2c28363ce, src/build/cli-bins.ts::cliBinRslibEntries() supplies the web runtime alias, inlined host-page module, and web options to generatedCliBinEntrySource(). src/build/package-build.ts::planPackageEntries() builds the npm CLI without that web wiring. The test must therefore execute the bin actually named by the packed consumer's package.json, not just run artifact/bin/<name>.mjs manually.

    Required journey: pack/install a plugin exposing both a projected CLI operation and an App; remove its source and framework checkout; invoke the installed package bin's operation and web command; verify its MCP/App assets resolve from the installed canonical root. Compare that with the local-artifact launch using the same supported inputs. Do not restore locateFrameworkCli, checkout crawling, a post-install rebuild, or stdout URL parsing to bridge the mismatch.

    Dependency caveat: for a fixture containing definePrebuilt({ runtimeDependencies: [...] }), retain/install those declared consumer-runtime dependencies. Deleting all node_modules is a valid self-containment test for compiler-owned surfaces with no declared external requirements, not a valid universal requirement for opaque payloads that explicitly need packages. Report the two proof cases separately.

    Keep S5's install/replace/uninstall, selected-host instructions, marketplace identity, and authored AGENTS.md preservation distinct from this executable-parity task. #604's inventory/reference integration is a prerequisite for claiming a single authoritative package root, not a reason to rebuild a second application during packaging.

  11. ScriptedAlchemy commented on Sep 5, 2026

    @ScriptedAlchemy
    OwnerAuthor

    W2-S3 landed: #604 merged as 632847a39b — consumers read the composite root through agent-bundle.manifest.json (manifest v2). Follow-up #650 carries the last self-review fixes (host document ↔ launch record agreement, mcp run without a manifest-only fallback, install mode drift, one portable path rule) and is on auto-merge.

    What S3 gives the remaining waves:

    Hand-off — S4 (npm root, #639): distribution.channels already carries 'npm' when compiler.project.packageName is set. Put npm-specific pointers under distribution (closed keys → schemas/agent-bundle.manifest.schema.json + parseDistribution in build/manifest.ts + website/docs/{en,zh}/reference/artifact-manifest.mdx), never under projections[]. Adding an optional key is a manifestVersion bump only if an older reader would reject it — both readers are closed-key, so it does: bump to 3 and update artifactManifestVersion in web-host/manifest.ts (single source; build/manifest.ts re-exports it).

    Hand-off — S5 (INSTALL.md / install.mjs): distribution.install.instructions / .script are the pointers; AB6023/AB6024 already require both files per builtInHosts(selected). Author the contents as files[] rows (the writer indexes them). The generated install.mjs (install/surface.ts) still reads .cursor-plugin/plugin.json and probes .cursor-plugin/mcp.json/mcp.json for runtime state roots because it also installs Agent Plugins packs that have no artifact manifest — when S5 rewrites it, read agent-bundle.manifest.json first (projections[cursor].documents.{plugin,mcp}) and keep the probe only for manifest-less packs.

    W2 stays open for S4/S5.

  12. ScriptedAlchemy commented on Sep 5, 2026

    @ScriptedAlchemy
    OwnerAuthor

    Ponytail reference: focused follow-ups and distribution acceptance

    I filed #651 for negative cross-host discovery/precedence conformance and #652 for a lightweight skills/plain-hooks fixture. They are bounded implementation/test slices beneath this issue, not replacements for composite assembly.

    Ponytail provides an important packaging caveat. At 974d940a1c5344210874150b98ff0d2c861fab6a, its package.json ships OpenCode/Pi/Qoder integrations and shared content, but its files allowlist omits the checkout's .claude-plugin/, .codex-plugin/, .agents/plugins/, and gemini-extension.json. This can be intentional for Ponytail; it is not proof that its Git and npm forms contain the same composite.

    Keep Agent Bundle's stronger S4/S5 contract explicit. For the same selected projections:

    • Verify the actual tarball/extracted root contains each declared host manifest and every referenced hook/skill/asset/executable. Compare the manifest-declared plugin file set and bytes, not merely CLI command availability.
    • Add a negative fixture where an authored npm files/ignore policy would omit a selected hidden host directory. The canonical packaging path must include it or fail before publication; it must not silently publish a narrower plugin.
    • Preserve Composite conformance: test cross-host auto-discovery and precedence, not only file collisions #651's forbidden-discovery assertions in the packed copy as well as the Git/local root. Absence of an incompatible conventional file is part of correctness.
    • Package metadata and distribution-specific catalogs/instructions may differ; selected application behavior, host visibility, and shared executable bytes must not. Do not rebuild the plugin during installation to repair missing content.
    • Preserve authored repository instructions when publishing at a Git root; do not turn a generated instruction projection into an overwrite of an unrelated authored AGENTS.md.

    This belongs to the existing #555 distribution work rather than a duplicate npm-root issue. #639 is now closed without merge, so these requirements are attached here for the continuing implementation, not presented as changes to an active #639 PR.

  13. ScriptedAlchemy commented on Sep 6, 2026

    @ScriptedAlchemy
    OwnerAuthor

    W2-S4/S5 landed in #656 as 45beb5479292eb89b58fafd33b7143c2dc889fe4.

    Closing map:

    Packed acceptance is packages/agent-bundle/tests/packed-web-command.test.ts: scripts-enabled tarball install, source removal, installed package.json bin operation + web journey, and artifact/npm executable-byte parity. packed-host-install-proof.test.ts additionally checks the manifest-declared tarball file set, bytes, modes, hidden host roots, and forbidden fallback manifests.

    S5 was unblocked by S3 and is now landed with S4. I found no unaddressed item remaining in #555, so #656 closed this umbrella.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions