* feat(adapters): add Antigravity CLI (agy) + GitHub Copilot CLI support
Add two agentic CLI adapters onto next's existing adapter registration —
without the abandoned PR's setup subcommand / consolidated registry.
Antigravity CLI (agy):
- MCP + capture-only PostToolUse hook adapter (agy honors no stdout veto in
auto-run mode; verified against agy 1.0.5). The agy hook payload
{conversationId, toolCall, workspacePaths} is mapped onto the shared
capture pipeline.
- Ships a Claude-layout plugin bundle (configs/antigravity-cli/) installed via
`npm run install:agy` (mirrors install:openclaw), with a version-skew
capture-hook probe in the installer.
GitHub Copilot CLI (1.0.59):
- json-stdio hook adapter with six events: PreToolUse, PostToolUse, PreCompact,
SessionStart, UserPromptSubmit, Stop. Overrides CopilotBaseAdapter to emit the
FLAT {type,command} + top-level "version": 1 hook config Copilot CLI requires.
- MCP install via `copilot mcp add context-mode -- context-mode`.
- Fix a latent Stop-hook bug: a session_end event with no `data` threw inside
insertEvent (createHash(undefined)) and was silently dropped.
Cross-cutting:
- #774: probe agy/copilot config markers before the generic ~/.claude check.
The copilot marker is narrowed to context-mode-written files
(~/.copilot/mcp-config.json | hooks/context-mode.json), not a bare ~/.copilot/
dir, so a co-installed-but-unconfigured Copilot CLI cannot steal detection
from a Claude Code user.
- Dispatcher fails OPEN (exit 0) on a missing hook script: GitHub Copilot CLI
treats an exit-1 PreToolUse hook as DENY, so a version skew (a newer adapter's
hook command on an older global) would otherwise brick the agent.
Fixes #774. Fixes #775.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci: regenerate bundles for antigravity-cli + copilot-cli support
Picks up the new HOOK_MAP entries, client-map keys, validPlatforms,
getSessionDirSegments cases, and the fail-open dispatcher into the
esbuild-generated runtime bundles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(platform-support): sync support docs to 18 platforms + fix stale Kiro classification
Make README.md and docs/platform-support.md internally consistent and aligned
with the adapter source of truth.
Header sync (18 platforms everywhere):
- The Main Comparison Table (was 11 cols), the Capability Matrix (was 11), and
the README Platform Compatibility table (was 17, missing Kimi Code) now list
the SAME 18 platforms in one shared order. Adds the two branch-new platforms
(GitHub Copilot CLI, Antigravity CLI `agy`) plus previously-omitted Qwen Code,
KiloCode, OpenClaw, Zed, Pi as columns. Each cell sourced from the per-platform
detail sections / adapter source and independently verified.
- Fix five ragged rows in the Main Comparison Table (a dropped trailing OMP cell)
and add CLI Hook Dispatcher rows for qwen-code + copilot-cli.
- GitHub Copilot CLI section: normalize the `**Hook Names:**` label and add the
missing `**Output Modification:**` field for json-stdio-family parity.
Fix stale Kiro classification (code is the source of truth):
- The kiro adapter is json-stdio with working preToolUse/postToolUse hooks
(hooks/kiro/{pretooluse,posttooluse}.mjs + a kiro HOOK_MAP entry), yet the docs
called it "MCP-only (Phase 2 — not implemented)" and the README contradicted
itself ("no hook support" in one place, "native preToolUse/postToolUse" in two
others).
- Reclassify Kiro as json-stdio with PreToolUse + PostToolUse + exit-code-2
blocking across the Overview paradigm table, both wide tables, the dispatcher
table, and the Kiro detail section; document that agentSpawn (SessionStart) and
stop are not yet wired, so session restore after compaction is unavailable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(antigravity-cli): drop vestigial .mcp.json dependency that broke fresh clones
The agy plugin-bundle test asserted configs/antigravity-cli/.mcp.json, but
.mcp.json is gitignored repo-wide and was never committed — so the test passed
on the dev machine (file present locally) yet failed on a fresh clone with
ENOENT. Committing the file is the wrong fix: the .gitignore comment documents
that shipping .mcp.json has silently broken fresh installs before (#253/#531).
- The bundle declares MCP the Claude way via .claude-plugin/plugin.json
mcpServers (committed — the mechanism agy reads on `agy plugin install`),
mirrored by the agy-native mcp_config.json (committed). Remove the vestigial
bundle .mcp.json and stop the test + docs from requiring it. Every file the
plugin test reads is now git-tracked, so a fresh clone passes.
- README: Kiro was still grouped under "Non-hook platforms" in the routing-
enforcement note. Kiro has native preToolUse/postToolUse hooks; it needs the
manual KIRO.md copy only because agentSpawn/SessionStart is not yet wired.
Reword to say so.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(adapters): cross-platform agy installer + copilot-cli COPILOT_HOME parity
Windows fix (real): replace the bash-only agy plugin installer with a
cross-platform Node script so `npm run install:agy` runs natively on Windows
(PowerShell/cmd), not just Git Bash/WSL. agy runs on Windows, so its installer
must too — the old `node -e` wrapper hard-exited 1 on win32. openclaw stays
bash-only (it is genuinely POSIX-only). Removes
scripts/install-antigravity-cli-plugin.sh in favor of
scripts/install-antigravity-cli-plugin.mjs (same preflight + version-skew probe).
copilot-cli hardening (COPILOT_HOME edge case only — the default ~/.copilot
install was and remains correct):
- CopilotCliAdapter.getSessionDir() now roots at getConfigDir() (COPILOT_HOME-
aware), mirroring codex/kimi, so the TS server reads sessions from the same
place the hook runtime (COPILOT_OPTS configDirEnv: COPILOT_HOME) writes them.
Previously a relocated COPILOT_HOME split hook writes ($COPILOT_HOME/...) from
server reads (~/.copilot/...), making sessions appear empty.
- detect.ts copilot-cli marker honors COPILOT_HOME, not just ~/.copilot.
No change to the default (COPILOT_HOME-unset) behavior; a regression test pins
both the ~/.copilot default and the COPILOT_HOME-rooted path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci: regenerate bundles for copilot-cli COPILOT_HOME parity
Picks up CopilotCliAdapter.getSessionDir() and the COPILOT_HOME-aware detect.ts
marker into the esbuild runtime bundles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(antigravity-cli): installer registers the MCP server (agy plugin install skips it)
`npm run install:agy` ran only `agy plugin install`, which — verified against
agy 1.0.5 — processes a bundle's skills + hooks but logs "mcpServers : skipped
(not found)" and registers NO MCP server. agy reads a plugin's MCP only from a
bundle `.mcp.json` (intentionally not shipped — gitignored repo-wide after
#253/#531) and has no `agy mcp add` command, so context-mode's MCP server was
never registered: users had to add it to ~/.gemini/config/mcp_config.json by hand
(reported on Windows; reproduced on Linux: `mcpServers : skipped (not found)`).
The installer now also writes context-mode into agy's GLOBAL MCP profile
~/.gemini/config/mcp_config.json (idempotent JSON merge, preserves other servers,
tolerates a malformed file) — the file agy actually loads and `context-mode
doctor` checks. Verified end-to-end on agy 1.0.5: `npm run install:agy` →
mcp_config.json gains context-mode → `agy -p "... ctx_execute ... 7 + 5"` → 12.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(server): emit Gemini-safe tool schemas so agy/Gemini CLI expose ctx_* tools
Antigravity CLI (agy) and Gemini CLI use Gemini's function-calling API, which
rejects JSON Schema `const` and `additionalProperties`. When a tool's parameter
schema contains either, the host SILENTLY DROPS that tool from the model's
function list — so agy never sees the ctx_* tools and works around them by
hand-rolling the MCP protocol through its Bash tool (verified on Windows: agy
wrote scratch/call_ctx_stats.js + list_mcp_tools.js MCP clients instead of
calling the tools natively). That defeats the point of context-mode — bash
output floods the context window instead of staying in the sandbox.
context-mode builds schemas with Zod, which emits `const` (from coerce/preprocess
constructs) and `additionalProperties`, with no Gemini sanitization. Wrap the
SDK's tools/list handler to rewrite the EMITTED schema:
- `const: X` -> `enum: [X]` (an identical single-value constraint)
- drop `additionalProperties` (advisory-only; every ctx_* handler parses args
with Zod, which strips unknown keys server-side regardless)
Both transforms are behavior-preserving for every other client (Claude Code,
Copilot, Cursor): const and a one-value enum are equivalent, and no model sends
undeclared properties — only the wire schema changes, never validation or how a
tool is called. Best-effort: if the MCP SDK internals shift, the original handler
is left untouched (no regression). Verified on the real tools/list: all 11 ctx_*
tools now emit 0 `const` / 0 `additionalProperties`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci: regenerate bundles for Gemini-safe tool schema sanitizer
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(antigravity-cli): clear agy's stale MCP tool-schema cache on install
agy caches each MCP server's tool schemas under
~/.gemini/antigravity-cli/mcp/<server>/ and does NOT refresh them on reconnect
(verified on agy 1.0.6 against a live Windows install). A cache captured by a
context-mode older than the Gemini-safe-schema fix (ae6e7d3) keeps the
`const` / `additionalProperties` schemas that make Antigravity CLI silently drop
the ctx_* tools from the model's function list — so the schema fix never reaches
the model and the agent keeps working around the tools via shell scripts.
The installer now clears that cache after registering the MCP server, so agy
re-fetches the current Gemini-safe tools/list on its next launch. Verified on
Windows: clearing the cache + reconnecting makes agy re-store ctx_execute.json
with 0 `const` / 0 `additionalProperties`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: document agy Gemini-safe schemas + installer cache-clear + copilot COPILOT_HOME
Reflect this branch's recent behavior changes in the support docs:
- agy: context-mode emits Gemini-safe tool schemas (const->enum, additionalProperties
stripped) so Antigravity CLI exposes the ctx_* tools instead of silently dropping
them; agy caches tool schemas and never refreshes them, so `npm run install:agy`
clears that cache. Added to the agy Known Issues + install steps (platform-support.md
+ README).
- copilot-cli: COPILOT_HOME now relocates the session-DB root too (getSessionDir honors
it), and the detection marker honors COPILOT_HOME.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: correct GitHub Copilot CLI plugin capability (plugins DO support MCP + hooks)
The README + platform-support docs claimed Copilot CLI plugins register only
skills/agents — not MCP servers or hooks. That's wrong: `copilot plugin --help`
and `copilot mcp --help` (Copilot CLI 1.x) confirm a plugin can register MCP
servers (a `.mcp.json` in the plugin root or `.github/mcp.json`) and hooks
(`hooks.json`), installed in one command via `copilot plugin install owner/repo:path`
(from a GitHub repo subdirectory, no clone). The "direct installs deprecated for
plugin@marketplace" note was also inaccurate (all source forms are current).
Corrected both docs. context-mode still registers via `copilot mcp add` +
`context-mode upgrade` today; a shippable Copilot plugin bundle
(configs/copilot-cli/) is noted as a planned follow-up.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(copilot-cli): ship a GitHub Copilot CLI plugin bundle (MCP + skill, phase 1)
`copilot plugin install mksglu/context-mode:configs/copilot-cli` registers the
context-mode MCP server + routing skill in one command — no `context-mode
upgrade` / agent call.
The bundle's .mcp.json pins CONTEXT_MODE_PLATFORM=copilot-cli so the server
self-identifies as Copilot. This fixes the detection trap where a co-installed
Claude Code (~/.claude/plugins/installed_plugins.json) makes standalone
`context-mode upgrade` — and even ctx_upgrade — resolve claude-code and write
Claude's config instead of Copilot's.
Real Copilot plugins discover MCP from a root `.mcp.json`, so this is the one
bundle whose .mcp.json is committed: .gitignore un-ignores exactly this path
(the repo-wide ignore from #253/#531 guards the repo-ROOT dev file, not a
plugin's own config).
Phase 2 (capture hooks via the plugin's hooks.json) follows once its format is
verified on Windows.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(copilot-cli): add capture hooks to the Copilot CLI plugin bundle (phase 2)
configs/copilot-cli/hooks.json registers all six Copilot hook events
(PreToolUse, PostToolUse, SessionStart, UserPromptSubmit, Stop, PreCompact),
each dispatching `context-mode hook copilot-cli <event>` against the global
binary. It is byte-equivalent to what `context-mode upgrade` writes to
~/.copilot/hooks/context-mode.json (the format verified against the
@github/copilot binary), so `copilot plugin install …:configs/copilot-cli` now
registers MCP + skill + capture hooks in one command — no `upgrade` / agent call.
Verified on Windows: with the plugin's env-pinned MCP config + a current global
context-mode, Copilot calls ctx_execute (→ 12) and ctx_upgrade resolves
copilot-cli (writes the Copilot hook, leaves Claude Code's config untouched).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(copilot-cli): document the plugin bundle as the recommended install
README + platform-support now lead with `copilot plugin install
mksglu/context-mode:configs/copilot-cli` (one command: MCP + hooks + skill, no
upgrade/agent call), keeping `copilot mcp add` + `context-mode upgrade` as the
manual no-plugin path. Notes the .mcp.json env pin (CONTEXT_MODE_PLATFORM=
copilot-cli) that fixes detection under a co-installed Claude Code, the
.gitignore un-ignore for the bundle's .mcp.json, and the `copilot --plugin-dir`
local-test path. Drops the earlier "planned follow-up" wording.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(antigravity-cli): ship .mcp.json so `agy plugin install` registers MCP directly
The agy bundle declared MCP in two places that nothing consumed — a `mcpServers`
block in .claude-plugin/plugin.json (which `agy plugin install` SKIPS) and a dead
mcp_config.json (read by no code) — and relied on the installer writing agy's
GLOBAL ~/.gemini/config/mcp_config.json as a workaround for not shipping .mcp.json.
agy's plugin system is Claude-compatible and reads MCP from a bundle `.mcp.json`,
exactly like the Copilot bundle. Verified on agy 1.0.6: `agy plugin install` with
a bundle .mcp.json logs "mcpServers : 1 processed" and registers the server (env
preserved) into ~/.gemini/config/plugins/<name>/mcp_config.json. So:
- ship configs/antigravity-cli/.mcp.json (un-ignored via a .gitignore negation),
pinning CONTEXT_MODE_PLATFORM=antigravity-cli so the server self-identifies as
agy — fixing the #774 mis-detection at the MCP level, not only via dir markers;
- drop the dead mcp_config.json and the manifest's redundant mcpServers;
- simplify the installer: `agy plugin install` now registers MCP + skill + hook;
it keeps the stale tool-schema cache-clear + version-skew probe, and now
self-verifies the plugin-scoped MCP registration (one-line manual fallback if a
future agy skips it) instead of blindly writing the global profile.
Both CLI plugin bundles (copilot-cli, antigravity-cli) are now consistent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(antigravity-cli): doctor recognizes the plugin-scoped MCP + hook registration
After the bundle moved to `.mcp.json` (so `agy plugin install` registers MCP +
the capture hook into agy's plugin profile ~/.gemini/config/plugins/context-mode/),
doctor still only checked the global ~/.gemini/config/{mcp_config,hooks}.json and
warned "context-mode not found" / "capture hook not configured" on a working install.
- checkPluginRegistration + validateHooks now accept the plugin profile (the
canonical `agy plugin install` location) OR the global path (manual fallback).
- getInstalledVersion reads the installed plugin.json version so the version line
shows a real semver (PASS when current) instead of the bogus "vconfigured".
- fix hints point to `npm run install:agy`.
Unit-tested (plugin-scoped PASS for both MCP + hook). Runtime already confirmed on
agy 1.0.6: `npm run install:agy` + `agy -p "...ctx_execute...7+5..."` → 12.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: clarify supported client count
* fix(copilot-cli): fail-open PreToolUse hook + gate debug logs (#787 review)
A thrown PreToolUse hook exited non-zero with empty stdout, which GitHub
Copilot CLI 1.0.59 treats as "Denied by preToolUse hook (hook errored)" and
uses to block EVERY tool — bricking the agent. parseStdin runs JSON.parse, so
a malformed payload alone triggers it. Wrap the hook body in a fail-open
try/catch: a legitimate veto is a normal stdout write + return (never a
throw), so only real errors are swallowed (empty stdout + exit 0 => ALLOW).
Adds a regression test that spawns the hook with a throwing payload.
Also gate the per-invocation debug logs (posttooluse/precompact/sessionstart)
behind CONTEXT_MODE_DEBUG, matching the kimi hooks — the PostToolUse log grew
on every tool call under the user's config dir.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(util/jsonc): string-aware trailing-comma strip (#787 review)
stripJsonComments stripped trailing commas with a regex over the whole string,
silently eating commas INSIDE string values (e.g. "[1, ]" -> "[1 ]") on the
comment-strip path (reached whenever strict JSON.parse fails). Move the
trailing-comma removal into a second string-aware pass over the comment-free
output: in-string commas are preserved while real trailing commas — including
those separated from } or ] by a comment — are still stripped. Regenerated
bundles (jsonc is bundled into cli/server.bundle.mjs).
The identical duplicates in src/server.ts and src/adapters/opencode/index.ts
are left for a follow-up consolidation PR (they parse third-party configs;
wider blast radius).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test: consolidate per-adapter test files per CONTRIBUTING (#787 review)
CONTRIBUTING.md ("Test file organization") keeps one test file per adapter /
core module. Merge the standalone bundle-guard + schema files into their
canonical homes and delete the standalones — zero net-new test files:
- copilot-cli-plugin.test.ts -> adapters/copilot-cli.test.ts
- antigravity-cli-plugin.test.ts -> adapters/antigravity.test.ts
- strict-client-schema.test.ts -> core/server.test.ts (sanitizeSchemaForStrictClients)
Also add the jsonc string-aware regression test to core/server.test.ts (its
home per the domain table; jsonc.ts has no test file of its own).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test: rename copilot capture hooks file to the <platform>-hooks convention (#787 review)
The repo's per-platform hook test files are named tests/hooks/<platform>-hooks.test.ts
(cursor-hooks, gemini-hooks, vscode-hooks, jetbrains-hooks, kiro-hooks, kimi-hooks).
copilot-cli's was the lone deviation (copilot-cli-capture.test.ts). Rename it to
copilot-cli-hooks.test.ts and add the matching row to the CONTRIBUTING.md test-file
table. (antigravity-cli stays folded into antigravity.test.ts — capture-only single
hook, mirroring the GUI variant in the same family file, per the repo's precedent.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(version-sync): register the Copilot CLI bundle manifest (#787 review)
configs/copilot-cli/.github/plugin/plugin.json carries a pinned "version" but,
unlike the antigravity-cli bundle, was missing from version-sync — so it would
freeze on the next `npm version` bump (the .cursor-plugin v1.0.111 drift class
the version-sync test guards against). Add it to scripts/version-sync.mjs targets,
the package.json `version` git-add list, and the version-sync test (targets + pkg
list + SHIPPED lockstep + end-to-end), mirroring the agy bundle.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(antigravity-cli): bounded PreToolUse enforcement via agy's native decision contract
agy honors a top-level PreToolUse decision `{"decision":"deny"|"ask",reason}`
(verified on agy 1.0.6) — not Claude's permissionDecision/additionalContext — so
context-mode can ENFORCE routing on agy, not just capture.
- PreToolUse routing hook (hooks/antigravity-cli/pretooluse.mjs) emits agy's
native decision; deny/ask enforce, context/modify collapse to an enforceable
deny (agy ignores additionalContext). Fail-open.
- Shared agy payload mapper (hooks/antigravity-cli/payload.mjs) used by
pre/post/stop; posttooluse refactored onto it. New capture-only Stop hook
(best-effort — agy Stop firing unconfirmed, so it's excluded from doctor health).
- Native root bundle: ships plugin.json + mcp_config.json + hooks.json +
rules/context-mode.md (agy reads bundle-ROOT files); .mcp.json and
.claude-plugin/plugin.json removed. hooks/hooks.json kept as the validate/install
mirror — agy runtime fires from root hooks.json, but `agy plugin validate/install`
only REPORTS hooks when the subdir hooks/hooks.json also exists.
- routing.mjs agy aliases (run_command->Bash, view_file->Read, ...) + CommandLine/
AbsolutePath/URL extractors; tool-naming.mjs maps agy to context-mode/<tool>.
- adapter: capabilities preToolUse/postToolUse true, paradigm json-stdio, native
decision formatter, doctor; cli.ts HOOK_MAP pretooluse/posttooluse/stop;
version-sync tracks the bundle plugin.json.
Fixes a marker-handoff bug: pretooluse keyed rejected/redirect markers on
conversationId while posttooluse reads via getSessionId (which prefers the
transcript UUID) — both now use getSessionId, with a <uuid>.jsonl round-trip
regression test. Also corrects a stale core-routing assertion to agy's
context-mode/<tool> surface, adds the CONTRIBUTING test-file row, and includes
incidental CODEX_* test-env isolation hardening.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(antigravity-cli): review polish — modify guidance, ask fallback, sync comments, test placement
- formatters: agy `modify` now surfaces routing's per-tool redirect guidance
(curl/build-tool/inline-HTTP) extracted from the echo payload instead of one
generic line; `ask` carries a fallback reason so a security-policy confirmation
prompt is never bare. Adapter formatPreToolUseResponse ask branch mirrored.
- comments: cross-reference the three agy tool-name maps (payload.mjs /
routing.mjs / extract.ts) and the two agyContextReason copies (formatters.mjs /
adapter) so they don't silently drift (single shared table = follow-up).
- tests: move the agy formatter tests to the canonical tests/hooks/formatters.test.ts
(formatDecision wrapper style, beside the other per-platform blocks); assert the
surfaced modify guidance + the ask fallback. Update the run_command deny test to
the specific (non-generic) guidance.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(antigravity-cli): default exec timeout under agy + anti-dump rules
Two agy-specific hardening fixes surfaced by interactive testing:
- ctx_execute / ctx_execute_file / ctx_batch_execute apply a default execution
timeout (120s, tunable via CONTEXT_MODE_AGY_EXEC_TIMEOUT_MS) ONLY under agy.
agy does not enforce an MCP RPC timeout, so a runaway/blocking script hung
forever and had to be interrupted; every other host keeps the unbounded
behavior (Issue #406). resolveExecTimeout() centralizes this; timed-out
messages now report the effective timeout (was "undefinedms"). Unit-tested +
e2e-verified (runaway ctx_execute killed at the bound instead of hanging).
- rules/context-mode.md: add a prominent "Do not dump — derive" section. agy
artifacts each MCP tool's stdout to a step file the model then reads back, so
a whole-file dump costs the context window twice; steer the model to
value/match/known-slice extraction instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(copilot-cli): use camelCase hook event names so hooks actually fire
GitHub Copilot CLI (verified against the @github/copilot 1.0.60 binary)
dispatches hooks by camelCase event names ONLY — preToolUse / postToolUse /
sessionStart / userPromptSubmitted / agentStop / preCompact. The adapter
shipped PascalCase keys (PreToolUse / ...), which the binary silently ignores,
so context-mode's PreToolUse routing enforcement and PostToolUse capture never
fired on Copilot CLI. MCP tool exposure (.mcp.json auto-discovery) was
unaffected, which masked the regression.
- HOOK_TYPES values -> Copilot's camelCase. UserPromptSubmit->userPromptSubmitted
and Stop->agentStop are NAME changes, not just casing.
- Decouple the CLI dispatch token from the event name: buildHookCommand now
derives the token from the .mjs script base (pretooluse, ...), so the event
KEY can be camelCase while the dispatcher and cli.ts hook handler stay stable.
- Update configs/copilot-cli/hooks.json keys, README, index.ts comments, tests.
Verified e2e on real Copilot CLI 1.0.60 via the documented plugin install:
PreToolUse denied a raw `curl` and redirected to ctx_fetch_and_index (the model
obeyed); PostToolUse fired (posttooluse-debug.log advanced under
CONTEXT_MODE_DEBUG). The internal DB event-type labels in hooks/copilot-cli/*.mjs
are context-mode's cross-adapter taxonomy and are intentionally unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Keep Copilot CLI plugin MCP config loadable on older CLI
Mac smoke testing found that Copilot CLI 1.0.44 rejects the plugin MCP entry before startup unless the no-argument server still declares an explicit empty args array.
Constraint: Copilot CLI 1.0.44 requires an explicit args array for plugin stdio MCP entries
Rejected: Omit args because context-mode takes no arguments | older Copilot CLI rejects the plugin config before MCP startup
Confidence: high
Scope-risk: narrow
Directive: Keep args: [] in the Copilot plugin .mcp.json unless Copilot documents it as optional across supported versions
Tested: vitest copilot-cli adapter and hook suites; real Copilot CLI 1.0.44 loaded context-mode MCP after patch; real agy prompt returned 12
Not-tested: Copilot prompt completion, because local Copilot CLI fails to list models even without this plugin
Co-authored-by: OmX <omx@oh-my-codex.dev>
* Ship the agy installer in the npm package
Clean-install testing exposed that the package declared npm run install:agy but omitted the installer file from package.json files, so the installed tarball failed before agy plugin install could run.
Constraint: npm tarball contents are limited by package.json files
Rejected: Rely on repository-local installer presence | npm install -g ships only allowlisted files
Confidence: high
Scope-risk: narrow
Directive: Keep package scripts and package.json files in lockstep for shipped install commands
Tested: vitest antigravity and copilot adapter hook suites; npm pack includes scripts/install-antigravity-cli-plugin.mjs; npm uninstall -g context-mode then npm install -g tarball; npm --prefix installed package run install:agy; real agy prompt returned 12; Copilot loaded installed plugin MCP
Not-tested: Copilot prompt completion, because local Copilot CLI fails to list models after MCP startup
Co-authored-by: OmX <omx@oh-my-codex.dev>
* test(server): use valid tsc option for on-demand build
* fix(copilot-cli): validate plugin runtime hooks
* docs(copilot-cli,antigravity-cli): correct hook comments + fields to match upstream refs
Ground the new Copilot CLI / Antigravity CLI adapters against the real
upstream sources (refs/platforms) and fix misleading comments + one
contradicted field. No runtime behavior change to working paths.
Copilot CLI:
- version:1 is OPTIONAL, not mandatory — the CLI accepts hook configs
that omit the version field (copilot-cli changelog.md:1109). Keep
emitting version:1 (harmless, self-documenting); fix the comments,
README, and docs that claimed hooks never fire without it.
- PascalCase event names are ACCEPTED and fire — the CLI loads configs
across VS Code / Claude Code / CLI by accepting PascalCase alongside
camelCase (changelog.md:1065, :811, :1081). Drop the 'silently
ignored / never fires' claim; we use camelCase as the native naming.
- session_id (snake_case) is the documented payload field
(changelog.md:811). Read it first; keep sessionId (camelCase) as a
defensive, undocumented fallback.
Antigravity CLI:
- The only refs-backed payload field is workspace.current_dir, an object
field (examples/title/title.sh:10, examples/title/README.md:11). Read
workspace.current_dir FIRST for the project dir, falling back to the
empirically-derived workspacePaths[0]. Annotate conversationId /
workspacePaths as unverified. Stop stays best-effort/unverified on
agy 1.0.6.
Docs: platform-support table + README continuity matrix now show
Antigravity CLI Stop as best-effort/unverified and the corrected
session-id / project-dir fields; 17-platform count unchanged (correct).
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Mert Köseoğlu <bm.ksglu@gmail.com>
Co-authored-by: OmX <omx@oh-my-codex.dev>
18 KiB
Contributing to context-mode
This project is licensed under the Elastic License 2.0 (ELv2) and moves forward with your support. Every issue, every PR, every idea matters.
Don't overthink it. Don't ask yourself "is my PR good enough?" or "is this issue too small?" -- just send it. A rough draft beats a perfect plan that never ships. If you found a bug, report it. If you have an idea, open an issue. If you wrote a fix, submit the PR.
That said, I'm a solo maintainer with limited time. The best way to help me help you: follow the templates, run the debug script (bash scripts/ctx-debug.sh), and write tests for your changes. The more context you give me, the faster I can review.
I genuinely love open source and I'm grateful to have you here. Don't hesitate to reach out -- whether it's a question, a suggestion, or just to say hi. Let's build this together.
This guide covers the local development workflow so you can test changes in a live Claude Code session before submitting a PR.
Architecture Overview
context-mode uses a flat src/ structure:
src/
server.ts → MCP server, tool handlers, auto-indexing
store.ts → FTS5 content store (index, search, chunking)
executor.ts → Polyglot code executor (12 languages)
security.ts → Permission enforcement (deny/allow rules)
runtime.ts → Runtime detection (Node, Bun, Python, etc.)
db-base.ts → SQLite base class (shared by store + session)
truncate.ts → Smart output truncation
cli.ts → CLI commands (setup, doctor)
types.ts → Shared type definitions
session/
db.ts → SessionDB — persistent event storage
extract.ts → Event extractors for PostToolUse hook
snapshot.ts → Resume snapshot builder (priority tiers)
adapters/
types.ts → HookAdapter interface, RoutingInstructionsConfig
detect.ts → Platform detection via env vars
claude-code/ → Claude Code adapter (index.ts, hooks.ts, config.ts)
qwen-code/ → Qwen Code adapter (extends Claude Code wire protocol)
gemini-cli/ → Gemini CLI adapter
opencode/ → OpenCode adapter
codex/ → Codex CLI adapter
vscode-copilot/ → VS Code Copilot adapter
omp/ → OMP (Oh My Pi) adapter — MCP-only, isolated ~/.omp/ storage (#473)
openclaw/
workspace-router.ts → Workspace path resolution for Pi Agent sessions
openclaw-plugin.ts → OpenClaw gateway plugin entry (sync register)
hooks/ → Plain JS hooks (.mjs) — no build needed
configs/ → Per-platform install files (settings.json, mcp.json, CLAUDE.md, etc.)
tsc compiles src/ → build/. start.mjs loads server.bundle.mjs (CI-built) if present, otherwise falls back to build/server.js.
Critical for local dev: Delete
server.bundle.mjsin your local clone or yourbuild/server.jschanges will never be loaded:rm server.bundle.mjs # forces start.mjs to use build/server.js
Session Continuity Architecture
Session events flow through a two-database system:
-
SessionDB (persistent, per-project):
~/.claude/context-mode/sessions/<hash>.db- PostToolUse hook captures events in real-time
- PreCompact hook builds resume snapshots
- UserPromptSubmit hook captures user prompts
-
ContentStore (ephemeral, per-process):
/tmp/context-mode-<PID>.db- FTS5 full-text search index for tool outputs
- Auto-indexes session events file written by SessionStart hook
- Dies when MCP server process exits
Session restore flow (compact/resume):
SessionStart hook → reads SessionDB → writes events as markdown file
→ injects ~275 token directive (summary + search queries)
MCP server → detects markdown file on next getStore() call
→ auto-indexes into FTS5 → deletes file
LLM → searches source:"session-events" for details on demand
Raw session events are never injected into context. Only a compact summary table + search queries are injected. The LLM searches for details via the existing ctx_search() MCP tool.
Multi-writer contract (v1.0.130 — see docs/adr/0001-sessiondb-multi-writer.md)
Both SessionDB and ContentStore are multi-writer-safe. Two processes may open the same on-disk dbPath simultaneously — that is the legitimate multi-window UX shape. Write contention is handled by withRetry() on top of SQLite's built-in busy_timeout (30000ms). Do NOT add acquireDbLock-style file locks or locking_mode = EXCLUSIVE pragmas to SQLiteBase or applyWALPragmas. Process-identity invariants (one MCP per project) live in src/util/sibling-mcp.ts, not the DB layer.
Prerequisites
- Claude Code CLI installed
- Node.js 20+ or Bun (recommended for speed)
- context-mode plugin installed via marketplace
Local Development Setup
1. Clone and install
git clone https://github.com/mksglu/context-mode.git
cd context-mode
npm install
npm run build # tsc compiles src/ → build/
2. Symlink the cache to your local clone
Claude Code's plugin system manages ~/.claude/plugins/installed_plugins.json and will revert manual edits on restart. The reliable approach is to replace the cache directory with a symlink to your local clone.
First, find your cached version:
ls ~/.claude/plugins/cache/context-mode/context-mode/
# Example output: 0.9.23
Then replace it with a symlink:
# Back up the cache (use your actual version number)
mv ~/.claude/plugins/cache/context-mode/context-mode/0.9.23 \
~/.claude/plugins/cache/context-mode/context-mode/0.9.23.bak
# Symlink to your local clone
ln -s /path/to/your/clone/context-mode \
~/.claude/plugins/cache/context-mode/context-mode/0.9.23
Replace /path/to/your/clone/context-mode with your actual local path.
Why symlink? The plugin system overwrites
installed_plugins.jsonon every session start, reverting any manual path changes. A symlink lets the plugin system keep its managed path while the actual code resolves to your local clone.
Critical: The symlink must point to the root of your clone (where
hooks/,build/, andsrc/all live). Hooks registered inhooks.jsonuse${CLAUDE_PLUGIN_ROOT}which resolves to this directory.
3. Update PreToolUse hook in settings
The symlink in step 2 ensures hooks.json (which registers PostToolUse, PreCompact, SessionStart, and UserPromptSubmit) resolves to your local clone via the plugin system. You only need to override PreToolUse in ~/.claude/settings.json since its broader matcher is needed for dev mode:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Read|Grep|WebFetch|Agent|mcp__plugin_context-mode_context-mode__ctx_execute|mcp__plugin_context-mode_context-mode__ctx_execute_file|mcp__plugin_context-mode_context-mode__ctx_batch_execute|mcp__(?!plugin_context-mode_)",
"hooks": [
{
"type": "command",
"command": "node /path/to/your/clone/context-mode/hooks/pretooluse.mjs"
}
]
}
]
}
}
Replace /path/to/your/clone/context-mode with your actual local path.
Important: Do NOT add PostToolUse, PreCompact, SessionStart, or UserPromptSubmit to
settings.json— they are already registered inhooks.jsonand the symlink makes them resolve to your local clone. Adding them to both causes double invocations, split session IDs, and SQLite locking errors.
4. Bump the version for verification
Change the version in your local clone to something recognizable:
# All 4 files must be updated:
# 1. package.json: "version": "0.9.23-dev"
# 2. src/server.ts: const VERSION = "0.9.23-dev";
# 3. .claude-plugin/plugin.json: "version": "0.9.23-dev"
# 4. .claude-plugin/marketplace.json: "version": "0.9.23-dev"
Then rebuild:
npm run build
5. Kill cached MCP processes and restart
# Kill any running context-mode processes
pkill -f "context-mode.*start.mjs"
# Verify no processes remain
ps aux | grep context-mode | grep -v grep
# Should return nothing
Restart Claude Code (/exit then claude).
6. Verify local dev mode
Run /context-mode:ctx-doctor in Claude Code. You should see your dev version:
npm (MCP): WARN — local v0.9.23-dev, latest v0.9.23
The version warning is expected -- it confirms you're running from your local clone, not the cache.
Restoring marketplace version
To switch back to the marketplace version:
# Remove symlink and restore backup
rm ~/.claude/plugins/cache/context-mode/context-mode/0.9.23
mv ~/.claude/plugins/cache/context-mode/context-mode/0.9.23.bak \
~/.claude/plugins/cache/context-mode/context-mode/0.9.23
Then revert hooks in ~/.claude/settings.json and restart Claude Code.
Development Workflow
Build and test your changes
# TypeScript compilation
npm run build
# Run all tests (parallel via Vitest)
npm test
# Type checking only
npm run typecheck
# Watch mode
npm run test:watch
What needs rebuild?
| Changed | Rebuild needed? | Why |
|---|---|---|
hooks/*.mjs |
No | Plain JS, loaded fresh each invocation |
src/*.ts |
Yes | Compiles to build/ (MCP server, executor, store) |
src/session/*.ts |
Yes | Compiles to build/session/, imported by hooks |
src/adapters/**/*.ts |
Yes | Compiles to build/adapters/, platform detection + hooks |
configs/* |
No | Static files, served directly |
After rebuilding, restart your Claude Code session. The MCP server reloads on session start.
Tip: If you only changed hook files (
hooks/*.mjs), just restart Claude Code — no rebuild needed. Hooks are plain JS loaded fresh on each invocation.
Key files to know
| File | Purpose |
|---|---|
src/server.ts |
MCP server, tool handlers, auto-indexing of session events |
src/store.ts |
FTS5 content store (index, search, chunking) |
src/executor.ts |
Polyglot code executor (JS, Python, Shell, etc.) |
src/session/db.ts |
SessionDB — persistent session event storage |
src/session/extract.ts |
Event extractors for PostToolUse hook |
src/adapters/detect.ts |
Platform detection (Claude Code, Gemini CLI, etc.) |
src/adapters/types.ts |
HookAdapter interface, shared adapter types |
hooks/sessionstart.mjs |
Session lifecycle (startup/compact/resume/clear) |
hooks/posttooluse.mjs |
Real-time event capture from tool calls |
hooks/precompact.mjs |
Resume snapshot builder (fires before compact) |
hooks/pretooluse.mjs |
Tool routing + context window protection |
hooks/session-helpers.mjs |
Shared utilities (stdin reader, session ID, DB paths) |
TDD Workflow
We follow test-driven development. Every PR must include tests.
We strongly recommend installing the context-mode-ops skill — it includes TDD enforcement, issue triage, PR review, and release automation with parallel subagent orchestration.
The skill lives under .claude/skills/context-mode-ops/ in this repo (moved from the deprecated skills/ location in #439). Install via the direct path:
npx skills add https://github.com/mksglu/context-mode/tree/main/.claude/skills/context-mode-ops
Red-Green-Refactor
- Red -- Write a failing test for the behavior you want
- Green -- Write the minimum code to make it pass
- Refactor -- Clean up while keeping tests green
Test file organization
Do NOT create new test files. Add your tests to the existing file that covers the same domain. We maintain a small number of well-organized test files — one per adapter, one per core module. Creating a new file per feature or per PR leads to fragmentation that makes the suite harder to navigate and maintain.
| Domain | Test File |
|---|---|
| Adapters | tests/adapters/<platform>.test.ts |
| Client detection | tests/adapters/detect.test.ts, tests/adapters/client-map.test.ts |
| Search & FTS5 | tests/core/search.test.ts |
| Server & tools | tests/core/server.test.ts |
| CLI & bundle | tests/core/cli.test.ts |
| Routing | tests/core/routing.test.ts |
| Hook routing | tests/hooks/core-routing.test.ts |
| Hook formatting | tests/hooks/formatters.test.ts |
| Hook integration | tests/hooks/integration.test.ts |
| Cursor hooks | tests/hooks/cursor-hooks.test.ts |
| Gemini hooks | tests/hooks/gemini-hooks.test.ts |
| VS Code hooks | tests/hooks/vscode-hooks.test.ts |
| JetBrains hooks | tests/hooks/jetbrains-hooks.test.ts |
| Kiro hooks | tests/hooks/kiro-hooks.test.ts |
| Copilot CLI hooks | tests/hooks/copilot-cli-hooks.test.ts |
| Antigravity CLI hooks | tests/hooks/antigravity-cli-hooks.test.ts |
| Session DB | tests/session/session-db.test.ts |
| Session extract | tests/session/session-extract.test.ts |
| Session snapshot | tests/session/session-snapshot.test.ts |
| Session continuity | tests/session/continuity.test.ts |
| Session pipeline | tests/session/session-pipeline.test.ts |
| Executor | tests/executor.test.ts |
| Store/Search | tests/store.test.ts |
| Security | tests/security.test.ts |
| OpenClaw plugin | tests/plugins/openclaw.test.ts |
If your change doesn't fit any existing file, discuss with the maintainer before creating a new one.
Output quality matters
When your change affects tool output (ctx_execute, ctx_search, ctx_fetch_and_index, etc.), always compare before and after:
- Run the same prompt before your change (on
main) - Run it again with your change
- Include both outputs in your PR
Testing the OpenClaw Adapter
The OpenClaw adapter has its own test suite and installation workflow.
Running tests
npx vitest run tests/plugins/openclaw.test.ts tests/adapters/openclaw.test.ts
These tests run without a live OpenClaw instance — they mock the plugin API.
Local OpenClaw testing
To test against a running OpenClaw gateway:
-
Install the plugin:
npm run install:openclaw # Or with a custom state directory: npm run install:openclaw -- /path/to/openclaw-stateThe script picks up
$OPENCLAW_STATE_DIRfrom your environment (default:/openclaw). It handles building, native dependency rebuild, extension registration, and gateway restart in one step. -
Open a Pi Agent session and verify hooks fire by checking the debug log output.
See docs/adapters/openclaw.md for hook registration details and known upstream issues.
Prose-style policy (issue #482)
context-mode does not dictate how the model writes its final answer. The four pillars (sandbox routing, session continuity, think-in-code, no prose-style enforcement) keep raw data out of context but leave editorial style — brevity vs. completeness, formatting, tone — entirely to the model and the user's own CLAUDE.md / AGENTS.md.
Why: aggressive brevity instructions have been shown to degrade coding/reasoning benchmarks. Moonshot AI's report on kimi-k2.5 (cited in #482, with the OpenCode fix at anomalyco/opencode#20259) showed that prompts like "minimize output tokens", "MUST answer concisely with fewer than 4 lines", and "one-word answers are best" pushed coding models to drop assumptions, caveats, verification evidence, failure modes, and security warnings the user actually needed.
What this means for contributors:
- Do not add brevity directives to MCP tool descriptions in
src/server.ts. - Do not add
<communication_style>or<response_format>blocks tohooks/routing-block.mjs. - Do not put "Terse like caveman" / "Only fluff die" / "Drop articles, filler" / "fewer than N lines" wording in any shipped adapter config under
configs/*/. - Workflow-discipline rules — "write artifacts to FILES", "use descriptive
ctx_searchsource labels",<artifact_policy>— are fine. They describe what to do (file vs. inline), not how to write.
The regression test at tests/core/server.test.ts > prose-style policy (#482) pins the deletion: any caveman-style language landing in src/server.ts, hooks/routing-block.mjs, or README.md will fail CI.
If you genuinely need to nudge the model on style for a specific use case, do it in your own project's CLAUDE.md / AGENTS.md. Don't ship it inside the framework.
For Pi developers
context-mode works on Pi now. The extension injects routing rules, registers
ctx_* tools through the MCP bridge, and the lean configs/pi/AGENTS.md keeps
context budget tight.
First-time setup: If you open this project in Pi before running npm install
and npm run build, you will see errors. That's normal — the extension needs
the compiled server bundle. Run the build once and restart.
- If you use Pi: remove
CLAUDE.mdfrom your project root. Pi.dev reads both CLAUDE.md and AGENTS.md, burning double context on duplicated routing instructions the extension already injects. - Use
ctx_searchto recall decisions, errors, and blockers from prior sessions instead of re-reading raw files. - Use
ctx_insightfor personal analytics — session activity, tool usage, error rate, project focus.
For the full local dev workflow, build commands, and test instructions, see the contributing guide above.
Submitting a Bug Report
When filing a bug, always include your prompt. The exact message you sent to the agent is critical for reproduction. Without it, we can't debug the issue.
Required information:
- Debug script output:
bash scripts/ctx-debug.sh(collects OS, runtimes, configs, hooks, SQLite diagnostics) - The prompt that triggered the bug
- Full error output (expand with
Ctrl+Oin Claude Code) - Steps to reproduce
Submitting a Pull Request
- Fork the repository
- Create a feature branch from
next - Follow the local development setup above
- Write tests first (TDD)
- Run
npm testandnpm run typecheck - Test in a live Claude Code session
- Compare output quality before/after
- Open a PR using the template
Quick Reference
| Task | Command |
|---|---|
| Check version | /context-mode:ctx-doctor |
| Upgrade plugin | /context-mode:ctx-upgrade |
| View session stats | /context-mode:ctx-stats |
| Purge knowledge base | /context-mode:ctx-purge |
| Run diagnostics | bash scripts/ctx-debug.sh |
| See background steps | Ctrl+O |
| Kill cached server | pkill -f "context-mode.*start.mjs" |
| Rebuild after changes | npm run build |
| Run all tests | npm test |
| Watch mode | npm run test:watch |