Diagnostics reference
This page is a build-time copy of docs/diagnostics.md, the repository's diagnostics contract. Change that file, not this page.
Every agent-bundle failure or nudge is one structured diagnostic: a stable
code (AB + four digits), a severity (error, warning, or info), a
message, and usually a sourcePath and a recovery hint. The
diagnostic-gated commands (build, prepack, validate, doctor, install,
dev) exit nonzero only when an error diagnostic is present; warnings and
infos never gate a build, a validation, or a dev rebuild. eval and inspect
additionally exit 1 for a failing or inconclusive trial or an invalid model
even when no error diagnostic was reported.
Code families
Cursor built-artifact validation (AB6026–AB6029)
Codex host validation (AB6030–AB6033)
Codex 0.147.0 publishes plugin installation commands but no plugin-validation developer tool. Agent Bundle therefore validates built Codex JSON documents against its vendored pinned schemas and treats the app-server schema generator as a separate drift signal, never as a substitute plugin contract.
Agent Skills emitted spec lint (AB6034)
Agent Bundle evaluated @skill-tools/core@0.2.2 on 2026-09-02 and found a
genuine Agent Skills utility whose lint is nevertheless lower fidelity than
the pinned specification contract. Its parser does not pin a specification
revision, misses closed-frontmatter rules already enforced here, and contains
an internally inconsistent description-length warning. The package is not a
runtime dependency; the one missing mandatory rule it identified is enforced
locally against emitted bytes.
Agent Plugins portable validation (AB6035–AB6038)
The portable target is the Agent Plugins open standard
(specification 1.0.0) adapter. Its contract is pinned in
packages/agent-bundle/src/adapters/schemas/portable/PROVENANCE.json (schema
hashes, specification repository commit, retrieval and re-verification dates).
The standard publishes machine-readable schemas plus normative text the schemas
cannot express; the text wins on conflict, so validation runs both lanes and
never spawns a client CLI (the standard publishes no reference validator).
Validation happens at three moments, all fail-closed:
- Plan time (
agent-bundle build/validate): the emittedplugin.jsonandmcp.jsonare validated against the pinned schemas before they are written (portable.schema.plugin,portable.schema.mcp), authored manifest metadata is checked field by field (portable.manifest.<field>.invalid), MCP path tokens are refused where the standard forbids them (portable.mcp.token.*), and the normative MCP rules the schemas cannot express are applied to each server as it will be written (portable.mcp.{command,cwd,env,url,headers}.standard: command form, cwd containment, env-key placeholders, URL form, header names/values/casing). These target-scoped codes are errors, so a standard-invalid server never reaches an artifact. - Artifact time (
agent-bundle build,validate --artifact): the generic target-contract pass reports a missing required document asAB6011and a pinned-schema rejection asAB6012, and the Agent Plugins byte lane below (AB6035–AB6037) runs over every tree emitted by the built-in portable adapter, so a standard-invalid layout fails the ordinary build before publication (a tree already carrying a symlink or other unsupported entry is reported asAB6013and never read by this lane).validate --artifact --host-validationadditionally returns the same lane as aportablehost validation report with theAB6038provenance note. - Installed bytes (
agent-bundle doctor): a Cursor local plugin whose rootplugin.jsondeclares an Agent Plugins$schemais validated with the same byte lane and reported underAB7320(an error marks the entrycorrupt).
npm prepack gate (AB7010–AB7013)
Declaration generation (AB4716)
A lib entry with dts enabled compiles its source directory as its own
TypeScript program. When that declaration emit fails, the bundler aborts with
one prose line naming only its own environment, so the framework replays
declaration emit over the same synthesized project (the consumer's own
typescript, the same tsconfig, --declaration --emitDeclarationOnly) and
reports one AB4716 error per recovered TypeScript diagnostic, each
carrying the file, the (line,column) position, the TS code, and the
compiler's message, plus a sourcePath:
When no diagnostic can be recovered — the project has no resolvable
typescript, or the replay passes because the failure was elsewhere in
declaration generation — the failure still reports as a single AB4716
carrying the bundler's own message. Declaration failures never fall through
to the AB5000 catch-all, whose dev-lock meaning previously misdirected
triage.
The recovery hint names the trap these failures share: declaration-emit
errors such as TS4023 (an exported value whose inferred type names a type
its module does not export) are invisible to tsc --noEmit, so a green
typecheck script proves nothing about them. Reproduce them with
tsc --declaration --emitDeclarationOnly over the lib entry source
directory.
Release identity (AB4001, AB4008–AB4011, AB4013)
package.json is authoritative for release identity (issue #94): its name
and version become the packageName and packageVersion axes carried on
the project context, artifact manifests, inspect output, and dev status.
plugin.name stays the host-native slug and is never derived from the npm
package name.
plugin.version is deprecated and optional. New projects declare the
release version only in package.json; removal of the compatibility field
follows the normal breaking-change policy rather than a fixed window. When it
is omitted, the version every surface reports — manifests, host projections,
dev status, and the agent-bundle/meta constant compiled into plugin code —
is the package.json version. When it is declared, the declared value still
wins so a legacy config never changes meaning mid-migration, and a
disagreement reports the AB4008 warning. Declaring it as anything but a
nonempty string is an AB4001 error.
A project with neither an authored plugin.version nor a valid package.json
version has no release identity. Development commands (dev, inspect,
validate) keep running on the labeled 0.0.0-dev.<short-revision> fallback,
because an unpackaged scratch project is a normal development state. A
development-only fallback can never produce a release artifact, so
agent-bundle build alone refuses it with AB4013.
Migration nudges and convention claims (AB4730–AB4738)
The entry conventions and the framework-owned stdio lifecycle shell (RFC #50)
replaced patterns consumers previously wrote by hand. When validate,
inspect, build, or dev prepares project source and finds one of those
pre-convention patterns, it reports a migration diagnostic. AB4730–AB4735
are informational nudges and never block anything. AB4736–AB4738 are
errors: the removed top-level authored-document locations are no longer
discovered, and a conventional script whose bin entry would run an export
the artifact script ignores cannot ship on both surfaces, so the compiler
refuses to omit or misbuild them silently. The CLI prints these in
human validate output and includes them in every --json diagnostics array.
Which explicit config keys claim a conventional module out of discovery is
tabulated in docs/entry-conventions.md ("Which config keys claim a
conventional module"). In short: scripts, hooks, lib, and mcp entries
claim the module they reference; a bin entry claims every conventional
module except a safely named direct src/scripts/<name> child, which
keeps shipping as an artifact script beside the bin because the two outputs
are disjoint and both envelopes run the same main. That dual-surface shape
is intentional and raises no diagnostic.
AB4730 — self-connecting stdio MCP entry
A local MCP server entry module (explicit entry: or the conventional
src/mcp/<server-id>.ts) has no default export, so the build bundles it
byte-for-byte instead of wrapping it in the framework stdio lifecycle shell
(console-to-stderr guard, SIGINT/SIGTERM, stdin-EOF exit, bounded shutdown,
heartbeat). The detection is the same static default-export scan the build
uses, so the nudge and the build always agree.
Adopt: default-export a server factory from the entry module. Silence: keep the self-connecting entry — its behavior is preserved exactly.
AB4731 — src/cli.ts shadowed by explicit bin config
src/cli.ts (or .tsx) exists, but the explicit bin configuration never
references it, so the conventional package bin is silently shadowed.
bin: false is a deliberate opt-out and stays silent.
Adopt: remove the explicit bin configuration, or point one entry at the
file. Silence: remove the file, or keep the explicit config knowingly.
AB4732 — src/index.ts shadowed by explicit lib config
src/index.ts (or .tsx) exists, but the explicit lib configuration
points elsewhere. lib: false is a deliberate opt-out and stays silent.
Adopt: remove the explicit lib configuration, or point it at the file.
Silence: remove the file, or keep the explicit config knowingly.
AB4733 — src/mcp/<server-id>.ts shadowed by explicit server config
The conventional stdio entry file exists for a declared server, but that
server names an explicit entry, command, or url that does not resolve
to it — a confusable state where the file on disk is not what runs.
Adopt: drop the explicit entry/command/url so the convention applies.
Silence: remove the shadowed file.
AB4734 — conventional skill shadowed by explicit skills config
A src/skills/<name>/SKILL.md (or rendered SKILL.tsx/SKILL.ts) directory
exists, but the explicit skills configuration does not cover it — the
conventional skill is silently shadowed. When config is silent, every
src/skills/<name>/ directory ships by convention and this nudge never fires.
Adopt: remove the explicit skills configuration so the convention applies,
or add the directory to skills. Silence: remove the directory.
AB4735 — rendered skill source shadowed by hand-authored SKILL.md
A skill directory contains both a hand-authored SKILL.md and a rendered
skill source (SKILL.tsx/SKILL.ts). The authored file wins — an authored
document beats a generated one — so the component module never compiles.
Adopt: remove SKILL.md so the rendered skill compiles at build. Silence:
remove the component module.
AB4736 — legacy top-level authored document location
A document still matches a removed top-level convention:
skills/<name>/SKILL.md (or rendered SKILL.tsx/SKILL.ts),
commands/*.md, or rules/*.mdc. These locations are no longer discovered,
and every unignored legacy document is reported as an error. A top-level
skill covered by explicit skills configuration is claimed and stays valid;
commands and rules have no equivalent override.
Recover: move the document under src/skills/, src/commands/, or
src/rules/. Explicit skills paths remain valid anywhere. Published
artifact paths remain skills/, commands/, and rules/.
AB4737 — rendered script claimed as a package bin entry lacks main or the component
An explicit bin entry references a conventional rendered script
(src/scripts/<name>.tsx or .jsx) that does not export both an async
default Server Component and a named main. The component check is the
route compiler's own static scan — the default export must be an async
function, so export default {} does not count; a default re-exported from
another module (export { default } from './component.tsx') cannot be judged
statically and is accepted (the rendered worker still verifies it at run
time). A plain src/scripts/<name>.ts module
ships happily on both surfaces — the npm bin envelope calls its main(argv)
and the artifact script is the same bundle — but a rendered script's default
export is an async Server Component the Agent renderer drives with
{ argv, signal } props. The bin envelope prefers a named main export and
only falls back to the default export, so without main it would call that
component as main(argv) and produce a bin that renders nothing; without the
default component, the bin works but scripts/<name>.mjs fails at run time
with no component to render. The compiler refuses either shape instead of
emitting a broken surface beside a working one. A rendered script that
exports both serves both surfaces and is not gated. The message names every
bin entry referencing the module and which export is missing.
Recover: export both an async default Server Component and a named
main(argv) from the module; point the bin entry at a plain module that exports main;
rename the script to .ts so one plain module ships as both the bin and the
artifact script; or prefix a path segment with _ (src/scripts/_name.tsx)
to keep the module out of script discovery and bin-only.
AB4738 — plain script claimed as a package bin entry runs only as the bin
An explicit bin entry references a conventional plain script
(src/scripts/<name>.ts) that exports a default but no named main. Both
the bin envelope and the artifact-script envelope wrap a main(argv) export
and bundle a self-executing module (no main, no default) byte for byte,
so those shapes run identically on both surfaces. Only the bin envelope falls
back to invoking a default export: the artifact scripts/<name>.mjs would
merely define the function and exit, so a successful build would publish an
inert script beside a working bin. The detection is the same static export
scan the package build uses. The message names every bin entry referencing
the module.
Recover: export a named main(argv) so both surfaces run the same entry;
make the module self-executing (drop the default export and run at top
level); or prefix a path segment with _ (src/scripts/_name.ts) to keep
the module out of script discovery and bin-only.
Prebuilt payloads (AB4740–AB4750)
The payload block and { prebuilt: ... } entries (see
docs/entry-conventions.md) package files the framework did not compile.
The consumer's own build produces them, so their diagnostics split by
moment: configuration mistakes are validation errors, a payload that has
simply not been built yet is a validation warning that only
agent-bundle build escalates, and freshness is an info nudge.
Build-time identity outside the compiler (AB4760)
agent-bundle/meta (see docs/entry-conventions.md) is a reserved specifier
the compiler replaces in every compiled surface with the project's exact
{ name, packageName, packageVersion, version }. The published
dist/meta.js module behind that specifier therefore never carries an
identity of its own: every binding — name, version, packageName,
packageVersion, meta, and the default export — throws this diagnostic at
module evaluation, so a module that reaches it fails on import rather than
observing a fabricated identity. The thrown value is an Error named
AgentBundleMetaUnavailableError whose code, recovery, and structured
diagnostic fields carry the same data the message prints, so a bare node
process and a test runner both show the fix. The importing module is not
observable from a module evaluated through ESM linking, so the message names
the situation, not a file; the runner's own "failed to load" line names the
file.
Unit tests are the common way to reach it (issue #386): a plain Rstest pool
imports a source module that imports agent-bundle/meta, no compiled surface
replaced the specifier, and every test that touches that module fails at
import. agentBundleRstest() and agentBundleBrowserRstest() prevent this by
aliasing the specifier to .agent-bundle/test/meta.mjs, generated from the
same compiler pass. When that pass produced no plugin model (the configuration
could not be loaded or normalized) there is no identity to stamp, so the
aliased module throws the same AB4760 naming the compiler diagnostics and
the recovery "fix them, then rerun Rstest" — the manifest's placeholder
identity is never served as a real one.
Artifact-hosted routed CLI (AB4765–AB4766)
A generated-mode src/cli/** surface compiles into the npm package bin
(dist/bin/<name>.js) and into every host artifact whose adapter
publishes a supported cli capability, as bin/<plugin-name>.mjs (plus
bin/<plugin-name>-flight.mjs when any command renders). Every built-in
target hosts it; the two codes cover a target that does not and a host file
that claims the same path. See “The routed CLI shell” in
docs/entry-conventions.md for the layout and the sibling-path convention.
Config beside a route-generated MCP server (AB4340)
A mcp.servers.<id> block for a server the route graph compiles in
generated mode augments that server (env, args, targets, apps,
transport: 'stdio') — see the precedence table in
Entry conventions.
The local-entry field rules apply to it unchanged (AB4305, AB4308–AB4312,
AB432x), and it never triggers AB4304 or AB4322: the route modules are
its entry.
Conventional host components: rules and commands (AB4900–AB4908, AB4920–AB4928)
Conventional src/rules/*.mdc documents compile to the Rule IR (closed
frontmatter: description, globs, alwaysApply, plus the bundle-only
targets key that is peeled before emission) and src/commands/*.md
documents compile to the Command IR (closed frontmatter: description,
argumentHint, allowedTools, model, disableModelInvocation, plus
targets). Each host lowers only the surfaces its pinned capability table
supports; a document without targets is emitted where supported and
accounted as skipped with the host's judgment elsewhere (see
agent-bundle inspect), while a document that explicitly names a host without
the surface is a build error — unsupported components fail before artifact
publication rather than shipping as a broken half. Identity paths are
canonicalized so the model digest is root-independent.
Every frontmatter field is also a component feature (#100): each host
publishes one <kind>.<feature> capability row per field it can express
(commands.argumentHint, rules.globs, …; see
Host components). A component
that uses a feature the target's row does not support is judged per target:
an explicitly named target fails closed (AB4907 / AB4927), while an
implicitly selected target still receives the component minus the feature and
the omission is reported as a warning with the host's reason (AB4908 /
AB4928) and on the selected component in inspect (omittedFeatures).
Targets whose kind row is itself unsupported are judged by the kind-level codes
above, never per feature. Skills keep their own closed per-host schemas
(AB3006, AB3008, AB3010).
Route graph, state, layout, and provider conventions (AB4800–AB4832, AB4940–AB4942)
The route-graph compiler discovers conventional route modules
(src/mcp/<server>/{tools,resources,prompts,apps}/*, src/events/*/*,
src/providers/*, src/cli/**, src/scripts/**) and the shared layout
modules (src/layout.*, src/mcp/<server>/layout.*) into one immutable IR.
Discovery is not a packaging choice, so every collision is a hard error
and the compiler never silently picks a side. Modules that explicit
scripts, hooks, bin, lib, or mcp configuration references are
claimed by that declaration and never become routes — config always wins.
agent-bundle inspect --routes dumps the compiled graph.
Each route's config export is extracted statically — the module is parsed
with the TypeScript compiler, never executed — from a single top-level
export const config = <expression> declaration. The accepted expression
grammar is: object literals whose property names are identifiers, string
literals, or numeric literals (no computed names, spreads, shorthand
references, methods, or accessors); array literals without spreads or holes;
string literals and substitution-free template literals; numeric literals,
optionally wrapped in unary +/-; true, false, and null; and
as/satisfies casts, non-null assertions, and parentheses around any
accepted form. Two constrained reference forms are accepted for string
values, so an MCP App's resourceUri never has to be repeated as a literal
in every tool that opens it:
- A
conststring-literal identifier. A top-levelconst X = '<literal>'(optionallyas const) declared in the route module, or anexport const X = '<literal>'of a module reached through a relative import (import { X } from '../constants';.ts/.tsxresolution,.js-style specifiers map onto their TypeScript source, index modules resolve) inside the project root. The sibling module is parsed, never executed, and only that one hop is followed: the exported const's initializer must itself be a string literal. Because the identifier is a real import, the same value is available at run time (for example inAgent.Result metadata). appResourceUri('<app>')imported fromagent-bundle/routes. The compiler resolves the reference to the target App route's staticconfig.resourceUriwhile compiling the graph. The App must belong to the referencing route's own generated server — a generated server registers exactly its own Apps, so another server's URI could never be read through it. References are'<app>','<server>/<app>','app:<server>/<app>', or a module path relative to the referencing file ('../apps/dashboard', with or without its.ts/.tsxextension — a.js/.jsxspelling maps onto the TypeScript source, and any other suffix is part of the App name). The argument may be a string literal or a const identifier of the first form. An unknown reference — another server's App, an App whose ownresourceUriis not a static string, or any reference from a non-MCP route — isAB4826, and the route compiles with the empty config beside it. Routes of a server that is not generated (custom/command/remote, or anAB4800conflict) never ship their config, so their references are left as authored rather than reported. Whether referenced or written as a literal, an advertised_meta.ui.resourceUrimust name an App the server builds for every target it ships to (AB4828otherwise). At run time the helper returns the reference unchanged: generated servers read the compiled config, never the module's evaluatedconfig, so use the const form when the URI is also needed inside the component.
Anything else — any other identifier, a call, a package import, a relative
import that leaves the project or does not export a string-literal const —
is dynamic: the route compiles with an empty config beside a named AB4806
error whose recovery names both reference forms. A module without a config
export compiles silently with an empty config.
An MCP App route's config.template resolves relative to the route
module, the way its imports do (template: './dashboard.html'). The older
project-root-relative form ('./src/mcp/<server>/apps/dashboard.html') is
still accepted, without a diagnostic, while it is the only interpretation
that names an existing file. When both interpretations name different
existing files, or neither exists, AB4827 names both candidate paths; the
fix is to make the path route-relative. The IR keeps the authored path (so the
graph digest stays machine-independent) and the normalized model carries the
resolved absolute file. Config-declared Apps (mcp.servers.<server>.apps)
keep resolving entry and template from the project root, where the config
file lives.
Generated route declarations are published at .agent-bundle/routes.d.ts from
the same graph. Development writes a sibling temporary file and renames it over
the prior complete declaration atomically; invalid source retains the prior
last-good file, while a successful route-free, provider-free preparation
removes it. Beside AgentBundleRoutes, a graph with conventional providers
declares AgentBundleProviders (ProviderKey, ProviderValue<Key>) — each
camel-cased key mapped to its factory's awaited return type, in execution
order — and augments @agent-bundle/runtime's AgentProviderValues so
(await agent()).providers.<key> observes that type in projects whose
TypeScript program includes the file. Provider-free graphs emit no
augmentation, so the declaration never references a module the project has no
reason to depend on.
Conventional src/scripts/ routes ship through the same pipeline as
explicit scripts entries (#102 stage 1): a plain module directly under
src/scripts/ compiles to scripts/<name>.mjs in every selected target
artifact with provenance.kind: 'conventional'. A rendered module
(src/scripts/<name>.tsx/.jsx, #102 stage 3) compiles to the same
scripts/<name>.mjs plus a sibling scripts/<name>-flight.mjs react-server
worker: its async default component receives { argv, signal } and renders
through the Agent renderer with the full CLI output contract (--json,
--ndjson, interactive TTY progress, piped Markdown); the framework dialect
reserves exactly --json and --ndjson, every other argument passes
through as argv, and the exit code derives from the final document status
(0 on success, 1 otherwise). Explicit scripts config entries keep
ordinary Node semantics regardless of extension — config always wins, and
only the conventional route contract opts into rendering. Script routes
neither pipeline can ship are hard errors (AB4808/AB4809), never silent
omissions.
Conventional src/cli/** routes compile into one collision-checked command
graph (#102 stages 2-3): the file path below the CLI root is the command
nesting (src/cli/library/audit.ts runs as <bin> library audit), the
static config export supplies description, aliases, positionals, and
the exitCode policy, and the graph feeds one framework-generated package
executable named after the plugin (dist/bin/<plugin-name>.js), replacing
the src/cli.ts convention for that project. Every command route exports
inputSchema and resultSchema zod schemas plus one async default function
receiving { input, signal }, and runs inside the typed Agent request
context. A plain (.ts) command executes directly and writes one canonical
JSON line to stdout. A rendered (.tsx) command's async default Server
Component renders through the runtime dispatcher against a sibling
dist/bin/<plugin-name>-flight.mjs react-server worker with four output
modes: interactive TTY updates progress in place before the final document;
piped output emits exactly one final Markdown document (no partial
fallbacks); --json emits the canonical validated final value; --ndjson
emits the sequence-numbered render-event stream (an Agent Bundle CLI/script
dialect — never MCP JSON-RPC, never written to an MCP server's stdout).
Diagnostics go to stderr; machine output owns stdout. Exit codes: 0 on
success (or the validated result's integer exitCode under
config.exitCode: 'result'), 1 on execution/render failure, 2 on usage or
input-validation failure, 130/143 after SIGINT/SIGTERM. --help, --json,
--ndjson, and --version are owned by the generated shell.
The power-tier routes.mcpCommands option projects tools from generated MCP
servers into that same command graph. Each tool becomes
<server> <tool> with one optional --input '<JSON object>' argument;
mutation-capable tools also require the enforced --yes flag. Missing or
malformed annotations.readOnlyHint is mutation-capable by default. Include
and exclude patterns use only literal text plus *; every declared pattern
must match at least one eligible <server>:<tool> identity so misspellings
fail during compilation. Projected commands invoke the same tool render and
request-context contracts as the generated MCP server, and their mcp
metadata records server, tool, and confirmation provenance in inspect.
The argv projection of inputSchema is extracted statically — the module is
parsed, never executed — from a bounded zod grammar: the top level is
z.object({ ... }) or z.strictObject({ ... }) (optionally .strict());
each property chains from z.string(), z.number(), z.boolean(),
z.url() (a string option validated as a URL at run time),
z.enum([...string literals]), or z.array(<string/number/enum element>);
chains may add .optional(), .default(<static literal>), and
.describe('<string literal>'), plus validation-only refinements the
projection accepts without interpreting (strings: min/max/length/
regex/startsWith/endsWith/includes; numbers: int/min/max/gt/
gte/lt/lte/positive/nonnegative/negative/nonpositive/finite/
safe/multipleOf/step; arrays: min/max/length/nonempty) because
the module's real zod schema still validates every input at run time. Keys
project onto kebab-case options (maxFiles becomes --max-files); booleans
are flags and must carry .optional() or .default(...);
config.positionals names the keys consumed as bare arguments in order,
where only the trailing positional may be a z.array(...) (variadic).
Anything outside that grammar — identifier references (including shared
schema constants), unions, nested objects, transforms, coercions — raises
AB4814 naming the offending construct.
Read-only Doctor durable-state inventory (AB7316)
agent-bundle doctor inventories workspace-durable SQLite stores by directory
entry and filesystem metadata only. It never opens a database or creates
SQLite lock or shared-memory files.
Read-only runtime identity introspection (AB7317–AB7318)
Read-only Doctor static validation (AB7319–AB7320)
Doctor reuses the pinned, process-free host document and loader validators. These checks read installed or supplied bundle bytes only; they never invoke a host CLI, repair a bundle, or perform a live protocol exchange.
Install replacement and Doctor install comparison (AB7005, AB7307–AB7309, AB7321)
agent-bundle install <host> and the emitted standalone install.mjs share one
replace policy, and agent-bundle doctor --from <bundle-dir> reports the same
verdict read-only. Every Cursor copy an agent-bundle installer places carries an
install receipt, .agent-bundle-install.json, beside the plugin manifest:
The receipt never participates in the content hash, and neither do empty
directories or runtime roots (state/): only regular files are plugin content,
so the artifact hash, the installed tree, and the receipt always describe the
same entries. Ownership of an existing
destination is decided as receipt (a receipt naming this plugin), legacy
(no receipt, but the emitted INSTALL.md + install.mjs and a manifest with
this plugin's name — a copy installed before receipts existed), or foreign
(anything else). Claude and Codex copies are located through the host's own
plugin list --json inventory (Doctor runs it once per host and also lists every
installed plugin from it; AB7303 is emitted only when that listing is unusable);
the host owns those copies, so replacement runs claude plugin uninstall --keep-data + install or codex plugin remove + add.
Every AB7005, AB7308, AB7309, and AB7321 message carries the comparison
installed <name>@<version> content <hash> vs artifact <name>@<version> content <hash> (same version, different content | different version | same content).
Cursor replacement is in place and touches owned files only: stale owned files
are removed and the emptied directories the installer itself created
(directories in the receipt) are pruned, staged files are renamed over their
predecessors, and the receipt lands last. Entries the installer does not own —
notably workspace-durable state/ stores, and any directory that already
existed before the installer wrote beneath it — are never removed or rewritten;
when a rebuilt artifact introduces a path that an existing unowned entry already
occupies, replacement aborts before any change (AB7004, "Refusing to overwrite
unowned files") and names the colliding paths. Receipt file and directory lists
are validated as strict POSIX-relative paths (no backslashes, no
.././empty segments, no drive letters, nothing under a runtime root such as
state/) before they can drive a deletion; a receipt that fails validation
reads as absent, and a receipt that is not a regular file (a symbolic link, a
FIFO) is refused outright (AB7004) before it is read. The same rules apply to
the artifact itself: a file whose path could not round-trip through a receipt
(a backslash in a POSIX name, reserved characters, a trailing dot or space) is
refused (AB7004) before anything is staged.
Live development into hosts (AB7200–AB7202, AB7210–AB7211, AB8024–AB8025)
agent-bundle dev keeps a host's one stdio MCP process connected while it
swaps the generated plugin behind it (dev proxy), re-syncs opted-in
development installs (--install-host) on every adopted epoch, and — when a
project declares dev.contracts — gates host-facing adoption on the
development contract matrix. Every failure on that path is a structured
diagnostic; none of them silently changes what a host serves. A failing gate
is not a build failure: the epoch publishes to the Workbench playground, and
the Overview page's Host adoption section names both the published and the
host-facing build together with the failed checks.
Development package build (AB7103)
agent-bundle dev rebuilds the framework-owned package build (dist/ bin
and lib outputs) inside the same serialized rebuild pass that publishes
artifact epochs. A package build failure never invalidates the artifact epoch
that already committed; it surfaces as one AB7103 warning on the
succeeded build attempt, and the package build retries on the next
invalidation. See docs/entry-conventions.md for the dev-watch contract.
Read-only Doctor Cursor hook registration and marketplace staging (AB7322–AB7324)
Cursor delivers a plugin's hooks from its .cursor-plugin/plugin.json hooks
declaration (observed 2026-09-03 on Cursor 3.18.25; see
docs/audits/2026-09-03-cursor-plugin-hooks-registration.md). Doctor proves
that registration statically and never writes ~/.cursor/hooks.json.
The installer side reuses the AB700x codes: AB7002 when git is missing
in marketplace mode, AB7003 when a mode is passed for a non-Cursor host, when
marketplace mode is requested for a bundle without .cursor-plugin/plugin.json,
or when the bundle contains nested Git metadata (.git, which git add would
record as an empty gitlink), AB7004 when a git step fails or the committed
tree does not hold the staged bundle bytes (the installer disables text,
eol, filter, ident and working-tree-encoding attributes through
.git/info/attributes, adds with core.autocrlf=false, and proves every
blob id in git ls-tree -r HEAD against the staged files; requires Git ≥ 2.29
for git init --object-format=sha1), and AB7005 for staged version or
content collisions (including a working tree that differs from committed HEAD).