Skip to content

feat(components): model lsp, native-diagnostics, native-extension, and event-route as canonical component kinds (#100) - #425

Merged
ScriptedAlchemy merged 1 commit into
mainfrom
feat/100-component-kinds
Sep 3, 2026
Merged

feat(components): model lsp, native-diagnostics, native-extension, and event-route as canonical component kinds (#100)#425
ScriptedAlchemy merged 1 commit into
mainfrom
feat/100-component-kinds

Conversation

@ScriptedAlchemy

Copy link
Copy Markdown
Owner

Summary

Closes the "lsp / native-diagnostics / native-extension canonical kinds" and "event-route as a distinct kind" items from the #100 remaining-scope comment (2026-09-03).

  • AgentComponentKind (new core/components.ts, exported from agent-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-route is judged per component (event:<canonical event>), script needs none, agent keeps 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.
  • Event routes (src/events/**) report as event-route with name <family>/<event> and the host's event:* row from the Compile semantic event route v2 families into native host hooks #258 matrix, separately from config-declared hooks. The composite plugin adapter's componentCapabilities now includes the intersected event:* rows so inspection matches what the bundle hook contract emits (previously the missing row read as a spurious unavailable).
  • LSP servers: claude.lspServers entries are normalized into NormalizedPlugin.lspServers (lsp kind, declaredBy: 'claude') and accounted against every selected target: Claude emits .lsp.json (supported), the composite plugin rides the Claude half (union), Codex/Cursor/portable omit with their dated unavailable reasons. New lsp rows on Cursor and portable (previously absent → generic missing-row text).
  • nativeDiagnostics / nativeExtension: new dated unavailable rows 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.json diagnostics option rather than inventing a component.
  • Every InspectionPlan gains kinds: 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. Human inspect prints kinds this host cannot emit: agent (unavailable), lsp (unavailable), ….
  • capabilityFromTableRow shared helper (capability-state.ts) for table-driven four-state rows.
  • adapterRevision bumps: claude 1.23.0, codex 1.10.0, cursor 1.9.0, portable 1.7.0, plugin 1.25.0.
  • Docs: new Host components matrix in docs/framework-mode.md; docs/entry-conventions.md inspect accounting updated. Changeset: agent-bundle minor (inspect JSON kind for event routes changes; InspectionPlan gains 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 with claude.lspServers + src/events/session/start.tsx across portable/codex/claude/cursor/plugin — lsp selected on claude+plugin (.lsp.json entry present), skipped unsupported-capability with no LSP server reasons on codex/cursor/portable; event-route selected on the four hook hosts with event:session/start, excluded-by-targets on portable; full 12-kind matrix on every plan with agent carrying the #220 reason 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, and event:* parity between capabilities and componentCapabilities.
  • tests/cli.test.ts: human kinds this host cannot emit: lines for portable and codex; --json kinds entries.
  • tests/adapter-metadata.test.ts: revision pins.
  • Local gates: pnpm typecheck 0 errors, pnpm lint 0/0, pnpm test:unit 2772/2779 with the only failures being the pre-existing native-claude-contract 5 s timeout wobble (reproduced identically on a clean stash of origin/main on this loaded machine), pnpm test:route-unit 38/38 on rerun (one 5 s wobble), pnpm test:projection 66/66, api.test.ts 36/36 under the integration config.

Test plan

  • targeted rstest files above
  • typecheck / lint / unit / route-unit / projection
  • pnpm build && pnpm test:integration:run (running; will report)
  • CI green, @codex review on head

@changeset-bot

changeset-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6a75da5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
agent-bundle Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

ScriptedAlchemy added a commit that referenced this pull request Sep 3, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-03T13:50:55.210961Z 6a75da5 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@pkg-pr-new

pkg-pr-new Bot commented Sep 3, 2026

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

commit: 6a75da5

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 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".

Comment thread .changeset/canonical-component-kinds.md Outdated
'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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

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) => [

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 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).

Comment on lines +1072 to +1076
declaredBy: extension.key,
id: `lsp:${extension.key}:${name}`,
name,
provenance: { ...extension.provenance },
targets: [...targetNames],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

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.

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 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".

Comment on lines +1076 to +1077
for (const name of Object.keys(declared).sort((left, right) => left.localeCompare(right))) {
servers.push({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

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.

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 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}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 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.

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 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)}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 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'].

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 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.

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: 2b343c11a0

ℹ️ 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".

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

ScriptedAlchemy added a commit that referenced this pull request Sep 3, 2026
@ScriptedAlchemy
ScriptedAlchemy force-pushed the feat/100-component-kinds branch from 2b343c1 to e3ee13b Compare September 3, 2026 11:57
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: e3ee13b381

ℹ️ 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".

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@ScriptedAlchemy
ScriptedAlchemy force-pushed the feat/100-component-kinds branch from e3ee13b to 04c4ed0 Compare September 3, 2026 12:25

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 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}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 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.

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 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]),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 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.

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 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')!);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 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}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 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.

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: 5113947cc1

ℹ️ 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".

…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.
@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@ScriptedAlchemy
ScriptedAlchemy force-pushed the feat/100-component-kinds branch from 5113947 to 6a75da5 Compare September 3, 2026 13:45
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

Reviewed commit: 6a75da5a38

ℹ️ 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".

@ScriptedAlchemy
ScriptedAlchemy merged commit 9c5bf52 into main Sep 3, 2026
11 checks passed
@ScriptedAlchemy
ScriptedAlchemy deleted the feat/100-component-kinds branch September 3, 2026 14:17
ScriptedAlchemy added a commit that referenced this pull request Sep 3, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant