AGENTS.md · git:20261027.826e5c1 · 2026-10-27 · sha256 c5dc3d2717eda80e
AGENTS.md git:20261027.826e5c1A
Immutable. This exact content never changes and is served at /api/v1/blob/c5dc3d2717eda80e.
# AGENTS.md
Project context for coding agents working in this repository. This is the
**contract sheet**: each bullet states what must not regress, in present tense,
with a pointer to the full mechanics and rationale in
[docs/invariants.md](docs/invariants.md) (§ numbers below). Keep this file under
32 KB. `CLAUDE.md` imports it with `@AGENTS.md`, so there is one source
and nothing to mirror. Release history lives in
[CHANGELOG.md](./CHANGELOG.md) and [ROADMAP-TO-GA.md § Post-GA audit log](./ROADMAP-TO-GA.md#post-ga-audit-log), never here.
## Quick reference
- **Version**: 2.4.3 (kimi-code, subprocess transport, **native agent-core-v2**).
- **Native-v2 safety** (§1): every executed tool passes managed PreToolUse; the sole chain-breaking plan-file allow is NEVER armed. Preserve all four closures: same-child-home preflight rejects every normalized `default_plan_mode` spelling (before spawn and at spawn, including Stop hook; remedy the config key, not setup); deny Enter/ExitPlanMode for every label; resume requires proven native-v2 lineage and every-agent raw journal scan for plan taint; refuse tower/subagent_fork/master experimental selectors. Exact per-operation `NATIVE_V2_CERTIFIED` rows require source tag scan, plan-ON bypass control and operation smoke; legacy `KIMI_TESTED_MINORS` never gains 0.42. Require the first stream line to be the exact-version marker for v2 and refuse it on v1. Validate Bash.cwd against the trusted root before command policy. Full refusal codes, normalization rules and evidence: [docs/invariants.md §1](docs/invariants.md) and [certification provenance §2](docs/native-v2-certification-provenance.md).
- **Hook-pin durability** (§1): the canonical hook command never embeds a version-stamped Node token; `preferStableNodePath` prefers `process.argv0` only when absolute and `realpath`-identical; `verifyHookInstalled` runs `access(nodeBin, X_OK)` fail-closed on the token parsed back from the built command (`refusal_kind:"node-bin-not-executable"` is the one refusal whose remedy is NOT /kimi:setup). Re-pin-recoverable refusals carry `retryable_after_setup`; agents re-pin via `${CLAUDE_PLUGIN_ROOT}/scripts/companion.sh setup` and retry ONCE only when it is literally `true` and setup exits 0. **In-verifier auto-repin is REJECTED — do not re-propose.**
- **Test discipline** (§1): detached-worker hook-enforcement tests MUST run the companion as a real node subprocess through a symlink with NO `KIMI_PLUGIN_CC_NODE_BIN` override and NO `KIMI_PLUGIN_CC_SKIP_HOOK_CHECK`. A real-binary smoke MUST assert its own precondition when the behaviour depends on host state; never write a "plan-file write is denied under plan mode" smoke — that path is never armed, so green would be the vacuous-precondition failure.
- **Upstream compat** (§2): native-v2 certification is EXACT and per-operation in `runtime/kimi-engine.ts`; all eight operations support 0.42.0, 0.43.0, 0.43.1, 2.0.0–2.0.2, 2.1.0–2.1.1, 2.2.0 and 2.3.0–2.3.3. Legacy-v1 ends at 0.41.x for an explicitly pinned binary. The sole plan-service allow still bypasses hooks through 2.3.3 (MoonshotAI/kimi-code#3431); retain every no-plan closure. Each audit re-verifies ALL surfaces in [docs/upstream-compat-audit.md](docs/upstream-compat-audit.md) and [docs/invariants.md §2](docs/invariants.md), including engine/session provenance, hook ordering/schema/aggregation, Write/Edit path and Bash command+cwd, bootstrap/config/trust, plan/restore routes, print argv, goal interception/exits, child hooks, finite budgets and exported swarm concurrency. Monitor reports never certify. Exact 2.3.2/2.3.3 swarm activation/deactivation uses authenticated loopback web lifecycles around the single `kimi -p -r` runner; preserve model/auto/swarm profile with plan off, marker/version/session identity, memory-only token and journal proof. `runSwarm` owns deactivation, with remaining budget normally and one separate 2s cancellable recovery attempt; cancellation starts no fresh server. Process ownership/quiescence uncertainty prevents success and preserves diagnostics, raw output and write worktree. Root loss requires prior identity; PGID changes are not exit. Smoke must correlate distinct child journals, assigned Write/Edit paths and toolCallIds with results—not aggregate denials or coordinator-only work. Evidence: [docs/native-v2-status.md](docs/native-v2-status.md).
- **Toolchain**: Node >= 22.5, TypeScript, **bun** for development; npm CLI for registry operations, including package installation
- **Workflow**: edit `runtime/**/*.ts` → `bun run check` (build + typecheck + test + drift gate)
- **Installed-state verification**: For questions about what a host is actually running, resolve that host's active version-stamped install cache and run its bundled `scripts/companion.sh setup --check`. The checkout, package metadata, and another host's cache are not proof of active runtime state.
- **Credential-bearing config** (§3): `~/.kimi-code/config.toml` can hold a BYO-provider `apiKey`, and `~/.kimi-code/credentials/` is the real OAuth-token store (always has been — not a per-version migration). For routine checks never read, print, copy or retain either; use the sanitized `setup --check` or `setup --models [--json]` projection. Model listing internally parses config but emits only routing fields, never secrets or raw parser errors; it never reads the OAuth store or makes provider calls. An explicitly authorized diagnosis of config.toml is restricted to the managed hook block with credentials redacted; `credentials/` is never read or copied.
## Directory layout
```
.claude-plugin/ Claude Code plugin manifest (plugin.json, marketplace.json) — id stays as kimi/kimi-marketplace (no rename for v1); v0.4 users opt-in to v1 by updating the same install
.claude/ Gitignored local audit workspace. Upstream clones/reports live here, including daily monitor reports under kimi-code-research/daily-monitor/
.agents/ Codex repo marketplace sidecar (plugins/marketplace.json) — generated; source.path points at plugins/kimi-codex
commands/ Slash command markdown — thin wrappers over companion.sh (Claude Code surface)
agents/ Claude Code subagent definitions (kimi-rescue, kimi-review, kimi-challenge, kimi-ask, kimi-swarm, kimi-pursue, kimi-swarm-write)
hooks/ Stop hook for the review gate (kimi-code-side PreToolUse hook lives at runtime/hooks/)
scripts/ Shell entry points (companion.sh, review-gate-hook.sh) + dev-only surface generator (surface-registry.ts, generate-surfaces.ts)
.mcp.json Claude Code MCP stdio launch config — hash-pinned
plugins/kimi-codex/ Self-contained GENERATED Codex root; install copies forbid ../ escapes; never hand-edit:
├── .codex-plugin/plugin.json Codex manifest (skills + MCP)
├── .mcp.json Generated MCP stdio launch config
├── skills/ 12 shell-backed skills, kept out of repo root so Claude Code does not discover them
├── scripts/ Byte-mirrored shell entry points (0755)
└── dist/ Byte-mirror of compiled runtime
runtime/ TypeScript source — the real runtime
├── background-spawn.ts Shared detached-worker spawn helper (rescue + ask)
├── cli-client.ts Subprocess wrapper around `kimi -p --output-format stream-json`
├── swarm-activation.ts Exact-version loopback activation/deactivation for swarm sessions
├── stream-json.ts Pure parser for kimi-code's NDJSON output
├── cli-cancellation.ts AbortController-based cancellation for long-running commands
├── rescue-approval.ts Workspace-bound allowlist (called by the PreToolUse hook)
├── commands/ One file per companion subcommand
├── mcp/ Typed tools, stdio/protocol, supervised launch, and task ownership
├── hooks/ PreToolUse approval hook (entry script + policy + install verifier)
└── schemas/ Structured output contract for review_gate (review/challenge dropped theirs in v0.2.3)
dist/ Compiled JS — committed for zero-build install
tests/ bun test suite
```
## Commands
- `bun run check` — rebuild `dist/`, verify generated surfaces (`check:surfaces`: Claude hash gate + Codex sidecars + the `plugins/kimi-codex` runtime mirror/orphan checks), check the AGENTS.md size limit, typecheck, run full test suite, then drift gate (`git diff --exit-code -- dist plugins/kimi-codex`). If `dist/` or the bundled Codex copy has unstaged changes, check fails — stage them and retry.
- `bun test <path>` — run a single test file
- `bun run build` — compile `runtime/**/*.ts` → `dist/**/*.js`
The companion runs via `scripts/companion.sh <subcommand>`, which resolves `node` and runs `dist/companion.js`. Subcommands: `setup`, `review`, `task` (`task rescue`, `task challenge`, `task pursue`, `task swarm`), `ask`, `status`, `result`, `cancel`, `replay`, `repair-sessions`, `recover-swarm`, `mcp`.
## Architecture
**Thin plugin, rich runtime** — mirrors [codex-plugin-cc](https://github.com/openai/codex-plugin-cc):
- Plugin layer (commands/, agents/, hooks/, .mcp.json) handles routing only
- Runtime layer (runtime/, scripts/) owns subprocess lifecycle, SQLite job store, hook-side approval policy, and rendering
- Flow: `slash command → companion.sh → companion.js → kimi -p --output-format stream-json → job store → artifact`
- MCP: `tool call → companion.sh mcp → supervised mcp-child → the same execution plan, job store, and artifact`
**Key invariants** (each in full in docs/invariants.md §4):
- **Subprocess-first.** One `kimi -p` prompt runner per job; exact 2.3.2/2.3.3 swarm jobs add the bounded web activation lifecycle in §2, never a second prompt transport. Session-id capture is dual-source (stream-json `session.resume_hint` on 0.2.0+; anchored stderr `kimi -r <uuid>` on 0.1.x), first-announce-wins, pinned, idempotent; meta is filtered from `records[]`; the id round-trips verbatim via `kimi -r`.
- **MCP transport and task ownership** (§4): `companion.sh mcp` exposes seven model tools plus job status/result/cancel over stdio. Claude task handles require per-request tasks capability; Codex legacy gets ordinary results, not native tasks. `mcp-child` fixes the task/job UUID and attaches when cancellation is ready (60s cap), runtime-foreground (`background:false`); `mcp_tasks` holds launch metadata; `jobs` owns outcomes. Host workspace is bounded; bind repo/`operation_kind`, pin the data root, preserve all safety gates, and keep stdout JSON-only. Disconnects never cancel; restarts reattach by ID. MCP-owned jobs revalidate both identities at attach/signals; unconfirmed launches stay working. Failed jobs return completed tasks with `isError:true` and artifact; cancelled maps to `cancelled`; supervisor/protocol failures distinct.
- **Prompt and metadata separation** (§4): raw task text owns summaries/titles; full prefixed prompts own spawn/digest/recovery. Working-tree review includes untracked files; `--base` never treats dirty files as committed snapshots. Pursue uses `/goal --` and enforces the 4000-character objective budget including hints; `SetGoalBudget` stays denied.
- **Every model-spawning command is enforced by the PreToolUse hook** in `~/.kimi-code/config.toml` (`kimi -p`'s auto-approve cannot be overridden via argv). On native v2 the hook is load-bearing under the no-plan construction; the preflight refuses `default_plan_mode`, tainted/unknown resume and unsafe experimental selectors before Kimi CLI spawn. Ask/review/challenge/rescue/pursue/swarm refuse before spawning when hook verification fails; the review gate skips visibly. `KIMI_PLUGIN_CC_SKIP_HOOK_CHECK=1` is a tests/diagnostics bypass for hook-verification refusals only — never a repair path, and it does not bypass the v2 preflight.
- **Hook-escape monitor** (§1): a policy-denied streamed call whose result is not the managed denial (allowing upstream's repeat-breaker decoration) records `mismatch`; no result at settlement records `unresolved`. Findings are **detection evidence, never failure**: logged, persisted in `hook_escapes`, exposed by status/result/stderr and timeout details, and only annotated by the fail-open review gate. The stream has no outcome field, so upstream pre-hook rejection, another policy blocking first, or hook drift can look identical; never fail a job without positive execution evidence. Coverage is streamed read-only calls (swarm children are in-process) and pure plan/goal denials.
- **Hook verification is exact and parser-based** (§4): rebuild canonical command bytes from install-paths; parse COMPLETE TOML with vendored smol-toml@1.9.1, validate EVERY hook against the reviewed version schema, then managed-block grammar. Unknown top-level tables are tolerated; unsupported nonzero schemas fail closed. Comment markers are not durable. ONLY absent-state fallback accepts a real-parser-proven clean enforcing table: event=PreToolUse, byte-exact command, no matcher spelling, no keys outside event/command/timeout. Never substitute substring or line scanning. `findBareApprovalHookTables` remains conservative, host-scoped prune-only. Re-verify parser/matcher/bytes/absent-state/prune boundaries on each audit; exact schema/version mechanics live in [docs/invariants.md §4](docs/invariants.md).
- **Serialized config mutation** (§4): hold the private adjacent config lock for the entire read-modify-write; publish atomically. Lock reads are no-follow/nonblocking/regular-file-only and capped; acquisition bounded; live owners never stolen; stale recovery ABA-safe; inode/token ownership rechecked before writes. New home 0700, config/lock 0600. Normalize inline hooks with comment-loss warning. Setup/check fails on whole-file, hook-schema, block, path or shell-probe failure. Full protocol: [docs/invariants.md §4](docs/invariants.md).
- **Host-scoped managed blocks.** Claude Code and Codex install to different version-stamped paths but share one config.toml, so the managed block is keyed by host id (`# === BEGIN kimi-plugin-cc-managed:<host-id> (vX) ===`; `resolveHostId` derives it version-independently from the hook path, `KIMI_PLUGIN_CC_HOST_ID` overrides). **Never collapse back to one shared block** — the hosts overwrote each other. `parseManagedBlock`/`evaluateInstalled` are per-host and exact; `runInstall` upserts only this host's block and prunes only orphaned marker-less blocks whose command is strictly ours (`isOurApprovalHookCommand`; hand-rolled hooks are never touched); `runUninstall` is host-scoped (`--uninstall --all` sweeps every host). Coexistence is safe because kimi-code fires every `[[hooks]]` any-block-wins. Re-verify: scoped install/uninstall preserves other hosts' live blocks; `--uninstall --all` is the explicit exception.
- **Rescue defines the direct-workspace write allowlist** used by rescue and pursue (`runtime/rescue-approval.ts`, via `evaluateRescueHookRequest`): shell-quote parser, mutating-flag detector (`--fix`/`--write`/`--apply`/`--in-place`/`-i`/`-w` plus the output-to-file class `--output*`/`--output-dir*`, exact and `=` forms; per-tool `-o`), symlink reject, realpath check, a global `hasExecDelegatingFlag` (`--open-files-in-pager` — a CRITICAL RCE class — `-vettool`/`-toolexec`/`-exec`; git-local `-O`), and report-writer rejects (`mypy --junit-xml`/`--*-report`, `pytest --junitxml`/`--result-log`/`--report-log`). Test runners still execute repo code by design; internal Git context commands also trust repository Git configuration and can execute helpers outside tool hooks, including read-only swarm — explicit trust boundaries in `docs/safety.md` and `docs/invariants.md`.
- **Rescue cannot mutate git state.** The calling host owns branch and commit decisions.
- **`/kimi:pursue` (experimental)** (§4): runtime-foreground-only autonomous `/goal --`, with rescue workspace/write policy and hook enforcement on every turn; no git mutation, no resume, default mandatory finite budget 45m. Preserve the legacy 0.8–0.11 goal env flag. Only trusted operation `pursue` plus a trusted root permits exact GetGoal and terminal UpdateGoal complete/blocked; creation/reactivation/budget mutation remain denied and older coexisting hooks can veto. Persist rescue lineage, export the immutable operation instead of ambient values, and treat exits 0/3/6 as terminal. Turns are a hint; wall-clock budget is the bound. Full mechanics: [docs/invariants.md §4](docs/invariants.md).
- **`/kimi:swarm` = read-only parallel fan-out** (`AgentSwarm`, 0.12.0+). Label `swarm` allows the read-only set PLUS the exact tool name `AgentSwarm` (the singular `Agent` is denied); every subagent inherits the label and fires the same hook through its own eager external-hooks service → no additional direct tool-write permission; the internal Git trust boundary still applies. `command_type:"review"` lineage. Refuses without the hook. Bounded by mandatory `--budget` (default 30m); `--cap N` is a SOFT total-count hint, `--max-concurrency N` a HARD peak cap on 0.18.0+ (`KIMI_CODE_AGENT_SWARM_MAX_CONCURRENCY`, default 4, always exported). **Never re-merge the two flags.** The model-invocable `kimi-swarm` agent wraps it. `docs/safety.md` § "Read-only swarm".
- **`/kimi:swarm --write` = patch-only parallel writes** (§4): create an ephemeral detached worktree off born HEAD under the plugin data root; spawn there while `job.cwd` remains the user workspace. The forge-proof `KIMI_PLUGIN_CC_WORKSPACE_ROOT` confines every coordinator/child write through the rescue policy; missing trusted root refuses. Children are coder agents with disjoint targets, no git and no nested swarm; re-verify eager child hooks and shared-workspace permission stacks each audit. Requires CLI ≥0.18, verified hook, finite budget, dirty-tree warning and default HARD concurrency 1. Capture an untruncated binary patch on every ordinary terminal path BEFORE removal; capture/cleanup failure retains the tree. Never apply or commit. Abandoned worktrees are never deleted by age. Runtime-foreground-only does not prohibit the caller detaching its shell. Cancel detached shell runs with no-id `companion.sh cancel` (latest running repo job; disambiguate concurrent jobs); MCP uses its durable job ID. Prefer cancellation over Esc/TaskStop: harness hard-kill grace can end before teardown/patch capture. Pursue writes the REAL workspace; swarm-write returns a discardable patch. The model-invocable agent requires BOTH disjoint write targets and explicit fan-out intent. Full mechanics: [docs/safety.md](docs/safety.md).
- **`goal.summary`** (0.8.0+, role-less) is captured on `CliClientResult.goalSummary`; `turn.step.retrying` meta is modeled and filtered like the other wrapper metadata; malformed shapes still enter diagnostics.
- **Plugin data ownership** (§5): `KIMI_PLUGIN_CC_DATA` is the explicit absolute data-root override. Standard cache installs derive the expected host-specific directory from the loaded package, not shared root variables; conflicting shared data variables refuse before store access (`PLUGIN_DATA_CONFLICT`). Custom launches retain unambiguous legacy paths. No automatic data migration; detached workers pin the selected root and the Stop hook skips visibly on resolution failures.
- **Jobs in SQLite are the source of truth.** Terminal states are permanent; nullable `budget_ms` persists response-time budgets for ask/rescue/review/challenge (NULL = command default resolved at execution; a patience knob, never a safety-field pattern), including resume inheritance and detached-worker replay; pursue/swarm persist their effective safety ceilings. Additive `budget_source` records explicit/inherited/default provenance (old NULL rows render default for a NULL budget, otherwise unknown); inherited ask/rescue budgets are printed at launch.
- **Session visibility and titles** (§4): settled native-v2 sync fills only missing redacted previews (≤4,000 chars) and invalidates the shared index; manual/native titles are preserved, fallback titles replaceable. Never auto-invoke the native title endpoint (it resumes an agent). `repair-sessions` defaults to preview, writes only with `--apply` after a prompt-digest match, and excludes review-gate sessions. See [docs/session-visibility.md](docs/session-visibility.md).
- **Model selection** (§4): `--thinking <effort>` requests effort through `KIMI_MODEL_THINKING_EFFORT`; omission preserves ambient env and resumes never inherit a prior request. Non-trait providers and an `off` base may ignore it; `alwaysThinking` receives `off` unnormalized. Stream-json has no thinking deltas, so honoring is unverifiable. Omit `-m` for fresh defaults; resumes keep session models; per-run aliases do not change saved defaults. `setup --models [--json]` is a credential-safe local inventory, not a connection test. Agents require one unambiguous configured alias, never guess/fall back; provider setup stays in `/login` or `/provider`, saved-default changes only in explicit `/model`. Review gate uses `KIMI_PLUGIN_CC_REVIEW_GATE_MODEL` when set; swarm children may use `[secondary_model]`. See [docs/models.md](docs/models.md).
- **Prose pass-through**: review/challenge/ask/rescue emit raw prose; the review gate is the only command that parses Kimi output (JSON allow/block), as a Stop hook, disabled by default, fail-open on malformed output and timeout (a hook-escape finding only annotates `systemMessage`), skipping visibly (with the reason in `systemMessage`) when enforcement is missing or invalid.
- **LLM-caller discipline**: stderr is for humans only; anything load-bearing goes in stdout, exit codes or SQLite. Parsers hard-fail on unknown flag-shaped tokens (`INVALID_ARGS`); `--help`/`-h` before `--` prints subcommand usage from the same supported-flag constants to stdout and exits 0. `RuntimeError.details` carries structured failure context. Role-less CLI errors stay bounded diagnostics (`cliError`, failure `details.cli_error`), never prose/lineage or exit overrides; marker-first still applies. `result <jobId> --json` opts into the structured envelope, including `budget_ms`, `budget_source`, the captured session ID and `kimi_session_id_source`.
- **Cancellation** (§4): AbortController + SIGTERM → SIGKILL (1500ms), with teardown a settlement barrier. POSIX captures and revalidates PID/PGID/start-time identities before signalling, confirms bounded post-kill quiescence, never signals a replacement and emergency-kills the owned root on teardown failure; Windows remains direct-child-only. Ask/review/challenge default to 30m, rescue 45m; `--budget` ≤24h, unaffected by backgrounding. Clean non-aborted exit-0 wins same-tick budget expiry. Only budget timeouts may retain settled partial prose and a proven session (announced, or exactly one session matching cwd/time/full prompt digest); cancellation never captures partial prose. Preserve `settled_post_abort`, session-source diagnostics and all resume lineage/plan-taint gates. Full settlement/recovery rules: [docs/invariants.md §4](docs/invariants.md).
- **Durable swarm recovery** (§4): exact 2.3.2/2.3.3 records launch intent/verified identities and session ownership BEFORE enabling swarm; no token or credential is stored. `recover-swarm <job-id>` previews; `--apply` only recovers the current repo’s stopped owner, revalidates identities, refuses ambiguous/changed groups, disables the exact recorded session through the verified CLI/hook, and captures but never applies/deletes the retained worktree. Concurrent recovery is identity-locked. Terminal job outcomes never change; abandoned running jobs become failed, not successful. Normal cancellation never launches recovery. A kill between spawn and identity persistence remains an explicit manual-inspection case, not proven cleanup. No age-only orphan deletion. See [docs/swarm-recovery.md](docs/swarm-recovery.md).
- **Spawn diagnostics** (§4): preserve structured process causes; do not send argument/resource exhaustion to reinstall advice. Windows rejects known-overlimit UTF-16 command lines before spawn with `CLI_COMMAND_LINE_TOO_LONG`, without retaining the prompt in that diagnostic. Print prompts remain argv-backed; no stdin transport or Windows large-review support is implied. PR #22 does not close issue #14.
- **Config writes preserve mode 0o600** (`writeConfigAtomic` chmods the temp file before rename).
## Post-GA roadmap
See [ROADMAP-TO-GA.md](./ROADMAP-TO-GA.md) for the pre-GA history and the living Post-GA audit log. v1.0.0 GA shipped 2026-05-26. Remaining open items: H1 enforcement (2.2.1 ships the read-only DETECTION slice; failing/stopping a run needs positive execution evidence — filed upstream as MoonshotAI/kimi-code#4241, an `outcome` field on print-mode tool records), filesystem-dependent write-label coverage, swarm subagent coverage. H5 is closed as a requested thinking-effort knob, not an enforced guarantee. H3/H4/H8/H9 closed in v1.2.x; H7 (real-binary smoke, `bun run smoke:real`) ships as a manual-dispatch CI workflow; model calls require `KIMI_MODEL_API_KEY`. Adding the secret does not enable per-push runs.
## When editing
- Read the code before changing it — the runtime has specific invariants that aren't obvious from file names
- Run `bun run check` before considering any change done
- After any `runtime/**` (or `scripts/*.sh`) change, regenerate the Codex package: `bun run build && bun run generate:surfaces`, then stage `dist/` AND `plugins/kimi-codex/`. The `plugins/kimi-codex/` tree is GENERATED — never hand-edit it; edit `runtime/`/`scripts/`/`scripts/surface-registry.ts` and regenerate.
- Codex sidecars are generated by `bun run generate:surfaces` from `scripts/surface-registry.ts`. `bun run check:surfaces` (run after `build` inside `check`) verifies: the Claude plugin bytes match the locked hash gate, the generated Codex text surfaces are current, the bundled runtime mirror (`plugins/kimi-codex/{scripts,dist}`) byte-matches root, and no orphaned generated files remain.
- Editing a Claude command/agent or any `.claude-plugin/*` file changes its bytes, so you MUST re-pin its sha256 in `CLAUDE_SURFACE_HASHES` (`scripts/surface-registry.ts`) — `shasum -a 256 <file>` — or `check:surfaces` fails closed.
- `dist/` and `plugins/kimi-codex/` are committed intentionally (zero-build install for both hosts; Codex copies the subfolder to its cache). The drift gate catches forgotten rebuilds/regenerations.
- Agent files register at session start. Adding or editing `agents/*.md` mid-session doesn't activate them until Claude Code reloads — reach for slash commands or direct `companion.sh` in the same session.
- `.claude/` is gitignored — notes, worktrees, internal docs under it stay local. Don't try to commit them.
- **Tests never inherit host plugin env.** `bunfig.toml` preloads `tests/helpers/preload.ts`, which deletes `CLAUDE_PLUGIN_DATA`/`PLUGIN_DATA`/`CLAUDE_PLUGIN_ROOT`/`PLUGIN_ROOT` (and the per-spawn `KIMI_PLUGIN_CC_*` overlays) before every test file. A host session exports these into the shell — possibly ANOTHER plugin's data dir — and a test that spreads `process.env` would otherwise write into a live plugin store (it did, 2026-09-09). Tests that need a plugin root/data dir set their own explicitly; never remove the preload.
- **Comment hygiene**: keep only current, non-obvious constraints and rationale; delete stale cutover/release essays and redundant narration. History belongs in `CHANGELOG.md` or `ROADMAP-TO-GA.md`; update or remove comments when behavior changes.
- **Changing a contract**: edit the full statement in `docs/invariants.md` AND its one-line summary here. Rationale and archaeology go there or in the log files, not here.
## Releasing
Plugin versions advance independently of upstream using ordinary SemVer; a release may
deliberately align its number with the kimi-code version it certifies (2.2.0 certifies
CLI 2.2.0), but the number is never evidence — state the exact certified CLI versions separately. Never reuse a published version or
tag, and never encode an upstream patch with leading zeros. The source audit, exact-
binary controls, per-operation smoke and full check remain mandatory. The runtime
certification table is the compatibility authority. See [docs/invariants.md](docs/invariants.md) §2.
`runtime/version.ts` is the SINGLE version source — `scripts/surface-registry.ts` (`PLUGIN_VERSION`) imports it, so the Codex manifest/marketplace version propagates automatically. A version bump touches these and then regenerates:
- `runtime/version.ts` (`KIMI_PLUGIN_CC_VERSION` — written into the managed-block marker comment by /kimi:setup; imported by surface-registry)
- `package.json`
- `.claude-plugin/plugin.json` — **also re-pin its sha256 in `CLAUDE_SURFACE_HASHES`** (its bytes change)
- `.claude-plugin/marketplace.json` — **also re-pin its sha256 in `CLAUDE_SURFACE_HASHES`**
- `AGENTS.md` — bump the version number, and **REPLACE** a contract bullet if the release changes a standing contract (present tense, one statement, never appended). What a release *did* goes in `CHANGELOG.md` / the ROADMAP audit log; the full mechanics go in `docs/invariants.md`.
- `CHANGELOG.md` (add a new version section — see its top banner. Docs-only compat checkups that DON'T bump the version are logged in ROADMAP's Post-GA audit log instead, not here.)
- then run `bun run build && bun run generate:surfaces` to regenerate `plugins/kimi-codex/` (manifest version + bundled runtime), and stage it.
Before release validation, run `bun install --frozen-lockfile`; never reuse an
unchecked local dependency directory as proof of a reproducible build. Regenerate
and stage both distributions, then run `bun run check`. Compiled-runtime
regressions must execute the emitted JavaScript, not only Bun's TypeScript loader.
Commit and push the release commit, wait for its CI check to pass, then
`git tag -a vX.Y.Z -m "..."`, `git push origin vX.Y.Z`, and
`gh release create --verify-tag`. A published tag is immutable; fix forward.
### npm distribution
`package.json` allowlists publication files. `bun run check:package` packs it,
byte-checks both compiled distributions, and runs isolated setup/check,
unsupported-version refusal, and modern/legacy MCP protocol smokes on both.
No live credentials, plugin state, or model calls. CI runs it after the full check. `publish.yml` builds/tests a release tag, then publishes that exact tarball
through npm OIDC in the `npm` environment. See `docs/registry-releases.md`.