agent-ecosystem/docs/extensions/plugin-kit-ai-integration-plan.md

90 KiB

plugin-kit-ai Integration Plan for Extensions Plugins

Status: Draft
Date: 2026-04-18
Owner repos:

  • claude_team
  • plugin-kit-ai

Purpose

Replace the current Claude-only plugin backend in claude_team with a provider-aware backend powered by plugin-kit-ai, while keeping the existing Extensions -> Plugins UI.

The integration must support two different truths at the same time:

  • Universal plugins managed through plugin-kit-ai
  • Native external installed plugins that already exist in Claude or Codex and are not yet part of universal managed state

Those are different objects and must remain different in UI, state, and actions.

One-Page Summary

What we are building

  • keep the current Extensions -> Plugins UI in claude_team
  • bundle plugin-kit-ai as a backend engine
  • use plugin-kit-ai for:
    • universal catalog
    • native discovery
    • universal lifecycle actions

What we are not building

  • not embedding a second plugin UI
  • not parsing prose CLI output
  • not scraping repo layout from claude_team
  • not pretending native installed plugins are the same thing as universal managed plugins
  • not promising local scope before backend really supports it

User-visible outcome

  • installed plugins come first
  • universal plugins are the main storefront
  • native installed Claude/Codex plugins stay visible and are labeled honestly
  • install/update/remove/repair results stay target-granular

Safe delivery order

  1. add stable JSON contracts to plugin-kit-ai
  2. add universal catalog in plugin-kit-ai
  3. add native discovery in plugin-kit-ai
  4. integrate read-only mixed plugin view in claude_team
  5. add universal lifecycle actions in claude_team
  6. consider optional native convenience flows only later

Non-negotiable no-go items

  • no auto-merge by display name
  • no silent local -> project downgrade
  • no destructive actions on native external entries unless backend explicitly declares them safe
  • no app-side inference where backend truth is missing

Glossary

Universal plugin

A plugin from the universal plugin catalog that can be managed through plugin-kit-ai.

Native external plugin

A plugin that already exists in a native agent surface such as Claude or Codex, but is not part of plugin-kit-ai managed state.

Managed universal plugin

A universal plugin that plugin-kit-ai has installed or is tracking in ~/.plugin-kit-ai/state.json.

Catalog

The backend surface that answers:

  • what universal plugins exist
  • what targets and scopes they support
  • what storefront metadata should be shown

Discover

The backend surface that answers:

  • what native plugins already exist outside managed universal state
  • what target and scopes they belong to
  • whether the app may safely manage them

List

The backend surface that answers:

  • what universal plugins are already managed

Doctor

The backend surface that answers:

  • which managed universal plugins need attention because of drift, auth, or activation state

Hard Product Decisions

These are fixed unless a new ADR explicitly changes them.

1. Two plugin classes

The page shows:

  • Universal
  • Native external installed

They are never silently merged.

2. Installed-first ranking

Ranking order:

  1. installed universal
  2. installed native external
  3. available universal

3. Universal is the main storefront

Universal plugins are the default source for new installs. Native external plugins are primarily visibility and compatibility surfaces.

4. discover before adopt

Phase 1 needs visibility, not ownership conversion.

5. No fake scope parity

If the backend target does not support a scope, the UI must not pretend it does.

Definition of Done

This migration is done only when all of the following are true:

  • plugin-kit-ai exposes stable machine-readable catalog, discover, and lifecycle contracts
  • claude_team renders universal and native external entries as distinct classes
  • direct Claude mode works end-to-end for universal install/update/remove/repair
  • multimodel Anthropic + Codex mode works end-to-end with target-granular results
  • native external plugins remain visible and truthfully labeled
  • the page stays useful when one backend view fails or is stale
  • rollback is possible through a feature flag without destructive cleanup

If any of these is false, the migration is still in progress.

What Is Already True in plugin-kit-ai

This plan should build on real current code, not on an imagined backend.

Already present today

  • integrationctl already exposes a public lifecycle facade
  • target adapters already expose:
    • Capabilities
    • Inspect
    • Plan*
    • Apply*
    • Repair
  • post-apply verification already re-inspects the target and rejects false-positive installs
  • managed lifecycle state already exists in ~/.plugin-kit-ai/state.json
  • read-only managed views already exist conceptually:
    • list
    • doctor
  • lifecycle update/remove already re-resolve the source and reject identity drift if the resolved manifest no longer matches the stored integration_id

Important current gaps

  • integrations CLI currently prints prose, not versioned JSON
  • there is no public catalog surface yet
  • there is no public discover surface yet
  • current managed Report.Targets do not carry enough integration-level context for the app
  • current service composition still depends on os.Getwd() for workspace semantics

Important current model split

There are two useful metadata layers today:

  • the richer authored plugin model used by pluginmodel / pluginmanifest
  • the narrower integrationctl.IntegrationManifest

Current integrationctl manifest loading preserves:

  • name
  • version
  • description
  • targets
  • derived deliveries
  • derived capability surface

But it currently drops richer authored metadata such as:

  • homepage
  • repository
  • keywords
  • author
  • license

Practical consequence:

  • lifecycle can already use the current IntegrationManifest
  • storefront catalog cannot get all desired detail fields from the current IntegrationManifest alone

Packaging nuance from current code

  • evidence registry already has an embedded fallback, which lowers packaging risk
  • workspace-lock storage is still repo-root oriented

Practical consequence:

  • list, doctor, add, update, remove, and repair are the right first app surfaces
  • sync is not a phase-1 or phase-2 app surface
  • enable and disable can stay out of the first app rollout

Current Adapter Truth From Code

These are backend facts the plan must respect.

Claude adapter

  • install mode: native_cli
  • supports native update: yes
  • supports native remove: yes
  • supports scopes: user, project
  • does not currently advertise local
  • requires reload after install

Codex adapter

  • install mode: marketplace_prepare
  • supports native update: no
  • supports native remove: no
  • supports scopes: user, project
  • does not currently advertise local
  • requires restart and a new thread
  • current inspect logic distinguishes:
    • fully installed
    • disabled
    • prepared but not activated
    • degraded

Consequence for the app

The app must treat scope support as backend-owned truth.

That means:

  • phase 1 and phase 2 should expose only user and project for plugin-kit-backed universal installs
  • if local is important later, it must be added as a real backend capability first

Architecture Boundary

Correct boundary

  • plugin-kit-ai = lifecycle engine, universal catalog backend, native discovery backend
  • claude_team = frontend, state, UX, feature-flagged rollout

Wrong boundaries

  • do not embed a second plugin UI
  • do not parse human CLI output
  • do not scrape universal repo layout directly in claude_team
  • do not link Go code directly into Electron instead of using the CLI contract

Different backend surfaces should be built on different existing code paths. Trying to force one internal model to answer every question would make the result worse.

Surface Best current basis in plugin-kit-ai Why
catalog pluginmanifest.Inspect + publicationmodel + targetcontracts richer authored metadata and stronger target/output truth
list integrationctl managed state and current list service managed universal truth already exists here
doctor integrationctl current doctor service managed drift/auth/activation truth already exists here
lifecycle mutate integrationctl facade and adapters real install/update/remove/repair engine already exists here
discover new dedicated native discovery layer built using native surface readers and inspect helpers external observed truth is different from managed lifecycle truth
  • build catalog from the richer authored-model path
  • build list, doctor, and lifecycle mutations from integrationctl
  • build discover as a new surface that may reuse inspect helpers, but is not just Inspect

This is the cleanest way to avoid overloading one narrow model with too many jobs.

One of the highest-risk failure modes is building the right contract in the wrong layer. This section fixes ownership up front.

Chosen seam by package

Concern Recommended home in plugin-kit-ai Why
CLI flags, --format json, envelopes, exit semantics cli/plugin-kit-ai/cmd/plugin-kit-ai command boundary belongs here, not business truth
catalog projection for app consumption new helper near cli/plugin-kit-ai/internal/pluginmanifest such as internal/catalogview catalog truth comes from authored inspection and needs a stable projection layer
managed lifecycle grouping and integration-level fields install/integrationctl/domain + install/integrationctl/usecase lifecycle truth already lives here and should not be reconstructed in the CLI
native discovery orchestration new discovery usecase under install/integrationctl/usecase with types in install/integrationctl/domain discovery is backend truth, not a frontend heuristic
target-specific native enumeration and evidence collection existing adapter packages under install/integrationctl/adapters/<target> adapters already own target-specific path and native-surface knowledge
source resolution install/integrationctl/adapters/source canonical source resolution already lives here
workspace-root normalization into target-specific native roots install/integrationctl/adapters/pathpolicy plus adapter path helpers app must not duplicate ProjectRoot vs EffectiveGitRoot logic

Raw internal models must not leak as app contracts

The app should not consume any of these raw internal shapes directly:

  • raw pluginmanifest.Inspection
  • raw integrationctl lifecycle domain.Report
  • raw adapter InspectResult

Instead:

  • CLI commands should project them into stable app-facing JSON contracts
  • projection should happen once in plugin-kit-ai
  • claude_team should consume only those projected contracts

Why this chosen seam is safer

  • it keeps target/path/source truth backend-owned
  • it avoids Electron reconstructing lifecycle groupings or source provenance
  • it allows plugin-kit-ai to change internal models without breaking the app contract
  • it makes E2E failures easier to localize to one layer

