feat(components): model lsp, native-diagnostics, native-extension, and event-route as canonical component kinds (#100) - #425
Conversation
🦋 Changeset detectedLatest commit: 6a75da5 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a34515e48f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| 'agent-bundle': minor | ||
| --- | ||
|
|
||
| Model the full canonical component kind set from #100 in `agent-bundle inspect` and the adapter capability tables. `AgentComponentKind` (exported from `agent-bundle/api`) now covers `agent`, `command`, `event-route`, `hook`, `lsp`, `mcp-app`, `mcp-server`, `native-diagnostics`, `native-extension`, `rule`, `script`, and `skill`; filesystem `src/events` routes report as the distinct `event-route` kind judged by the host's `event:<canonical event>` row instead of folding into `hook`, and `claude.lspServers` entries report as `lsp` components against every target's `lsp` row (Claude and the composite `plugin` emit `.lsp.json`; Codex, Cursor, and portable omit with dated reasons). Every inspection plan gains `kinds`, a per-kind matrix carrying the host's four-state judgment and selected/omitted counts even for kinds the project never declares, and the human output prints `kinds this host cannot emit:`. New dated `unavailable` capability rows land for `lsp` on Cursor and portable and for `nativeDiagnostics` / `nativeExtension` on every host and the composite; adapter revisions bump to claude 1.23.0, codex 1.10.0, cursor 1.9.0, portable 1.7.0, plugin 1.25.0 (#425) |
There was a problem hiding this comment.
Rewrite the changeset summary to match repository format
The summary does not mention any diagnostic code and largely inventories internal capability-table and adapter-revision changes. Repository guidance requires changeset summaries to be user-facing, mention diagnostic codes, and avoid implementation notes, so this should be rewritten before release.
AGENTS.md reference: AGENTS.md:L27-L28
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Rewritten in 13d19fc as a user-facing agent-bundle inspect summary (what the JSON/human output now reports, the breaking kind/name change for event routes, the exported type) and it states explicitly that no diagnostic codes change in this PR — there are none to cite; the feature-set enforcement diagnostics (AB4907/AB4908, AB4927/AB4928) land in the follow-up PR.
| const componentCapabilities = Object.freeze({ | ||
| ...compositeEventCapabilities, | ||
| ...Object.fromEntries( | ||
| ['commands', 'hooks', 'lsp', 'mcp', 'nativeDiagnostics', 'nativeExtension', 'rules', 'skills'].map((capability) => [ |
There was a problem hiding this comment.
Include the agent row in composite component capabilities
When inspecting the plugin target, api.ts uses componentCapabilities wholesale instead of falling back per missing key. Because this dispatch list omits agents, every new agent kind report synthesizes “The plugin adapter publishes no agents capability row” even though pluginAdapter.capabilities.agents contains the intended G5 deferral judgment; this makes both JSON and human inspection lose the target's published reason.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 13d19fc: componentCapabilities now spreads the full published intersection record (pluginCapabilities) first and overrides only the emission-dispatch kinds with the union, so every row including agents and its G5 (#220) reason reaches inspect for the composite. Pinned by the new assertion in api.test.ts ('accounts lsp servers and event routes…': plugin agent kind report carries the #220 reason).
| declaredBy: extension.key, | ||
| id: `lsp:${extension.key}:${name}`, | ||
| name, | ||
| provenance: { ...extension.provenance }, | ||
| targets: [...targetNames], |
There was a problem hiding this comment.
Restrict opaque LSP declarations to adapters that can lower them
With an advanced registry containing another config extension whose lspServers payload is adapter-specific, assigning every normalized server all selected targets causes inspect to mark it selected for any target publishing lsp: supported. The configuration is explicitly opaque and only its declaring adapter can lower it—for example, Claude's planner reads only model.extensions.claude—so a custom extension's server can be counted as emitted by Claude even though Claude writes no .lsp.json; the declaration target must participate in emission eligibility rather than being ignored.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Agreed — fixed in 13d19fc. NormalizedLspServer.targets is now the selected targets whose adapter lowers the declaring extension key: TargetRegistry.lowersConfigExtension(target, key) is true for the adapter owning the configExtension key and for adapters that declare it in the new TargetAdapter.lowersConfigExtensions (the composite plugin declares ['claude', 'codex'] because it plans those sides). Other hosts read as excluded-by-targets and still carry their dated lsp judgment. New regression 'never counts an opaque third-party lspServers declaration as emitted by a host that does not lower it' registers a synthetic adapter with lsp: supported + its own lspServers and asserts Claude skips it (no .lsp.json) while the synthetic target selects it.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 13d19fca0e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for (const name of Object.keys(declared).sort((left, right) => left.localeCompare(right))) { | ||
| servers.push({ |
There was a problem hiding this comment.
Derive LSP accounting from servers that survive planning
When claude.lspServers contains entries that do not survive planClaudeLsp—for example, two otherwise valid servers claiming the same file extension, which suppresses the entire .lsp.json document—this loop still creates a component for every raw key. inspect subsequently marks those components as selected based only on their targets and the supported lsp capability, even though plan.entries contains no LSP artifact, producing incorrect selected and kinds counts. Normalize only successfully planned servers, or reconcile component accounting with the planner result.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
This case cannot produce a ready plan with wrong counts: every path in planClaudeLsp that drops a server (claude.lsp.declaration.invalid, claude.lsp.server.invalid, claude.lsp.field.unknown, claude.lsp.command.required, claude.lsp.extensions.required, claude.lsp.token.env.key, claude.lsp.extension.conflict, schema failure) is an error diagnostic, and ProjectService.prepare folds every adapter's plan(model).diagnostics into the preparation, so inspect returns state: 'invalid' with plans: [] — no component is counted at all. The only silent path is a non-record lspServers, and normalizeLspServers mirrors that (isRecord guard → no component). Pinned in 42a34fa by the new test 'never reports an lsp component as selected when the declaring planner rejects the servers (#100)' (two servers claiming .ts → invalid inspection carrying claude.lsp.extension.conflict). Deriving components from planner output instead would invert the dependency (normalization → adapter plan) that #390/#409 deliberately kept one-way, so I kept the model-level enumeration.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 42a34faa0e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| lowersConfigExtension(name: string, key: string): boolean { | ||
| const adapter = this.#adapters.get(name); | ||
| if (adapter === undefined) return false; | ||
| return adapter.configExtension?.key === key || (adapter.lowersConfigExtensions ?? []).includes(key); |
There was a problem hiding this comment.
Validate additional extension keys during adapter registration
When a JavaScript or otherwise unchecked adapter supplies a malformed lowersConfigExtensions value, registration succeeds but this call later assumes the value has an array .includes() method. If that adapter is selected while another extension declares lspServers, normalization throws instead of producing project diagnostics, making build and inspect unusable. Validate and snapshot this new adapter field as an array of strings at the existing registry boundary.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 83b488f: snapshotLowersConfigExtensions runs in TargetRegistry.register and throws Target adapter "<name>" lowersConfigExtensions must be an array of nonempty extension keys. for a non-array, non-string, or blank entry; the deduplicated frozen snapshot is what lowersConfigExtension() consults (never the raw adapter field). Pinned by 'validates lowersConfigExtensions at registration and answers extension lowering per target (#100)' in adapter-capability-states.test.ts (three malformed shapes rejected; claude/plugin/cursor/portable/missing answers).
| for (const name of Object.keys(declared).sort((left, right) => left.localeCompare(right))) { | ||
| servers.push({ | ||
| declaredBy: extension.key, | ||
| id: `lsp:${extension.key}:${name}`, |
There was a problem hiding this comment.
Prevent ambiguous normalized LSP component IDs
With advanced adapters, extension keys and server names are unrestricted strings, so concatenating them with : is ambiguous: extension a with server b:c and extension a:b with server c both produce lsp:a:b:c. The normalized-component uniqueness check in config/validate.ts also omits model.lspServers, so this reaches inspection as two distinct components with the same public identity. Encode the tuple unambiguously or include these components in uniqueness validation and reject collisions.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 83b488f both ways: the id is now lsp:<encodeURIComponent(key)>:<encodeURIComponent(name)> so a+b:c and a:b+c encode differently (lsp:a:b%3Ac vs lsp:a%3Ab:c) while the ordinary lsp:claude:typescript shape is unchanged, and model.lspServers now participates in the AB4101 normalized-component-ID uniqueness check in config/validate.ts alongside hooks/commands/rules.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 83b488f0bc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // adapters, so each segment is percent-encoded to keep the tuple | ||
| // unambiguous (`a` + `b:c` never collides with `a:b` + `c`); the | ||
| // common `lsp:claude:typescript` shape is unchanged. | ||
| id: `lsp:${encodeURIComponent(extension.key)}:${encodeURIComponent(name)}`, |
There was a problem hiding this comment.
Encode lone surrogates without throwing
When an LSP server name or advanced-adapter extension key contains an unpaired surrogate (for example, a computed "\uD800" key), it passes the current nonempty string/property-name validation and can be serialized by JSON, but encodeURIComponent throws URIError: URI malformed. Because this runs during normalization, otherwise valid build, validate, and inspect operations collapse to the generic AB7001 failure before the declaring adapter can plan the server. Use an encoding routine that accepts every JavaScript/JSON string, or explicitly validate and diagnose Unicode scalar values before constructing the ID.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 571fb13: the id segment escape is now the total, injective lspIdSegment (% → %25 first, then : → %3A) instead of encodeURIComponent, so any JavaScript string — lone surrogates included — is accepted and the tuple stays recoverable. Pinned by 'enumerates lsp components with unambiguous ids for any server name (#100)' in normalization.test.ts, which declares a:b, pct%, typescript, and a computed \uD800 server name and asserts the four ids (lsp:claude:a%3Ab, lsp:claude:pct%25, lsp:claude:typescript, lsp:claude:\uD800) plus targets: ['claude', 'plugin'].
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 571fb135e2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| lowersConfigExtension(name: string, key: string): boolean { | ||
| const adapter = this.#adapters.get(name); | ||
| if (adapter === undefined) return false; | ||
| return adapter.configExtension?.key === key || (this.#lowersConfigExtensions.get(name) ?? []).includes(key); |
There was a problem hiding this comment.
Use the snapshotted config-extension key
When an unchecked JavaScript adapter registers with a mutable configExtension object and that object's key is changed afterward, configExtensions() still exposes the original frozen descriptor while this method consults the live adapter value. Normalization therefore discovers the original extension but gives its LSP components no targets, incorrectly reporting them as excluded even from their declaring adapter. Determine ownership from the registry's snapshotted extension metadata rather than rereading adapter.configExtension.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 2b343c1: lowersConfigExtension now answers from the registration snapshots only — the frozen #extensions descriptor (this.#extensions.get(key)?.target === name) plus the snapshotted #lowersConfigExtensions — and never rereads adapter.configExtension. Pinned in adapter-capability-states.test.ts: a mutable adapter registers with configExtension.key = 'mutable', the live object is renamed afterwards, and the registry still answers mutable → true, renamed → false.
|
@codex review |
|
Codex Review: Didn't find any major issues. Keep them coming! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@codex review |
2b343c1 to
e3ee13b
Compare
|
Codex Review: Didn't find any major issues. 🎉 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@codex review |
e3ee13b to
04c4ed0
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 04c4ed0814
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ...model.hooks.map((hook) => hook.eventRoute === undefined | ||
| ? fixedKindComponent('hook', { id: hook.id, name: hook.event, targets: hook.targets }) | ||
| : { | ||
| capability: `event:${hook.eventRoute.event}`, |
There was a problem hiding this comment.
Treat admitted degraded event routes as selected
For an advanced adapter that publishes a degraded event:* row, validateEventRoutes explicitly admits the route (config/validate.ts handles degraded like supported), allowing its planner to emit it. Inspection now attaches that row here and later calls capabilityIsSupported, which treats every degraded row as unsupported, so the ready plan reports the emitted route under skipped and gives the kind incorrect selected/skipped counts. Align event-route accounting with the validation/emission rule, or reject degraded routes during validation.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in c76fc22: inspection now uses admitsComponent(kind, capability) — event-route components ship on supported or degraded rows (the same rule validateEventRoutes applies), every other kind on supported only — so an admitted degraded route is selected with its degraded judgment and the kinds counts match the plan. Pinned by 'accounts an admitted degraded event route as selected, matching the validation rule (#100)' in api.test.ts: a synthetic adapter publishing a degraded event:session/start row (with eventRouteNames so its planner emits the wrapper) yields a ready plan whose hookEntries include the route, selected carries { name: 'event:session/start', state: 'degraded', evidence, reason }, and kinds reports event-route 1/0.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c76fc22cb0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // The composite plans the Claude and Codex sides from their own config | ||
| // extensions, so host-scoped declarations under those keys (for example | ||
| // `claude.lspServers`) are eligible for emission here. | ||
| lowersConfigExtensions: Object.freeze([claudeAdapter.name, codexAdapter.name]), |
There was a problem hiding this comment.
Scope composite LSP lowering to Claude declarations
With an unchecked JavaScript config containing codex: { lspServers: { ... } } and the plugin target, this declaration makes lowersConfigExtension('plugin', 'codex') true, so normalization targets the LSP component at plugin and inspection marks it selected using the composite's supported LSP union. However, the composite's Codex planner ignores lspServers, while planClaudeLsp reads only model.extensions.claude, so no .lsp.json is emitted. Fresh evidence after the prior extension-scoping fix is this newly added inclusion of the LSP-incapable Codex extension in the composite lowering list; LSP eligibility needs to be scoped per surface or restricted to claude here.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 3c11729: normalizeLspServers now targets the declaring host unconditionally (so codex.lspServers is judged by Codex's own lsp: unavailable row → unsupported-capability) and lets a composite inherit the declaration only when the declaring host itself supports lsp (registry.supports(extension.target, 'lsp')), so codex.lspServers never reaches the plugin bundle while claude.lspServers still does. Pinned in the extended 'never counts an opaque third-party lspServers declaration…' test: codex.lspServers with targets ['codex','plugin'] → model targets ['codex'], Codex skips unsupported-capability, the composite skips excluded-by-targets, selects no lsp component, and writes no .lsp.json.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3c11729beb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // lowers the extension inherits it only when the declaring host can lower | ||
| // LSP servers at all — a composite planning a host with no LSP surface | ||
| // (Codex) emits nothing for that host's `lspServers`. | ||
| const declaringHostLowersLsp = registry.supports(extension.target, componentKindCapabilityName('lsp')!); |
There was a problem hiding this comment.
Gate inherited LSP declarations on component capability
For an advanced declaring adapter whose top-level lsp row is an unavailable intersection but whose componentCapabilities.lsp is supported—using the same composite-emission pattern as the built-in plugin adapter—this gate returns false and prevents another selected composite that lowers the extension from inheriting its servers. Fresh evidence after the earlier extension-scoping fix is that TargetRegistry.hostsComponent() now defines emission from componentCapabilities ?? capabilities, while this new code still calls top-level supports(); use the component-emission judgment here so normalized targets match what adapters can actually emit.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 5113947: the inheritance gate now uses the component-emission judgment (registry.componentCapabilityState?.(declaringHost, 'lsp') ?? registry.capabilityState?.(…), falling back to supports() only when neither is published) via a local hostsComponent helper in normalize.ts, so a declaring adapter whose top-level lsp is an intersection but whose componentCapabilities.lsp is supported still lets a composite that lowers its extension inherit the servers. Pinned by 'judges event-route admission and lsp inheritance by the component-emission override…' in api.test.ts (declaring adapter: top-level unavailable + override supported; second adapter with lowersConfigExtensions: ['synthetic'] → model targets both).
| ...model.hooks.map((hook) => hook.eventRoute === undefined | ||
| ? fixedKindComponent('hook', { id: hook.id, name: hook.event, targets: hook.targets }) | ||
| : { | ||
| capability: `event:${hook.eventRoute.event}`, |
There was a problem hiding this comment.
Validate event routes against component capability overrides
When an advanced adapter supplies an event:* row in componentCapabilities that differs from its top-level row, this new event-route accounting uses the component override, but validateEventRoutes still checks registry.capabilityState() in config/validate.ts. Fresh evidence after the degraded-route accounting fix is that a top-level unavailable row plus a supported component override is rejected before inspection, while the inverse is admitted and then reported skipped; validation should consult componentCapabilityState with the top-level fallback so admission and component emission use the same judgment.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 5113947: validateEventRoutes now reads registry.componentCapabilityState?.(target, 'event:<event>') ?? registry.capabilityState?.(…), the same judgment inspect and hostsComponent() use, so admission and emission agree for adapters whose component override differs from the top-level row (built-in adapters are unaffected: the composite spreads its full row set into componentCapabilities). Same new test: a top-level unavailable event:session/start with a supported component override is admitted and reported selected.
|
@codex review |
|
Codex Review: Didn't find any major issues. 🎉 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
…d event-route as canonical component kinds (#100) - AgentComponentKind (core/components.ts) is the compiler's inspection kind type; filesystem event routes report as event-route judged by the host's event:<canonical> row; claude.lspServers enumerate as lsp components scoped to adapters that lower the declaring extension (TargetAdapter.lowersConfigExtensions, snapshotted at registration). - Every InspectionPlan carries a per-kind matrix (kinds) and the human inspect output prints 'kinds this host cannot emit'. - Dated unavailable rows for lsp (Cursor, portable) and nativeDiagnostics/nativeExtension (every host + composite); composite componentCapabilities keeps every published row and overrides emission dispatch kinds with the union; shared capabilityFromTableRow helper. - adapterRevision: claude 1.23.0, codex 1.10.0, cursor 1.10.0, portable 1.7.0, plugin 1.26.0.
|
@codex review |
5113947 to
6a75da5
Compare
|
Codex Review: Didn't find any major issues. Nice work! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
main (#420) adds --replace/--force to every emitted installer, automatic same-version replacement, Cursor install receipts, and Doctor's current / stale / version-mismatch / foreign / not-installed verdicts. main (#425) reports every canonical component kind from inspect and exports AgentComponentKind and componentKindCapability from agent-bundle/api. The CLI reference, installation guide, and API overview cover both, in both locales.
Summary
Closes the "
lsp/native-diagnostics/native-extensioncanonical kinds" and "event-routeas a distinct kind" items from the #100 remaining-scope comment (2026-09-03).AgentComponentKind(newcore/components.ts, exported fromagent-bundle/api) is now the compiler's inspection kind type:agent | command | event-route | hook | lsp | mcp-app | mcp-server | native-diagnostics | native-extension | rule | script | skill. Each kind names the capability row a host must publish (componentKindCapability);event-routeis judged per component (event:<canonical event>),scriptneeds none,agentkeeps the G5 deferral (docs(capabilities): record the #100 stage 2 agents-component deferral in pinned capability notes #220) — no producer, but the row name is published so the deferral reads as a dated host judgment.src/events/**) report asevent-routewith name<family>/<event>and the host'sevent:*row from the Compile semantic event route v2 families into native host hooks #258 matrix, separately from config-declaredhooks. The compositepluginadapter'scomponentCapabilitiesnow includes the intersectedevent:*rows so inspection matches what the bundle hook contract emits (previously the missing row read as a spuriousunavailable).claude.lspServersentries are normalized intoNormalizedPlugin.lspServers(lspkind,declaredBy: 'claude') and accounted against every selected target: Claude emits.lsp.json(supported), the compositepluginrides the Claude half (union), Codex/Cursor/portable omit with their datedunavailablereasons. Newlsprows on Cursor and portable (previously absent → generic missing-row text).nativeDiagnostics/nativeExtension: new datedunavailablerows on Claude, Codex, Cursor, portable and the composite (intersection + union), each citing the pinned closed manifest schema; Claude's diagnostics row points at the.lsp.jsondiagnosticsoption rather than inventing a component.InspectionPlangainskinds: one entry per canonical kind in kind order with the host's four-state row and selected/omitted counts, even for kinds the project never declares. Humaninspectprintskinds this host cannot emit: agent (unavailable), lsp (unavailable), ….capabilityFromTableRowshared helper (capability-state.ts) for table-driven four-state rows.docs/framework-mode.md;docs/entry-conventions.mdinspect accounting updated. Changeset:agent-bundleminor (inspect JSONkindfor event routes changes;InspectionPlangains a required field).Evidence
packages/agent-bundle/tests/api.test.ts"accounts lsp servers and event routes as distinct canonical kinds with a per-kind host matrix (Compile capability-aware host component bundles #100)": real project withclaude.lspServers+src/events/session/start.tsxacross portable/codex/claude/cursor/plugin — lsp selected on claude+plugin (.lsp.jsonentry present), skippedunsupported-capabilitywithno LSP serverreasons on codex/cursor/portable; event-route selected on the four hook hosts withevent:session/start,excluded-by-targetson portable; full 12-kind matrix on every plan withagentcarrying the#220reason on Claude.tests/adapter-capability-states.test.ts: three new tests pin the dated rows (2026-09-03:evidence prefix on every new entry), composite union/intersection, andevent:*parity betweencapabilitiesandcomponentCapabilities.tests/cli.test.ts: humankinds this host cannot emit:lines for portable and codex;--jsonkindsentries.tests/adapter-metadata.test.ts: revision pins.pnpm typecheck0 errors,pnpm lint0/0,pnpm test:unit2772/2779 with the only failures being the pre-existingnative-claude-contract5 s timeout wobble (reproduced identically on a clean stash of origin/main on this loaded machine),pnpm test:route-unit38/38 on rerun (one 5 s wobble),pnpm test:projection66/66,api.test.ts36/36 under the integration config.Test plan
pnpm build && pnpm test:integration:run(running; will report)@codex reviewon head