context-mode 1.0.103 → 1.0.104

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.
Files changed (97) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.openclaw-plugin/openclaw.plugin.json +1 -1
  4. package/.openclaw-plugin/package.json +1 -1
  5. package/README.md +66 -5
  6. package/bin/statusline.mjs +321 -0
  7. package/build/adapters/antigravity/index.d.ts +6 -0
  8. package/build/adapters/antigravity/index.js +10 -0
  9. package/build/adapters/base.d.ts +23 -0
  10. package/build/adapters/base.js +29 -0
  11. package/build/adapters/codex/index.d.ts +10 -0
  12. package/build/adapters/codex/index.js +22 -4
  13. package/build/adapters/cursor/index.d.ts +7 -0
  14. package/build/adapters/cursor/index.js +11 -0
  15. package/build/adapters/detect.d.ts +12 -1
  16. package/build/adapters/detect.js +69 -7
  17. package/build/adapters/gemini-cli/index.d.ts +8 -1
  18. package/build/adapters/gemini-cli/index.js +19 -7
  19. package/build/adapters/jetbrains-copilot/index.d.ts +7 -0
  20. package/build/adapters/jetbrains-copilot/index.js +12 -0
  21. package/build/adapters/kiro/index.d.ts +8 -0
  22. package/build/adapters/kiro/index.js +12 -0
  23. package/build/adapters/openclaw/index.d.ts +17 -0
  24. package/build/adapters/openclaw/index.js +29 -4
  25. package/build/adapters/opencode/index.d.ts +8 -0
  26. package/build/adapters/opencode/index.js +18 -6
  27. package/build/adapters/qwen-code/index.d.ts +1 -0
  28. package/build/adapters/qwen-code/index.js +3 -0
  29. package/build/adapters/types.d.ts +33 -0
  30. package/build/adapters/vscode-copilot/index.d.ts +6 -0
  31. package/build/adapters/vscode-copilot/index.js +10 -0
  32. package/build/adapters/zed/index.d.ts +1 -0
  33. package/build/adapters/zed/index.js +3 -0
  34. package/build/cli.d.ts +15 -0
  35. package/build/cli.js +62 -16
  36. package/build/concurrency/runPool.d.ts +36 -0
  37. package/build/concurrency/runPool.js +51 -0
  38. package/build/executor.d.ts +11 -1
  39. package/build/executor.js +59 -16
  40. package/build/fetch-cache.d.ts +13 -0
  41. package/build/fetch-cache.js +15 -0
  42. package/build/lifecycle.d.ts +6 -2
  43. package/build/lifecycle.js +29 -2
  44. package/build/opencode-plugin.d.ts +6 -0
  45. package/build/opencode-plugin.js +60 -1
  46. package/build/routing-block.d.ts +8 -0
  47. package/build/routing-block.js +86 -0
  48. package/build/runtime.d.ts +1 -0
  49. package/build/runtime.js +54 -3
  50. package/build/search/auto-memory.d.ts +23 -10
  51. package/build/search/auto-memory.js +64 -26
  52. package/build/search/unified.d.ts +3 -0
  53. package/build/search/unified.js +2 -2
  54. package/build/server.d.ts +42 -0
  55. package/build/server.js +693 -164
  56. package/build/session/analytics.d.ts +49 -1
  57. package/build/session/analytics.js +278 -16
  58. package/build/session/db.d.ts +39 -8
  59. package/build/session/db.js +170 -19
  60. package/build/session/extract.js +124 -2
  61. package/build/tool-naming.d.ts +4 -0
  62. package/build/tool-naming.js +24 -0
  63. package/cli.bundle.mjs +201 -159
  64. package/configs/antigravity/GEMINI.md +11 -0
  65. package/configs/claude-code/CLAUDE.md +11 -0
  66. package/configs/codex/AGENTS.md +11 -0
  67. package/configs/cursor/context-mode.mdc +11 -0
  68. package/configs/gemini-cli/GEMINI.md +11 -0
  69. package/configs/jetbrains-copilot/copilot-instructions.md +3 -0
  70. package/configs/kilo/AGENTS.md +11 -0
  71. package/configs/kiro/KIRO.md +11 -0
  72. package/configs/openclaw/AGENTS.md +11 -0
  73. package/configs/opencode/AGENTS.md +11 -0
  74. package/configs/pi/AGENTS.md +11 -0
  75. package/configs/qwen-code/QWEN.md +11 -0
  76. package/configs/vscode-copilot/copilot-instructions.md +3 -0
  77. package/configs/zed/AGENTS.md +11 -0
  78. package/hooks/auto-injection.mjs +36 -10
  79. package/hooks/cache-heal-utils.mjs +231 -0
  80. package/hooks/codex/sessionstart.mjs +7 -4
  81. package/hooks/core/routing.mjs +5 -0
  82. package/hooks/cursor/sessionstart.mjs +7 -4
  83. package/hooks/formatters/claude-code.mjs +20 -0
  84. package/hooks/gemini-cli/sessionstart.mjs +7 -2
  85. package/hooks/jetbrains-copilot/sessionstart.mjs +7 -2
  86. package/hooks/normalize-hooks.mjs +184 -0
  87. package/hooks/session-db.bundle.mjs +33 -14
  88. package/hooks/session-extract.bundle.mjs +2 -2
  89. package/hooks/session-helpers.mjs +68 -20
  90. package/hooks/session-loaders.mjs +8 -2
  91. package/hooks/sessionstart.mjs +8 -2
  92. package/hooks/vscode-copilot/sessionstart.mjs +7 -2
  93. package/openclaw.plugin.json +1 -1
  94. package/package.json +2 -1
  95. package/server.bundle.mjs +164 -125
  96. package/skills/ctx-insight/SKILL.md +1 -1
  97. package/start.mjs +63 -3