Which Backend Surface Answers Which UI Question

UI question Backend surface Why
What universal plugins can I install? catalog storefront availability
What universal plugins are already managed? list managed truth
What managed universal plugins need attention? doctor drift, auth, activation
What native plugins already exist outside plugin-kit management? discover observed external truth
Can this plugin be installed for Claude? catalog capability + scope metadata install intent
Can this plugin be installed for Codex? catalog capability + scope metadata install intent
Can I safely uninstall this native external plugin from the app? discover.manageability destructive authority must come from backend
What exactly happened after install/update/remove? lifecycle result JSON target-granular mutation truth

Rule:

  • if the answer is not available from one of these surfaces, the app should not invent it

Backend View Consistency Matrix

This section makes cross-surface ownership explicit. It should be possible to answer every “which surface wins?” question from this table alone.

| Field or question | Winning surface | Allowed fallback | Forbidden fallback | |---|---|---| | managed existence | list | none | catalog, discover, app heuristics | | managed health / degraded / auth-pending | doctor | list only for neutral installed state | catalog, discover | | universal availability | catalog | stale cached catalog with explicit stale marker | discover, app heuristics | | native external existence | discover | stale cached discovery with explicit stale marker | catalog, list | | destructive authority for native external entries | discover.manageability | none | app heuristics | | installability by target/scope | catalog plus lifecycle capability projection | conservative disable in app | inferred support from authored target alone | | managed target result after mutation | lifecycle mutate result | immediate doctor refresh | app optimism without payload evidence | | storefront detail metadata | catalog detail projection | explicit missing-detail UI | generated operational docs |

Required contradiction handling

If surfaces disagree:

  • list vs catalog
    • keep the managed entry
    • mark catalog/detail support degraded if needed
  • discover vs catalog
    • keep both truths
    • do not rewrite ownership
  • doctor vs list
    • prefer doctor for health state
    • prefer list for managed existence
  • lifecycle mutate result vs stale cached list
    • prefer the fresh mutate payload, then refetch
    • do not let stale cache overwrite the mutation outcome

Data Flow

flowchart LR
  A["universal-plugins-for-ai-agents"] --> B["plugin-kit-ai catalog"]
  C["Claude native surfaces"] --> D["plugin-kit-ai discover"]
  E["Codex native surfaces"] --> D
  F["plugin-kit-ai state.json"] --> G["plugin-kit-ai list"]
  F --> H["plugin-kit-ai doctor"]
  I["plugin-kit-ai lifecycle actions"] --> F
  B --> J["claude_team Plugins UI"]
  D --> J
  G --> J
  H --> J
  J --> I

Target Naming and Mapping

Authored target names and app-facing provider labels are not always the same thing.

Examples from current code:

  • authored claude maps to app/runtime target claude
  • authored codex-package maps to app/runtime target codex
  • authored gemini maps to app/runtime target gemini
  • authored cursor maps to app/runtime target cursor
  • authored opencode maps to app/runtime target opencode

Surface-specific target id rule

The same plugin may be described through different target vocabularies depending on the backend surface:

  • authored/catalog truth:
    • claude
    • codex-package
    • codex-runtime
    • gemini
    • cursor
    • cursor-workspace
    • opencode
  • lifecycle-manageable truth:
    • claude
    • codex
    • gemini
    • cursor
    • opencode
  • app-facing provider labeling:
    • Anthropic
    • Codex
    • optionally later other providers

This is already visible in current code:

  • authored plugin metadata and pluginmanifest preserve codex-package
  • integrationctl normalizes that into lifecycle target codex
  • the UI should render a provider lane label such as Codex, not leak raw lifecycle ids everywhere

Recommended rule:

  • never force one single target field to carry all three meanings
  • preserve target ids separately by surface
  • use explicit fields such as:
    • authored_targets
    • manageable_targets
    • available_app_targets

Catalog entries should preserve both:

  • authored target identifiers
  • normalized app/runtime target identifiers

Why:

  • authored compatibility and generated outputs still care about authored target names
  • the app needs stable provider-level labels like Anthropic and Codex
  • this avoids lossy translation

App-Facing Target Subset

plugin-kit-ai understands more targets than the claude_team plugin page needs to action directly.

For this integration, the primary app-facing actionable subset should be:

  • claude
  • codex-package

These map to the current app-facing provider lanes:

  • Anthropic
  • Codex

Out of scope for first actionability

These may still exist in authored metadata, but they should not drive primary install buttons in the first rollout:

  • codex-runtime
  • gemini
  • cursor
  • cursor-workspace
  • opencode
  • the backend catalog may preserve full authored target support
  • claude_team should derive primary provider labels and actions only from the app-relevant subset
  • broader target support may appear as secondary detail later, but should not confuse the main install surface

Important nuance

This is not because those other targets are “fake”. It is because this app rollout has a narrower action surface than the full authored/plugin backend target space.

For example:

  • plugin-kit-ai lifecycle already knows targets like gemini, cursor, and opencode
  • pluginmanifest and targetcontracts know even broader authored/runtime distinctions such as codex-package vs codex-runtime

But the first plugin page rollout in claude_team should optimize for a clear and reliable main surface, not for exposing the entire backend target universe at once.

Why packaged support still does not mean first-class app actionability

Current platformmeta already exposes packaged profiles for:

  • claude
  • codex-package
  • codex-runtime

That still does not mean all three should become first-class action lanes in claude_team.

Recommended rule:

  • packaged/backend support answers “can the backend understand this target family?”
  • app-primary actionability answers “should this app expose install/manage actions for this target in the first rollout?”
  • those questions are related but not identical

This is one of the most important places where the plan must stay conservative.

Catalog Support Projection Rules

Catalog generation should preserve three different truths without collapsing them:

1. Authored targets

What the plugin repo declares in plugin.yaml.

Examples:

  • claude
  • codex-package
  • codex-runtime
  • gemini
  • cursor
  • cursor-workspace
  • opencode

2. Backend-manageable lifecycle targets

What the current integrationctl lifecycle can actually manage today.

From current code, that target set is:

  • claude
  • codex-package
  • gemini
  • cursor
  • opencode

Notably, it does not include:

  • codex-runtime
  • cursor-workspace

3. App-primary action targets

What claude_team should expose as first-class install lanes in this rollout.

Recommended set:

  • claude
  • codex-package

Current public target universe in plugin-kit-ai

From current platformmeta code, the public target universe is already split into:

  • packaged profiles:
    • claude
    • codex-package
    • codex-runtime
  • tooling profiles:
    • gemini
    • cursor
    • cursor-workspace
    • opencode

This is useful context because it shows that the backend target universe is intentionally broader than the first plugin-page rollout.

Required contract rule

A catalog entry should be able to preserve all three layers separately, for example:

  • authored_targets
  • manageable_targets
  • primary_action_targets

This keeps the system honest:

  • authored truth stays intact
  • backend actionability stays explicit
  • app UI stays focused

Product Model

Universal plugins

Source:

Properties:

  • installable through plugin-kit-ai
  • available for one or more targets
  • primary browse/search source
  • explicit target support labels

Native external installed plugins

Properties:

  • already installed in a native agent surface
  • not necessarily managed by plugin-kit-ai
  • still visible in UI
  • clearly labeled by target ownership

Example labels:

  • Installed - Anthropic only
  • Installed - Codex only

Source of Truth Model

Surface Owner Meaning
catalog plugin-kit-ai what universal plugins are available
discover plugin-kit-ai what native plugins are already observed
list plugin-kit-ai what universal plugins are managed
doctor plugin-kit-ai health and drift for managed universal plugins
renderer cache claude_team temporary UI cache only

Consistency rules

  • every rendered universal_installed entry must be explainable by list
  • every degraded managed universal entry must be explainable by doctor
  • discover may overlap conceptually with universal entries, but never redefines managed ownership
  • catalog must not advertise target/scope support that lifecycle will reject under normal supported conditions

Freshness and partial-view rules

These rules matter because catalog, discover, list, and doctor do not have the same source or refresh cost.

  • list is authoritative for managed existence, even if catalog is stale or temporarily missing an entry
  • doctor is authoritative for managed health, even if catalog is stale
  • discover is authoritative for observed native external existence, unless suppressed by stronger managed-overlap evidence
  • failed or stale catalog must not make a managed entry disappear from the page
  • failed or stale discover must not invent that native external entries were removed
  • the app may mark data stale, but it must not rewrite ownership because one backend view is temporarily unavailable

Consistency invariants the backend contract should preserve

  1. every managed entry returned by doctor must also be representable in list by the same managed grouping key
  2. discover must either suppress managed overlap or mark it explicitly, but must not silently contradict list
  3. catalog may omit optional storefront metadata, but must not change canonical integration_id
  4. claude_team must never resolve a contradiction by guessing - it should preserve both truths and degrade the UI honestly

Identity, Matching, and Dedupe

Universal identity

Canonical key:

  • integration_id

Native external identity

Canonical key:

  • native_target + native_plugin_id + scope-set

Matching rule

matched_integration_id is advisory only. It does not convert a native external entry into a universal entry.

Match confidence

Recommended values:

  • match_confidence: exact | heuristic | none
  • match_basis: same_repo_same_plugin_id | same_marketplace_identity | manual_mapping | name_heuristic | unknown

Target-specific matching ladder

The backend should classify matches conservatively.

