Repository navigation
Emit one composite plugin artifact; use targets to select the host projections inside it #555
Description
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.tsselects registered target names and synthesizes apluginlowering target.adapters/registry.tsregisterspluginAdapteras an ordinary selectable target;portableis the current default.build/build.tsplans each selected adapter independently and stages it at<artifactRoot>/<target.name>.adapters/plugin.tsis an always-Claude-plus-Codex-plus-Cursor adapter. It filters resources through the syntheticplugintarget, 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.tscurrently 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.tsprobes the supplied root, then<root>/<host>, then<root>/plugin;install/doctor.tsprobes 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:
- Projection selection:
claude | codex | cursor | portable. - 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 pluginas unknown. - Remove the synthetic
plugintarget 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
pluginAdaptermay 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 atartifactRoot. - 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 underartifact/<host>orartifact/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
cursoris selected, include the existing self-containedinstall.mjsand 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.mjsonly 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
targets: ['claude', 'codex']emits one root with.claude-plugin/and.codex-plugin/, noclaude/,codex/,portable/, orplugin/output partitions, and no Cursor projection.- Cursor-only emits one root with Cursor files,
INSTALL.md, and the self-containedinstall.mjs --uninstalllifecycle. pluginis rejected as a config or CLI target.- Omitting targets emits one composite root with the documented default projection set.
- Reversing target order produces byte-identical output.
- Shared implementation files compile once; each selected host manifest references them correctly; host-scoped components do not leak.
- Selected native hook documents and wrappers survive composition; unselected host files are absent.
- The artifact manifest and hook index list real projections only.
portable + codexandportable + cursorpreserve the native host feature set or fail with the documented incompatibility diagnostic.npm pack --jsonshows manifests and plugin content atpackage/root, with noartifact/**; installing the packed tarball uses no source checkout and no post-install Agent Bundle build.- The same committed root passes native local/Git marketplace install checks for Claude and Codex, and the supported npm-source install checks.
- 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.
- Marketplace name configuration is consistent in all manifests, instructions, receipts, and lifecycle commands.
- Authored
AGENTS.mdpreservation and generated-instruction behavior are covered.
References
- Claude marketplace sources, commands, and npm package fields: https://code.claude.com/docs/en/plugin-marketplaces
- Claude plugin root and component layout: https://code.claude.com/docs/en/plugins-reference
- OpenAI plugin packaging documentation: https://developers.openai.com/plugins/build/plugins
- OpenAI marketplace and plugin-root examples: https://github.com/openai/plugins
- Codex npm marketplace-source implementation, including archive validation and end-to-end installation, merged 2026-06-26: [codex] Support npm marketplace plugin sources openai/codex#29375
- Codex marketplace manifest and local-path conventions: https://github.com/openai/codex/blob/main/codex-rs/skills/src/assets/samples/plugin-creator/references/plugin-json-spec.md
- npm package layout examples inspected: https://www.npmjs.com/package/@prismer/claude-code-plugin/v/1.9.0, https://www.npmjs.com/package/@ours.network/codex/v/1.1.0, https://www.npmjs.com/package/@useorgx/codex-plugin/v/0.1.19
- Existing Agent Bundle work: Feature: emit ready-to-install host packs (and/or an 'agent-bundle install <host>' command) so projects don't hand-maintain install scripts #181, Package generated host packs into publishable npm plugins with a package-relative installer #252, Native Claude proofs run
claude plugin validateon the bundle directory, which Claude treats as a marketplace (hooks/skills/agents never checked) #475 - Cargo Hauler follow-up: Claude plugin fails to load: plugin.json "hooks" re-registers the auto-loaded hooks/hooks.json (no MCP tools, no hooks in session) cargo-hauler#48
Version audited
Agent Bundle main at 42539ff5f and issue/code state inspected on 2026-09-04.
Activity
[-]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[/+]ScriptedAlchemy commentedon Sep 4, 2026
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 withnpm pack --ignore-scripts, requires the standard npmpackage/archive root, verifies thatpackage.jsonnames 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>orartifact/pluginnesting. 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.
ScriptedAlchemy commentedon Sep 4, 2026
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 fromorigin/mainat42539ff5f(the "Version audited" commit). Owner directives folded in: the codebase is unreleased, so every step deletes rather than shims — no<root>/<host>or<root>/pluginprobing survives, no per-host partitions, no dual-mode flags, no deprecation warnings. Each PR ships oneminorchangeset per AGENTS.md's pre-1.0 convention. Steps that touchbuild/pack-inventory.ts,build/pack-dependencies.ts,api.tsprepack, orinstall/surface.tsare sequenced after PR #547 (which also allocatesAB7014/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:
{"source":"npm","package":"@scope/name","version":"...","registry":"https://..."}. Codex materializes it withnpm pack --ignore-scripts, requires the standard npmpackage/archive root, verifies thatpackage.jsonnames the requested package, and installs the extracted plugin root without executing package scripts."postinstall/preparedependency,package.jsonnameequals the configured npm package name; S6: generated Claude/Codex catalogs can emit annpmsource entry from distribution config; S8a: proof against a local registry (theregistryfield makes this CI-testable — see F4)artifact/<host>orartifact/pluginnesting."install.mjsonly whencursorselected; new test: runinstall.mjsfrom an extractedpackage/directorynpmis a plugin source inside a marketplace catalog, not a replacement for the marketplace itself."sourceentries; never a target (change 4)./or a documented relative subdirectory"distribution.git.subdirectory(default./) written into every selected catalog'ssourceINSTALL.mdmust use only source types actually supported by that host."adapters/capabilities/*.json(Claude: git/local/npm; Codex: git/local/npm; Cursor: local + staged git, no npm) and consulted when rendering catalogs andINSTALL.md; unsupported → build diagnostic (AB4106, §2)package.jsonis identical with and without npm packaging; S8a: install from tarball invokes no build1. "Current implementation" — claims verified against
mainpackages/agent-bundle/src/…)config/normalize.tsselects registered target names and synthesizes apluginlowering targetconfig/normalize.ts:84-86(plugin→ skill hostsclaude+codex);:1237adds apluginskill document only whentargetNames.includes('plugin')adapters/registry.tsregisterspluginAdapteras an ordinary selectable target;portableis defaultadapters/registry.ts:780-786—.register(portableAdapter, { default: true })….register(pluginAdapter)build/build.tsplans each selected adapter independently and stages it at<artifactRoot>/<target.name>build/build.ts:193resolve(options.artifactRoot, target.name)insideassertInsideadapters/plugin.tsis always Claude+Codex+Cursor, filters through the syntheticplugintarget, always invokes those planners, static validation requirements for Claude and Codexplugin.ts:66pluginName = 'plugin';:436isSelected = targets.includes(pluginName);:450-451always callsplanClaudeArtifacts/planCodexArtifacts;:462,:487,:509,:521always plans Cursor marketplace/MCP/hooks/manifest;:218and:223mark.claude-plugin/plugin.jsonand.codex-plugin/plugin.jsonrequired: true. Addition the issue's change 2 relies on::441buildshookFreeModel = { ...model, hooks: [], nativeHooks: undefined }and passes it to both host planners — this is the "hook-free intermediate model" that strips native hooks.:97documents the deliberate omission of rootplugin.json(the Codex-precedence claim behind change 3)api.ts, package inventory, artifact validation, dev/workbench services, eval harnesses, test helpers assume<output>/<target>api.ts:768join(artifact, target),:1260pluginDirectory: join(output, target),:719-744,1237aHostValidatedTargetunion that includes'plugin';build/pack-inventory.ts:201join(artifactRoot, target.name, path)and:105-110case 'plugin'expecting three manifests;build/validate-artifact.ts:460-470per-target prefix scan +resolve(options.artifactRoot, target.name);build/validate-artifact-mcp.ts:133targetRoot = resolve(artifactRoot, target.name)(and:208cwd containment against it);dev/epoch-store.ts:787,861join(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:287expandsplugin→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 referenceartifact/<host>or<host>/.<host>-plugin/…literals (e.g.tests/host-install-proof.test.ts:68,247). Workbench (packages/workbench/src/**) only liststarget.nameas a label (artifacts/artifacts-model.ts:199,playground/playground-page.tsx:177,749) — no path joins; it inherits the fix from the dev servicesbuild/package-build.tscreates a sibling npm package whose installer reaches back to a nested artifact; the npm root is not the plugin rootpackage-build.ts:196-197installHostsfiltered bytarget.name === host || target.name === 'plugin';:206-227emits a<name>-installbin whoseartifactRelativeUrl(:215,:226) points fromdist/binback intoartifactRootinstall/install.tsprobes root →<root>/<host>→<root>/plugin;install/doctor.tsprobes root →<root>/<host>install/install.ts:181-194(pluginRoot = join(root, 'plugin')at:187,AB7001message at:189-192);install/doctor.ts:452-462(nopluginfallback — 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):adapters/claude.ts:3247,adapters/codex.ts:1258targetName === 'plugin' ? 'plugin' : 'claude'/'codex'install-surface branchinginstall/surface.ts:17,250-251BuiltInTargetincludes'plugin';case 'plugin'concatenates all three host instruction blocksinstall/surface.ts:1343(needsCursorInstaller)install.mjsby target name, incl.pluginevents/projection.ts:496target === 'plugin'at projection timebuild/entry-shell.ts:1060-1062allowedEventTargetswidened to all three hosts whentarget === 'plugin'adapters/hook-contract.ts:642-647AGENT_BUNDLE_HOOK_HOST/PLUGIN_ROOTwhenentry.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 thepluginnamepackages/create-agent-bundle/src/options.ts:7targetNamesincludes'plugin'; templatemcp-server/agent-bundle.config.ts:13selects['portable','codex','claude']examples/host-test/scripts/probe.mjs:235,README.md:79install <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:17dist/plugins/<host>/…paths.github/workflows/ci.yml:232-277host-install-proofstests/host-install-proof.test.ts, which buildstargets: ['claude','codex','cursor','plugin']and installs fromartifact/<host>scripts/record-claude-hooks-schema-fixtures.mjs:9,32/bundle/claude(cosmetic; fixture text only)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 theplugintarget)artifact/<host>trees and theplugintargetREADME.md(2),docs/framework-mode.md(8),docs/diagnostics.mdCorrections 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 thatinstallanddoctoralready 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:19and the inlinedinstall.mjstemplate at:514,:556,:1050,:1066all hardcode${name}-marketplace; receipts ininstall.ts:599-608/uninstall.ts:1047-1323key on${host}-marketplaceregistration kinds.2. Diagnostic codes to allocate
docs/diagnostics.mdfamilies:AB41xx= "Normalized model invariants (unknown targets, duplicate IDs and outputs)" — currentlyAB4100(unknown target,config/validate.ts:2327),AB4101,AB4102(multiple inputs produce one generated path,:2524).AB70xxinstall:AB7000–AB7013on main,AB7014/AB7015taken by #547.AB4100(existing)targets: ['plugin']/--target pluginis an unknown target — no new code; the message already saysUnknown target "plugin"once registration is removedAB4103AB4104portableselected together withcodexorcursorcannot yet be represented in one root without degrading a host (rootplugin.json/mcp.json); names the exact colliding root files and the reasonAB4105skills/,commands/,agents/,hooks/hooks.json,.mcp.json); names the component, its target set, and the leaking hostAB4106cursorselected), or the npm package name is missing while an npm source is declaredAB7001(existing, reworded)install <host>on a root with no<host>projection: "No<host>projection in<root>(.<host>-plugin/plugin.jsonmissing). Selected projections: …" — reuse, not a new code (recommendation D2)Change 3 recommendation: ship option (c),
AB4104, first, in S2, and make it the only thing standing between the composite andportable. 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.jsonprecedence, which the repo asserts (plugin.ts:97) but does not prove (no fixture underhost-contracts/ortests/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
minorchangeset, en+zh docs listed,pnpm typecheck && pnpm lint && pnpm test:unit.Wave 0 — prerequisite
pack-inventory.ts,pack-dependencies.ts,api.tsprepack,install/surface.ts,AB7014/AB7015). S4, S5, S6 rebase on it.Wave 1 — serial foundation (one agent, one worktree, S1 then S2)
S1 — Remove
pluginfrom the public target model; deleteadapters/plugin.ts. (size M; satisfies acceptance 3; prerequisite of everything)adapters/registry.ts:786(drop registration);config/normalize.ts:84-86,1237(delete thepluginbranches;loweringHostsbecomes a pureisSkillHostfilter);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 onentry.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.tshelp text.adapters/plugin.tsin full — its only export ispluginAdapter(:1121) and its only importer isregistry.ts, so nothing else breaks; the composite-specific constants private to it (codexBundleMcpPath,cursorBundleHookContract) are re-homed inadapters/codex.ts/adapters/cursor.tsby S2 when the composite stage needs them. Also deleteplugin-specific fixtures/tests (tests/plugin-adapter*.test.tsif present, thepluginrows oftests/install-surface.test.ts,tests/host-install-proof.test.ts:68,247).tests/config-validate.test.tsassertsAB4100fortargets: ['plugin']andtests/cli.test.tsfor--target plugin;git grep -n "'plugin'" packages/agent-bundle/srcreturns only host-CLI argv literals andplugin.jsonschema names.reference/targets-artifacts.mdx(remove thepluginrow),reference/cli.mdx;docs/diagnostics.md(no code change, noteAB4100coversplugin).S2 — One stage at
artifactRoot; compose selected projections. (size L; satisfies 1, 4, 5, 6, 7, 8, 9 (diagnostic form); depends on S1)build/build.ts:193(root = artifactRoot; delete the per-targetassertInside(... target.name)partition and the per-target loop's directory creation); newbuild/compose.ts(the only new module; imported bybuild.tsin the same commit): callplanClaudeArtifacts/planCodexArtifacts/planCursorArtifacts/planPortableArtifactsonly for selected hosts, with the full model (no hook-free copy — fixesplugin.ts:441's hook stripping so native hooks survive); include a declaration whendeclaration.targets ∩ selected ≠ ∅; merge entries by path — identical bytes → one entry, differing bytes →AB4103; emitAB4104whenportable ∈ selectedand(codex ∈ selected ∨ cursor ∈ selected); emitAB4105for 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.tscapability checks) computed over the selected set. Artifact manifesttargets= selected real hosts only (build/manifest.ts:89,245-261— already sorted-unique, keep); hook index likewise.build/validate-artifact.ts:460-470andbuild/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 whenclaudeselected; CursorAB6026notice only whencursorselected).build.ts;standardArtifactLayoutper-target root documents (adapters/types.ts:436rootDocuments) if nothing else uses them after AGENTS.md goes (S7).tests/build-compose.test.ts+ existingtests/build.test.ts,tests/hooks.test.ts,tests/mcp.test.ts):['claude','codex']root has.claude-plugin/,.codex-plugin/, noclaude/|codex/|portable/|plugin/dirs, no.cursor-plugin/(1); omitted targets → portable-only root (4);['codex','claude']byte-identical to['claude','codex'](5); onescripts/*.mjsper shared script, referenced from every selected manifest; Claude-only skill absent from a Codex-only build (6);nativeHooksfor 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.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)
install/install.ts:181-194andinstall/doctor.ts:452-462→ oneresolveBundleRoot(from)that checksjoin(root, manifestPath(host))and otherwise throwsAB7001reworded as the missing-projection diagnostic; move it to a leaf module both import (install/bundle-root.ts) — one implementation, not two;install/uninstall.tslikewise;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.<root>/<host>and<root>/pluginprobes (no back-compat branch);doctor.ts's private copy of the resolver.tests/install.test.ts/tests/doctor.test.ts— install from a two-host root for each host;install cursoron a['claude']root →AB7001missing-projection; existing epoch-store / hook-playground / eval-harness tests rewired to root paths. Workbench needs no code change (labels only), butpnpm --filter workbench testruns.guide/distribution/installation.mdx(--from <root>),guide/distribution/validation.mdx(Doctor);docs/diagnostics.md(AB7001text).S4 — npm package root = plugin root. (size M; satisfies 10; depends on S2 and #547)
build/package-build.ts:196-227— delete the<name>-installsibling bin andartifactRelativeUrl;build/pack-inventory.ts— expected files are the composite root's files atpackage/root (manifests,hooks/,skills/,scripts/,bin/,mcp/,mcp-apps/,assets/,INSTALL.md,install.mjswhen present,agent-bundle.manifest.json);api.tsprepack — the artifact root is the package root (files/main/binderived from it);config/validate.tsAB4706("artifact output overlapsdist") — re-specify:output.distPathand 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.jsonfiles.installEntryRuntimePath/installEntryRuntimeSpecifieralias plumbing (build/entry-shell.ts:129-131,package-build.ts:19-20,218,316-317) and theinstall-entryruntime moduleruntimeModulePath('install-entry')resolves to; theartifact/entry inpack-inventory.tsexpectations.tests/prepack.test.ts—npm pack --jsonlistspackage/.claude-plugin/plugin.jsonetc. and nothing underpackage/artifact/; extract the tarball into a temp dir and runagent-bundle install claude --from <extracted>with a stub host runner: no source checkout, no build invoked (10);package.jsondeclares nopostinstall/prepare/installscript and itsnameequalsdistribution.npm.package(Codex verifies it); the digest of every root file exceptpackage.jsonis identical between a build with and without npm packaging (follow-up: delivery choice must not change internal bytes).guide/distribution/index.mdx,guide/distribution/validation.mdx(prepack),reference/cli.mdx.S5 —
INSTALL.md/install.mjsfrom selected projections and distribution form. (size M; satisfies 2, and the instruction half of 12; depends on S2 and #547)install/surface.ts—installMarkdowntakes the selected projection set and a distribution form (git|npm|local; derived:npmwhenpackage.jsonis emitted,localalways,gitwhen the config declares a repository); Claude/Codex sections use native CLIs (claude plugin marketplace add <root|owner/repo>/codex plugin marketplace add …thenplugin install|add <name>@<marketplace>), Cursor section keeps the self-containedinstall.mjslocal 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.casefan-out for unselected hosts; anypluginwording left in the template.tests/install-surface.test.ts— Cursor-only build emitsINSTALL.mdwith only the Cursor section andinstall.mjs;['claude','codex']emits noinstall.mjs;install.mjs --uninstalllifecycle against a tempHOME(2);install.mjsrun from an extracted npmpackage/directory installs the same bytes (follow-up: "the bundled helper installs from the acquired package root").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 inplugin.ts:279,589, so after S1 nothing emits it)config/normalize.ts:360(dropAGENTS.mdfrom the reserved root-document list if that is its only purpose);adapters/types.ts:436rootDocumentsif now unused.INSTALL.mdis the generated instruction surface, and an authoredAGENTS.mdat a repository root can then never be overwritten by construction.AGENTS.md(build must not clean it — see F1) and assert it is byte-identical afterwards; assert no build emitsAGENTS.md(14).guide/start/project-structure.mdx(removeAGENTS.mdrow).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)
config/marketplace-identity.tsexportingmarketplaceName(model)(config keymarketplace.name, default = plugin name) and the reserved/kebab-case validation moved out ofadapters/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 inclaude.ts), plus Codex's and Cursor's if the capability JSONs record any.distribution(separate fromtargets, 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}.jsongain amarketplaceSourceslist (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 thenpmsource entry whendistribution.npmis set;install/surface.tsrenders the exactmarketplace add/install|addcommands for the declared forms only.${name}-marketplaceliteral;claude.marketplace.nameconfig key (replaced, not aliased); any hardcodedsource: './'literal in the adapters.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.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)
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 --jsonthe root, extract, run the Codex/Claude proof from the extractedpackage/);.github/workflows/ci.yml:232-277.join(artifactRoot, host)fixtures.S8b — Examples and
create-agent-bundletemplates. (size M; depends on S3, S4, S5)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.artifact/<host>path in examples.S8c — Website sweep. (size M; depends on S2–S7 text being final)
reference/index.mdxis rendered fromdocs/diagnostics.md(do not hand-edit).pnpm docs:site:buildis the gate.artifact/<host>tree and theplugintarget mention.Wave 4 — additive, not blocking
S9 — Portable alongside Codex/Cursor (change 3, options a/b). (size M–L; depends on S2)
plugin.jsonover.codex-plugin/plugin.json(host-contracts/, pinned Codex CLI perscripts/host-cli-pins.mjs). If Codex prefers.codex-plugin/, deleteAB4104forportable+codexand emit rootplugin.json(option b). Otherwise implement a namespaced portable view (hosts/portable/plugin.json+mcp.json, referenced fromINSTALL.md's portable section;install's portable materialization copies it to a root) and deleteAB4104. Cursor rootmcp.jsonvs portable rootmcp.json: same approach.S10 — Host-specific views for scoped components (lifts
AB4105). (size M; depends on S2) — emithosts/<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 (itsplugin.jsonschema does not redirectskills/). 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 ofartifact/<host>; the per-host marketplace sources collapse to one.Parallelism summary
install/*vsbuild/package-build.ts+pack-inventory.tsvsinstall/surface.tsvs AGENTS.md renderer)install/*,surface.ts, catalog renderers after S3/S5 land; S8a–c touch tests/examples/docs only. S8a's npm-source proof needs S6b'sdistribution.npm— land the local-registry half of S8a after S6build/compose.ts; sequence if the same agent pool4. Docs, changesets, follow-ups (compressed)
minorchangeset per PR (pre-1.0 convention); summaries name the command/config key/diagnostic code, e.g. S2: "Emit one composite plugin root at--output;targetsselect the host projections inside it; remove theplugintarget (AB4100); addAB4103–AB4105(#PR)".enandzhtwin in the same PR;reference/index.mdxand the diagnostics table are generated fromdocs/diagnostics.md.5. Spec points the current code cannot satisfy as written, and the smallest reconciliation
artifactRoot.build.tstreats the output root as build-owned (creates/cleans/validates ownership,AB6014ownership recovery atvalidate-artifact.ts:479), andAB4706forbids the artifact overlappingdist. 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.mdtest 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.plugin.ts:97states Codex prefers rootplugin.json; no fixture records it.AB4104(S2) is honest about that ("cannot yet be represented faithfully"); S9 replaces belief with a recorded probe.skills/,commands/,agents/,.mcp.jsonconventionally — the pinnedadapters/schemas/claude/plugin*.jsonexposes onlyhooks,lspServers, andsettingsas 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 inhosts/codex/skills/, point Codex there); a Claude-only skill cannot — anything inskills/is Claude's, and Codex's singleskillspath must then point at a directory that duplicates the shared skills. Reconciliation: S2 fails withAB4105; S10 adds the duplicatedhosts/<host>/view as the sanctioned "host-specific representation". The contract's "emitted once" should read "emitted once unless a host needs a scoped view".npm pack <name>against a registry; CI has no published package. Reconciliation: the source schema quoted in the follow-up carries aregistryfield, so S8a starts a throwaway local registry (e.g. verdaccio) in thehost-install-proofsjob, publishes the packed root to it, writes a catalog entry withregistry: http://127.0.0.1:…, and runs the realcodex plugin marketplace add+plugin addagainst it. If the pinned Codex CLI rejects loopback registries, fall back to proving the layout (npm pack --json→ extract → install frompackage/) in CI and keep the live check as a skip-gated smoke likeci.yml:304-313.mcp.jsonvs portablemcp.jsonis the same collision as Codex's and the issue folds it into change 3 —AB4104coversportable+cursortoo (S2), which is why option (c) first is the only combination-complete choice.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)
adapters/plugin.tsone PR before S2 lands (window without a composite) — recommended; alternative: S1+S2 as one L PR.AB7001with a new message (it already means "no manifest for this host at the root"); alternative: allocateAB7016.AGENTS.md: delete outright (S7); alternative: opt-inoutput.agentsMarkdown: truewritingAGENTS.generated.md.AB4104first (S2), evidence-driven S9 later — as reasoned in §2.AB4105in S2, addhosts/<host>/views in S10; alternative: ship the views in S2 (grows S2 past L).marketplace.name, default plugin name,claude.marketplace.namedeleted (S6); alternative: keep it underclaudeand mirror — rejected as a second copy.distribution: { git?, npm? }key (S6b), consumed only by catalog renderers andINSTALL.md; alternative: infer npm frompackage.jsonnamealone and git from the repository's remote — rejected because generated catalogs must be reproducible from config, not from the checkout.targets: keep the framework default (portable) untouched per the issue; templates select['claude','codex']so a scaffolded project installs on both native hosts without hittingAB4104.Not established: whether Codex prefers root
plugin.json(F2); whether any current example uses host-scoped skills/commands (a grep ofexamples/*/src/skillsfortargets:finds none, but the check belongs in S2's PR before merge, since it decides whetherAB4105fires onexamples/*).fix(codex): host validation follows the manifest's hooks/mcpServers p…
feat(install): --from names the plugin root itself; nothing nested un…
ScriptedAlchemy commentedon Sep 5, 2026
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
mainafter #569 merges. One directive from the plan was folded into #569 before merge: nothing nested under the built root is probed byinstall/doctor(no<root>/<host>or<root>/plugincompatibility), since the codebase is unreleased.ScriptedAlchemy commentedon Sep 5, 2026
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
AB4106and consumer/doc changes before it leaves draft, and it is being brought current withmain(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 onmainafter #578 merges.ScriptedAlchemy commentedon Sep 5, 2026
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:
targetsstrictly as host projection selection. Do not let target names become shared application identity, runtime identity, distribution identity, or another filesystem partitioning concept.No scope needs to be removed from #555; this comment clarifies how its implementation fits the longer-term compiler architecture.
ScriptedAlchemy commentedon Sep 5, 2026
Consumer evidence from cargo-hauler: the old partition model has leaked broadly into application code, not just docs. Current
masterhard-codesartifact/<host>inagent-bundle.config.tscomments, README/install docs, AGENTS.md, version/packed tests, andsrc/cli/dashboard.tspath 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.test(browser): mount each MCP App as one host of the composite select…
docs(manifest): agent-bundle.manifest.json v2 reference; consumers re…
feat(build): emit one composite plugin root; targets select host proj…
ScriptedAlchemy commentedon Sep 5, 2026
Wave 1 landed: #578 squash-merged to
mainas62b69c068. S1 (remove theplugintarget) and S2 (one composite root;targetsselect 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 buildwrites one directory at the artifact output. There is noartifact/<host>orartifact/pluginpartition: 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.mdare emitted once,install.mjswhencursororportableis selected.targetsacceptsclaude,codex,cursor,portable(defaultportable).targets: ['plugin']/--target pluginfail withAB4100; the generatedAGENTS.mdis gone;create-agent-bundle --targetaccepts only the four hosts;TargetAdapter.lowersConfigExtensions,TargetRegistry.lowersConfigExtension(),NormalizationTargetRegistry.lowersConfigExtension()are removed.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, andbuildreport all three.agent-bundle install <host> --from <root>anddoctor --from <root>read the host manifest directly under the root (AB7001when absent; nothing nested is probed).mcp run,serve-app,dev proxy --target <host>resolve the host's MCP document in the same root;openInstalledHostMcpServerreads the composite root as every host's bundle root; the packed pool readsbin/and the singlemcp/entry from the root.hooks/<name>.<host>.mjs, each wrapper baking its host;AGENT_BUNDLE_HOOK_HOSTis no longer read. Compiled MCP entries, scripts, bins, and MCP Apps are attributed to the sorted composite identity (claude+codex) inbuild --json/inspect --bundleronly as report metadata; reorderingtargetsyields byte-identical output.agentBundleBrowserRstest({ target })now names the host each app mounts as; apps compile once for the whole selection.<root>/<host>path becomes<root>; nothing else in the layout is host-partitioned.#592 constraints carried into the remaining waves
targetsis 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:install,doctor, the packed/installed harnesses) key on them → S6 records the built-in projection identity in the manifest.selected = model.targets…for wrapper file names and the shield rule) → S3/S10: composite context passed intoTargetAdapter.planexplicitly, or wrapper naming and the shadow rule move into assembly.compositeNoticeDeliveryresolves capabilities during assembly → S9/S10 (details: PR body "Listed, not changed in W1").compose.ts: normalization owns application meaning, host adapters own projection, assembly merges bytes.Handoff
Wave 2 (parallel, separate worktrees, rebase on
mainat62b69c068):AB7001rewording (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.AB6005") is already onmainand merged into this branch; AB6005: also refuse bare createRequire/require/import.meta.resolve loads in compiled modules #591 extends that scanner (barecreateRequire/require/import.meta.resolve).package.jsonname= configured package name, no lifecycle-script dependency,npm packarchive root = plugin root.INSTALL.md/install.mjsfrom 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-bundletemplates; 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/mcpServerspointers (Cursor's does);compose.tssameBytescompares copy entries by source path (conservativeAB4103); onereachesSelectionhelper for the four "targets reach the selection" predicates.ScriptedAlchemy commentedon Sep 5, 2026
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 (npmdist/binlacks the web wiring the artifact bin gets — an agent is on it), S5 install instructions/marketplace identity/authored repo instructions, execution-boundary consolidation.ScriptedAlchemy commentedon Sep 5, 2026
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, andweboptions togeneratedCliBinEntrySource().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'spackage.json, not just runartifact/bin/<name>.mjsmanually.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
webcommand; 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 restorelocateFrameworkCli, 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 allnode_modulesis 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.mdpreservation 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.feat(manifest): authoritative agent-bundle.manifest.json v2 — consume…
ScriptedAlchemy commentedon Sep 5, 2026
W2-S3 landed: #604 merged as
632847a39b— consumers read the composite root throughagent-bundle.manifest.json(manifest v2). Follow-up #650 carries the last self-review fixes (host document ↔ launch record agreement,mcp runwithout a manifest-only fallback, install mode drift, one portable path rule) and is on auto-merge.What S3 gives the remaining waves:
install/doctor/uninstallreadapplication.{id,name,version},projections[].builtInHost,projections[].documents.{plugin,marketplace,mcp,hooks}, andfiles[]{path,bytes,sha256,kind,mode?}from the manifest at the root.<from>/<host>is never probed;AB7001is the root-level manifest failure.installre-measures bytes, mode, and digest againstfiles[](fix(manifest): host documents project the launch record — kinds, order, transport, pointer (AB6017); install re-measures mode; one portable path rule (#604 follow-up) #650) and re-hashes the copy.agent-bundle:path:*tokens; the state root is derived from the code root (fix: manage derived state across lifecycle #642) orAGENT_BUNDLE_STATE_ROOT.state/and.agent-bundle-install.jsonare reserved and neverfiles[]rows.agent-bundle.compile-evidence.json(build: persist compile evidence beside emitted files (#619 step 4) #638) is the one evidence store;reindexArtifactManifestrefuses to touchbundlerows or the sidecar.Hand-off — S4 (npm root, #639):
distribution.channelsalready carries'npm'whencompiler.project.packageNameis set. Put npm-specific pointers underdistribution(closed keys →schemas/agent-bundle.manifest.schema.json+parseDistributioninbuild/manifest.ts+website/docs/{en,zh}/reference/artifact-manifest.mdx), never underprojections[]. Adding an optional key is amanifestVersionbump only if an older reader would reject it — both readers are closed-key, so it does: bump to 3 and updateartifactManifestVersioninweb-host/manifest.ts(single source;build/manifest.tsre-exports it).Hand-off — S5 (
INSTALL.md/install.mjs):distribution.install.instructions/.scriptare the pointers;AB6023/AB6024already require both files perbuiltInHosts(selected). Author the contents asfiles[]rows (the writer indexes them). The generatedinstall.mjs(install/surface.ts) still reads.cursor-plugin/plugin.jsonand probes.cursor-plugin/mcp.json/mcp.jsonfor runtime state roots because it also installs Agent Plugins packs that have no artifact manifest — when S5 rewrites it, readagent-bundle.manifest.jsonfirst (projections[cursor].documents.{plugin,mcp}) and keep the probe only for manifest-less packs.W2 stays open for S4/S5.
ScriptedAlchemy commentedon Sep 5, 2026
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 itsfilesallowlist omits the checkout's.claude-plugin/,.codex-plugin/,.agents/plugins/, andgemini-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:
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.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.
ScriptedAlchemy commentedon Sep 6, 2026
W2-S4/S5 landed in #656 as
45beb5479292eb89b58fafd33b7143c2dc889fe4.Closing map:
INSTALL.md/install.mjs, marketplace identity, authoredAGENTS.mdpreservation: feat: make the npm root the canonical distribution #656Packed acceptance is
packages/agent-bundle/tests/packed-web-command.test.ts: scripts-enabled tarball install, source removal, installedpackage.jsonbin operation +webjourney, and artifact/npm executable-byte parity.packed-host-install-proof.test.tsadditionally 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.