@@ -44,7 +44,7 @@ export class CodexAdapter extends BaseAdapter {
44
44
  toolName: input.tool_name ?? "",
45
45
  toolInput: input.tool_input ?? {},
46
46
  sessionId: this.extractSessionId(input),
47
- projectDir: input.cwd,
47
+ projectDir: this.getProjectDir(input),
48
48
  raw,
49
49
  };
50
50
  }
@@ -55,7 +55,7 @@ export class CodexAdapter extends BaseAdapter {
55
55
  toolInput: input.tool_input ?? {},
56
56
  toolOutput: input.tool_response,
57
57
  sessionId: this.extractSessionId(input),
58
- projectDir: input.cwd,
58
+ projectDir: this.getProjectDir(input),
59
59
  raw,
60
60
  };
61
61
  }
@@ -63,7 +63,7 @@ export class CodexAdapter extends BaseAdapter {
63
63
  const input = raw;
64
64
  return {
65
65
  sessionId: this.extractSessionId(input),
66
- projectDir: input.cwd,
66
+ projectDir: this.getProjectDir(input),
67
67
  raw,
68
68
  };
69
69
  }
@@ -87,7 +87,7 @@ export class CodexAdapter extends BaseAdapter {
87
87
  return {
88
88
  sessionId: this.extractSessionId(input),
89
89
  source,
90
- projectDir: input.cwd,
90
+ projectDir: this.getProjectDir(input),
91
91
  raw,
92
92
  };
93
93
  }