Recommended ladder:

Claude native external -> universal

exact only when the backend can prove the same marketplace identity, for example:

  • same native plugin ref
  • or same marketplace identity pair such as:
    • plugin id
    • marketplace name

heuristic only when:

  • display name matches strongly
  • and target is the same
  • and there is no stronger conflicting candidate

none when:

  • only loose name similarity exists
  • or more than one universal plugin could plausibly match

Important note:

  • current managed Claude installs use synthetic refs such as integration_id@integrationctl-<integration_id>
  • native external Claude installs from official or third-party marketplaces may use different marketplace identities
  • therefore name equality alone is not enough for exact

Codex native external -> universal

exact only when the backend can prove the same native plugin identity, for example:

  • marketplace entry name equals integration id
  • and the same plugin reference is observed consistently in:
    • marketplace catalog
    • config toggle ref
    • or managed plugin root path

heuristic only when:

  • marketplace entry name strongly matches integration id
  • but not every supporting surface is available

none when:

  • only title-level similarity exists
  • or multiple universal integrations could map to the same observed native name

Important note:

  • current Codex-managed installs use integration_id as the marketplace entry name
  • they also create related evidence in:
    • .agents/plugins/marketplace.json
    • plugin root path under plugins/<integration_id>
    • config plugin ref <integration_id>@<marketplace_name>
  • that is good raw evidence, but the backend should still keep external discovery conservative

Preferred matching evidence by target

Target Strongest evidence Weaker evidence Unsafe alone
Claude plugin ref, marketplace name + plugin id pair stable display name + target display name alone
Codex marketplace entry name + config plugin ref + plugin root agreement marketplace entry name only title similarity alone

Recommended rule:

  • only the strongest evidence column may justify exact
  • weaker evidence may justify heuristic
  • the unsafe column must map to none

Matching invariants

  • exact must be explainable from stable identity-bearing fields, not from display text
  • heuristic must never unlock destructive actions
  • heuristic must never collapse two entries into one
  • if confidence is below exact, the UI should treat the relation as advisory only
  • the app must not recalculate confidence differently from the backend
  • overlap suppression and matching are different decisions
  • an entry may be suppressed as managed overlap without ever being exposed as a native external match candidate

Renderer rule:

  • only strong bases may drive stronger UI hints
  • heuristic matches must never auto-merge entries or unlock stronger actions

Catalog Production Model

This must be explicit because the universal repo is the source of truth for available universal plugins, but it must not become the app contract directly.

Source of universal entries

