@mxalbert/context-mode 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +33 -0
- package/.claude-plugin/plugin.json +31 -0
- package/.codex-plugin/hooks.json +65 -0
- package/.codex-plugin/mcp.json +13 -0
- package/.codex-plugin/plugin.json +31 -0
- package/.openclaw-plugin/index.ts +12 -0
- package/.openclaw-plugin/openclaw.plugin.json +38 -0
- package/.openclaw-plugin/package.json +28 -0
- package/LICENSE +94 -0
- package/README.md +1619 -0
- package/bin/statusline.mjs +378 -0
- package/build/adapters/antigravity/index.d.ts +51 -0
- package/build/adapters/antigravity/index.js +169 -0
- package/build/adapters/antigravity-cli/index.d.ts +51 -0
- package/build/adapters/antigravity-cli/index.js +342 -0
- package/build/adapters/base.d.ts +92 -0
- package/build/adapters/base.js +135 -0
- package/build/adapters/claude-code/config.d.ts +8 -0
- package/build/adapters/claude-code/config.js +8 -0
- package/build/adapters/claude-code/hooks.d.ts +106 -0
- package/build/adapters/claude-code/hooks.js +183 -0
- package/build/adapters/claude-code/index.d.ts +80 -0
- package/build/adapters/claude-code/index.js +569 -0
- package/build/adapters/claude-code-base.d.ts +49 -0
- package/build/adapters/claude-code-base.js +113 -0
- package/build/adapters/client-map.d.ts +10 -0
- package/build/adapters/client-map.js +42 -0
- package/build/adapters/codex/config.d.ts +8 -0
- package/build/adapters/codex/config.js +8 -0
- package/build/adapters/codex/hooks.d.ts +55 -0
- package/build/adapters/codex/hooks.js +64 -0
- package/build/adapters/codex/index.d.ts +87 -0
- package/build/adapters/codex/index.js +995 -0
- package/build/adapters/codex/paths.d.ts +1 -0
- package/build/adapters/codex/paths.js +12 -0
- package/build/adapters/codex/usage.d.ts +107 -0
- package/build/adapters/codex/usage.js +227 -0
- package/build/adapters/copilot-base.d.ts +94 -0
- package/build/adapters/copilot-base.js +297 -0
- package/build/adapters/copilot-cli/hooks.d.ts +33 -0
- package/build/adapters/copilot-cli/hooks.js +64 -0
- package/build/adapters/copilot-cli/index.d.ts +48 -0
- package/build/adapters/copilot-cli/index.js +341 -0
- package/build/adapters/cursor/config.d.ts +4 -0
- package/build/adapters/cursor/config.js +4 -0
- package/build/adapters/cursor/hooks.d.ts +55 -0
- package/build/adapters/cursor/hooks.js +82 -0
- package/build/adapters/cursor/index.d.ts +64 -0
- package/build/adapters/cursor/index.js +517 -0
- package/build/adapters/detect.d.ts +136 -0
- package/build/adapters/detect.js +623 -0
- package/build/adapters/gemini-cli/config.d.ts +8 -0
- package/build/adapters/gemini-cli/config.js +8 -0
- package/build/adapters/gemini-cli/hooks.d.ts +83 -0
- package/build/adapters/gemini-cli/hooks.js +115 -0
- package/build/adapters/gemini-cli/index.d.ts +80 -0
- package/build/adapters/gemini-cli/index.js +483 -0
- package/build/adapters/jetbrains-copilot/config.d.ts +8 -0
- package/build/adapters/jetbrains-copilot/config.js +8 -0
- package/build/adapters/jetbrains-copilot/hooks.d.ts +59 -0
- package/build/adapters/jetbrains-copilot/hooks.js +87 -0
- package/build/adapters/jetbrains-copilot/index.d.ts +31 -0
- package/build/adapters/jetbrains-copilot/index.js +131 -0
- package/build/adapters/kimi/config.d.ts +8 -0
- package/build/adapters/kimi/config.js +8 -0
- package/build/adapters/kimi/hooks.d.ts +28 -0
- package/build/adapters/kimi/hooks.js +34 -0
- package/build/adapters/kimi/index.d.ts +66 -0
- package/build/adapters/kimi/index.js +537 -0
- package/build/adapters/kimi/paths.d.ts +21 -0
- package/build/adapters/kimi/paths.js +52 -0
- package/build/adapters/kimi/usage.d.ts +82 -0
- package/build/adapters/kimi/usage.js +217 -0
- package/build/adapters/kiro/hooks.d.ts +61 -0
- package/build/adapters/kiro/hooks.js +94 -0
- package/build/adapters/kiro/index.d.ts +61 -0
- package/build/adapters/kiro/index.js +301 -0
- package/build/adapters/omp/index.d.ts +65 -0
- package/build/adapters/omp/index.js +182 -0
- package/build/adapters/omp/plugin.d.ts +81 -0
- package/build/adapters/omp/plugin.js +331 -0
- package/build/adapters/omp/usage.d.ts +49 -0
- package/build/adapters/omp/usage.js +110 -0
- package/build/adapters/openclaw/config.d.ts +8 -0
- package/build/adapters/openclaw/config.js +8 -0
- package/build/adapters/openclaw/hooks.d.ts +50 -0
- package/build/adapters/openclaw/hooks.js +61 -0
- package/build/adapters/openclaw/index.d.ts +74 -0
- package/build/adapters/openclaw/index.js +439 -0
- package/build/adapters/openclaw/mcp-tools.d.ts +54 -0
- package/build/adapters/openclaw/mcp-tools.js +207 -0
- package/build/adapters/openclaw/plugin.d.ts +141 -0
- package/build/adapters/openclaw/plugin.js +818 -0
- package/build/adapters/openclaw/session-db.d.ts +55 -0
- package/build/adapters/openclaw/session-db.js +88 -0
- package/build/adapters/openclaw/usage.d.ts +34 -0
- package/build/adapters/openclaw/usage.js +52 -0
- package/build/adapters/openclaw/workspace-router.d.ts +29 -0
- package/build/adapters/openclaw/workspace-router.js +64 -0
- package/build/adapters/opencode/config.d.ts +8 -0
- package/build/adapters/opencode/config.js +8 -0
- package/build/adapters/opencode/hooks.d.ts +38 -0
- package/build/adapters/opencode/hooks.js +50 -0
- package/build/adapters/opencode/index.d.ts +144 -0
- package/build/adapters/opencode/index.js +638 -0
- package/build/adapters/opencode/plugin.d.ts +249 -0
- package/build/adapters/opencode/plugin.js +1536 -0
- package/build/adapters/opencode/v2.d.ts +117 -0
- package/build/adapters/opencode/v2.js +224 -0
- package/build/adapters/opencode/zod3tov4.d.ts +1 -0
- package/build/adapters/opencode/zod3tov4.js +132 -0
- package/build/adapters/pi/extension.d.ts +81 -0
- package/build/adapters/pi/extension.js +861 -0
- package/build/adapters/pi/index.d.ts +57 -0
- package/build/adapters/pi/index.js +173 -0
- package/build/adapters/pi/mcp-bridge.d.ts +268 -0
- package/build/adapters/pi/mcp-bridge.js +894 -0
- package/build/adapters/qwen-code/hooks.d.ts +26 -0
- package/build/adapters/qwen-code/hooks.js +29 -0
- package/build/adapters/qwen-code/index.d.ts +40 -0
- package/build/adapters/qwen-code/index.js +361 -0
- package/build/adapters/qwen-code/usage.d.ts +90 -0
- package/build/adapters/qwen-code/usage.js +222 -0
- package/build/adapters/types.d.ts +360 -0
- package/build/adapters/types.js +118 -0
- package/build/adapters/vscode-copilot/config.d.ts +8 -0
- package/build/adapters/vscode-copilot/config.js +8 -0
- package/build/adapters/vscode-copilot/hooks.d.ts +71 -0
- package/build/adapters/vscode-copilot/hooks.js +98 -0
- package/build/adapters/vscode-copilot/index.d.ts +32 -0
- package/build/adapters/vscode-copilot/index.js +222 -0
- package/build/adapters/zed/index.d.ts +40 -0
- package/build/adapters/zed/index.js +163 -0
- package/build/cli.d.ts +34 -0
- package/build/cli.js +1828 -0
- package/build/db-base.d.ts +250 -0
- package/build/db-base.js +756 -0
- package/build/executor.d.ts +72 -0
- package/build/executor.js +678 -0
- package/build/exit-classify.d.ts +19 -0
- package/build/exit-classify.js +12 -0
- package/build/fetch/blocks.d.ts +145 -0
- package/build/fetch/blocks.js +244 -0
- package/build/fetch/extract.d.ts +68 -0
- package/build/fetch/extract.js +123 -0
- package/build/fetch/page-store.d.ts +66 -0
- package/build/fetch/page-store.js +174 -0
- package/build/fetch-cache.d.ts +13 -0
- package/build/fetch-cache.js +15 -0
- package/build/lifecycle.d.ts +124 -0
- package/build/lifecycle.js +278 -0
- package/build/package-identity.d.ts +26 -0
- package/build/package-identity.js +28 -0
- package/build/runPool.d.ts +36 -0
- package/build/runPool.js +51 -0
- package/build/runtime.d.ts +71 -0
- package/build/runtime.js +659 -0
- package/build/search/auto-memory.d.ts +47 -0
- package/build/search/auto-memory.js +170 -0
- package/build/search/ctx-search-schema.d.ts +90 -0
- package/build/search/ctx-search-schema.js +135 -0
- package/build/search/flood-guard.d.ts +57 -0
- package/build/search/flood-guard.js +80 -0
- package/build/search/unified.d.ts +56 -0
- package/build/search/unified.js +104 -0
- package/build/security.d.ts +206 -0
- package/build/security.js +780 -0
- package/build/server.d.ts +240 -0
- package/build/server.js +5034 -0
- package/build/session/analytics.d.ts +773 -0
- package/build/session/analytics.js +2420 -0
- package/build/session/db.d.ts +468 -0
- package/build/session/db.js +1330 -0
- package/build/session/error-classifier.d.ts +87 -0
- package/build/session/error-classifier.js +303 -0
- package/build/session/event-emit.d.ts +48 -0
- package/build/session/event-emit.js +101 -0
- package/build/session/extract.d.ts +333 -0
- package/build/session/extract.js +2688 -0
- package/build/session/model-prices.json +429 -0
- package/build/session/persist-tool-calls.d.ts +54 -0
- package/build/session/persist-tool-calls.js +105 -0
- package/build/session/pricing.d.ts +64 -0
- package/build/session/pricing.js +151 -0
- package/build/session/project-attribution.d.ts +73 -0
- package/build/session/project-attribution.js +245 -0
- package/build/session/purge.d.ts +138 -0
- package/build/session/purge.js +240 -0
- package/build/session/retrieval-marker.d.ts +39 -0
- package/build/session/retrieval-marker.js +65 -0
- package/build/session/snapshot.d.ts +46 -0
- package/build/session/snapshot.js +532 -0
- package/build/store-directory.d.ts +56 -0
- package/build/store-directory.js +254 -0
- package/build/store.d.ts +147 -0
- package/build/store.js +1714 -0
- package/build/truncate.d.ts +57 -0
- package/build/truncate.js +142 -0
- package/build/types.d.ts +117 -0
- package/build/types.js +20 -0
- package/build/util/claude-config.d.ts +32 -0
- package/build/util/claude-config.js +84 -0
- package/build/util/hook-config.d.ts +27 -0
- package/build/util/hook-config.js +76 -0
- package/build/util/jsonc.d.ts +14 -0
- package/build/util/jsonc.js +104 -0
- package/build/util/plugin-cache-integrity.d.ts +51 -0
- package/build/util/plugin-cache-integrity.js +146 -0
- package/build/util/project-dir.d.ts +149 -0
- package/build/util/project-dir.js +340 -0
- package/build/util/sibling-mcp.d.ts +79 -0
- package/build/util/sibling-mcp.js +181 -0
- package/cli.bundle.mjs +1495 -0
- package/configs/antigravity/GEMINI.md +68 -0
- package/configs/antigravity/mcp_config.json +7 -0
- package/configs/antigravity-cli/hooks/hooks.json +37 -0
- package/configs/antigravity-cli/hooks.json +37 -0
- package/configs/antigravity-cli/mcp_config.json +10 -0
- package/configs/antigravity-cli/plugin.json +14 -0
- package/configs/antigravity-cli/rules/context-mode.md +77 -0
- package/configs/antigravity-cli/skills/context-mode/SKILL.md +77 -0
- package/configs/claude-code/CLAUDE.md +91 -0
- package/configs/codex/AGENTS.md +99 -0
- package/configs/codex/config.toml +8 -0
- package/configs/codex/hooks.json +47 -0
- package/configs/copilot-cli/.github/plugin/plugin.json +23 -0
- package/configs/copilot-cli/.mcp.json +12 -0
- package/configs/copilot-cli/README.md +47 -0
- package/configs/copilot-cli/hooks.json +41 -0
- package/configs/copilot-cli/skills/context-mode/SKILL.md +38 -0
- package/configs/cursor/context-mode.mdc +76 -0
- package/configs/cursor/hooks.json +21 -0
- package/configs/cursor/mcp.json +7 -0
- package/configs/gemini-cli/GEMINI.md +88 -0
- package/configs/gemini-cli/mcp.json +7 -0
- package/configs/gemini-cli/settings.json +60 -0
- package/configs/jetbrains-copilot/copilot-instructions.md +80 -0
- package/configs/jetbrains-copilot/hooks.json +16 -0
- package/configs/jetbrains-copilot/mcp.json +7 -0
- package/configs/kilo/AGENTS.md +84 -0
- package/configs/kilo/kilo.json +6 -0
- package/configs/kimi/hooks.json +54 -0
- package/configs/kiro/KIRO.md +84 -0
- package/configs/kiro/agent.json +18 -0
- package/configs/kiro/mcp.json +7 -0
- package/configs/omp/SYSTEM.md +85 -0
- package/configs/omp/mcp.json +7 -0
- package/configs/openclaw/AGENTS.md +87 -0
- package/configs/openclaw/openclaw.json +13 -0
- package/configs/opencode/AGENTS.md +84 -0
- package/configs/opencode/opencode.json +6 -0
- package/configs/pi/AGENTS.md +3 -0
- package/configs/qwen-code/QWEN.md +91 -0
- package/configs/vscode-copilot/copilot-instructions.md +80 -0
- package/configs/vscode-copilot/hooks.json +16 -0
- package/configs/vscode-copilot/mcp.json +7 -0
- package/configs/zed/AGENTS.md +68 -0
- package/hooks/antigravity-cli/payload.mjs +98 -0
- package/hooks/antigravity-cli/posttooluse.mjs +138 -0
- package/hooks/antigravity-cli/pretooluse.mjs +78 -0
- package/hooks/antigravity-cli/stop.mjs +58 -0
- package/hooks/auto-injection.mjs +102 -0
- package/hooks/cache-heal-utils.mjs +379 -0
- package/hooks/codex/platform.mjs +1 -0
- package/hooks/codex/posttooluse.mjs +66 -0
- package/hooks/codex/precompact.mjs +70 -0
- package/hooks/codex/pretooluse.mjs +42 -0
- package/hooks/codex/sessionstart.mjs +121 -0
- package/hooks/codex/stop.mjs +138 -0
- package/hooks/codex/userpromptsubmit.mjs +75 -0
- package/hooks/copilot-cli/posttooluse.mjs +79 -0
- package/hooks/copilot-cli/precompact.mjs +66 -0
- package/hooks/copilot-cli/pretooluse.mjs +41 -0
- package/hooks/copilot-cli/sessionstart.mjs +121 -0
- package/hooks/copilot-cli/stop.mjs +59 -0
- package/hooks/copilot-cli/userpromptsubmit.mjs +77 -0
- package/hooks/core/codex-caps.mjs +112 -0
- package/hooks/core/formatters.mjs +356 -0
- package/hooks/core/mcp-ready.mjs +109 -0
- package/hooks/core/platform-detect.mjs +49 -0
- package/hooks/core/routing.mjs +1050 -0
- package/hooks/core/stdin.mjs +90 -0
- package/hooks/core/tool-naming.mjs +57 -0
- package/hooks/cursor/afteragentresponse.mjs +74 -0
- package/hooks/cursor/hooks.json +31 -0
- package/hooks/cursor/posttooluse.mjs +72 -0
- package/hooks/cursor/pretooluse.mjs +26 -0
- package/hooks/cursor/sessionstart.mjs +97 -0
- package/hooks/cursor/stop.mjs +48 -0
- package/hooks/ensure-deps.mjs +254 -0
- package/hooks/formatters/claude-code.mjs +113 -0
- package/hooks/formatters/cursor.mjs +37 -0
- package/hooks/formatters/gemini-cli.mjs +55 -0
- package/hooks/formatters/vscode-copilot.mjs +55 -0
- package/hooks/gemini-cli/aftermodel.mjs +70 -0
- package/hooks/gemini-cli/aftertool.mjs +61 -0
- package/hooks/gemini-cli/beforeagent.mjs +99 -0
- package/hooks/gemini-cli/beforetool.mjs +27 -0
- package/hooks/gemini-cli/precompress.mjs +55 -0
- package/hooks/gemini-cli/sessionstart.mjs +137 -0
- package/hooks/heal-partial-install.mjs +712 -0
- package/hooks/hooks.json +143 -0
- package/hooks/jetbrains-copilot/posttooluse.mjs +61 -0
- package/hooks/jetbrains-copilot/precompact.mjs +55 -0
- package/hooks/jetbrains-copilot/pretooluse.mjs +27 -0
- package/hooks/jetbrains-copilot/sessionstart.mjs +124 -0
- package/hooks/kimi/platform.mjs +1 -0
- package/hooks/kimi/posttooluse.mjs +72 -0
- package/hooks/kimi/precompact.mjs +80 -0
- package/hooks/kimi/pretooluse.mjs +42 -0
- package/hooks/kimi/sessionend.mjs +61 -0
- package/hooks/kimi/sessionstart.mjs +113 -0
- package/hooks/kimi/stop.mjs +132 -0
- package/hooks/kimi/userpromptsubmit.mjs +90 -0
- package/hooks/kiro/agentspawn.mjs +97 -0
- package/hooks/kiro/posttooluse.mjs +51 -0
- package/hooks/kiro/pretooluse.mjs +64 -0
- package/hooks/kiro/userpromptsubmit.mjs +88 -0
- package/hooks/normalize-hooks.mjs +323 -0
- package/hooks/platform-bridge.mjs +340 -0
- package/hooks/posttooluse.mjs +225 -0
- package/hooks/precompact.mjs +100 -0
- package/hooks/pretooluse.mjs +227 -0
- package/hooks/qwen-code/platform.mjs +1 -0
- package/hooks/qwen-code/stop.mjs +168 -0
- package/hooks/routing-block.mjs +105 -0
- package/hooks/run-hook.mjs +95 -0
- package/hooks/security.bundle.mjs +2 -0
- package/hooks/session-attribution.bundle.mjs +1 -0
- package/hooks/session-db.bundle.mjs +145 -0
- package/hooks/session-directive.mjs +520 -0
- package/hooks/session-extract.bundle.mjs +3 -0
- package/hooks/session-helpers.mjs +428 -0
- package/hooks/session-loaders.mjs +448 -0
- package/hooks/session-snapshot.bundle.mjs +32 -0
- package/hooks/sessionstart.mjs +464 -0
- package/hooks/stop.mjs +82 -0
- package/hooks/suppress-stderr.mjs +25 -0
- package/hooks/userpromptsubmit.mjs +108 -0
- package/hooks/vscode-copilot/posttooluse.mjs +61 -0
- package/hooks/vscode-copilot/precompact.mjs +55 -0
- package/hooks/vscode-copilot/pretooluse.mjs +27 -0
- package/hooks/vscode-copilot/sessionstart.mjs +129 -0
- package/openclaw.plugin.json +38 -0
- package/package.json +132 -0
- package/scripts/heal-better-sqlite3.mjs +430 -0
- package/scripts/heal-installed-plugins.mjs +635 -0
- package/scripts/plugin-cache-integrity.mjs +248 -0
- package/scripts/postinstall.mjs +397 -0
- package/server.bundle.mjs +1421 -0
- package/server.js +5 -0
- package/skills/.ignore +7 -0
- package/skills/context-mode/SKILL.md +300 -0
- package/skills/context-mode/references/anti-patterns.md +283 -0
- package/skills/context-mode/references/patterns-javascript.md +298 -0
- package/skills/context-mode/references/patterns-python.md +304 -0
- package/skills/context-mode/references/patterns-shell.md +277 -0
- package/skills/ctx-doctor/SKILL.md +22 -0
- package/skills/ctx-index/SKILL.md +46 -0
- package/skills/ctx-insight/SKILL.md +24 -0
- package/skills/ctx-purge/SKILL.md +49 -0
- package/skills/ctx-search/SKILL.md +35 -0
- package/skills/ctx-stats/SKILL.md +26 -0
- package/skills/ctx-upgrade/SKILL.md +31 -0
- package/start.mjs +616 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/antigravity-cli — Google Antigravity CLI (`agy`) adapter.
|
|
3
|
+
*
|
|
4
|
+
* Integration: MCP tools plus agy's Claude-compatible hook surface. `agy`
|
|
5
|
+
* reads its global MCP profile from `~/.gemini/config/mcp_config.json`
|
|
6
|
+
* (distinct from the Antigravity IDE's `~/.gemini/antigravity/mcp_config.json`)
|
|
7
|
+
* and hooks from `~/.gemini/config/hooks.json`, or from an installed agy
|
|
8
|
+
* plugin's root `hooks.json`.
|
|
9
|
+
*
|
|
10
|
+
* context-mode wires only the surfaces that have a verified mapping:
|
|
11
|
+
* - PreToolUse for bounded routing enforcement on Bash/Read/Grep/WebFetch
|
|
12
|
+
* - PostToolUse capture for executed tool calls
|
|
13
|
+
* - best-effort Stop capture for session-end continuity when agy emits it
|
|
14
|
+
*
|
|
15
|
+
* PreInvocation/PostInvocation are intentionally not registered here: there is
|
|
16
|
+
* no verified payload/response contract or shared context-mode pipeline target
|
|
17
|
+
* for those agy events yet.
|
|
18
|
+
*/
|
|
19
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { dirname, resolve } from "node:path";
|
|
21
|
+
import { homedir } from "node:os";
|
|
22
|
+
import { AntigravityAdapter } from "../antigravity/index.js";
|
|
23
|
+
import { parseJsonc } from "../../util/jsonc.js";
|
|
24
|
+
export function antigravityCliMcpConfigPath() {
|
|
25
|
+
return resolve(homedir(), ".gemini", "config", "mcp_config.json");
|
|
26
|
+
}
|
|
27
|
+
export function antigravityCliConfigDir() {
|
|
28
|
+
return resolve(homedir(), ".gemini", "antigravity-cli");
|
|
29
|
+
}
|
|
30
|
+
/** agy reads user hooks from ~/.gemini/config/hooks.json (sibling of mcp_config.json). */
|
|
31
|
+
export function antigravityCliHooksPath() {
|
|
32
|
+
return resolve(homedir(), ".gemini", "config", "hooks.json");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* `agy plugin install <bundle>` registers MCP + hook + skill into agy's plugin
|
|
36
|
+
* profile under ~/.gemini/config/plugins/<name>/ (verified on agy 1.0.6, re-verified 1.0.10) — the
|
|
37
|
+
* canonical install. The global mcp_config.json / hooks.json paths above are the
|
|
38
|
+
* manual (no-plugin) fallback. doctor must recognize BOTH.
|
|
39
|
+
*/
|
|
40
|
+
export function antigravityCliPluginDir() {
|
|
41
|
+
return resolve(homedir(), ".gemini", "config", "plugins", "context-mode");
|
|
42
|
+
}
|
|
43
|
+
function antigravityCliPluginMcpPath() {
|
|
44
|
+
return resolve(antigravityCliPluginDir(), "mcp_config.json");
|
|
45
|
+
}
|
|
46
|
+
function antigravityCliPluginHooksPath() {
|
|
47
|
+
return resolve(antigravityCliPluginDir(), "hooks.json");
|
|
48
|
+
}
|
|
49
|
+
/** True if context-mode's MCP is registered in any agy profile (plugin or global). */
|
|
50
|
+
function readMcpRegistered(paths) {
|
|
51
|
+
for (const path of paths) {
|
|
52
|
+
try {
|
|
53
|
+
const config = parseJsonc(readFileSync(path, "utf-8")) ?? {};
|
|
54
|
+
const mcpServers = config?.mcpServers ?? {};
|
|
55
|
+
if ("context-mode" in mcpServers)
|
|
56
|
+
return { ok: true, where: path };
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
/* unreadable/missing — try next */
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { ok: false };
|
|
63
|
+
}
|
|
64
|
+
function asRecord(value) {
|
|
65
|
+
return value && typeof value === "object" ? value : {};
|
|
66
|
+
}
|
|
67
|
+
function agyProjectDir(raw) {
|
|
68
|
+
// Refs-backed FIRST: the only real upstream hook-payload example
|
|
69
|
+
// (refs/platforms/antigravity-cli/examples/title/title.sh:10, README.md:11)
|
|
70
|
+
// reads the working directory from `workspace.current_dir` — an OBJECT field,
|
|
71
|
+
// not an array. Prefer it.
|
|
72
|
+
const workspace = asRecord(raw.workspace);
|
|
73
|
+
if (typeof workspace.current_dir === "string" && workspace.current_dir) {
|
|
74
|
+
return workspace.current_dir;
|
|
75
|
+
}
|
|
76
|
+
// Fallback: `workspacePaths[0]` is empirically-derived/unverified (no upstream
|
|
77
|
+
// doc or example confirms this shape) — retained as a defensive fallback only.
|
|
78
|
+
const workspacePaths = raw.workspacePaths;
|
|
79
|
+
return Array.isArray(workspacePaths) && workspacePaths.length > 0
|
|
80
|
+
? String(workspacePaths[0])
|
|
81
|
+
: undefined;
|
|
82
|
+
}
|
|
83
|
+
function agySessionId(raw) {
|
|
84
|
+
// `conversationId` is empirically-derived/unverified — no upstream agy doc or
|
|
85
|
+
// example confirms a session-id field in the hook payload. Kept as the
|
|
86
|
+
// best-available identifier; falls back to the process id when absent.
|
|
87
|
+
return typeof raw.conversationId === "string" && raw.conversationId
|
|
88
|
+
? raw.conversationId
|
|
89
|
+
: `pid-${process.ppid}`;
|
|
90
|
+
}
|
|
91
|
+
function hookEntryHasCommand(entry, command) {
|
|
92
|
+
const e = asRecord(entry);
|
|
93
|
+
const nested = Array.isArray(e.hooks) ? e.hooks : [];
|
|
94
|
+
return nested.some((hook) => asRecord(hook).command === command);
|
|
95
|
+
}
|
|
96
|
+
function hookEntryJsonHasCommand(entry, command) {
|
|
97
|
+
return JSON.stringify(entry).includes(command);
|
|
98
|
+
}
|
|
99
|
+
function matcherCoversAgyPreToolUse(matcher) {
|
|
100
|
+
if (typeof matcher !== "string")
|
|
101
|
+
return false;
|
|
102
|
+
return ["run_command", "view_file", "grep_search", "web_fetch", "read_url_content"].every((tool) => matcher.includes(tool));
|
|
103
|
+
}
|
|
104
|
+
function hookGroupHasCommand(group, command) {
|
|
105
|
+
return Array.isArray(group) && group.some((entry) => hookEntryHasCommand(entry, command));
|
|
106
|
+
}
|
|
107
|
+
function hookGroupHasPreToolUse(group) {
|
|
108
|
+
return Array.isArray(group) && group.some((entry) => {
|
|
109
|
+
const e = asRecord(entry);
|
|
110
|
+
return matcherCoversAgyPreToolUse(e.matcher) && hookEntryHasCommand(entry, PRE_HOOK_COMMAND);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/** True if all context-mode agy hooks are registered in plugin/global profiles. */
|
|
114
|
+
function readRegisteredHooks(paths) {
|
|
115
|
+
const found = { preOk: false, postOk: false, stopOk: false, where: undefined };
|
|
116
|
+
for (const path of paths) {
|
|
117
|
+
try {
|
|
118
|
+
const config = parseJsonc(readFileSync(path, "utf-8")) ?? {};
|
|
119
|
+
const hooks = config.hooks ?? {};
|
|
120
|
+
const preOk = hookGroupHasPreToolUse(hooks.PreToolUse);
|
|
121
|
+
const postOk = hookGroupHasCommand(hooks.PostToolUse, POST_HOOK_COMMAND);
|
|
122
|
+
const stopOk = hookGroupHasCommand(hooks.Stop, STOP_HOOK_COMMAND);
|
|
123
|
+
if ((preOk || postOk || stopOk) && !found.where)
|
|
124
|
+
found.where = path;
|
|
125
|
+
found.preOk ||= preOk;
|
|
126
|
+
found.postOk ||= postOk;
|
|
127
|
+
found.stopOk ||= stopOk;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
/* unreadable/missing — try next */
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Stop is registered when possible, but agy 1.0.6 `-p` probes did not emit it
|
|
134
|
+
// (no Stop-hook change in agy's changelog through 1.0.10). Treat it as
|
|
135
|
+
// best-effort so doctor does not mark a working Pre/Post install
|
|
136
|
+
// as degraded solely because Stop is absent.
|
|
137
|
+
return { ...found, ok: found.preOk && found.postOk };
|
|
138
|
+
}
|
|
139
|
+
/** Dispatcher commands agy invokes from hooks.json. */
|
|
140
|
+
const PRE_HOOK_COMMAND = "context-mode hook antigravity-cli pretooluse";
|
|
141
|
+
const PRE_HOOK_MATCHER = "run_command|view_file|grep_search|web_fetch|read_url_content";
|
|
142
|
+
const POST_HOOK_COMMAND = "context-mode hook antigravity-cli posttooluse";
|
|
143
|
+
const STOP_HOOK_COMMAND = "context-mode hook antigravity-cli stop";
|
|
144
|
+
// Keep in sync with the identical agyContextReason in hooks/core/formatters.mjs:
|
|
145
|
+
// two copies exist because the bundled .mjs formatter (runtime hook path) and
|
|
146
|
+
// this TS adapter are separate layers; the deny-reason text must not drift.
|
|
147
|
+
function agyContextReason(additionalContext) {
|
|
148
|
+
const text = String(additionalContext ?? "")
|
|
149
|
+
.replace(/<\/?context_guidance>/g, " ")
|
|
150
|
+
.replace(/<\/?tip>/g, " ")
|
|
151
|
+
.replace(/\s+/g, " ")
|
|
152
|
+
.trim();
|
|
153
|
+
return text
|
|
154
|
+
? `context-mode: use the context-mode MCP tools instead of this native tool. ${text}`
|
|
155
|
+
: "context-mode: use the context-mode MCP tools instead of this native tool so raw bytes stay out of the conversation.";
|
|
156
|
+
}
|
|
157
|
+
function configureHookEntry(hooks, type, desired, command) {
|
|
158
|
+
const current = Array.isArray(hooks[type]) ? hooks[type] : [];
|
|
159
|
+
const desiredJson = JSON.stringify(desired);
|
|
160
|
+
const alreadyExact = current.some((entry) => JSON.stringify(entry) === desiredJson) &&
|
|
161
|
+
current.every((entry) => !hookEntryJsonHasCommand(entry, command) || JSON.stringify(entry) === desiredJson);
|
|
162
|
+
if (alreadyExact)
|
|
163
|
+
return false;
|
|
164
|
+
hooks[type] = [
|
|
165
|
+
...current.filter((entry) => !hookEntryJsonHasCommand(entry, command)),
|
|
166
|
+
desired,
|
|
167
|
+
];
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
export class AntigravityCliAdapter extends AntigravityAdapter {
|
|
171
|
+
name = "Antigravity CLI";
|
|
172
|
+
paradigm = "json-stdio";
|
|
173
|
+
capabilities = {
|
|
174
|
+
preToolUse: true,
|
|
175
|
+
postToolUse: true,
|
|
176
|
+
preCompact: false,
|
|
177
|
+
sessionStart: false,
|
|
178
|
+
canModifyArgs: false,
|
|
179
|
+
canModifyOutput: false,
|
|
180
|
+
canInjectSessionContext: false,
|
|
181
|
+
};
|
|
182
|
+
getSettingsPath() {
|
|
183
|
+
return antigravityCliMcpConfigPath();
|
|
184
|
+
}
|
|
185
|
+
getConfigDir(_projectDir) {
|
|
186
|
+
return antigravityCliConfigDir();
|
|
187
|
+
}
|
|
188
|
+
parsePreToolUseInput(raw) {
|
|
189
|
+
const payload = asRecord(raw);
|
|
190
|
+
const toolCall = asRecord(payload.toolCall);
|
|
191
|
+
return {
|
|
192
|
+
toolName: typeof toolCall.name === "string" ? toolCall.name : "",
|
|
193
|
+
toolInput: asRecord(toolCall.args),
|
|
194
|
+
sessionId: agySessionId(payload),
|
|
195
|
+
projectDir: agyProjectDir(payload),
|
|
196
|
+
raw,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
parsePostToolUseInput(raw) {
|
|
200
|
+
const payload = asRecord(raw);
|
|
201
|
+
const toolCall = asRecord(payload.toolCall);
|
|
202
|
+
const error = typeof payload.error === "string" ? payload.error : "";
|
|
203
|
+
return {
|
|
204
|
+
toolName: typeof toolCall.name === "string" ? toolCall.name : "",
|
|
205
|
+
toolInput: asRecord(toolCall.args),
|
|
206
|
+
toolOutput: error,
|
|
207
|
+
isError: error.length > 0,
|
|
208
|
+
sessionId: agySessionId(payload),
|
|
209
|
+
projectDir: agyProjectDir(payload),
|
|
210
|
+
raw,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
formatPreToolUseResponse(response) {
|
|
214
|
+
if (response.decision === "deny") {
|
|
215
|
+
return { decision: "deny", reason: response.reason ?? "Denied by context-mode" };
|
|
216
|
+
}
|
|
217
|
+
if (response.decision === "ask") {
|
|
218
|
+
// Fallback reason so a security-policy ask is never a bare prompt
|
|
219
|
+
// (mirrors hooks/core/formatters.mjs antigravity-cli.ask).
|
|
220
|
+
return {
|
|
221
|
+
decision: "ask",
|
|
222
|
+
reason: response.reason ?? "Action requires user confirmation",
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
if (response.decision === "context" && response.additionalContext) {
|
|
226
|
+
return {
|
|
227
|
+
decision: "deny",
|
|
228
|
+
reason: agyContextReason(response.additionalContext),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
formatPostToolUseResponse(_response) {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
checkPluginRegistration() {
|
|
237
|
+
// Accept the plugin profile (the canonical `agy plugin install` location,
|
|
238
|
+
// ~/.gemini/config/plugins/context-mode/mcp_config.json) OR the global
|
|
239
|
+
// mcp_config.json (the manual no-plugin fallback).
|
|
240
|
+
const { ok, where } = readMcpRegistered([
|
|
241
|
+
antigravityCliPluginMcpPath(),
|
|
242
|
+
this.getSettingsPath(),
|
|
243
|
+
]);
|
|
244
|
+
if (ok) {
|
|
245
|
+
return {
|
|
246
|
+
check: "MCP registration",
|
|
247
|
+
status: "pass",
|
|
248
|
+
message: `context-mode found in Antigravity CLI mcpServers (${where})`,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
check: "MCP registration",
|
|
253
|
+
status: "fail",
|
|
254
|
+
message: "context-mode not found in Antigravity CLI mcpServers",
|
|
255
|
+
fix: "agy plugin install https://github.com/mksglu/context-mode/tree/main/configs/antigravity-cli",
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
getInstalledVersion() {
|
|
259
|
+
// Plugin install: read the real version from the installed plugin manifest so
|
|
260
|
+
// the doctor compares a true semver against npm latest (PASS when current,
|
|
261
|
+
// a meaningful "outdated bundle" WARN otherwise) — not the literal "configured"
|
|
262
|
+
// which produced the bogus "vconfigured, latest vX" line.
|
|
263
|
+
try {
|
|
264
|
+
const manifest = parseJsonc(readFileSync(resolve(antigravityCliPluginDir(), "plugin.json"), "utf-8"));
|
|
265
|
+
if (manifest && typeof manifest.version === "string" && manifest.version) {
|
|
266
|
+
return manifest.version;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
/* not plugin-installed — fall through */
|
|
271
|
+
}
|
|
272
|
+
// Manual (global-profile) registration has no plugin manifest: report the
|
|
273
|
+
// version-less "standalone" MCP mode so doctor shows INFO, not a false WARN.
|
|
274
|
+
if (readMcpRegistered([this.getSettingsPath()]).ok)
|
|
275
|
+
return "standalone";
|
|
276
|
+
return "not installed";
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Write/merge the agy hooks into ~/.gemini/config/hooks.json (manual,
|
|
280
|
+
* non-plugin path). Idempotent — unrelated hook entries are preserved.
|
|
281
|
+
*/
|
|
282
|
+
configureAllHooks(_pluginRoot) {
|
|
283
|
+
const changes = [];
|
|
284
|
+
const hooksPath = antigravityCliHooksPath();
|
|
285
|
+
let config = {};
|
|
286
|
+
try {
|
|
287
|
+
config = parseJsonc(readFileSync(hooksPath, "utf-8")) ?? {};
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
/* fresh file */
|
|
291
|
+
}
|
|
292
|
+
const hooks = config.hooks ?? {};
|
|
293
|
+
const desiredPre = {
|
|
294
|
+
matcher: PRE_HOOK_MATCHER,
|
|
295
|
+
hooks: [{ type: "command", command: PRE_HOOK_COMMAND }],
|
|
296
|
+
};
|
|
297
|
+
const desiredPost = {
|
|
298
|
+
matcher: "",
|
|
299
|
+
hooks: [{ type: "command", command: POST_HOOK_COMMAND }],
|
|
300
|
+
};
|
|
301
|
+
const desiredStop = {
|
|
302
|
+
matcher: "",
|
|
303
|
+
hooks: [{ type: "command", command: STOP_HOOK_COMMAND }],
|
|
304
|
+
};
|
|
305
|
+
const changedPre = configureHookEntry(hooks, "PreToolUse", desiredPre, PRE_HOOK_COMMAND);
|
|
306
|
+
const changedPost = configureHookEntry(hooks, "PostToolUse", desiredPost, POST_HOOK_COMMAND);
|
|
307
|
+
const changedStop = configureHookEntry(hooks, "Stop", desiredStop, STOP_HOOK_COMMAND);
|
|
308
|
+
const changed = changedPre || changedPost || changedStop;
|
|
309
|
+
if (changed) {
|
|
310
|
+
config.hooks = hooks;
|
|
311
|
+
mkdirSync(dirname(hooksPath), { recursive: true });
|
|
312
|
+
writeFileSync(hooksPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
313
|
+
changes.push(`Configured Antigravity CLI PreToolUse/PostToolUse hooks and best-effort Stop hook in ${hooksPath}`);
|
|
314
|
+
}
|
|
315
|
+
return changes;
|
|
316
|
+
}
|
|
317
|
+
/** Report agy hook status across plugin and manual profiles. */
|
|
318
|
+
validateHooks(_pluginRoot) {
|
|
319
|
+
// Accept the plugin profile (the canonical `agy plugin install` location,
|
|
320
|
+
// ~/.gemini/config/plugins/context-mode/hooks.json) OR the global hooks.json
|
|
321
|
+
// (the manual `context-mode upgrade` fallback).
|
|
322
|
+
const { ok, where, preOk, postOk, stopOk } = readRegisteredHooks([
|
|
323
|
+
antigravityCliPluginHooksPath(),
|
|
324
|
+
antigravityCliHooksPath(),
|
|
325
|
+
]);
|
|
326
|
+
const missing = [
|
|
327
|
+
preOk ? null : "PreToolUse",
|
|
328
|
+
postOk ? null : "PostToolUse",
|
|
329
|
+
// Stop is best-effort; do not include it in the health gate.
|
|
330
|
+
].filter(Boolean).join(", ");
|
|
331
|
+
return [
|
|
332
|
+
{
|
|
333
|
+
check: "Antigravity CLI hooks",
|
|
334
|
+
status: ok ? "pass" : "warn",
|
|
335
|
+
message: ok
|
|
336
|
+
? `PreToolUse guard and PostToolUse capture configured in ${where}${stopOk ? "; best-effort Stop hook also configured" : ""}`
|
|
337
|
+
: `Antigravity CLI hooks incomplete (${missing || "none found"} missing) — MCP tools still work, but bounded routing enforcement and session capture are degraded. Run \`agy plugin install https://github.com/mksglu/context-mode/tree/main/configs/antigravity-cli\` or \`context-mode upgrade\` to repair hooks.`,
|
|
338
|
+
...(ok ? {} : { fix: "agy plugin install https://github.com/mksglu/context-mode/tree/main/configs/antigravity-cli" }),
|
|
339
|
+
},
|
|
340
|
+
];
|
|
341
|
+
}
|
|
342
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseAdapter — shared implementation for methods identical across all adapters.
|
|
3
|
+
*
|
|
4
|
+
* Each concrete adapter extends this and provides platform-specific logic.
|
|
5
|
+
*
|
|
6
|
+
* Shared methods:
|
|
7
|
+
* - getSessionDir() — builds session dir from sessionDirSegments
|
|
8
|
+
* - backupSettings() — copies settings file to .bak
|
|
9
|
+
*
|
|
10
|
+
* Adapters with custom logic override the relevant method:
|
|
11
|
+
* - vscode-copilot: overrides getSessionDir (checks .github dir)
|
|
12
|
+
* - opencode: overrides getSessionDir (XDG_CONFIG_HOME / APPDATA)
|
|
13
|
+
* and backupSettings (calls checkPluginRegistration first)
|
|
14
|
+
* - openclaw: overrides backupSettings (searches 3 config paths)
|
|
15
|
+
*
|
|
16
|
+
* NOTE — C2 narrowing (2026-05): `getSessionDBPath` and `getSessionEventsPath`
|
|
17
|
+
* were removed. Both were SHALLOW pure derivatives of `getSessionDir() +
|
|
18
|
+
* projectDir` (interface complexity == implementation complexity). All
|
|
19
|
+
* adapter-storage path computation now flows through ONE site:
|
|
20
|
+
* `resolveSessionDbPath({ projectDir, sessionsDir: adapter.getSessionDir() })`
|
|
21
|
+
* in `src/session/db.ts`. Adapters expose only `getSessionDir()` for
|
|
22
|
+
* storage-related path concerns.
|
|
23
|
+
*
|
|
24
|
+
* Issue #649 — `CONTEXT_MODE_DATA_DIR` universal storage override. Many
|
|
25
|
+
* adapters (Pi, OMP, Gemini CLI, Codex, Cursor, …) had storage hardcoded to
|
|
26
|
+
* `~/.<platform>/context-mode/sessions/` with no env-var escape hatch. CI
|
|
27
|
+
* runners on NFS homes, dev containers, and shared-workspace setups need to
|
|
28
|
+
* point context-mode storage at a writable volume without patching source or
|
|
29
|
+
* abusing the host platform's own config-dir variable. The override applies
|
|
30
|
+
* only to context-mode-owned state (`getSessionDir`, `getMemoryDir`) — never
|
|
31
|
+
* to platform-native config (`getConfigDir`, `getSettingsPath`), which must
|
|
32
|
+
* stay where the host platform's own tooling expects it. Adapters that
|
|
33
|
+
* override `getSessionDir`/`getMemoryDir` directly (claude-code, codex,
|
|
34
|
+
* opencode, vscode-copilot) honor the override by routing through
|
|
35
|
+
* `resolveContextModeDataRoot()` at the top of their override.
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* Universal storage-root override. Returns the resolved absolute path when
|
|
39
|
+
* `CONTEXT_MODE_DATA_DIR` is set to a non-blank value, otherwise `null` so
|
|
40
|
+
* callers fall back to their platform-native default.
|
|
41
|
+
*
|
|
42
|
+
* Mirrors the `resolveClaudeConfigDir` contract for env-var handling
|
|
43
|
+
* (whitespace guard, tilde expansion, relative-path resolution) so users
|
|
44
|
+
* get one consistent set of rules across every override site.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveContextModeDataRoot(env?: NodeJS.ProcessEnv): string | null;
|
|
47
|
+
export declare abstract class BaseAdapter {
|
|
48
|
+
protected readonly sessionDirSegments: string[];
|
|
49
|
+
constructor(sessionDirSegments: string[]);
|
|
50
|
+
getSessionDir(): string;
|
|
51
|
+
/**
|
|
52
|
+
* Default: build config dir from sessionDirSegments rooted at $HOME.
|
|
53
|
+
*
|
|
54
|
+
* Contract: ALWAYS returns an absolute path. Adapters with project-scoped
|
|
55
|
+
* or non-home-rooted config dirs (cursor, vscode-copilot, jetbrains-copilot,
|
|
56
|
+
* openclaw, opencode) override this and resolve their segments against
|
|
57
|
+
* `projectDir` (or `process.cwd()` when omitted).
|
|
58
|
+
*
|
|
59
|
+
* NOT relocated by `CONTEXT_MODE_DATA_DIR` (#649). The platform owns its
|
|
60
|
+
* settings.json / hooks.json / config.toml location — relocating that
|
|
61
|
+
* would silently fork platform behaviour from the platform's own tooling.
|
|
62
|
+
* Use `CLAUDE_CONFIG_DIR`, `CODEX_HOME`, `XDG_CONFIG_HOME`, etc. to move
|
|
63
|
+
* platform-native config; use `CONTEXT_MODE_DATA_DIR` to move context-mode
|
|
64
|
+
* storage independently.
|
|
65
|
+
*
|
|
66
|
+
* @param _projectDir Unused by the home-rooted default — accepted so
|
|
67
|
+
* project-scoped overrides honor the same signature.
|
|
68
|
+
*/
|
|
69
|
+
getConfigDir(_projectDir?: string): string;
|
|
70
|
+
/**
|
|
71
|
+
* Default: Claude Code convention. Most adapters override with their
|
|
72
|
+
* own platform-specific instruction file name (AGENTS.md, GEMINI.md, ...).
|
|
73
|
+
*/
|
|
74
|
+
getInstructionFiles(): string[];
|
|
75
|
+
/**
|
|
76
|
+
* Default: <configDir>/memory/<projectHash>. Always absolute (configDir is
|
|
77
|
+
* absolute by contract). Adapters with a different memory dir name (e.g.,
|
|
78
|
+
* codex uses "memories" plural) override this.
|
|
79
|
+
*
|
|
80
|
+
* Issue #649: when `CONTEXT_MODE_DATA_DIR` is set, memory follows storage
|
|
81
|
+
* to `<DATA_DIR>/context-mode/memory/` since persistent memory is
|
|
82
|
+
* context-mode-owned state, not platform-native config.
|
|
83
|
+
*
|
|
84
|
+
* Issue #663: when `projectDir` is supplied the path is scoped via
|
|
85
|
+
* `hashProjectDirCanonical(projectDir)` so two projects running in
|
|
86
|
+
* parallel never share auto-memory contents. When omitted (legacy
|
|
87
|
+
* callers), the unscoped path is returned for backwards compatibility.
|
|
88
|
+
*/
|
|
89
|
+
getMemoryDir(projectDir?: string): string;
|
|
90
|
+
backupSettings(): string | null;
|
|
91
|
+
abstract getSettingsPath(): string;
|
|
92
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseAdapter — shared implementation for methods identical across all adapters.
|
|
3
|
+
*
|
|
4
|
+
* Each concrete adapter extends this and provides platform-specific logic.
|
|
5
|
+
*
|
|
6
|
+
* Shared methods:
|
|
7
|
+
* - getSessionDir() — builds session dir from sessionDirSegments
|
|
8
|
+
* - backupSettings() — copies settings file to .bak
|
|
9
|
+
*
|
|
10
|
+
* Adapters with custom logic override the relevant method:
|
|
11
|
+
* - vscode-copilot: overrides getSessionDir (checks .github dir)
|
|
12
|
+
* - opencode: overrides getSessionDir (XDG_CONFIG_HOME / APPDATA)
|
|
13
|
+
* and backupSettings (calls checkPluginRegistration first)
|
|
14
|
+
* - openclaw: overrides backupSettings (searches 3 config paths)
|
|
15
|
+
*
|
|
16
|
+
* NOTE — C2 narrowing (2026-05): `getSessionDBPath` and `getSessionEventsPath`
|
|
17
|
+
* were removed. Both were SHALLOW pure derivatives of `getSessionDir() +
|
|
18
|
+
* projectDir` (interface complexity == implementation complexity). All
|
|
19
|
+
* adapter-storage path computation now flows through ONE site:
|
|
20
|
+
* `resolveSessionDbPath({ projectDir, sessionsDir: adapter.getSessionDir() })`
|
|
21
|
+
* in `src/session/db.ts`. Adapters expose only `getSessionDir()` for
|
|
22
|
+
* storage-related path concerns.
|
|
23
|
+
*
|
|
24
|
+
* Issue #649 — `CONTEXT_MODE_DATA_DIR` universal storage override. Many
|
|
25
|
+
* adapters (Pi, OMP, Gemini CLI, Codex, Cursor, …) had storage hardcoded to
|
|
26
|
+
* `~/.<platform>/context-mode/sessions/` with no env-var escape hatch. CI
|
|
27
|
+
* runners on NFS homes, dev containers, and shared-workspace setups need to
|
|
28
|
+
* point context-mode storage at a writable volume without patching source or
|
|
29
|
+
* abusing the host platform's own config-dir variable. The override applies
|
|
30
|
+
* only to context-mode-owned state (`getSessionDir`, `getMemoryDir`) — never
|
|
31
|
+
* to platform-native config (`getConfigDir`, `getSettingsPath`), which must
|
|
32
|
+
* stay where the host platform's own tooling expects it. Adapters that
|
|
33
|
+
* override `getSessionDir`/`getMemoryDir` directly (claude-code, codex,
|
|
34
|
+
* opencode, vscode-copilot) honor the override by routing through
|
|
35
|
+
* `resolveContextModeDataRoot()` at the top of their override.
|
|
36
|
+
*/
|
|
37
|
+
import { join, resolve } from "node:path";
|
|
38
|
+
import { accessSync, copyFileSync, constants, mkdirSync } from "node:fs";
|
|
39
|
+
import { homedir } from "node:os";
|
|
40
|
+
import { hashProjectDirCanonical } from "../session/db.js";
|
|
41
|
+
/**
|
|
42
|
+
* Universal storage-root override. Returns the resolved absolute path when
|
|
43
|
+
* `CONTEXT_MODE_DATA_DIR` is set to a non-blank value, otherwise `null` so
|
|
44
|
+
* callers fall back to their platform-native default.
|
|
45
|
+
*
|
|
46
|
+
* Mirrors the `resolveClaudeConfigDir` contract for env-var handling
|
|
47
|
+
* (whitespace guard, tilde expansion, relative-path resolution) so users
|
|
48
|
+
* get one consistent set of rules across every override site.
|
|
49
|
+
*/
|
|
50
|
+
export function resolveContextModeDataRoot(env = process.env) {
|
|
51
|
+
const raw = env.CONTEXT_MODE_DATA_DIR;
|
|
52
|
+
if (!raw || raw.trim() === "")
|
|
53
|
+
return null;
|
|
54
|
+
if (raw.startsWith("~")) {
|
|
55
|
+
return resolve(homedir(), raw.replace(/^~[/\\]?/, ""));
|
|
56
|
+
}
|
|
57
|
+
return resolve(raw);
|
|
58
|
+
}
|
|
59
|
+
export class BaseAdapter {
|
|
60
|
+
sessionDirSegments;
|
|
61
|
+
constructor(sessionDirSegments) {
|
|
62
|
+
this.sessionDirSegments = sessionDirSegments;
|
|
63
|
+
}
|
|
64
|
+
getSessionDir() {
|
|
65
|
+
const override = resolveContextModeDataRoot();
|
|
66
|
+
const dir = override
|
|
67
|
+
? join(override, "context-mode", "sessions")
|
|
68
|
+
: join(homedir(), ...this.sessionDirSegments, "context-mode", "sessions");
|
|
69
|
+
mkdirSync(dir, { recursive: true });
|
|
70
|
+
return dir;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Default: build config dir from sessionDirSegments rooted at $HOME.
|
|
74
|
+
*
|
|
75
|
+
* Contract: ALWAYS returns an absolute path. Adapters with project-scoped
|
|
76
|
+
* or non-home-rooted config dirs (cursor, vscode-copilot, jetbrains-copilot,
|
|
77
|
+
* openclaw, opencode) override this and resolve their segments against
|
|
78
|
+
* `projectDir` (or `process.cwd()` when omitted).
|
|
79
|
+
*
|
|
80
|
+
* NOT relocated by `CONTEXT_MODE_DATA_DIR` (#649). The platform owns its
|
|
81
|
+
* settings.json / hooks.json / config.toml location — relocating that
|
|
82
|
+
* would silently fork platform behaviour from the platform's own tooling.
|
|
83
|
+
* Use `CLAUDE_CONFIG_DIR`, `CODEX_HOME`, `XDG_CONFIG_HOME`, etc. to move
|
|
84
|
+
* platform-native config; use `CONTEXT_MODE_DATA_DIR` to move context-mode
|
|
85
|
+
* storage independently.
|
|
86
|
+
*
|
|
87
|
+
* @param _projectDir Unused by the home-rooted default — accepted so
|
|
88
|
+
* project-scoped overrides honor the same signature.
|
|
89
|
+
*/
|
|
90
|
+
getConfigDir(_projectDir) {
|
|
91
|
+
return join(homedir(), ...this.sessionDirSegments);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Default: Claude Code convention. Most adapters override with their
|
|
95
|
+
* own platform-specific instruction file name (AGENTS.md, GEMINI.md, ...).
|
|
96
|
+
*/
|
|
97
|
+
getInstructionFiles() {
|
|
98
|
+
return ["CLAUDE.md"];
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Default: <configDir>/memory/<projectHash>. Always absolute (configDir is
|
|
102
|
+
* absolute by contract). Adapters with a different memory dir name (e.g.,
|
|
103
|
+
* codex uses "memories" plural) override this.
|
|
104
|
+
*
|
|
105
|
+
* Issue #649: when `CONTEXT_MODE_DATA_DIR` is set, memory follows storage
|
|
106
|
+
* to `<DATA_DIR>/context-mode/memory/` since persistent memory is
|
|
107
|
+
* context-mode-owned state, not platform-native config.
|
|
108
|
+
*
|
|
109
|
+
* Issue #663: when `projectDir` is supplied the path is scoped via
|
|
110
|
+
* `hashProjectDirCanonical(projectDir)` so two projects running in
|
|
111
|
+
* parallel never share auto-memory contents. When omitted (legacy
|
|
112
|
+
* callers), the unscoped path is returned for backwards compatibility.
|
|
113
|
+
*/
|
|
114
|
+
getMemoryDir(projectDir) {
|
|
115
|
+
const override = resolveContextModeDataRoot();
|
|
116
|
+
const base = override
|
|
117
|
+
? join(override, "context-mode", "memory")
|
|
118
|
+
: join(this.getConfigDir(), "memory");
|
|
119
|
+
if (!projectDir)
|
|
120
|
+
return base;
|
|
121
|
+
return join(base, hashProjectDirCanonical(projectDir));
|
|
122
|
+
}
|
|
123
|
+
backupSettings() {
|
|
124
|
+
const settingsPath = this.getSettingsPath();
|
|
125
|
+
try {
|
|
126
|
+
accessSync(settingsPath, constants.R_OK);
|
|
127
|
+
const backupPath = settingsPath + ".bak";
|
|
128
|
+
copyFileSync(settingsPath, backupPath);
|
|
129
|
+
return backupPath;
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/claude-code/config — Thin re-exports from ClaudeCodeAdapter.
|
|
3
|
+
*
|
|
4
|
+
* This module exists for backward compatibility. All logic lives in the
|
|
5
|
+
* adapter class (index.ts). New code should use getAdapter() from detect.ts.
|
|
6
|
+
*/
|
|
7
|
+
export { ClaudeCodeAdapter } from "./index.js";
|
|
8
|
+
export { HOOK_TYPES, HOOK_SCRIPTS, PRE_TOOL_USE_MATCHER_PATTERN } from "./hooks.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/claude-code/config — Thin re-exports from ClaudeCodeAdapter.
|
|
3
|
+
*
|
|
4
|
+
* This module exists for backward compatibility. All logic lives in the
|
|
5
|
+
* adapter class (index.ts). New code should use getAdapter() from detect.ts.
|
|
6
|
+
*/
|
|
7
|
+
export { ClaudeCodeAdapter } from "./index.js";
|
|
8
|
+
export { HOOK_TYPES, HOOK_SCRIPTS, PRE_TOOL_USE_MATCHER_PATTERN } from "./hooks.js";
|