@@ -148,6 +148,14 @@ export class CodexAdapter extends BaseAdapter {
148
148
  getSettingsPath() {
149
149
  return resolve(homedir(), ".codex", "config.toml");
150
150
  }
151
+ getInstructionFiles() {
152
+ // Codex CLI honors AGENTS.md plus an optional override file.
153
+ return ["AGENTS.md", "AGENTS.override.md"];
154
+ }
155
+ getMemoryDir() {
156
+ // Codex uses "memories" (plural), not the default "memory".
157
+ return resolve(homedir(), ".codex", "memories");
158
+ }
151
159
  generateHookConfig(pluginRoot) {
152
160
  return {
153
161
  PreToolUse: [
@@ -298,6 +306,16 @@ export class CodexAdapter extends BaseAdapter {
298
306
  }
299
307
  }
300
308
  // ── Internal helpers ───────────────────────────────────
309
+ /**
310
+ * Resolve the project directory for a Codex hook input.
311
+ * Priority: input.cwd > CODEX_PROJECT_DIR env > process.cwd().
312
+ * Mirrors the cursor / opencode pattern so downstream hooks always
313
+ * receive a defined projectDir even under worktrees or when the
314
+ * platform omits cwd from the wire payload.
315
+ */
316
+ getProjectDir(input) {
317
+ return input.cwd ?? process.env.CODEX_PROJECT_DIR ?? process.cwd();
318
+ }
301
319
  /**
302
320
  * Extract session ID from Codex CLI hook input.
303
321
  * Priority: session_id field > fallback to ppid.
@@ -32,6 +32,13 @@ export declare class CursorAdapter extends BaseAdapter implements HookAdapter {
32
32
  text: string;
33
33
  };
34
34
  getSettingsPath(): string;
35
+ /**
36
+ * Cursor stores conventions per project under .cursor/. Always returned
37
+ * as an absolute path resolved against `projectDir` (or `process.cwd()`
38
+ * when omitted) per the HookAdapter.getConfigDir contract.
39
+ */
40
+ getConfigDir(projectDir?: string): string;
41
+ getInstructionFiles(): string[];
35
42
  generateHookConfig(_pluginRoot: string): HookRegistration;
36
43
  readSettings(): Record<string, unknown> | null;
37
44
  writeSettings(settings: Record<string, unknown>): void;
@@ -128,6 +128,17 @@ export class CursorAdapter extends BaseAdapter {
128
128
  getSettingsPath() {
129
129
  return resolve(".cursor", "hooks.json");
130
130
  }
131
+ /**
132
+ * Cursor stores conventions per project under .cursor/. Always returned
133
+ * as an absolute path resolved against `projectDir` (or `process.cwd()`
134
+ * when omitted) per the HookAdapter.getConfigDir contract.
135
+ */
136
+ getConfigDir(projectDir) {
137
+ return resolve(projectDir ?? process.cwd(), ".cursor");
138
+ }
139
+ getInstructionFiles() {
140
+ return ["context-mode.mdc"];
141
+ }
131
142
  generateHookConfig(_pluginRoot) {
132
143
  const hooks = {
133
144
  [CURSOR_HOOK_NAMES.PRE_TOOL_USE]: [
@@ -23,7 +23,18 @@ import type { PlatformId, DetectionSignal, HookAdapter } from "./types.js";
23
23
  * Single source of truth — consumed by detectPlatform() below and by
24
24
  * tests that need to clear platform-related env vars deterministically.
25
25
  */
26
- export declare const PLATFORM_ENV_VARS: readonly [readonly ["claude-code", readonly ["CLAUDE_PROJECT_DIR", "CLAUDE_SESSION_ID"]], readonly ["gemini-cli", readonly ["GEMINI_PROJECT_DIR", "GEMINI_CLI"]], readonly ["openclaw", readonly ["OPENCLAW_HOME", "OPENCLAW_CLI"]], readonly ["kilo", readonly ["KILO", "KILO_PID"]], readonly ["opencode", readonly ["OPENCODE", "OPENCODE_PID"]], readonly ["codex", readonly ["CODEX_CI", "CODEX_THREAD_ID"]], readonly ["cursor", readonly ["CURSOR_TRACE_ID", "CURSOR_CLI"]], readonly ["vscode-copilot", readonly ["VSCODE_PID", "VSCODE_CWD"]], readonly ["jetbrains-copilot", readonly ["IDEA_INITIAL_DIRECTORY", "IDEA_HOME", "JETBRAINS_CLIENT_ID"]], readonly ["qwen-code", readonly ["QWEN_PROJECT_DIR", "QWEN_SESSION_ID"]]];
26
+ export declare const PLATFORM_ENV_VARS: readonly [readonly ["claude-code", readonly ["CLAUDE_PROJECT_DIR", "CLAUDE_SESSION_ID"]], readonly ["antigravity", readonly ["ANTIGRAVITY_CLI_ALIAS"]], readonly ["cursor", readonly ["CURSOR_TRACE_ID", "CURSOR_CLI"]], readonly ["kilo", readonly ["KILO_PID"]], readonly ["opencode", readonly ["OPENCODE", "OPENCODE_PID"]], readonly ["zed", readonly ["ZED_SESSION_ID", "ZED_TERM"]], readonly ["codex", readonly ["CODEX_THREAD_ID", "CODEX_CI"]], readonly ["gemini-cli", readonly ["GEMINI_PROJECT_DIR", "GEMINI_CLI"]], readonly ["vscode-copilot", readonly ["VSCODE_PID", "VSCODE_CWD"]], readonly ["jetbrains-copilot", readonly ["IDEA_INITIAL_DIRECTORY"]], readonly ["qwen-code", readonly ["QWEN_PROJECT_DIR"]], readonly ["pi", readonly ["PI_PROJECT_DIR"]]];
27
+ /**
28
+ * Sync map from platform identifier → home-relative path segments where that
29
+ * platform stores its config. Mirrors the `super([...])` argument passed by
30
+ * each adapter — kept in sync as the single source of truth used when we need
31
+ * a session dir BEFORE an adapter has been instantiated (race window between
32
+ * MCP server start and `initialize` handshake completion).
33
+ *
34
+ * Returns `null` for "unknown" or any string outside the supported set so the
35
+ * caller can decide on a safe fallback.
36
+ */
37
+ export declare function getSessionDirSegments(platform: string): string[] | null;
27
38
  /**
28
39
  * Detect the current platform by checking env vars and config dirs.
29
40
  *
@@ -27,17 +27,79 @@ import { CLIENT_NAME_TO_PLATFORM } from "./client-map.js";
27
27
  * tests that need to clear platform-related env vars deterministically.
28
28
  */
29
29
  export const PLATFORM_ENV_VARS = [
30
+ // Order matters: forks listed BEFORE the fork's parent so collision
31
+ // detection works. Every entry verified against platform's own runtime
32
+ // source code (PR #376 follow-up: full audit, May 2026 — see git blame).
30
33
  ["claude-code", ["CLAUDE_PROJECT_DIR", "CLAUDE_SESSION_ID"]],
31
- ["gemini-cli", ["GEMINI_PROJECT_DIR", "GEMINI_CLI"]],
32
- ["openclaw", ["OPENCLAW_HOME", "OPENCLAW_CLI"]],
33
- ["kilo", ["KILO", "KILO_PID"]],
34
- ["opencode", ["OPENCODE", "OPENCODE_PID"]],
35
- ["codex", ["CODEX_CI", "CODEX_THREAD_ID"]],
34
+ // antigravity (Electron/VSCode fork) — google-gemini/gemini-cli
35
+ // packages/core/src/ide/detect-ide.ts checks ANTIGRAVITY_CLI_ALIAS as the
36
+ // canonical Antigravity marker. Listed before vscode-copilot.
37
+ ["antigravity", ["ANTIGRAVITY_CLI_ALIAS"]],
38
+ // cursor (VSCode fork) — listed before vscode-copilot. CURSOR_TRACE_ID has
39
+ // 800+ hits in major OSS detection libs (Vercel Next.js, Bun, Google
40
+ // gemini-cli, Nx, CrewAI).
36
41
  ["cursor", ["CURSOR_TRACE_ID", "CURSOR_CLI"]],
42
+ // kilo (OpenCode fork) — Kilo-Org/kilocode packages/opencode/src/index.ts:140
43
+ // sets `process.env.KILO_PID = String(process.pid)`. Bare KILO is NEVER set
44
+ // (verified). Kilo also sets OPENCODE=1 (fork) — listed before opencode.
45
+ ["kilo", ["KILO_PID"]],
46
+ // opencode — sst/opencode packages/opencode/src/index.ts:108-109 sets
47
+ // OPENCODE=1 + OPENCODE_PID=<pid> on every CLI invocation.
48
+ ["opencode", ["OPENCODE", "OPENCODE_PID"]],
49
+ // zed — zed-industries/zed crates/terminal/src/terminal.rs sets ZED_TERM=true
50
+ // in `insert_zed_terminal_env()`. Google's gemini-cli uses ZED_SESSION_ID.
51
+ ["zed", ["ZED_SESSION_ID", "ZED_TERM"]],
52
+ // codex — openai/codex codex-rs/core/src/exec_env.rs sets CODEX_THREAD_ID
53
+ // per exec; unified_exec/process_manager.rs sets CODEX_CI in CI mode.
54
+ ["codex", ["CODEX_THREAD_ID", "CODEX_CI"]],
55
+ // gemini-cli — GEMINI_PROJECT_DIR per google-gemini/gemini-cli
56
+ // docs/hooks/index.md; GEMINI_CLI is the MCP-server sentinel.
57
+ ["gemini-cli", ["GEMINI_PROJECT_DIR", "GEMINI_CLI"]],
58
+ // vscode-copilot — VSCODE_PID + VSCODE_CWD set by microsoft/vscode bootstrap.
59
+ // Listed AFTER cursor and antigravity since they inherit these vars as forks.
37
60
  ["vscode-copilot", ["VSCODE_PID", "VSCODE_CWD"]],
38
- ["jetbrains-copilot", ["IDEA_INITIAL_DIRECTORY", "IDEA_HOME", "JETBRAINS_CLIENT_ID"]],
39
- ["qwen-code", ["QWEN_PROJECT_DIR", "QWEN_SESSION_ID"]],
61
+ // jetbrains-copilot IDEA_INITIAL_DIRECTORY set by JetBrains launcher.
62
+ // (IDEA_HOME and JETBRAINS_CLIENT_ID removed — no source-line evidence.)
63
+ ["jetbrains-copilot", ["IDEA_INITIAL_DIRECTORY"]],
64
+ // qwen-code — QWEN_PROJECT_DIR per QwenLM/qwen-code docs/users/features/hooks.md.
65
+ // (QWEN_SESSION_ID removed — 0 hits in qwen-code repository.)
66
+ ["qwen-code", ["QWEN_PROJECT_DIR"]],
67
+ // pi — PI_PROJECT_DIR consumed by src/pi-extension.ts:154 + src/server.ts:153
68
+ // — implies the Pi runtime sets it before invoking the extension.
69
+ ["pi", ["PI_PROJECT_DIR"]],
70
+ // openclaw — removed (runtime never sets OPENCLAW_HOME or OPENCLAW_CLI;
71
+ // detection falls through to ~/.openclaw/ config-dir tier below).
72
+ // kiro — not listed (no auto-set process env vars; ~/.kiro/ config-dir tier).
40
73
  ];
74
+ /**
75
+ * Sync map from platform identifier → home-relative path segments where that
76
+ * platform stores its config. Mirrors the `super([...])` argument passed by
77
+ * each adapter — kept in sync as the single source of truth used when we need
78
+ * a session dir BEFORE an adapter has been instantiated (race window between
79
+ * MCP server start and `initialize` handshake completion).
80
+ *
81
+ * Returns `null` for "unknown" or any string outside the supported set so the
82
+ * caller can decide on a safe fallback.
83
+ */
84
+ export function getSessionDirSegments(platform) {
85
+ switch (platform) {
86
+ case "claude-code": return [".claude"];
87
+ case "gemini-cli": return [".gemini"];
88
+ case "antigravity": return [".gemini"];
89
+ case "openclaw": return [".openclaw"];
90
+ case "codex": return [".codex"];
91
+ case "cursor": return [".cursor"];
92
+ case "vscode-copilot": return [".vscode"];
93
+ case "kiro": return [".kiro"];
94
+ case "pi": return [".pi"];
95
+ case "qwen-code": return [".qwen"];
96
+ case "kilo": return [".config", "kilo"];
97
+ case "opencode": return [".config", "opencode"];
98
+ case "zed": return [".config", "zed"];
99
+ case "jetbrains-copilot": return [".config", "JetBrains"];
100
+ default: return null;
101
+ }
102
+ }
41
103
  /**
42
104
  * Detect the current platform by checking env vars and config dirs.
43
105
  *
@@ -34,6 +34,7 @@ export declare class GeminiCLIAdapter extends BaseAdapter implements HookAdapter
34
34
  formatPreCompactResponse(response: PreCompactResponse): unknown;
35
35
  formatSessionStartResponse(response: SessionStartResponse): unknown;
36
36
  getSettingsPath(): string;
37
+ getInstructionFiles(): string[];
37
38
  generateHookConfig(pluginRoot: string): HookRegistration;
38
39
  readSettings(): Record<string, unknown> | null;
39
40
  writeSettings(settings: Record<string, unknown>): void;
@@ -43,7 +44,13 @@ export declare class GeminiCLIAdapter extends BaseAdapter implements HookAdapter
43
44
  configureAllHooks(pluginRoot: string): string[];
44
45
  setHookPermissions(pluginRoot: string): string[];
45
46
  updatePluginRegistry(pluginRoot: string, version: string): void;
46
- /** Get the project directory from environment variables. */
47
+ /**
48
+ * Resolve the project directory for a Gemini CLI hook input.
49
+ * Priority: input.cwd > GEMINI_PROJECT_DIR > CLAUDE_PROJECT_DIR > process.cwd().
50
+ * Mirrors the cursor / opencode pattern so downstream hooks always
51
+ * receive a defined projectDir even when the platform omits cwd
52
+ * from the wire payload (e.g. under worktrees).
53
+ */
47
54
  private getProjectDir;
48
55
  /**
49
56
  * Extract session ID from Gemini CLI hook input.
@@ -51,7 +51,7 @@ export class GeminiCLIAdapter extends BaseAdapter {
51
51
  toolName: input.tool_name ?? "",
52
52
  toolInput: input.tool_input ?? {},
53
53
  sessionId: this.extractSessionId(input),
54
- projectDir: this.getProjectDir(),
54
+ projectDir: this.getProjectDir(input),
55
55
  raw,
56
56
  };
57
57
  }
@@ -63,7 +63,7 @@ export class GeminiCLIAdapter extends BaseAdapter {
63
63
  toolOutput: input.tool_output,
64
64
  isError: input.is_error,
65
65
  sessionId: this.extractSessionId(input),
66
- projectDir: this.getProjectDir(),
66
+ projectDir: this.getProjectDir(input),
67
67
  raw,
68
68
  };
69
69
  }
@@ -71,7 +71,7 @@ export class GeminiCLIAdapter extends BaseAdapter {
71
71
  const input = raw;
72
72
  return {
73
73
  sessionId: this.extractSessionId(input),
74
- projectDir: this.getProjectDir(),
74
+ projectDir: this.getProjectDir(input),
75
75
  raw,
76
76
  };
77
77
  }
@@ -95,7 +95,7 @@ export class GeminiCLIAdapter extends BaseAdapter {
95
95
  return {
96
96
  sessionId: this.extractSessionId(input),
97
97
  source,
98
- projectDir: this.getProjectDir(),
98
+ projectDir: this.getProjectDir(input),
99
99
  raw,
100
100
  };
101
101
  }
@@ -160,6 +160,9 @@ export class GeminiCLIAdapter extends BaseAdapter {
160
160
  getSettingsPath() {
161
161
  return resolve(homedir(), ".gemini", "settings.json");
162
162
  }
163
+ getInstructionFiles() {
164
+ return ["GEMINI.md"];
165
+ }
163
166
  generateHookConfig(pluginRoot) {
164
167
  return {
165
168
  [GEMINI_HOOK_NAMES.BEFORE_TOOL]: [
@@ -394,9 +397,18 @@ export class GeminiCLIAdapter extends BaseAdapter {
394
397
  }
395
398
  }
396
399
  // ── Internal helpers ───────────────────────────────────
397
- /** Get the project directory from environment variables. */
398
- getProjectDir() {
399
- return process.env.GEMINI_PROJECT_DIR ?? process.env.CLAUDE_PROJECT_DIR;
400
+ /**
401
+ * Resolve the project directory for a Gemini CLI hook input.
402
+ * Priority: input.cwd > GEMINI_PROJECT_DIR > CLAUDE_PROJECT_DIR > process.cwd().
403
+ * Mirrors the cursor / opencode pattern so downstream hooks always
404
+ * receive a defined projectDir even when the platform omits cwd
405
+ * from the wire payload (e.g. under worktrees).
406
+ */
407
+ getProjectDir(input) {
408
+ return (input.cwd
409
+ ?? process.env.GEMINI_PROJECT_DIR
410
+ ?? process.env.CLAUDE_PROJECT_DIR
411
+ ?? process.cwd());
400
412
  }
401
413
  /**
402
414
  * Extract session ID from Gemini CLI hook input.
@@ -18,6 +18,13 @@ export declare class JetBrainsCopilotAdapter extends CopilotBaseAdapter {
18
18
  protected readonly hookSubdir = "jetbrains-copilot";
19
19
  protected extractSessionId(input: CopilotHookInput): string;
20
20
  protected getProjectDir(): string;
21
+ /**
22
+ * JetBrains Copilot honors .github/copilot-instructions.md per project.
23
+ * Always returned absolute, resolved against the supplied `projectDir`,
24
+ * the JetBrains-specific project env vars, or `process.cwd()`.
25
+ */
26
+ getConfigDir(projectDir?: string): string;
27
+ getInstructionFiles(): string[];
21
28
  validateHooks(pluginRoot: string): DiagnosticResult[];
22
29
  checkPluginRegistration(): DiagnosticResult;
23
30
  getInstalledVersion(): string;
@@ -9,6 +9,7 @@
9
9
  * - validateHooks: JetBrains-specific warnings
10
10
  */
11
11
  import { readFileSync, } from "node:fs";
12
+ import { resolve } from "node:path";
12
13
  import { CopilotBaseAdapter } from "../copilot-base.js";
13
14
  // ─────────────────────────────────────────────────────────
14
15
  // Hook constants (re-exported from hooks.ts)
@@ -42,6 +43,17 @@ export class JetBrainsCopilotAdapter extends CopilotBaseAdapter {
42
43
  getProjectDir() {
43
44
  return process.env.IDEA_INITIAL_DIRECTORY || process.env.CLAUDE_PROJECT_DIR || process.cwd();
44
45
  }
46
+ /**
47
+ * JetBrains Copilot honors .github/copilot-instructions.md per project.
48
+ * Always returned absolute, resolved against the supplied `projectDir`,
49
+ * the JetBrains-specific project env vars, or `process.cwd()`.
50
+ */
51
+ getConfigDir(projectDir) {
52
+ return resolve(projectDir ?? this.getProjectDir(), ".github");
53
+ }
54
+ getInstructionFiles() {
55
+ return ["copilot-instructions.md"];
56
+ }
45
57
  // ── Diagnostics (doctor) ─────────────────────────────────
46
58
  validateHooks(pluginRoot) {
47
59
  const results = [];
@@ -33,6 +33,14 @@ export declare class KiroAdapter extends BaseAdapter implements HookAdapter {
33
33
  formatPreCompactResponse(_response: PreCompactResponse): unknown;
34
34
  formatSessionStartResponse(_response: SessionStartResponse): unknown;
35
35
  getSettingsPath(): string;
36
+ /**
37
+ * Kiro stores per-project context under .kiro/ (steering files, etc).
38
+ * Auto-memory + rule detection use this project-relative dir, returned as
39
+ * an absolute path resolved against `projectDir` (or `process.cwd()`).
40
+ * (Settings/MCP config still live under ~/.kiro/.)
41
+ */
42
+ getConfigDir(projectDir?: string): string;
43
+ getInstructionFiles(): string[];
36
44
  generateHookConfig(pluginRoot: string): HookRegistration;
37
45
  readSettings(): Record<string, unknown> | null;
38
46
  writeSettings(settings: Record<string, unknown>): void;
@@ -98,6 +98,18 @@ export class KiroAdapter extends BaseAdapter {
98
98
  getSettingsPath() {
99
99
  return resolve(homedir(), ".kiro", "settings", "mcp.json");
100
100
  }
101
+ /**
102
+ * Kiro stores per-project context under .kiro/ (steering files, etc).
103
+ * Auto-memory + rule detection use this project-relative dir, returned as
104
+ * an absolute path resolved against `projectDir` (or `process.cwd()`).
105
+ * (Settings/MCP config still live under ~/.kiro/.)
106
+ */
107
+ getConfigDir(projectDir) {
108
+ return resolve(projectDir ?? process.cwd(), ".kiro");
109
+ }
110
+ getInstructionFiles() {
111
+ return ["KIRO.md"];
112
+ }
101
113
  generateHookConfig(pluginRoot) {
102
114
  // Kiro CLI hook config format: { preToolUse: [{ matcher, command }] }
103
115
  // Note: This generates the entries for agent config files
@@ -31,6 +31,15 @@ export declare class OpenClawAdapter extends BaseAdapter implements HookAdapter
31
31
  formatPreCompactResponse(response: PreCompactResponse): unknown;
32
32
  formatSessionStartResponse(response: SessionStartResponse): unknown;
33
33
  getSettingsPath(): string;
34
+ /**
35
+ * OpenClaw stores everything in the project root — no separate config
36
+ * dir. Returned as the absolute project directory itself per the
37
+ * HookAdapter.getConfigDir contract (always-absolute).
38
+ */
39
+ getConfigDir(projectDir?: string): string;
40
+ getInstructionFiles(): string[];
41
+ /** Absolute <projectRoot>/memory directory. */
42
+ getMemoryDir(): string;
34
43
  generateHookConfig(_pluginRoot: string): HookRegistration;
35
44
  readSettings(): Record<string, unknown> | null;
36
45
  writeSettings(settings: Record<string, unknown>): void;
@@ -41,6 +50,14 @@ export declare class OpenClawAdapter extends BaseAdapter implements HookAdapter
41
50
  backupSettings(): string | null;
42
51
  setHookPermissions(_pluginRoot: string): string[];
43
52
  updatePluginRegistry(_pluginRoot: string, _version: string): void;
53
+ /**
54
+ * Resolve the project directory for an OpenClaw hook input.
55
+ * Priority: input.cwd > OPENCLAW_PROJECT_DIR env > process.cwd().
56
+ * Mirrors the cursor / opencode pattern so downstream hooks always
57
+ * receive a defined projectDir even under worktrees or when the
58
+ * platform omits cwd from the wire payload.
59
+ */
60
+ private getProjectDir;
44
61
  /**
45
62
  * Extract session ID from OpenClaw hook input.
46
63
  */
@@ -48,7 +48,7 @@ export class OpenClawAdapter extends BaseAdapter {
48
48
  toolName: input.toolName ?? input.tool_name ?? "",
49
49
  toolInput: input.params ?? input.tool_input ?? {},
50
50
  sessionId: this.extractSessionId(input),
51
- projectDir: process.cwd(),
51
+ projectDir: this.getProjectDir(input),
52
52
  raw,
53
53
  };
54
54
  }
@@ -60,7 +60,7 @@ export class OpenClawAdapter extends BaseAdapter {
60
60
  toolOutput: input.output ?? input.tool_output,
61
61
  isError: input.isError ?? input.is_error,
62
62
  sessionId: this.extractSessionId(input),
63
- projectDir: process.cwd(),
63
+ projectDir: this.getProjectDir(input),
64
64
  raw,
65
65
  };
66
66
  }
@@ -68,7 +68,7 @@ export class OpenClawAdapter extends BaseAdapter {
68
68
  const input = raw;
69
69
  return {
70
70
  sessionId: this.extractSessionId(input),
71
- projectDir: process.cwd(),
71
+ projectDir: this.getProjectDir(input),
72
72
  raw,
73
73
  };
74
74
  }
@@ -92,7 +92,7 @@ export class OpenClawAdapter extends BaseAdapter {
92
92
  return {
93
93
  sessionId: this.extractSessionId(input),
94
94
  source,
95
- projectDir: process.cwd(),
95
+ projectDir: this.getProjectDir(input),
96
96
  raw,
97
97
  };
98
98
  }
@@ -143,6 +143,21 @@ export class OpenClawAdapter extends BaseAdapter {
143
143
  // OpenClaw uses openclaw.json in the project root or ~/.openclaw/openclaw.json
144
144
  return resolve("openclaw.json");
145
145
  }
146
+ /**
147
+ * OpenClaw stores everything in the project root — no separate config
148
+ * dir. Returned as the absolute project directory itself per the
149
+ * HookAdapter.getConfigDir contract (always-absolute).
150
+ */
151
+ getConfigDir(projectDir) {
152
+ return resolve(projectDir ?? process.cwd());
153
+ }
154
+ getInstructionFiles() {
155
+ return ["AGENTS.md"];
156
+ }
157
+ /** Absolute <projectRoot>/memory directory. */
158
+ getMemoryDir() {
159
+ return join(this.getConfigDir(), "memory");
160
+ }
146
161
  generateHookConfig(_pluginRoot) {
147
162
  // OpenClaw uses TS plugin paradigm — hooks are registered via
148
163
  // api.registerHook() in the plugin entry point, not via config files.
@@ -394,6 +409,16 @@ export class OpenClawAdapter extends BaseAdapter {
394
409
  // OpenClaw manages plugins through npm/openclaw.json — no separate registry
395
410
  }
396
411
  // ── Internal helpers ───────────────────────────────────
412
+ /**
413
+ * Resolve the project directory for an OpenClaw hook input.
414
+ * Priority: input.cwd > OPENCLAW_PROJECT_DIR env > process.cwd().
415
+ * Mirrors the cursor / opencode pattern so downstream hooks always
416
+ * receive a defined projectDir even under worktrees or when the
417
+ * platform omits cwd from the wire payload.
418
+ */
419
+ getProjectDir(input) {
420
+ return input.cwd ?? process.env.OPENCLAW_PROJECT_DIR ?? process.cwd();
421
+ }
397
422
  /**
398
423
  * Extract session ID from OpenClaw hook input.
399
424
  */
@@ -36,6 +36,14 @@ export declare class OpenCodeAdapter extends BaseAdapter implements HookAdapter
36
36
  getSettingsPath(): string;
37
37
  private paths;
38
38
  getSessionDir(): string;
39
+ /**
40
+ * OpenCode/KiloCode honor XDG_CONFIG_HOME on POSIX and APPDATA on Windows.
41
+ * Falls back to ~/.config/<platform> (or %APPDATA%\<platform>).
42
+ * Always absolute. `_projectDir` is accepted for interface symmetry but
43
+ * unused — config is home/XDG-rooted, never project-scoped.
44
+ */
45
+ getConfigDir(_projectDir?: string): string;
46
+ getInstructionFiles(): string[];
39
47
  generateHookConfig(_pluginRoot: string): HookRegistration;
40
48
  readSettings(): Record<string, unknown> | null;
41
49
  writeSettings(settings: Record<string, unknown>): void;
@@ -171,16 +171,28 @@ export class OpenCodeAdapter extends BaseAdapter {
171
171
  ];
172
172
  }
173
173
  getSessionDir() {
174
- let configDir;
174
+ const dir = join(this.getConfigDir(), "context-mode", "sessions");
175
+ mkdirSync(dir, { recursive: true });
176
+ return dir;
177
+ }
178
+ /**
179
+ * OpenCode/KiloCode honor XDG_CONFIG_HOME on POSIX and APPDATA on Windows.
180
+ * Falls back to ~/.config/<platform> (or %APPDATA%\<platform>).
181
+ * Always absolute. `_projectDir` is accepted for interface symmetry but
182
+ * unused — config is home/XDG-rooted, never project-scoped.
183
+ */
184
+ getConfigDir(_projectDir) {
185
+ let root;
175
186
  if (process.platform === "win32") {
176
- configDir = process.env.APPDATA || join(homedir(), "AppData", "Roaming");
187
+ root = process.env.APPDATA || join(homedir(), "AppData", "Roaming");
177
188
  }
178
189
  else {
179
- configDir = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
190
+ root = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
180
191
  }
181
- const dir = join(configDir, this.platform, "context-mode", "sessions");
182
- mkdirSync(dir, { recursive: true });
183
- return dir;
192
+ return join(root, this.platform);
193
+ }
194
+ getInstructionFiles() {
195
+ return ["AGENTS.md"];
184
196
  }
185
197
  generateHookConfig(_pluginRoot) {
186
198
  // OpenCode uses TS plugin paradigm — hooks are registered via plugin array
@@ -21,6 +21,7 @@ export declare class QwenCodeAdapter extends ClaudeCodeBaseAdapter implements Ho
21
21
  protected readonly projectDirEnvVar = "QWEN_PROJECT_DIR";
22
22
  readonly capabilities: PlatformCapabilities;
23
23
  getSettingsPath(): string;
24
+ getInstructionFiles(): string[];
24
25
  generateHookConfig(pluginRoot: string): HookRegistration;
25
26
  readSettings(): Record<string, unknown> | null;
26
27
  writeSettings(settings: Record<string, unknown>): void;
@@ -40,6 +40,9 @@ export class QwenCodeAdapter extends ClaudeCodeBaseAdapter {
40
40
  getSettingsPath() {
41
41
  return resolve(homedir(), ".qwen", "settings.json");
42
42
  }
43
+ getInstructionFiles() {
44
+ return ["QWEN.md"];
45
+ }
43
46
  generateHookConfig(pluginRoot) {
44
47
  // Qwen Code passes native tool names in hook stdin (verified from
45
48
  // packages/core/src/tools/tool-names.ts). Claude-style names (Bash, Read)
@@ -160,6 +160,39 @@ export interface HookAdapter {
160
160
  getSessionDBPath(projectDir: string): string;
161
161
  /** Compute per-project session events file path. */
162
162
  getSessionEventsPath(projectDir: string): string;
163
+ /**
164
+ * Platform config directory.
165
+ *
166
+ * Contract: ALWAYS returns an absolute path. Never returns a relative
167
+ * segment, never returns an empty string. This eliminates the leaky-seam
168
+ * where callers could not tell whether the return needed further resolution.
169
+ *
170
+ * Resolution rules:
171
+ * - Home-rooted platforms (claude-code, codex, qwen, gemini, antigravity,
172
+ * zed, opencode, …) return paths under `homedir()` / XDG / APPDATA.
173
+ * - Project-scoped platforms (cursor → `.cursor`, vscode-copilot &
174
+ * jetbrains-copilot → `.github`, kiro → `.kiro`, openclaw → project root)
175
+ * resolve their segment against the supplied `projectDir`. When
176
+ * `projectDir` is omitted, `process.cwd()` is used as the fallback.
177
+ *
178
+ * @param projectDir Optional project root used to resolve project-scoped
179
+ * adapters. Ignored by home-rooted adapters.
180
+ */
181
+ getConfigDir(projectDir?: string): string;
182
+ /**
183
+ * Names of platform-native instruction/rule files that act as the
184
+ * project's "user CLAUDE.md equivalent" (e.g., ["CLAUDE.md"],
185
+ * ["AGENTS.md"], ["GEMINI.md"]). Auto-memory scans for these in the
186
+ * project root and config dir, and rule-detection emits "rule" events
187
+ * when they are read.
188
+ */
189
+ getInstructionFiles(): string[];
190
+ /**
191
+ * Directory where persistent per-user memory is stored
192
+ * (e.g., ~/.claude/memory, ~/.codex/memories). Auto-memory scans
193
+ * *.md files in this directory.
194
+ */
195
+ getMemoryDir(): string;
163
196
  /** Generate hook registration config for this platform. */
164
197
  generateHookConfig(pluginRoot: string): HookRegistration;
165
198
  /** Read current platform settings. */
@@ -20,6 +20,12 @@ export declare class VSCodeCopilotAdapter extends CopilotBaseAdapter {
20
20
  protected extractSessionId(input: CopilotHookInput): string;
21
21
  protected getProjectDir(): string;
22
22
  getSessionDir(): string;
23
+ /**
24
+ * VS Code Copilot honors .github/copilot-instructions.md per project.
25
+ * Always returned absolute, resolved against `projectDir` (or `cwd`).
26
+ */
27
+ getConfigDir(projectDir?: string): string;
28
+ getInstructionFiles(): string[];
23
29
  validateHooks(pluginRoot: string): DiagnosticResult[];
24
30
  checkPluginRegistration(): DiagnosticResult;
25
31
  getInstalledVersion(): string;
@@ -53,6 +53,16 @@ export class VSCodeCopilotAdapter extends CopilotBaseAdapter {
53
53
  mkdirSync(dir, { recursive: true });
54
54
  return dir;
55
55
  }
56
+ /**
57
+ * VS Code Copilot honors .github/copilot-instructions.md per project.
58
+ * Always returned absolute, resolved against `projectDir` (or `cwd`).
59
+ */
60
+ getConfigDir(projectDir) {
61
+ return resolve(projectDir ?? process.cwd(), ".github");
62
+ }
63
+ getInstructionFiles() {
64
+ return ["copilot-instructions.md"];
65
+ }
56
66
  // ── Diagnostics (doctor) ─────────────────────────────────
57
67
  validateHooks(pluginRoot) {
58
68
  const results = [];
@@ -26,6 +26,7 @@ export declare class ZedAdapter extends BaseAdapter implements HookAdapter {
26
26
  formatPreCompactResponse(_response: PreCompactResponse): unknown;
27
27
  formatSessionStartResponse(_response: SessionStartResponse): unknown;
28
28
  getSettingsPath(): string;
29
+ getInstructionFiles(): string[];
29
30
  generateHookConfig(_pluginRoot: string): HookRegistration;
30
31
  readSettings(): Record<string, unknown> | null;
31
32
  writeSettings(settings: Record<string, unknown>): void;
@@ -66,6 +66,9 @@ export class ZedAdapter extends BaseAdapter {
66
66
  getSettingsPath() {
67
67
  return resolve(homedir(), ".config", "zed", "settings.json");
68
68
  }
69
+ getInstructionFiles() {
70
+ return ["AGENTS.md"];
71
+ }
69
72
  generateHookConfig(_pluginRoot) {
70
73
  // Zed does not support hooks — return empty registration
71
74
  return {};