Universal catalog entries should be generated from:

  • 777genius/universal-plugins-for-ai-agents/plugins/*

using plugin-kit-ai, not using app-side parsing.

  1. select a pinned revision of universal-plugins-for-ai-agents
  2. enumerate plugin directories under plugins/*
  3. load each plugin through the richer authored plugin model, ideally the same pluginmanifest.Inspect path that already exposes:
    • manifest metadata
    • publication model
    • target contract details
  4. derive normalized catalog entries
  5. write a bundled snapshot for app packaging
  6. optionally refresh from the same source later

Why publicationmodel is helpful but not enough on its own

Current publicationmodel.Model is useful for catalog generation because it already normalizes:

  • package targets
  • package families
  • channel families
  • install model
  • authored docs
  • managed artifacts

But by itself it does not carry the full storefront metadata set the app wants, such as:

  • homepage
  • repository
  • keywords
  • author
  • license

Those live in the richer authored manifest path exposed through pluginmanifest.Inspection.Manifest.

Recommended rule:

  • catalog generation should use pluginmanifest.Inspect as the main authored inspection entry point
  • then combine:
    • Inspection.Manifest for storefront metadata
    • Inspection.Publication for publication/channel/package projection
    • Inspection.Targets plus target contracts for support and surface details

The app must not try to reconstruct that combination on its own.

Why this should not use the current narrow lifecycle loader only

The current integrationctl manifest loader is enough for lifecycle planning, but it preserves only:

  • name
  • version
  • description
  • targets
  • derived deliveries

That is not enough on its own for the desired storefront contract.

Recommended resolution:

  • treat catalog generation as its own backend translation layer
  • allow that layer to read richer authored metadata
  • still keep the final catalog output normalized and versioned

Why pluginmanifest.Inspect is a better catalog basis than the current lifecycle loader

From current code, pluginmanifest.Inspect already carries much richer input than integrationctl.Loader, including:

  • authored manifest metadata
  • publication model
  • target contract fields such as install model, activation model, native root, portable kinds, native surfaces, and managed artifacts

That makes it a much better source for storefront and support badges.

Performance boundary

Current source resolution for lifecycle work may clone GitHub or git URL sources. That is acceptable for install/update style mutations.

It is not acceptable as the way to build the storefront catalog.

Catalog generation should work from:

  • a pinned local checkout
  • a prepared snapshot
  • or another batch-friendly backend path

It should not resolve each storefront entry by cloning sources independently at runtime.

Alias rule

The first-party alias map is useful for CLI shortcuts like plugin-kit-ai add notion. It is not the storefront contract.

Source Reference Semantics

Source semantics must stay explicit. This is another place where the app should not invent meaning.

Current lifecycle source truth

From current integrationctl code, lifecycle source resolution already distinguishes:

  • requested source ref
  • resolved source ref
  • local materialized path
  • source digest

Examples:

  • local path request:
    • requested kind local_path
    • resolved kind local_path
  • GitHub repo-path request:
    • requested kind github_repo_path
    • resolved kind git_commit
  • git URL request:
    • requested kind git_url
    • resolved kind git_commit

This is good and should be preserved in the app-facing lifecycle contract.

Alias semantics

Current first-party aliases are only convenience input forms such as:

  • context7
  • stripe
  • notion

They resolve to concrete GitHub repo-path refs under the universal plugin repository.

Recommended rule:

  • aliases are accepted CLI input
  • aliases are not canonical identity
  • aliases should not become the only stored source value in app state

Catalog source semantics

Catalog source semantics are different from lifecycle source semantics.

For the first app rollout, catalog entries should be treated as coming from a curated catalog snapshot with its own provenance, for example:

  • snapshot source kind
  • catalog revision
  • generated-by backend version

The catalog should not pretend that every card was individually resolved through runtime lifecycle source resolution.

Keep these source layers separate:

  • requested_source_ref
    • lifecycle input truth
  • resolved_source_ref
    • lifecycle resolved truth
  • catalog_source
    • storefront snapshot provenance

The app should never collapse those into one ambiguous source string.

  • install detail may show requested and resolved source refs for managed universal installs
  • storefront cards should usually show catalog provenance only when needed for debugging or advanced detail
  • aliases may be accepted in user-facing install flows, but the stored and rendered lifecycle truth should remain normalized requested/resolved refs

Workspace Root and Project Scope Semantics

Project-scoped installs need one more rule-set because current target adapters do not all interpret project roots the same way.

Current code reality

plugin-kit-ai already distinguishes:

  • user scope
  • project scope
  • stored workspace_root on managed installation records

But current adapters derive effective native roots differently:

  • Claude
    • project settings path uses ProjectRoot(workspace_root, project_root)
  • Codex
    • project marketplace root uses EffectiveGitRoot(workspace_root, project_root)
  • OpenCode
    • project assets/config roots also use EffectiveGitRoot(workspace_root, project_root)
  • Cursor
    • project config path currently uses ProjectRoot(workspace_root, project_root)

This means the same raw workspace path can lead to different effective native roots depending on target semantics.

Why this matters

If the app assumes one global meaning for workspace_root, it can easily:

  • install into the wrong repo root
  • render the wrong project target path in detail
  • refresh the wrong context after mutation
  • mis-explain project scope differences between providers
  • the app passes the raw user-selected workspace_root
  • the backend owns target-specific effective-root normalization
  • the app must not try to emulate EffectiveGitRoot or ProjectRoot logic itself

Where useful, lifecycle and discovery payloads may expose explicit derived fields such as:

  • workspace_root
    • raw project context that was requested or persisted
  • effective_native_root
    • target-specific derived root actually used for native files
  • native_scope_root
    • optional friendlier alias if that reads better in the contract

Phase-1 minimum:

  • workspace_root is required for project-scoped managed installs
  • missing project workspace_root must remain a hard backend error
  • target-specific effective root may be additive if not ready immediately
  • UI selection should talk in terms of the chosen project/workspace
  • backend detail and diagnostics may show the effective native root when it differs
  • app logic for mutation, refresh, and cache keys should continue to use the raw workspace context plus target, not a home-grown rewritten root

Why this should stay backend-owned

Current target adapters already encode platform-specific expectations.

Examples:

  • Codex project installs intentionally anchor to effective git root
  • Claude project installs intentionally target project-local settings path

Trying to centralize those rules in Electron would duplicate platform policy and create drift.

Metadata Truth Table

One of the biggest ways this migration can go wrong is promising metadata that the backend does not actually know reliably.

Metadata that already exists today in authored source

From current authored plugin source, plugin-kit-ai can already obtain:

  • integration_id
  • version
  • description
  • homepage
  • repository
  • license
  • keywords
  • declared targets

Important caveat

The current integrationctl manifest loader does not carry all of those fields forward today.

So there are two safe paths:

  1. extend integrationctl manifest loading to preserve richer metadata
  2. build catalog from the richer authored-model path and translate it into the catalog contract

What must not happen:

  • the app inventing those fields
  • the app scraping raw repo files directly
  • two different backend paths returning contradictory storefront metadata

Metadata the backend can derive safely

The backend can also derive:

  • generated delivery_kinds
  • available_targets
  • supported_scopes_by_target from target adapter capabilities
  • readme location using a stable default rule
  • provenance fields such as source ref, revision, manifest digest, generated-by version

Metadata that is not a safe phase-1 assumption

These fields are optional curation data, not phase-1 requirements:

  • category
  • icon_url
  • install_count
  • popularity
  • featured_rank

Phase 1 should require only:

  • name
  • description
  • version
  • homepage / repository
  • keywords
  • target support
  • scope support
  • README/detail

If category, icon, or popularity are absent, the UI should hide or degrade those features honestly.

Storefront Detail and README Semantics

The plugin detail surface must use the authored human guide, not arbitrary generated root docs.

Current code reality

plugin-kit-ai already generates root-facing docs such as:

  • README.md
  • GENERATED.md
  • boundary guidance docs

But those are operational/generated root entrypoints. They are not the best source of storefront detail content.

Current code also makes the authored README explicit:

  • the managed root README.md points readers back to plugin/README.md
  • generated docs inventory also treats root docs differently from managed outputs

That is a strong signal that the authored README remains the source of truth for human-facing plugin detail.

For universal catalog entries, the default detail source should be:

  • authored plugin/README.md

Not:

  • generated root README.md
  • GENERATED.md
  • boundary docs like AGENTS.md

Why this matters

If the app accidentally uses generated root docs as storefront detail:

  • the detail view becomes noisy and operational
  • it may emphasize generate/normalize workflows instead of plugin value
  • the same plugin can appear to have unstable detail content depending on packaging mode

The catalog contract should prefer an explicit detail reference such as:

  • detail_kind: "authored_readme"
  • detail_ref

Recommended phase-1 default:

  • detail_kind = "authored_readme"
  • detail_ref points to the authored README location within the catalog source snapshot
  • plugin cards use catalog summary fields
  • plugin detail loads the authored detail reference when available
  • if detail content is missing, the app should degrade honestly instead of substituting generated operational docs

Catalog Field Ownership Matrix

This makes the contract much easier to implement because it is explicit about where each field should come from.

Catalog field Recommended source in plugin-kit-ai Notes
integration_id authored manifest name stable universal identity
display_name authored manifest name initially future curation can improve presentation
description authored manifest description required
version authored manifest version required
homepage_url richer authored model not present in current narrow lifecycle manifest
repository_url richer authored model not present in current narrow lifecycle manifest
keywords richer authored model phase-1 safe metadata
authored_targets authored manifest targets preserve exact authored truth
manageable_targets lifecycle target mapping / registered adapters what backend can actually act on
primary_action_targets app rollout policy narrower than full backend target universe
available_app_targets backend target projection provider-facing labels for claude_team
supported_scopes_by_target target adapter capabilities backend-owned truth
capabilities delivery mapping and/or target contract data do not invent in app
readme_url catalog translation layer use a stable default rule
category optional curation metadata do not block phase 1
icon_url optional curation metadata do not block phase 1
catalog_revision catalog generation pipeline provenance
generated_by_plugin_kit_version CLI/backend build info provenance

Important rule

If a field does not have a trustworthy backend source yet, phase 1 should omit or degrade it instead of synthesizing it in the app.

Effective Metadata Projection

Catalog generation must distinguish between:

  • shared plugin metadata
  • target-specific effective metadata

This matters because current plugin-kit-ai code already allows target-specific metadata overlays, especially for package-style targets such as codex-package.

Shared metadata

Shared metadata should come from the authored manifest layer:

  • name
  • version
  • description
  • base homepage
  • base repository
  • base license
  • base keywords
  • base author

This is the safest metadata for:

  • mixed-target storefront cards
  • cross-target search
  • universal identity

Target-specific effective metadata

For some targets, especially codex-package, the effective generated package metadata is:

  • base manifest metadata
  • plus allowed target-specific overrides from targets/<target>/package.yaml

Current code already proves this path exists:

  • codex-package generation merges base manifest metadata with optional targets/codex-package/package.yaml
  • validation checks the generated Codex package metadata against that merged expectation

The catalog contract should preserve both layers explicitly:

  • shared metadata for the universal entry itself
  • optional effective_target_metadata for targets that project different package metadata

Example shape:

  • shared_metadata
  • effective_target_metadata.codex

At minimum, per-target effective metadata may include:

  • homepage
  • repository
  • license
  • keywords
  • author
  • list cards should use shared metadata
  • provider-specific effective metadata should appear only in target detail sections or provider-specific support details
  • the app must not silently replace universal card metadata with one target's override

Why:

  • otherwise a codex-package override could accidentally become the visible truth for a plugin that is still conceptually universal
  • that would make shared cards unstable and misleading across providers

If effective target metadata is not yet emitted in the contract:

  • use shared metadata only
  • do not guess target-specific homepage/repository/license in the app
  • add effective target metadata later as an additive contract field

Conservative phase-1 metadata rule

For phase 1, treat target-specific effective metadata as enhancement, not as a dependency.

That means:

  • search, ranking, and primary cards use shared metadata only
  • target-specific metadata appears only when backend emits it explicitly
  • absence of effective target metadata must never block installability rendering
  • the app must not read target-specific docs or package manifests directly to reconstruct this layer

Native Discovery Model

discover is a genuinely new backend surface. It must not be implemented as a thin wrapper over current List or current per-target Inspect.

Why current per-target Inspect is not a safe discovery backend

This needs to be explicit because reusing current adapter Inspect can look tempting, but it is the wrong abstraction for external discovery.

Claude

Current Claude inspect logic is still strongly lifecycle-oriented:

  • it resolves inspect identity from:
    • in.IntegrationID
    • or in.Record.IntegrationID
  • native plugin-list confirmation then looks for a specific plugin ref:
    • defaulting to integration_id@integrationctl-<integration_id>
    • or a managed plugin ref from record metadata
  • if that specific confirmation path does not resolve, current inspect can still fall back to installed when native files or CLI availability make the managed candidate look plausible

That is appropriate for managed lifecycle verification. It is not appropriate for general native external discovery, because external installs may use:

  • a different marketplace name
  • a different plugin ref
  • or no managed lifecycle record at all

Codex

Current Codex inspect logic is also lifecycle-oriented:

  • inspect inputs derive integration_id from the managed record
  • scope/path construction depends on that managed integration identity
  • state classification assumes it is inspecting a known candidate plugin root
  • current observed-surface logic is built around a specific expected catalog path, plugin root, and config path
  • current lifecycle classification then reasons from managed cache presence plus that expected surface bundle

That is useful for verifying a managed installation. It is not enough for general native external enumeration, which first needs to discover candidates before it can classify them.

  • discover must be its own scanner-oriented surface
  • it may reuse helper functions from adapters where useful
  • but it must not simply loop over current adapter Inspect without an independent candidate-enumeration layer

Critical discovery anti-patterns

These are explicitly forbidden:

  1. calling current adapter Inspect on arbitrary filesystem hits and treating the result as native discovery truth
  2. suppressing a discovered entry before comparing it against managed lifecycle evidence
  3. upgrading a heuristic name match into an exact overlap suppression signal
  4. deriving native manageability from UI assumptions instead of backend evidence
  5. hiding a discovered entry only because catalog lookup failed or was stale

If implementation pressure pushes toward any of these shortcuts, the correct fix is to extend backend discovery evidence, not to make the app smarter.

Practical implementation shape

Recommended backend structure:

  1. enumerate native candidates from target-specific sources
  2. derive native identity and evidence for each candidate
  3. suppress candidates already explained by managed lifecycle state
  4. classify observed state
  5. compute advisory relation to universal catalog
  6. emit normalized discovery entries

This keeps discover honest:

  • enumeration first
  • classification second
  • matching last
  • read-only throughout

Claude discovery sources

  • ~/.claude/plugins/installed_plugins.json
  • ~/.claude/settings.json
  • <project>/.claude/settings.json
  • <project>/.claude/settings.local.json

Codex discovery sources

  • ~/.agents/plugins/marketplace.json
  • <project>/.agents/plugins/marketplace.json
  • ~/.agents/plugins/plugins/<integration-id>
  • <project>/.agents/plugins/plugins/<integration-id>
  • ~/.codex/plugins/cache/<marketplace>/<integration-id>/local
  • ~/.codex/config.toml

Required observed states

  • observed_active
  • observed_disabled
  • observed_prepared
  • observed_degraded

Observed-state classification should be target-specific and evidence-driven.

Codex

Current adapter code already implies a practical state ladder:

  • observed_active
    • cache bundle exists
    • and marketplace catalog + plugin root are present
    • and config does not mark the plugin disabled
  • observed_disabled
    • cache bundle exists
    • and config toggle is present and disabled
  • observed_prepared
    • marketplace entry exists and plugin root exists
    • but activation evidence such as cache bundle is not present yet
  • observed_degraded
    • only part of the expected prepared/install surface exists
    • or cache exists while managed marketplace source is missing or drifted

Important rule:

  • discover should keep this richer observed-state truth
  • the app should not collapse everything into plain installed/not installed

Codex evidence mapping table

The first implementation should stay conservative and evidence-driven.

Evidence seen by discovery Recommended observed state Why
marketplace entry + plugin root + installed cache, config not disabled observed_active strongest “prepared and activated” signal available today
installed cache + config toggle present and disabled observed_disabled disable state is explicit
marketplace entry + plugin root, but no installed cache yet observed_prepared package is staged but native activation is not complete
only one of marketplace entry or plugin root exists observed_degraded partial native surface
installed cache exists but marketplace entry or plugin root is missing observed_degraded drifted or partially removed managed/native surface
config references plugin, but marketplace entry and plugin root are both absent observed_degraded stale toggle or orphaned config

Conservative phase-1 defaults for Codex discovery

Until discovery evidence is richer, prefer these defaults:

  • if evidence is ambiguous, downgrade to observed_degraded
  • do not claim observed_active from config evidence alone
  • do not infer exact universal matching from marketplace entry name alone
  • do not infer safe removal from discovered Codex paths alone
  • do not suppress a discovered Codex entry unless managed-overlap evidence includes owned objects or stable lifecycle evidence

Claude

For phase 1, Claude discovery may stay simpler:

  • observed_active
    • plugin appears in native plugin list and is enabled
  • observed_disabled
    • plugin appears in native plugin list and is disabled
  • observed_degraded
    • settings or install evidence exists but plugin list cannot confirm a clean state
  • observed_prepared
    • optional future state only if backend gains a meaningful pre-install or staged marketplace concept for external Claude installs

Recommended rule:

  • do not force artificial state parity between Claude and Codex
  • preserve richer Codex states where the backend can actually justify them

Required extra fields

  • native_target
  • native_plugin_id
  • installed_scopes
  • detected_source
  • manageability
  • matched_integration_id
  • match_confidence
  • match_basis
  • identity_evidence
  • activation_hint

Discovery manageability rule

The backend must declare whether a native external entry is:

  • display_only
  • safe_remove
  • safe_adopt
  • or another explicit future mode

The app must not infer destructive authority.

Discovery overlap suppression rule

discover must not blindly report every observed native install as native_external_installed.

Why this is necessary:

  • managed universal installs also materialize into native agent surfaces
  • a naive scanner would rediscover those same installs and duplicate them as native external entries

Recommended backend rule:

  • discover should load managed lifecycle state, or equivalent managed evidence, before finalizing external entries
  • if an observed native install is already explained by managed lifecycle state with high confidence, it should either:
    • be suppressed from discovery output, or
    • be explicitly marked as managed overlap for app-side filtering

Recommended default:

  • suppress managed-overlap entries in the discovery payload
  • discovery should describe only installs that are not already explained by managed lifecycle state

Managed-overlap evidence examples

Claude examples from current code:

  • synthetic marketplace name pattern integrationctl-<integration_id>
  • managed plugin ref recorded in lifecycle metadata
  • managed materialized marketplace root under ~/.plugin-kit-ai/materialized/claude/<integration_id>

Codex examples from current code:

  • managed plugin root under .agents/plugins/plugins/<integration_id>
  • managed catalog entry name equal to integration_id together with lifecycle-owned native objects
  • managed config ref <integration_id>@<catalog_name> when that catalog name is already tied to a managed installation

Important rule:

  • suppression should prefer owned native object evidence and managed lifecycle state over name heuristics
  • name equality alone is not enough to classify something as managed overlap

Managed Lifecycle Model

Existing lifecycle surfaces

  • list for managed installations
  • doctor for managed drift / activation / auth attention
  • add, update, remove, repair for mutations

Important current gap

Current Report.Targets do not identify which integration a target belongs to.

The app needs lifecycle JSON to include integration-level context such as:

  • integration_id
  • managed_entry_key
  • source refs
  • policy scope
  • workspace root

Why the current raw lifecycle report is not enough for app integration

Today the raw integrationctl lifecycle query shape is still too flat for the plugin page.

In current code:

  • domain.Report contains only:
    • summary
    • targets
    • warnings
  • domain.TargetReport contains per-target state such as:
    • target
    • delivery_kind
    • state
    • activation_state
    • environment_restrictions
    • manual_steps

What it does not preserve at the same level:

  • integration_id
  • requested_source_ref
  • resolved_source_ref
  • resolved_version
  • policy.scope
  • workspace_root
  • a stable grouping boundary between one integration and another

That matters because the app needs to render cards and detail views at the integration-entry level, not as an ungrouped stream of target facts.

Recommended rule:

  • plugin-kit-ai should keep its current internal normalized lifecycle model
  • but the app-facing JSON contract must expose managed entries grouped by integration
  • claude_team must not try to reconstruct integration grouping from flat target rows by heuristics

Required grouped lifecycle identifiers

For app-facing lifecycle JSON, each managed entry should include at minimum:

  • managed_entry_key
  • integration_id
  • requested_source_ref
  • resolved_source_ref
  • resolved_version
  • policy.scope
  • workspace_root

Recommended rule:

  • managed_entry_key should be stable for one stored installation record
  • it should not depend on target row order
  • it should be safe for the app to use as the primary cache and merge key for managed lifecycle entries

Without this, the frontend will eventually drift into reconstructing groups from target arrays, which is fragile and unnecessary.

Conservative phase-1 grouped lifecycle rule

Until the backend exposes a more formal record identifier, phase 1 should still require:

  • one grouped managed entry per stored installation record
  • stable ordering of managed_entries
  • stable ordering of nested targets
  • explicit grouping keys in payload, not implied grouping by adjacent rows

The app must treat missing grouping keys as a compatibility problem, not as an invitation to reconstruct them heuristically.

Critical CLI semantic to freeze

Current integrations mutating commands default to --dry-run=true.

That is good for humans in a terminal, but dangerous for app integration.

Recommended rule:

  • machine-readable mutating calls from claude_team must always pass explicit execution mode
  • either:
    • --dry-run=false, or
    • a future clearer flag such as --apply

The app must never rely on CLI defaults for mutating behavior.

JSON Contract Style

plugin-kit-ai already has a public JSON contract style in surfaces like validate and publication. The new integrations contracts should follow that style instead of inventing a second JSON dialect.

Required envelope rules

  • top-level format
  • top-level schema_version
  • explicit request context fields where relevant
  • top-level warning_count
  • top-level warnings
  • one canonical payload field rather than many competing summary shapes

Requested context rule

Current public JSON reports in plugin-kit-ai already use request-context fields such as:

  • requested_target
  • requested_platform

Recommended rule for the integrations surfaces:

  • include explicit request-context fields when the command accepts them
  • examples:
    • requested_targets
    • requested_scope
    • requested_workspace_root
    • requested_integration_id

This makes automation and debugging much safer than inferring invocation context from payload shape.

Required array guarantees

In schema version 1, the following fields should be arrays, never null:

  • warnings
  • entries
  • managed_entries
  • targets

Compatibility rules

  • additive fields are allowed within the same schema_version
  • semantic changes to existing fields require a new schema_version
  • removing a field the app depends on requires a new schema_version
  • enum meaning changes require a new schema_version

App behavior on unsupported versions

If the backend returns a newer unsupported schema:

  • read-only views may continue only if safe
  • lifecycle actions must be disabled
  • the UI must explain the compatibility mismatch clearly

Process exit and payload semantics

This must be explicit because current plugin-kit-ai already has public JSON commands that can:

  • print a valid JSON payload to stdout
  • then still exit non-zero because the payload describes a failing or issue-bearing report

Current examples in code:

  • validate --format json
  • publication doctor --format json

Recommended rule for the integrations surfaces:

  • stdout JSON is the canonical machine-readable payload
  • process exit code is still meaningful, but it must not be the only signal the app uses
  • claude_team should:
    • first attempt to parse a valid JSON payload from stdout
    • then interpret payload-level fields such as outcome, ok, warning_count, failure_count, issue_count
    • only fall back to process-exit-only handling when no valid contract payload exists

Without this rule, the app will misclassify structured partial failures as transport failures.

For machine-readable integrations surfaces, outcome should be explicit in the payload instead of inferred only from process exit:

  • read-only reports:
    • may expose ok, warning_count, and optional issue_count
  • mutating results:
    • should expose explicit outcome
    • recommended values:
      • planned
      • applied
      • partial_success
      • failed

The exact enum may still evolve, but the contract must keep payload-level outcome explicit.

  • plugin-kit-ai/integrations-report
  • plugin-kit-ai/integrations-result
  • plugin-kit-ai/integrations-catalog
  • plugin-kit-ai/integrations-discovery

These drafts are intentionally close to the current integrationctl domain model. They should extend the existing normalized result shape, not invent a second unrelated response model.

Managed lifecycle list

{
  "format": "plugin-kit-ai/integrations-report",
  "schema_version": 1,
  "report_kind": "managed_list",
  "requested_targets": [],
  "warning_count": 0,
  "warnings": [],
  "summary": "1 managed integration(s) in state.",
  "managed_entries": [
    {
      "managed_entry_key": "project:/repo:context7",
      "integration_id": "context7",
      "requested_source_ref": {
        "kind": "github_repo_path",
        "value": "github:777genius/universal-plugins-for-ai-agents//plugins/context7"
      },
      "resolved_source_ref": {
        "kind": "git_commit",
        "value": "https://github.com/777genius/universal-plugins-for-ai-agents@abc123"
      },
      "resolved_version": "0.1.0",
      "workspace_root": "/repo",
      "policy": {
        "scope": "project",
        "auto_update": true,
        "adopt_new_targets": "manual"
      },
      "targets": [
        {
          "target_id": "claude",
          "delivery_kind": "claude-marketplace-plugin",
          "capability_surface": ["mcp"],
          "state": "installed",
          "activation_state": "reload_pending",
          "source_access_state": "ok"
        }
      ]
    }
  ]
}

Universal catalog

{
  "format": "plugin-kit-ai/integrations-catalog",
  "schema_version": 1,
  "requested_targets": ["claude", "codex"],
  "warning_count": 0,
  "warnings": [],
  "source": {
    "kind": "bundled_snapshot",
    "fetched_at": "2026-04-18T12:00:00Z",
    "revision": "abc123",
    "stale": false
  },
  "entries": [
    {
      "entry_kind": "universal_catalog",
      "integration_id": "context7",
      "display_name": "Context7",
      "description": "Shared MCP plugin for documentation lookup.",
      "authored_targets": ["claude", "codex-package"],
      "manageable_targets": ["claude", "codex-package"],
      "primary_action_targets": ["claude", "codex-package"],
      "available_app_targets": ["claude", "codex"],
      "keywords": ["mcp", "docs"],
      "category": null,
      "homepage_url": "https://context7.com",
      "repository_url": "https://github.com/upstash/context7",
      "readme_url": "https://raw.githubusercontent.com/777genius/universal-plugins-for-ai-agents/main/plugins/context7/plugin/README.md",
      "version": "0.1.0",
      "effective_target_metadata": {
        "codex": {
          "homepage_url": "https://context7.com",
          "repository_url": "https://github.com/upstash/context7",
          "keywords": ["mcp", "docs"]
        }
      },
      "supported_scopes_by_target": {
        "claude": ["user", "project"],
        "codex": ["user", "project"]
      },
      "capabilities": ["mcp"],
      "source_ref": "github:777genius/universal-plugins-for-ai-agents//plugins/context7",
      "catalog_revision": "abc123",
      "generated_by_plugin_kit_version": "0.0.0"
    }
  ]
}

Native discovery

{
  "format": "plugin-kit-ai/integrations-discovery",
  "schema_version": 1,
  "requested_targets": ["claude", "codex"],
  "requested_workspace_root": "/repo",
  "warning_count": 0,
  "warnings": [],
  "entries": [
    {
      "entry_kind": "native_external_installed",
      "native_target": "claude",
      "native_plugin_id": "context7@claude-plugins-official",
      "display_name": "Context7",
      "description": "Installed from Claude marketplace.",
      "installed_scopes": ["user"],
      "detected_source": "claude_marketplace",
      "manageability": "display_only",
      "matched_integration_id": "context7",
      "match_confidence": "exact",
      "match_basis": "same_marketplace_identity",
      "identity_evidence": [
        "native_plugin_ref=context7@official-marketplace",
        "marketplace_name=official-marketplace"
      ],
      "observed_state": "observed_active",
      "activation_hint": "none"
    }
  ]
}

Mutating lifecycle result

{
  "format": "plugin-kit-ai/integrations-result",
  "schema_version": 1,
  "requested_integration_id": "context7",
  "requested_targets": ["claude", "codex"],
  "requested_scope": "project",
  "requested_workspace_root": "/repo",
  "ok": false,
  "warning_count": 0,
  "warnings": [],
  "outcome": "partial_success",
  "report": {
    "operation_id": "add-context7-...",
    "summary": "Managed targets processed for integration \"context7\".",
    "integration_id": "context7",
    "targets": [
      {
        "target_id": "claude",
        "action_class": "install_target",
        "state": "installed",
        "activation_state": "reload_pending",
        "environment_restrictions": ["reload_required"],
        "manual_steps": ["reload Claude plugins"]
      },
      {
        "target_id": "codex",
        "action_class": "install_target",
        "state": "activation_pending",
        "activation_state": "restart_pending",
        "environment_restrictions": ["native_activation_required", "new_thread_required"],
        "manual_steps": ["restart Codex", "open a new thread"]
      }
    ]
  }
}

Entry Derivation and Conflict Resolution

This is the most important renderer rule-set in the whole integration.

The app must derive the plugin list deterministically from four backend surfaces:

  • catalog
  • discover
  • list
  • doctor

It must not invent entries or merge classes by guesswork.

Backend surface ownership

Each backend surface owns a different truth:

  • catalog
    • universal storefront truth
    • metadata for universal plugins
    • target support projection
    • provider-facing installability projection
  • list
    • managed universal installed truth
    • policy scope
    • workspace root
    • resolved source
    • installed targets
  • doctor
    • managed universal health augmentation
    • degraded/auth/activation attention
    • target-level restrictions and manual steps
  • discover
    • native external installed truth
    • observed scopes
    • detected source
    • explicit manageability
    • optional relation hints to universal entries

Entry derivation algorithm

Recommended deterministic algorithm:

  1. Load list and build a managed map keyed by integration_id.
  2. Overlay doctor onto that managed map by integration_id + target_id.
  3. Load catalog and build a universal catalog map keyed by integration_id.
  4. For every managed entry:
    • create one universal_installed entry
    • enrich it from matching catalog metadata when available
    • keep lifecycle-owned fields from list/doctor
  5. For every catalog entry without a managed match:
    • create one universal_available entry
  6. For every discovery entry:
    • create one native_external_installed entry
    • keep it separate even if it matches a universal integration
  7. Attach relation metadata between native_external_installed and universal entries only as advisory linkage, never as a merge.
  8. Sort using the installed-first ranking rules already defined in this plan.

Non-negotiable derivation invariants

  • doctor may augment managed entries, but must never create standalone entries
  • catalog may create only universal entries
  • discover may create only native external entries
  • catalog must never mark an entry as installed
  • discover must never mark an entry as managed
  • advisory matching must never change entry_kind
  • if two surfaces disagree, the surface that owns that truth wins

Field precedence rules

For universal_installed entries:

  • identity:
    • from list
  • installed state:
    • doctor if present
    • otherwise list
  • lifecycle actions:
    • from lifecycle capabilities and current runtime support
  • scope and workspace root:
    • from list
  • display metadata:
    • catalog first
    • lifecycle fallback only when catalog is missing

For universal_available entries:

  • identity and metadata:
    • from catalog
  • supported targets and scopes:
    • from catalog
  • installed state:
    • none

For native_external_installed entries:

  • identity:
    • from discover
  • observed state and scopes:
    • from discover
  • manageability:
    • from discover
  • relation to universal entries:
    • advisory only

Conflict resolution rules

If catalog says a universal plugin exists, but list has no managed installation:

  • render universal_available

If list has a managed installation, but catalog entry is missing because the catalog snapshot is stale or incomplete:

  • still render universal_installed
  • mark catalog metadata as unavailable
  • do not hide the installed entry

If discover finds a native external install that strongly matches a universal plugin:

  • show both entries
  • add relation hints such as Also available as universal plugin
  • do not collapse them into one card

If doctor returns a degraded target while list shows the same target as installed:

  • doctor wins for health/status presentation
  • list remains the source of integration ownership and policy context

Renderer field ownership

Recommended renderer ownership matrix:

  • entry_kind
    • derived by the app from surface class, never from heuristics
  • integration_id
    • catalog or list
    • never guessed from discovery display name alone
  • display_name
    • catalog for universal entries
    • discover for native external entries
  • description
    • catalog for universal entries
    • discover for native external entries
  • supported_targets
    • catalog
  • manageable_targets
    • catalog
  • primary_action_targets
    • catalog
  • installed_targets
    • list, augmented by doctor
  • health_state
    • doctor
  • observed_scopes
    • discover
  • manageability
    • discover
  • resolved_source_ref
    • list
  • workspace_root
    • list

Why this section matters

Without these derivation rules, the app will almost certainly drift into one of the following failure modes:

  • silently merging universal and native external installs
  • showing available when something is already installed
  • losing managed entries when the catalog snapshot is stale
  • inventing manageability for discovered native installs
  • letting catalog or discover override lifecycle truth they do not own

Worked Examples

These examples are intentionally concrete. They should be used as golden fixtures for both backend contracts and app normalization.

Example 1 - managed universal install with overlap suppression

Situation:

  • catalog contains universal context7
  • list contains managed installation context7
  • doctor says target is healthy
  • native surfaces also visibly contain the installed plugin because managed lifecycle already materialized it there

Expected result:

  • render one universal_installed entry for context7
  • do not render a second native_external_installed copy for the same managed install
  • health comes from doctor
  • metadata comes from catalog

Why:

  • discovery overlap suppression should remove the duplicate native observation

Example 2 - native Claude marketplace install with no managed lifecycle record

Situation:

  • catalog contains universal context7
  • list does not contain context7
  • discover finds a Claude-native install from a marketplace source
  • backend can only establish an advisory relation to universal context7

Expected result:

  • render one native_external_installed entry
  • optionally render the universal context7 catalog card separately if not installed through plugin-kit-ai
  • show relation hint such as Also available as universal plugin
  • do not collapse them into one entry
  • do not show destructive managed actions unless discovery explicitly says they are safe

Example 3 - stale or partial catalog snapshot

Situation:

  • list contains managed demo-plugin
  • doctor contains managed demo-plugin
  • catalog snapshot does not contain demo-plugin

Expected result:

  • still render demo-plugin as universal_installed
  • preserve lifecycle actions and managed state
  • degrade catalog-derived metadata gracefully
  • do not hide the entry just because storefront metadata is missing

Why:

  • managed installation truth belongs to list
  • catalog absence is not permission to erase installed truth

Example 4 - Codex prepared but not activated yet

Situation:

  • discover finds:
    • marketplace entry
    • plugin root
    • no cache bundle yet
  • backend classifies the Codex plugin as prepared but not active

Expected result:

  • render native_external_installed
  • show observed state prepared
  • do not claim it is fully active
  • if the backend has an activation hint, show it
  • do not pretend this equals a healthy managed universal install

Example 5 - degraded Codex native install

Situation:

  • cache bundle exists
  • but expected marketplace source or plugin root is missing or drifted

Expected result:

  • render native_external_installed
  • show observed state degraded
  • do not auto-remove or auto-adopt
  • keep relation to universal entries advisory only

Example 6 - heuristic match only

Situation:

  • discover finds native plugin display name Notes
  • catalog contains more than one plausible universal candidate with similar wording

Expected result:

  • either no match or heuristic
  • no merge
  • no stronger action unlock
  • no exact badge or stronger “same plugin” copy

Command Semantics Matrix

The app integration should treat command classes differently.

Surface Command class Side effects App expectation
catalog read-only none safe to retry, safe to cache
discover read-only none safe to retry, safe to cache
list read-only none safe to retry, source of managed ownership
doctor read-only none may report issues and still return structured JSON
add mutating yes must pass explicit apply mode and explicit context
update mutating yes must pass explicit apply mode
remove mutating yes must pass explicit apply mode
repair mutating yes must pass explicit apply mode

Read-only command rules

  • read-only commands must never mutate native state
  • read-only commands may return structured issues without that meaning a transport failure
  • the app may cache read-only payloads
  • the app may retry read-only commands automatically

Mutating command rules

  • mutating commands must never rely on CLI defaults for apply behavior
  • mutating commands must always receive explicit context:
    • target selection where relevant
    • scope where relevant
    • workspace root where relevant
  • post-mutation refresh in the app must use the origin operation context, not global last-view state

Payload vs process-failure rule

For app integration there are two different failure classes:

  • transport/process failure
    • no valid contract payload
    • process spawn failure
    • timeout
    • malformed JSON
  • structured domain failure
    • valid contract payload exists
    • payload says ok=false, outcome=failed, issue_count>0, or equivalent

The app must distinguish those two classes clearly.

Why this section matters

Without a command semantics matrix, the app will eventually do one or more of these:

  • treat every non-zero exit as an unstructured crash
  • lose useful report payloads on domain failures
  • accidentally rely on --dry-run=true
  • retry mutating commands as if they were read-only

UI and Entry Model in claude_team

Normalized entry kinds

  • universal_available
  • universal_installed
  • native_external_installed

Ranking order

  1. installed universal
  2. installed native external
  3. available universal

Card labels

Universal:

  • Universal
  • Anthropic + Codex
  • Anthropic only
  • Codex only

Native external:

  • Installed - Anthropic only
  • Installed - Codex only

Detail requirements

Universal detail must show:

  • keywords or tags
  • category only when curated metadata exists
  • target support
  • scope support
  • README/detail content
  • lifecycle actions where supported

Native external detail must show:

  • native target
  • detected source
  • observed scopes
  • manageability
  • relation to universal plugin when matched

Empty and degraded states

If catalog fails but discover works:

  • show native external entries
  • show a warning for universal catalog unavailability

If discover fails but catalog works:

  • show universal entries
  • do not claim “no installed plugins”
  • show a warning that native discovery is unavailable

If backend version is unsupported:

  • keep read-only view if safe
  • disable lifecycle actions
  • show explicit compatibility message

App Integration Mode

claude_team should integrate with plugin-kit-ai as a bundled CLI binary with versioned JSON I/O.

Not as:

  • parsed human terminal output
  • direct repo scraping
  • an embedded second UI
  • a Go library linked into Electron

Why:

  • Electron already knows how to run bundled binaries
  • JSON contracts are versionable and testable
  • rollout and rollback stay simple
  • the same backend surface can be exercised in dev and packaged builds

plugin-kit-ai Changes Required

Must-have for phase 0

  1. integrations --format json around the current normalized lifecycle model
    🎯 10 🛡️ 10 🧠 4
    Approximate change size: 150-300 lines

  2. integration-level fields in managed lifecycle JSON
    Needed because current Report.Targets do not identify which integration a target belongs to.
    🎯 10 🛡️ 10 🧠 5
    Approximate change size: 120-240 lines

  3. integrations catalog --format json
    🎯 10 🛡️ 9 🧠 5
    Approximate change size: 180-350 lines

  4. integrations discover --format json
    🎯 10 🛡️ 10 🧠 6
    Approximate change size: 220-450 lines

  5. --workspace-root
    🎯 9 🛡️ 10 🧠 4
    Approximate change size: 80-180 lines

  6. capability and scope metadata in catalog/discovery
    🎯 9 🛡️ 9 🧠 4
    Approximate change size: 80-160 lines

  7. stable detail path or detail endpoint
    🎯 8 🛡️ 8 🧠 4
    Approximate change size: 60-140 lines

  8. discovery trust/manageability metadata
    🎯 8 🛡️ 9 🧠 5
    Approximate change size: 80-180 lines

  9. provenance metadata
    🎯 8 🛡️ 9 🧠 4
    Approximate change size: 60-140 lines

Explicitly not required for the first app rollout

  • integrations sync
  • workspace-lock driven desired-state workflows
  • enable / disable UI
  • native convenience uninstall
  • adopt

Reason:

  • they are either repo-lock oriented, lower-value than core lifecycle, or too risky before discover is proven out

claude_team Changes Required

Main-process additions

  • PluginKitBinaryResolver
  • PluginKitService
  • PluginKitCatalogService
  • PluginKitDiscoveryService
  • PluginKitLifecycleService

Current app touchpoints this migration must replace or bypass

Current plugin flow in claude_team is still Claude-marketplace-shaped:

  • src/main/services/extensions/catalog/PluginCatalogService.ts
  • src/main/services/extensions/state/PluginInstallationStateService.ts
  • src/main/services/extensions/install/PluginInstallService.ts
  • src/main/services/extensions/ExtensionFacadeService.ts
  • src/shared/types/extensions/plugin.ts
  • src/renderer/store/slices/extensionsSlice.ts
  • src/renderer/components/extensions/plugins/*

Recommended rule:

  • do not keep stretching the current EnrichedPlugin model until it represents two different product classes badly
  • introduce a new normalized plugin-entry layer for the plugin-kit-backed flow
  • keep the legacy Claude-only model behind the feature flag until rollout is complete

Required app-side model split

Current EnrichedPlugin is shaped around one catalog plus installed counts:

  • one canonical pluginId
  • one marketplace-oriented metadata shape
  • one merged installed-state view

That is not a safe long-term shape for mixed:

  • universal_catalog
  • universal_installed
  • native_external_installed

Recommended rule:

  • phase 1 should add a new normalized entry model for the plugin page
  • old EnrichedPlugin can remain only inside the legacy backend path
  • the new renderer/store layer should be built around explicit entry_kind, not around legacy marketplace assumptions

Responsibilities

PluginKitBinaryResolver

  • resolve bundled binary
  • resolve dev binary
  • report version

PluginKitService

  • execute commands
  • validate format and schema_version
  • apply timeouts
  • normalize errors
  • redact diagnostics

PluginKitCatalogService

  • call catalog
  • cache normalized results

PluginKitDiscoveryService

  • call discover
  • normalize native external entries

PluginKitLifecycleService

  • call add/update/remove/repair/list/doctor
  • normalize target-level results

Store and cache rules

  • only one mutating operation per entryId + scope + projectPath
  • catalog and discover may refresh in parallel
  • stale responses must never overwrite newer state
  • post-mutation refresh must use the origin operation context

Feature flag

Recommended app flag:

  • extensions.plugins.backend = legacy | plugin-kit

Rollout Phases

Phase 0 - plugin-kit-ai contracts first

🎯 10 🛡️ 10 🧠 5
Approximate change size: 400-900 lines

Ship:

  • JSON envelopes
  • catalog
  • discover
  • --workspace-root
  • schema docs
  • source/provenance metadata

Acceptance:

  • contracts are versioned and testable
  • command classes are clearly read-only vs mutating
  • managed lifecycle JSON includes integration-level context
  • views are internally consistent across catalog, discover, list, and doctor

Phase 1 - read-only app integration

🎯 9 🛡️ 9 🧠 5
Approximate change size: 300-650 lines

Ship:

  • bundled binary
  • catalog rendering
  • discovery rendering
  • mixed-entry normalization
  • ranking and labels
  • feature-flagged backend switch

Acceptance:

  • native external entries render truthfully
  • universal catalog renders truthfully
  • no misleading install button on native external entries
  • page remains useful when catalog or discover partially fail
  • warm-load performance remains acceptable
  • plugin-kit-backed renderer state uses the new normalized entry model instead of overloading legacy EnrichedPlugin

Phase 2 - universal lifecycle actions

🎯 9 🛡️ 9 🧠 6
Approximate change size: 300-700 lines

Ship:

  • install
  • update
  • remove
  • repair
  • target-level result rendering

Acceptance:

  • direct Claude path green
  • multimodel Anthropic + Codex path green
  • user/project installs stable
  • partial target results rendered truthfully
  • safe retries do not corrupt state

Phase 3 - optional native convenience flows

🎯 7 🛡️ 8 🧠 7
Approximate change size: 150-400 lines

Optional:

  • native uninstall where backend declares safe
  • adopt where backend declares safe

Acceptance:

  • no ambiguity between universal managed and native external state

PR 1 - JSON envelopes for existing lifecycle commands

Ship:

  • integrations {list|doctor|add|update|remove|repair} --format json
  • schema identifiers
  • contract docs

Must not do:

  • change lifecycle semantics
  • invent catalog or discover early

PR 2 - managed lifecycle integration context

Ship:

  • integration_id
  • source refs
  • policy scope
  • workspace root
  • target grouping under managed entries

Must not do:

  • flatten per-target state into one integration status

PR 3 - explicit workspace root control

Ship:

  • --workspace-root
  • project-sensitive commands stop depending on implicit cwd

Must not do:

  • silently rewrite missing workspace root into current cwd for project commands

PR 4 - universal catalog

Ship:

  • normalized universal catalog JSON
  • bundled snapshot story
  • freshness metadata
  • README/detail default rule

Must not do:

  • block on category, icon, or popularity

PR 5 - native discovery

Ship:

  • native external discovery JSON
  • observed state
  • activation hints
  • manageability and match metadata

Must not do:

  • auto-merge native and universal entries
  • expose destructive actions without explicit backend manageability

PR 6 - claude_team read-only integration

Ship:

  • bundled binary
  • read-only mixed-entry rendering
  • ranking
  • degraded-state handling

Must not do:

  • wire mutating actions before backend contracts are pinned

PR Exit Criteria

These checks are intentionally strict. A PR is not “basically done” if these are still fuzzy.

Backend contract PRs

Required:

  • schema id is documented
  • schema version is documented
  • arrays are never null
  • one golden fixture exists
  • one failure fixture exists
  • one compatibility test exists

Backend discovery PRs

Required:

  • at least one Claude discovery fixture
  • at least one Codex discovery fixture
  • explicit observed-state coverage
  • explicit manageability coverage
  • no destructive side effects in read-only commands

App read-only integration PRs

Required:

  • mixed-entry rendering works with only catalog
  • mixed-entry rendering works with only discover
  • degraded-state copy is truthful
  • unsupported-version handling is visible and safe

App lifecycle PRs

Required:

  • explicit dry-run protection
  • target-level partial success rendering
  • stale-response protection
  • safe retry behavior

Risks and Lowest-Confidence Areas

1. Manifest model split between lifecycle and catalog

🎯 8 🛡️ 8 🧠 6

Why this is hard:

  • the richer authored model and the narrower lifecycle model do not preserve the same metadata
  • catalog wants richer storefront fields
  • lifecycle wants compact normalized install truth

Best resolution:

  • keep lifecycle and catalog as separate backend surfaces
  • allow catalog generation to read the richer authored model
  • normalize both into explicit JSON contracts

2. Codex native external discovery fidelity

🎯 7 🛡️ 8 🧠 7

Why this is hard:

  • current Codex inspect logic distinguishes installed, activation pending, disabled, and degraded by combining multiple native surfaces
  • that is richer than installed yes/no, but still not a full external discovery surface

Best resolution:

  • backend-owned discovery
  • explicit observed_state
  • explicit activation_hint
  • never collapse prepared Codex state into fully active installed state

3. Workspace-root and repo-root coupling

🎯 8 🛡️ 9 🧠 6

Why this is hard:

  • current service construction still derives important paths from os.Getwd()
  • workspace-lock paths are repo-root oriented
  • packaged Electron app must not inherit the wrong working directory semantics

Best resolution:

  • add explicit --workspace-root
  • reject missing workspace root for project-sensitive commands
  • keep sync and workspace-lock flows out of the first app rollout

4. Source resolution cost for lifecycle vs catalog

🎯 8 🛡️ 9 🧠 5

Why this is hard:

  • lifecycle source resolution may legitimately clone remote sources
  • storefront catalog must stay fast and bounded

Best resolution:

  • keep catalog generation batch-oriented
  • keep lifecycle resolution source-oriented
  • never build the app catalog by doing one source clone per entry at runtime

5. Popularity parity

🎯 6 🛡️ 9 🧠 5

Best resolution:

  • make popularity optional
  • add it later only from a first-class universal metric

6. Safe native convenience actions

🎯 6 🛡️ 8 🧠 7

Best resolution:

  • delay to phase 3
  • require backend-declared manageability

7. App-facing target subset confusion

🎯 8 🛡️ 9 🧠 5

Why this is hard:

  • plugin-kit-ai knows more targets than the app should expose as first-class plugin lanes
  • if the app shows every authored target equally, users will see support the app cannot actually manage yet

Best resolution:

  • preserve full authored support in backend catalog
  • use only claude and codex-package for primary actionability in claude_team
  • keep broader support as secondary metadata only

8. Target projection drift between authored, manageable, and app-primary targets

🎯 8 🛡️ 9 🧠 6

Why this is hard:

  • these three target sets are related but not identical
  • if they collapse into one field, the UI will eventually lie about support or actionability

Best resolution:

  • preserve separate target fields in catalog
  • test them with golden fixtures
  • never let renderer heuristics rebuild one layer from another

E2E and Contract Testing

plugin-kit-ai

Must add:

  • JSON contract tests for catalog, discover, list, doctor
  • JSON contract tests for add/update/remove/repair
  • temp-home tests
  • temp-project tests
  • target adapter tests for Claude and Codex
  • golden fixture tests against at least one real universal plugin

claude_team

Must add:

  • PluginKitService parsing tests
  • mixed-entry normalization tests
  • ranking tests
  • badge tests
  • manual-step rendering tests
  • project-context tests

Failure checks

  • bundled binary missing
  • bundled binary wrong architecture
  • schema mismatch
  • project install without workspace root
  • unsupported scope for selected target
  • partial target success
  • stale catalog with failed refresh
  • repeated idempotent retries

No-Go Conditions

Do not enable by default if any of these remain true:

  • universal and native external entries still auto-merge by display name
  • lifecycle still depends on parsing prose
  • local scope is silently remapped
  • partial target failures are flattened into one misleading status
  • native external entries expose destructive actions without backend-declared manageability

1. Should the app call GitHub directly for universal plugins?

Recommended default: No.

Use backend catalog.

2. Should native external entries merge into universal entries when matching looks obvious?

Recommended default: No.

Keep separate and use soft relations only.

3. Should local scope be shown for universal installs in phase 1?

Recommended default: No.

Only show scopes explicitly supported by backend target metadata.

4. Should popularity sorting block the migration?

Recommended default: No.

Hide or degrade it if there is no stable universal metric.

5. Should adopt block phase 1?

Recommended default: No.

Phase 1 needs discover, not adopt.

6. Should native uninstall for discovered entries ship immediately?

Recommended default: No.

Only where backend declares safe manageability.

7. Should category or icon metadata block phase 1?

Recommended default: No.

Ship phase 1 with honest minimum storefront metadata, then add optional curation metadata later.

8. Should discover be implemented by reusing current per-target Inspect directly?

Recommended default: No.

Use Inspect as one building block, but build a real native discovery scanner above it because external discovery and managed inspection are not the same problem.

9. Should catalog be generated from the current integrationctl manifest loader only?

Recommended default: No.

Prefer the richer authored-model path such as pluginmanifest.Inspect/publication/targetcontracts, or enrich the lifecycle loader first.

10. Should the claude_team plugin page expose every target known to plugin-kit-ai as a first-class action lane?

Recommended default: No.

Use the app-relevant target subset for primary actions:

  • claude
  • codex-package

Keep broader authored support as optional secondary detail, not as primary actionability.

11. Should the catalog expose only one target field?

Recommended default: No.

Keep separate fields for:

  • authored target support
  • backend-manageable lifecycle support
  • app-primary action targets

Phase-1 Conservative Defaults

These defaults are intentional. They should not be treated as missing polish or as accidental gaps.

Risky seam Phase-1 default Why
local scope for universal plugins hide it backend does not honestly support it yet
native external uninstall do not expose destructive authority is not proven yet
adopt do not expose visibility is needed before ownership conversion
popularity sorting optional, not blocking migration should not depend on a metric the backend does not yet own
target-specific metadata enhance detail only shared storefront truth should stay stable
heuristic universal matching advisory only avoids false ownership merge
ambiguous Codex observed state degrade to observed_degraded safer than over-claiming active state
stale catalog while managed installs exist keep managed entry visible list wins for managed existence
stale discover while catalog works keep catalog visible and warn do not claim “no installed plugins”
grouped lifecycle keys missing treat as incompatible payload do not reconstruct entry groups in app

Final Recommendation

Build this integration in three layers:

  1. make plugin-kit-ai expose stable JSON contracts
  2. make plugin-kit-ai the normalized backend for universal catalog, discovery, and lifecycle
  3. make claude_team a frontend over those contracts

This is the most reliable path because it:

  • keeps the current app UX
  • reuses the existing lifecycle engine in plugin-kit-ai
  • avoids false parity and false merging
  • keeps rollout reversible