This repo (hrishikeshmane/t3code-kiro) is a fork of pingdotgg/t3code that patches a Kiro ACP provider on top of upstream's ACP infrastructure.
Upstream PR #1355 landed the shared ACP/Cursor foundation. That infrastructure is now the source of truth — our fork only carries the Kiro-specific delta plus small additive extensions.
origin → https://github.com/hrishikeshmane/t3code.git (fork)
upstream → https://github.com/pingdotgg/t3code.git (source of truth)
Branches:
| Branch | Purpose | Status |
|---|---|---|
main |
Mirror of upstream/main. No Kiro code here. |
Sync-only — never commit direct |
kiro-acp |
Single-commit patch branch carrying the entire Kiro layer on top of main | Active — develop here |
main-pre-sync-YYYYMMDD |
Safety snapshot of main before a destructive sync |
Long-lived |
kiro-acp-rebase |
Historical pre-upstream-merge reference (Effect beta.43) | Frozen — deprecated |
backup-YYYYMMDD |
Transient safety snapshots | Transient |
Every change to the Kiro layer lives as one squashed commit on kiro-acp. This makes the upstream-sync workflow mechanical:
mainfast-forwards toupstream/main.kiro-acprebases onto the newmain. Since it's one commit, conflicts surface in one pass.- Open a PR from
kiro-acp→mainand merge (or leave open as a tracking PR — we squash-merge via the PR UI when we want to ship a release from main; otherwisekiro-acpis the deployable branch).
Always work on kiro-acp. Never commit to main — main exists only to mirror upstream.
git checkout kiro-acp
# ... make changes, write tests, run bun typecheck && vitest ...
git add -A
git commit --amend --no-edit # keep the Kiro patch as ONE commit
git push --force-with-lease origin kiro-acpAmend-and-force-push preserves the single-commit invariant. If you want to preserve a logical history locally while iterating, use a feature branch off kiro-acp, squash-merge back, and force-push.
When pingdotgg/t3code ships new changes on main:
# 1. Snapshot main before touching anything (the safety net)
TODAY=$(date +%Y%m%d)
git fetch origin upstream
git branch main-pre-sync-$TODAY origin/main
git push origin main-pre-sync-$TODAY
# 2. Fast-forward main to upstream/main
git checkout main
git merge --ff-only upstream/main
git push origin main
# 3. Rebase kiro-acp onto the new main
git checkout kiro-acp
git rebase main
# Resolve conflicts — see "Likely Conflict Zones". Stay as ONE commit.
git push --force-with-lease origin kiro-acp
# 4. Verify
bun install
bun typecheck
cd apps/server && bun run vitest run src/provider/Layers/KiroAdapter
cd ../../packages/shared && bun test src/model.test.ts
# Manual smoke: bun run dev from repo root — walk the verification checklist belowNever merge main into kiro-acp. Always rebase. A merge creates two parents, which breaks the single-commit invariant and makes the next rebase a mess.
Two options depending on scope:
- Point release from
kiro-acp: just run the build offkiro-acp. This is the default —kiro-acpis deployable. - Shipping Kiro features to
main: squash-merge the PRkiro-acp→mainvia GitHub UI. This collapses the Kiro layer intomainas one commit, after whichkiro-acpresets to zero-delta and new Kiro work starts fresh.
If kiro-acp diverges too far to rebase cleanly:
TODAY=$(date +%Y%m%d)
git branch kiro-acp-pre-reset-$TODAY kiro-acp
git push origin kiro-acp-pre-reset-$TODAY
git checkout kiro-acp
git reset --hard origin/main
git checkout origin/kiro-acp-pre-reset-$TODAY -- \
'apps/server/src/provider/Layers/KiroAdapter.ts' \
'apps/server/src/provider/Layers/KiroProvider.ts' \
'apps/server/src/provider/Services/KiroAdapter.ts' \
'apps/server/src/provider/Services/KiroProvider.ts' \
'apps/server/src/provider/Layers/KiroAdapter.integration.test.ts' \
'apps/server/src/provider/Layers/KiroAdapter.parsing.test.ts' \
'apps/server/scripts/kiro-mock-agent.ts' \
'PATCH.md' \
'docs/KIRO.md'
# Manually re-apply integration-point edits (next section)
git add -A && git commit -m "feat(kiro): rebuild Kiro patch on upstream main"
git push --force-with-lease origin kiro-acpKiro as a first-class ACP provider, layered on top of upstream's shared ACP infrastructure.
- Kiro provider in the model selector (purple owl icon)
- Agent picker in the composer (TraitsPicker) — rendered by the generic descriptor pipeline from each model's
optionDescriptors[{id: "agent"}] - Agent discovery via
kiro-cli agent list, cached at~/.t3/caches/kiro.json - Full ACP lifecycle: initialize, session/new, session/prompt, session/cancel, streaming session/update
- In-session model switching via
session/set_modelRPC (no respawn) - In-session agent switching via
session/set_modeRPC (no respawn) - Plan sidebar populated from Kiro — both native ACP
planupdates ANDtodo_listtool-call synthesis (create + complete) flow throughturn.plan.updated. Derives aninProgressmarker for the current task since Kiro'stodo_listAPI has no explicit in-progress signal. Seedocs/KIRO.md→ "Plan Sidebar Wiring" for state-model details. - Dynamic slash commands from
_kiro.dev/commands/availablenotifications - Context window usage from
_kiro.dev/metadatanotifications - Agent selection is persisted per-thread in the composer draft store
- Subagent crews fan out into collapsible Work-log groups:
_kiro.dev/subagent/list_updateroster transitions becometask.started/task.completedenvelopes keyed by the crew's ACP sessionId; per-tool activity inside each crew is pulsed astask.progressrows (one per distincttoolCallId) with labels formatted as"{title}: {detail}"(e.g.Read file: src/foo.ts,Ran command: bun test). Subagent ContentDelta / AssistantItem / PlanUpdated / ModeChanged events are dropped from the main thread — matching Claude Code's SDK behavior of hiding subagent internals behind task notifications.
apps/server/src/provider/Services/KiroAdapter.ts
apps/server/src/provider/Services/KiroProvider.ts
apps/server/src/provider/Layers/KiroAdapter.ts (~880 lines)
apps/server/src/provider/Layers/KiroProvider.ts (~375 lines)
apps/server/src/provider/Layers/KiroAdapter.integration.test.ts
apps/server/src/provider/Layers/KiroAdapter.parsing.test.ts
apps/server/src/provider/acp/KiroAcpExtension.ts (applyTodoToolCall — Plan sidebar synthesis)
apps/server/src/provider/acp/KiroAcpExtension.test.ts
apps/server/scripts/kiro-mock-agent.ts
Every shared-file edit is a pure addition (new case in a union, new entry in an array, new layer in a chain). None replace upstream behavior.
Contracts (packages/contracts/src/):
| File | Change |
|---|---|
orchestration.ts |
Add "kiro" to ProviderKind union; add KiroModelSelection |
model.ts |
Add KiroModelOptions { agent? }; default model "auto"; aliases |
settings.ts |
Add Kiro provider settings (enabled, binaryPath, customModels) |
server.ts |
(unchanged if upstream already has ServerProviderAgent) |
Shared (packages/shared/src/model.ts):
- Add Kiro default/alias entries
- Add
normalizeKiroModelOptionsWithCapabilities+case "kiro"innormalizeProviderModelOptionsWithCapabilities(preservesagentthrough dispatch — missing this silently strips agent selection)
Server (apps/server/src/):
| File | Change |
|---|---|
server.ts |
Wire KiroProviderLive into RuntimeServicesLive |
serverSettings.ts |
Include kiro settings |
provider/Layers/ProviderRegistry.ts |
Register KiroProvider |
provider/Layers/ProviderAdapterRegistry.ts |
Register KiroAdapter |
provider/providerStatusCache.ts |
Add "kiro" to PROVIDER_CACHE_IDS |
provider/makeManagedServerProvider.ts |
Add patchSnapshot (additive, does not replace enrichSnapshot) |
provider/acp/AcpSessionRuntime.ts |
authMethodId optional (Kiro uses OIDC); thread sessionId through AssistantSegmentState + re-emitted ToolCallUpdated so subagent events can be filtered out of the main thread |
provider/acp/AcpRuntimeModel.ts |
AcpParsedSessionEvent union gains sessionId on every variant so downstream consumers can tell main-session vs subagent events apart |
git/Services/TextGeneration.ts |
Handle kiro provider kind |
Web (apps/web/src/):
| File | Change |
|---|---|
components/Icons.tsx |
Add KiroIcon SVG |
components/chat/composerProviderRegistry.tsx |
Register kiro provider for composer |
components/chat/ChatComposer.tsx |
Include kiro in model-selector options |
components/chat/ProviderModelPicker.browser.tsx |
Kiro rendering |
components/chat/TraitsPicker.tsx |
Agent picker: provider === "opencode" || provider === "kiro" |
components/chat/providerIconUtils.ts |
Kiro icon resolution |
components/settings/SettingsPanels.tsx |
Kiro settings panel |
components/KeybindingsToast.browser.tsx |
Kiro in fixtures |
composerDraftStore.ts |
Three hardcoded provider arrays — see "Hidden Traps" |
modelSelection.ts |
Kiro custom model options |
session-logic.ts |
Kiro case in session routing |
Hidden Traps — Read Before Adding Another Provider
As of the 2026-04-24 sync, modelSelection.options is ReadonlyArray<ProviderOptionSelection> where each selection is { id, value }. Providers expose their supported options via ModelCapabilities.optionDescriptors on each ServerProviderModel. The per-provider normalizeXxxModelOptionsWithCapabilities helpers that used to exist in packages/shared/src/model.ts have been deleted — the generic descriptor pipeline in composerProviderState.tsx handles dispatch uniformly for every provider.
Kiro agent picker wiring: KiroProvider.ts injects a buildSelectOptionDescriptor({ id: "agent", label: "Agent", options: ... }) into every model's capabilities when kiro-cli agent list returns agents. No per-provider normalizer case is needed.
KiroAdapter reads agent selection via the generic helper:
import { getProviderOptionStringSelectionValue } from "@t3tools/shared/model";
const agent = getProviderOptionStringSelectionValue(modelSelection.options, "agent");Verify when adding a new provider feature:
rg 'buildSelectOptionDescriptor.*id: "<option-id>"' apps/server/src/provider/Layers/<Provider>Provider.tscomposerDraftStore.ts is the per-thread composer draft store. It has three hardcoded ProviderKind lists that every new provider must be added to. If you miss any one of them, the symptom is silent:
"I select the Kiro model but the UI reverts to Claude."
No error, no warning — just a silent no-op. Root cause: normalizeProviderKind filters unknown values to null, which makes normalizeModelSelection return null, which makes setModelSelection bail out.
The three lists (as of 2026-04-20):
normalizeProviderKind(value)— line ~533. Gatekeeper for all provider-kind normalization.legacyToModelSelectionByProvider— line ~780. Migrates legacy single-selection to per-provider map.setModelOptionsprovider loop — line ~2326. Iterates to clear stale options per provider.
Grep to audit:
rg '"codex"\s*,\s*"claudeAgent"\s*,\s*"cursor"\s*,\s*"opencode"' apps/web/src/composerDraftStore.tsEvery match must include "kiro" (or whatever new provider you are adding). Miss one → silent breakage.
Ideal fix (not yet done): export a PROVIDER_KINDS const readonly tuple from packages/contracts/src/orchestration.ts, derive ProviderKind as its element type, and import everywhere these lists are needed. This converts silent runtime no-ops into type errors at the next provider addition. Ticket-worthy follow-up.
Upstream treats authMethodId as required in AcpSessionRuntime. Kiro uses OIDC and returns empty authMethods from initialize, meaning the ACP spec says skip authenticate. The fork's patch makes authMethodId optional; session/new runs directly after initialize. Do not re-tighten this type.
Kiro (and other ACP agents) send _kiro.dev/* ext requests. If an adapter doesn't handle one, it must respond with JSON-RPC error -32601 ("method not found"). Returning {} as a success result is a spec violation that some agents tolerate silently and others deadlock on.
The fork pins registry=https://registry.npmjs.org/ in a committed .npmrc. If you see E401 errors during bun install or npm install -g, an internal CodeArtifact registry was re-injected. Restore the public registry line. For local tools (e.g. agent-browser), install with the flag: npm install -g <pkg> --registry=https://registry.npmjs.org/.
Upstream's makeManagedServerProvider exposes enrichSnapshot for static provider metadata. Kiro needs runtime-patched slash commands delivered mid-session from _kiro.dev/commands/available notifications. The fork adds a new patchSnapshot alongside enrichSnapshot rather than replacing it — do not consolidate these. They serve different lifetimes.
| File | Why it conflicts |
|---|---|
packages/contracts/src/orchestration.ts |
ProviderKind union + KiroModelSelection variant |
packages/contracts/src/model.ts |
kiro entries in DEFAULT_*/ALIASES records |
packages/contracts/src/settings.ts |
KiroSettings + KiroSettingsPatch + providers map entry |
apps/server/src/server.ts |
RuntimeServicesLive layer chain |
apps/server/src/provider/Layers/ProviderRegistry.ts |
kiro wired into createBuiltInProviderSources + KiroProviderLive merge |
apps/server/src/provider/Layers/ProviderAdapterRegistry.ts |
kiro wired into createBuiltInAdapterList |
apps/server/src/provider/builtInProviderCatalog.ts |
BUILT_IN_PROVIDER_ORDER + BuiltInAdapterMap extended with kiro |
apps/server/src/provider/providerStatusCache.ts |
PROVIDER_CACHE_IDS array includes "kiro" |
apps/server/src/provider/acp/AcpSessionRuntime.ts |
Optional authMethodId; sessionId plumbing for subagent filtering |
apps/server/src/provider/acp/AcpRuntimeModel.ts |
sessionId on AcpParsedSessionEvent variants |
apps/server/src/provider/makeManagedServerProvider.ts |
Added patchSnapshot + slashCommands merge across refreshes |
apps/server/src/git/Layers/RoutingTextGeneration.ts |
kiro routed through OpenCodeTextGenerationLive |
apps/web/src/composerDraftStore.ts |
Four provider-kind lists (normalizer + 3 loops) |
apps/web/src/modelSelection.ts |
kiro entry in getCustomModelOptionsByProvider Record |
apps/web/src/components/chat/composerProviderState.tsx |
TraitsRenderInput extended with open/onOpenChange for /agent |
apps/web/src/components/chat/ChatComposer.tsx |
providerHasAgentPicker replaced with descriptor check |
apps/web/src/components/chat/TraitsPicker.tsx |
controlled-open state support for /agent slash command |
apps/web/src/components/settings/SettingsPanels.tsx |
Provider panel registration |
After every sync, rebuild, or conflict resolution — run all of these:
rg '"kiro"' packages/contracts/src/orchestration.ts—"kiro"inProviderKindunionrg '"kiro"' apps/server/src/provider/providerStatusCache.ts— inPROVIDER_CACHE_IDSrg 'KiroProviderLive' apps/server/src/server.ts— wired inRuntimeServicesLiverg '"kiro"' apps/web/src/composerDraftStore.ts | wc -l— should be ≥ 4 (normalizer + 3 loops) 4a.rg 'buildSelectOptionDescriptor.*id: "agent"' apps/server/src/provider/Layers/KiroProvider.ts— agent descriptor wired when agents are discoveredbun install— lockfile resolves cleanlybun typecheck— 0 errorsbun fmt && bun lint— cleanbun run test— current baseline (post-2026-04-24 sync): 100 files / 933 passing, 1 file / 4 tests skippedbun run dev— launch, pair, enable Kiro in settings, select a Kiro model, open agent picker, verify agent list populates- Manual: type
/in composer → slash commands and MCP prompts populate
- Spawn:
kiro-cli acp --trust-all-tools [--agent <name>] - Auth: OIDC via
kiro-cli loginout-of-band — ACPauthenticateis never called (authMethods empty) mcpServers: []is required insession/new(omitting it can silently exit kiro-cli)- Streaming:
session/updatenotifications (noidfield) - Turn end: RPC response
{stopReason: "end_turn"}, not a notification - Discovery:
kiro-cli agent list→ parsed and cached at~/.t3/caches/kiro.json; injected into each model'soptionDescriptorsas an{id: "agent", type: "select"}descriptor (post-PR#2246 generic shape).
- Model switch:
session/set_modelRPC with{sessionId, modelId}→{}. Bypasses upstreamAcpSessionRuntime.setModel(which routes throughsession/set_config_option— Kiro rejects that with-32601). - Agent switch:
session/set_modeRPC with{sessionId, modeId}→{}. Same bypass rationale.session/set_modeis not exposed by effect-acp's agent SDK; the mock agents wire it viahandleExtRequest("session/set_mode", ...). - Do NOT pass
--modelat spawn. Passing--model <slug>makes Kiro silently ignore subsequentsession/set_modelRPCs — the RPC returns success but the active model never changes.--agentat spawn is safe. KiroSessionContexttracksactiveModeandactiveModel(bothundefinedat spawn). First turn fires the RPC to align Kiro's state with the user's selection; subsequent turns only fire on actual change.- Cross-family model switches (Claude → DeepSeek → Kimi) may surface AWS Bedrock
ValidationExceptionon the next prompt — Kiro's conversation-history replay is not portable across model families. We surface the error; a fix is upstream-Kiro's.
Fork runs in lockstep with upstream (currently Effect v4 beta.45+). If you see these legacy patterns while cherry-picking from kiro-acp-rebase, update before using:
| Pattern | Legacy | Current |
|---|---|---|
| Service tags | Context.Tag("key") |
Context.Service<Self, Shape>()("key") |
| Branded make | .makeUnsafe(value) |
.make(value) |
| Error handling | Effect.catchAll |
Effect.catch |
Bug: in-session model switch appeared to succeed but conversations stayed on the original model.
Four attempts on the same class of bug across threads e3a5c1bf, 55afbe27, 23f518fe, 3e377f8b, 593e16ae:
- PR #4 (original):
session/set_modelRPC viactx.acp.request(...). Worked when tested manually but had a first-turn gotcha: the gatemodel !== ctx.session.modelwas false on turn 1 (session.model pre-populated from user selection), so Kiro stayed on its internal default. - Respawn approach (commit 66faaf91, reverted): tear down kiro-cli and respawn with
--model <slug>. Didn't work —session/loadon the second spawn reloads the session's persisted model state from disk and ignores the new--modelCLI arg. - Hybrid (commit 7badf022, reverted): spawn with
--modelAND callsession/set_model. Worse: passing--modelat spawn locks Kiro's model and makes every subsequentsession/set_modelRPC a silent no-op. Model reported as switched but actual replies came from the original model. - Final (commit 4374b260 + cf82043b): Drop
--modelfrom spawn args entirely. Usesession/set_modelRPC for model switches andsession/set_modeRPC for agent switches. Gate fires onctx.activeModel/ctx.activeMode(both undefined at spawn) so the first turn always aligns Kiro's state with the user's selection.
Verified on thread 593e16ae: 1 initialize, 3 set_mode, 4 set_model, 7 prompts, 0 failures, 0 respawns across the whole conversation.
- Kiro's ACP protocol has product-specific quirks not documented in the spec.
--modelat spawn silently disables in-sessionset_model. The fix is "don't do that" — spawn bare, rely on RPCs. - "Who are you?" is not a reliable model-switch signal. Kiro's system prompt brand-protects the underlying model; every model answers "I'm Kiro" or "I'm an AI assistant, I don't know my model." Use
_kiro.dev/metadatacontextUsagePercentagevariance or per-model latency fingerprint as a behavioral check instead. session/loadis NOT a context preservation mechanism, it's a sessionId preservation mechanism. It doesn't replay history to the new process's model in a way that the new model can consume cross-family. That's why cross-family switches can ValidationException on subsequent prompts.- Kirodex does the same thing we now do — spawn bare kiro-cli and switch via
session/set_mode/setSessionModelRPCs. They suppress the occasional failure silently; we surface it. Both are defensible; we picked the honest one.
- File a Kiro upstream bug for cross-family ValidationException. Minimal repro: thread with Claude tool_use blocks in history, set_model to DeepSeek, next prompt fails.
- Kiro's brand-protection system prompt makes debugging opaque. If this bites again, grep
_kiro.dev/metadataforcontextUsagePercentage— same conversation history should produce different percentages on different models.
Bug: Selecting a Kiro model reverted to Claude in the UI.
- Server-side was fine:
~/.t3/caches/kiro.jsonhad 12 models × 17 agents discovered. ~/.t3/dev/settings.jsonhadtextGenerationModelSelection.provider = "kiro".- TraitsPicker rendered fine when forced.
- The store silently rejected every
setModelSelection({provider: "kiro", ...})call becausenormalizeProviderKindincomposerDraftStore.tsdidn't list"kiro". - Two adjacent hardcoded arrays (
legacyToModelSelectionByProvider,setModelOptions) had the same omission. - Fix: add
"kiro"to all three. Typecheck +composerDraftStore.test.ts(62/62) + manual smoke: green.
- Normalization gatekeepers produce silent no-ops. If you ever wonder "why did selecting X do nothing", look for a
normalize*function that filters unknown values tonull. - Enumerate from a single source. Hardcoded provider-kind arrays across a file are a maintenance trap — each new provider needs N coordinated edits with no type-level enforcement. Short-term: the verification grep in the checklist. Long-term: export
PROVIDER_KINDSconst tuple from contracts and deriveProviderKindfrom it. - Agent-browser installation. Use
npm install -g agent-browser --registry=https://registry.npmjs.org/to dodge internal CodeArtifact auth.
- Refactor to a single
PROVIDER_KINDSconst tuple exported from contracts to eliminate the three-list hazard permanently (also makesnormalizeProviderModelOptionsWithCapabilitiesexhaustiveness-check at compile time).
Bug: Kiro subagent crews flooded the main chat — one flat stream of "Read file" / "Ran command" / assistant-message rows per subagent, no grouping.
- Kiro's ACP transport multiplexes
session/updatefor the main session and every spawned subagent crew over one channel, tagged withsessionId. Upstream'sAcpRuntimeModel.parseSessionUpdateEventdropped thatsessionIdbefore reaching the adapter, so everything looked like main-session activity. - Fix landed in three passes:
- Thread
sessionIdthroughAcpParsedSessionEventandAcpSessionRuntime, track a roster of in-flight subagents by their ACP sessionId, and translate_kiro.dev/subagent/list_updatetransitions intotask.started/task.completedenvelopes. Subagent session/update events are dropped on the main thread. - Dropping everything from subagents made the Work-log look stuck — the group sat silent until the crew terminated. Emit one
task.progressper distinct subagenttoolCallId(tracked per-subagent inseenToolCallIds) so the Work-log shows live per-tool activity inside the collapsible group, matching Claude Code's native SDK behavior. - Work-log rows initially rendered only the generic category ("Ran command", "Read file"). Kiro's typed tool-call presentation puts the action in
titleand the payload indetail; combine them as"{title}: {detail}"via a newformatSubagentToolLabelhelper so rows show the actual command / path / query. 8 unit tests cover the helper.
- Thread
- Multiplexed channels need identity on every event. Any time a transport fans in multiple logical streams, the parser must preserve the stream identity all the way to the consumer. Dropping it at the parser layer is irreversible downstream.
- "Don't route" is not the same as "don't show". The first pass filtered subagent events out of main-thread routing entirely; the fix was to keep a single summarized breadcrumb (task.progress per tool call) so the user sees progress without being drowned in subagent internals.
Bug: selecting an agent in TraitsPicker had no effect — kiro-cli always ran with kiro_default.
- Server-side respawn logic worked in integration tests (2 tests green).
- The composer draft store correctly persisted
{ agent: "..." }per thread. - The TraitsPicker UI rendered the selected agent.
- But the agent never reached the server:
normalizeProviderModelOptionsWithCapabilitieshad nocase "kiro", so the options switch fell through and returnedundefinedon every dispatch. The server received a baremodelSelectionwith no agent. - Fix: add
normalizeKiroModelOptionsWithCapabilities(mirrors opencode) + wire the kiro case. 4 new unit tests cover agent preservation and the provider-level switch.
UX: TraitsPicker stayed open after agent selection.
- Fix:
closeOnClickon the AgentMenuRadioItem(base-ui built-in).
Feature: /agent slash command opens the TraitsPicker.
- Mirrors
/modelopening the model picker. Gated onhasAgentPickerSupportso it only appears for providers whose selected model exposesagentOptions.
Feature: Opus 4.7 added to Kiro built-in models.
- Alias
opusnow maps toclaude-opus-4.7for kiro.
- Passing integration tests do not imply end-to-end correctness. The KiroAdapter respawn test hit
sendTurndirectly with a fully-formedmodelSelection; the real bug was upstream in the web dispatch normalizer. Whenever a feature involves passing data across the web→server boundary, verify the payload on the wire once before trusting unit/integration tests. - Switch statements over
ProviderKindare landmines. At least three such switches exist (draft store normalizers, shared model normalizer, contractcreateModelSelection). Each new provider needs an explicit case. Today's bug was cause #4 of "add-a-provider-or-it-silently-breaks" and makes the case for aPROVIDER_KINDStuple stronger.
# Full patch (everything this fork adds on top of upstream):
git diff upstream/main..HEAD > /tmp/kiro-full.patch
# Kiro-only (excludes meta docs):
git diff upstream/main..HEAD -- \
':(exclude)PATCH.md' \
':(exclude)CLAUDE.md' \
':(exclude)docs/' \
> /tmp/kiro-code-only.patch