context-mode 1.0.99 → 1.0.101

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 (65) 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 +22 -8
  6. package/build/adapters/claude-code/hooks.d.ts +4 -2
  7. package/build/adapters/claude-code/hooks.js +11 -4
  8. package/build/adapters/codex/index.d.ts +1 -1
  9. package/build/adapters/codex/index.js +6 -5
  10. package/build/adapters/gemini-cli/hooks.js +2 -1
  11. package/build/adapters/jetbrains-copilot/hooks.js +2 -1
  12. package/build/adapters/kiro/hooks.js +2 -1
  13. package/build/adapters/qwen-code/index.d.ts +1 -1
  14. package/build/adapters/qwen-code/index.js +13 -10
  15. package/build/adapters/types.d.ts +13 -0
  16. package/build/adapters/types.js +23 -1
  17. package/build/adapters/vscode-copilot/hooks.js +2 -1
  18. package/build/openclaw-plugin.js +3 -2
  19. package/build/pi-extension.js +1 -1
  20. package/build/search/auto-memory.d.ts +29 -0
  21. package/build/search/auto-memory.js +121 -0
  22. package/build/search/unified.d.ts +41 -0
  23. package/build/search/unified.js +89 -0
  24. package/build/server.js +73 -24
  25. package/build/session/analytics.js +1 -1
  26. package/build/session/db.d.ts +17 -0
  27. package/build/session/db.js +28 -0
  28. package/build/session/extract.d.ts +4 -0
  29. package/build/session/extract.js +232 -1
  30. package/build/session/snapshot.js +31 -0
  31. package/build/store.js +67 -4
  32. package/build/types.d.ts +1 -0
  33. package/cli.bundle.mjs +254 -119
  34. package/configs/claude-code/CLAUDE.md +21 -1
  35. package/configs/codex/AGENTS.md +22 -1
  36. package/configs/cursor/context-mode.mdc +18 -1
  37. package/configs/gemini-cli/GEMINI.md +22 -1
  38. package/configs/jetbrains-copilot/copilot-instructions.md +22 -1
  39. package/configs/kilo/AGENTS.md +19 -2
  40. package/configs/kiro/KIRO.md +18 -1
  41. package/configs/openclaw/AGENTS.md +22 -2
  42. package/configs/opencode/AGENTS.md +18 -1
  43. package/configs/pi/AGENTS.md +18 -1
  44. package/configs/qwen-code/QWEN.md +38 -18
  45. package/configs/vscode-copilot/copilot-instructions.md +22 -1
  46. package/hooks/auto-injection.mjs +76 -0
  47. package/hooks/codex/userpromptsubmit.mjs +1 -1
  48. package/hooks/core/mcp-ready.mjs +7 -1
  49. package/hooks/ensure-deps.mjs +35 -7
  50. package/hooks/posttooluse.mjs +50 -1
  51. package/hooks/precompact.mjs +9 -0
  52. package/hooks/pretooluse.mjs +27 -0
  53. package/hooks/routing-block.mjs +7 -1
  54. package/hooks/session-db.bundle.mjs +19 -13
  55. package/hooks/session-extract.bundle.mjs +2 -2
  56. package/hooks/session-snapshot.bundle.mjs +18 -17
  57. package/hooks/sessionstart.mjs +17 -0
  58. package/hooks/userpromptsubmit.mjs +1 -1
  59. package/insight/server.mjs +379 -1
  60. package/insight/src/lib/api.ts +88 -16
  61. package/insight/src/routes/index.tsx +566 -5
  62. package/openclaw.plugin.json +1 -1
  63. package/package.json +1 -1
  64. package/server.bundle.mjs +222 -87
  65. package/start.mjs +3 -1
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Claude Code plugins by Mert Koseoğlu",
9
- "version": "1.0.99"
9
+ "version": "1.0.101"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "context-mode",
14
14
  "source": "./",
15
15
  "description": "Claude Code MCP plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
16
- "version": "1.0.99",
16
+ "version": "1.0.101",
17
17
  "author": {
18
18
  "name": "Mert Koseoğlu"
19
19
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-mode",
3
- "version": "1.0.99",
3
+ "version": "1.0.101",
4
4
  "description": "MCP server that saves 98% of your context window with session continuity. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and automatic state restore across compactions.",
5
5
  "author": {
6
6
  "name": "Mert Koseoğlu",
@@ -3,7 +3,7 @@
3
3
  "name": "Context Mode",
4
4
  "kind": "tool",
5
5
  "description": "OpenClaw plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
6
- "version": "1.0.99",
6
+ "version": "1.0.101",
7
7
  "sandbox": {
8
8
  "mode": "permissive",
9
9
  "filesystem_access": "full",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-mode",
3
- "version": "1.0.99",
3
+ "version": "1.0.101",
4
4
  "description": "OpenClaw plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
5
5
  "author": {
6
6
  "name": "Mert Koseoğlu",
package/README.md CHANGED
@@ -91,7 +91,7 @@ All checks should show `[x]`. The doctor validates runtimes, hooks, FTS5, and pl
91
91
  | `/context-mode:ctx-doctor` | Diagnostics — runtimes, hooks, FTS5, plugin registration, versions. |
92
92
  | `/context-mode:ctx-upgrade` | Pull latest, rebuild, migrate cache, fix hooks. |
93
93
  | `/context-mode:ctx-purge` | Permanently delete all indexed content from the knowledge base. |
94
- | `/context-mode:ctx-insight` | Personal analytics dashboard — 15+ metrics on tool usage, session activity, error rate, parallel work patterns, and mastery curve. Opens a local web UI. |
94
+ | `/context-mode:ctx-insight` | Personal analytics dashboard — 90 metrics, 37 insight patterns, 4 composite scores (productivity, quality, delegation, context health) across 23 event categories. Opens a local web UI. |
95
95
 
96
96
  > **Note:** Slash commands are a Claude Code plugin feature. On other platforms, type `ctx stats`, `ctx doctor`, `ctx upgrade`, or `ctx insight` in the chat — the model calls the MCP tool automatically. See [Utility Commands](#utility-commands).
97
97
 
@@ -930,18 +930,27 @@ Every tool call passes through hooks that extract structured events:
930
930
  |---|---|---|---|
931
931
  | **Files** | read, edit, write, glob, grep | Critical (P1) | PostToolUse |
932
932
  | **Tasks** | create, update, complete | Critical (P1) | PostToolUse |
933
+ | **Plans** | enter, exit, approved, rejected, file write | Critical (P1) | PostToolUse |
933
934
  | **Rules** | CLAUDE.md / GEMINI.md / AGENTS.md paths + content | Critical (P1) | SessionStart |
935
+ | **User Prompts** | Every user message (for last-prompt restore) | Critical (P1) | UserPromptSubmit |
934
936
  | **Decisions** | User corrections, preferences ("use X instead", "don't do Y") | High (P2) | UserPromptSubmit |
935
937
  | **Git** | checkout, commit, merge, rebase, stash, push, pull, diff, status | High (P2) | PostToolUse |
936
938
  | **Errors** | Tool failures, non-zero exit codes | High (P2) | PostToolUse |
937
- | **Environment** | cwd changes, venv, nvm, conda, package installs | High (P2) | PostToolUse |
939
+ | **Error Resolution** | Error fix pairs detected across sequential tool calls | High (P2) | PostToolUse |
940
+ | **Constraints** | Discovered limitations ("not supported", "permission denied") | High (P2) | PostToolUse |
941
+ | **Blockers** | "blocked on", "waiting for", "depends on" — tracked until resolved | High (P2) | UserPromptSubmit |
942
+ | **Rejected Approaches** | Tool calls denied by user (PreToolUse → PostToolUse marker) | High (P2) | PreToolUse |
943
+ | **Environment** | cwd changes, venv, nvm, conda, worktree, package installs | High (P2) | PostToolUse |
944
+ | **Agent Findings** | Completed subagent results (first 500 chars) | High (P2) | PostToolUse |
945
+ | **Iteration Loops** | Same tool called 3+ times with similar input (retry detection) | High (P2) | PostToolUse |
946
+ | **Latency** | Tool calls exceeding 5s (tool name + duration in ms) | Normal (P3) | PreToolUse |
938
947
  | **MCP Tools** | All `mcp__*` tool calls with usage counts | Normal (P3) | PostToolUse |
939
- | **Subagents** | Agent tool invocations | Normal (P3) | PostToolUse |
948
+ | **Subagents** | Agent tool launches and completions | Normal (P3) | PostToolUse |
940
949
  | **Skills** | Slash command invocations | Normal (P3) | PostToolUse |
950
+ | **External Refs** | URLs, GitHub issue references (#123), deduped | Normal (P3) | PostToolUse |
941
951
  | **Role** | Persona / behavioral directives ("act as senior engineer") | Normal (P3) | UserPromptSubmit |
942
- | **Intent** | Session mode classification (investigate, implement, debug) | Low (P4) | UserPromptSubmit |
952
+ | **Intent** | Session mode classification (investigate, implement, review) | Low (P4) | UserPromptSubmit |
943
953
  | **Data** | Large user-pasted data references (>1 KB) | Low (P4) | UserPromptSubmit |
944
- | **User Prompts** | Every user message (for last-prompt restore) | Critical (P1) | UserPromptSubmit |
945
954
 
946
955
  </details>
947
956
 
@@ -968,15 +977,20 @@ After compaction, the model receives a **Session Guide** — a structured narrat
968
977
 
969
978
  - **Last Request** — user's last prompt, so the model continues without asking "what were we doing?"
970
979
  - **Tasks** — checkbox format with completion status (`[x]` completed, `[ ]` pending)
980
+ - **Plans** — plan mode entries, exits, approvals, and rejections
971
981
  - **Key Decisions** — user corrections and preferences ("use X instead", "don't do Y")
972
982
  - **Files Modified** — all files touched during the session
973
- - **Unresolved Errors** — errors that haven't been fixed
983
+ - **Unresolved Errors** — errors that haven't been fixed, plus error→fix resolution pairs
984
+ - **Constraints** — discovered limitations and boundaries
985
+ - **Blockers** — open and resolved blockers ("blocked on X", "waiting for Y")
974
986
  - **Git** — operations performed (checkout, commit, push, status)
975
987
  - **Project Rules** — CLAUDE.md / GEMINI.md / AGENTS.md paths
976
988
  - **MCP Tools Used** — tool names with call counts
977
- - **Subagent Tasks** — delegated work summaries
989
+ - **Subagent Tasks** — delegated work summaries + agent findings
978
990
  - **Skills Used** — slash commands invoked
979
- - **Environment** — working directory, env variables
991
+ - **Rejected Approaches** — tool calls the user denied
992
+ - **External References** — URLs and GitHub issue references
993
+ - **Environment** — working directory, env variables, worktrees
980
994
  - **Data References** — large data pasted during the session
981
995
  - **Session Intent** — mode classification (implement, investigate, review, discuss)
982
996
  - **User Role** — behavioral directives set during the session
@@ -59,13 +59,15 @@ export declare function isContextModeHook(entry: {
59
59
  }, hookType: HookType): boolean;
60
60
  /**
61
61
  * Build the hook command string for a given hook type.
62
- * Uses absolute node path to avoid PATH issues (homebrew, nvm, volta, etc.).
62
+ * Uses process.execPath + forward slashes to avoid PATH issues and MSYS
63
+ * path mangling on Windows (#369, #372).
63
64
  * Falls back to CLI dispatcher if pluginRoot is not provided.
64
65
  */
65
66
  export declare function buildHookCommand(hookType: HookType, pluginRoot?: string): string;
66
67
  /**
67
68
  * Extract the hook script file path from a command string.
68
- * Returns the path if the command uses the `node "/path/to/hook.mjs"` format,
69
+ * Returns the path if the command uses the `node "/path/to/hook.mjs"` format
70
+ * or the new `"/path/to/node" "/path/to/hook.mjs"` format (#369, #372),
69
71
  * or null if it uses the CLI dispatcher format (which is path-independent).
70
72
  *
71
73
  * Handles both quoted and unquoted paths, and both forward/back slashes.
@@ -1,3 +1,4 @@
1
+ import { buildNodeCommand } from "../types.js";
1
2
  /**
2
3
  * adapters/claude-code/hooks — Claude Code hook definitions and matchers.
3
4
  *
@@ -113,25 +114,31 @@ export function isContextModeHook(entry, hookType) {
113
114
  }
114
115
  /**
115
116
  * Build the hook command string for a given hook type.
116
- * Uses absolute node path to avoid PATH issues (homebrew, nvm, volta, etc.).
117
+ * Uses process.execPath + forward slashes to avoid PATH issues and MSYS
118
+ * path mangling on Windows (#369, #372).
117
119
  * Falls back to CLI dispatcher if pluginRoot is not provided.
118
120
  */
119
121
  export function buildHookCommand(hookType, pluginRoot) {
120
122
  if (pluginRoot) {
121
123
  const scriptName = HOOK_SCRIPTS[hookType];
122
- return `node "${pluginRoot}/hooks/${scriptName}"`;
124
+ return buildNodeCommand(`${pluginRoot}/hooks/${scriptName}`);
123
125
  }
124
126
  return `context-mode hook claude-code ${hookType.toLowerCase()}`;
125
127
  }
126
128
  /**
127
129
  * Extract the hook script file path from a command string.
128
- * Returns the path if the command uses the `node "/path/to/hook.mjs"` format,
130
+ * Returns the path if the command uses the `node "/path/to/hook.mjs"` format
131
+ * or the new `"/path/to/node" "/path/to/hook.mjs"` format (#369, #372),
129
132
  * or null if it uses the CLI dispatcher format (which is path-independent).
130
133
  *
131
134
  * Handles both quoted and unquoted paths, and both forward/back slashes.
132
135
  */
133
136
  export function extractHookScriptPath(command) {
134
- // Match: node "/path/to/hooks/scriptname.mjs" or node /path/to/hooks/scriptname.mjs
137
+ // New format: "nodePath" "scriptPath.mjs" (from buildNodeCommand)
138
+ const newFmt = command.match(/"[^"]+"\s+"([^"]+\.mjs)"/);
139
+ if (newFmt)
140
+ return newFmt[1];
141
+ // Legacy format: node "/path/to/hooks/scriptname.mjs" or node /path/to/hooks/scriptname.mjs
135
142
  const match = command.match(/node\s+"?([^"]+\.mjs)"?/);
136
143
  return match?.[1] ?? null;
137
144
  }
@@ -14,7 +14,7 @@
14
14
  * Track: https://github.com/openai/codex/issues/18491
15
15
  */
16
16
  import { BaseAdapter } from "../base.js";
17
- import type { HookAdapter, HookParadigm, PlatformCapabilities, DiagnosticResult, PreToolUseEvent, PostToolUseEvent, PreCompactEvent, SessionStartEvent, PreToolUseResponse, PostToolUseResponse, PreCompactResponse, SessionStartResponse, HookRegistration } from "../types.js";
17
+ import { type HookAdapter, type HookParadigm, type PlatformCapabilities, type DiagnosticResult, type PreToolUseEvent, type PostToolUseEvent, type PreCompactEvent, type SessionStartEvent, type PreToolUseResponse, type PostToolUseResponse, type PreCompactResponse, type SessionStartResponse, type HookRegistration } from "../types.js";
18
18
  export declare class CodexAdapter extends BaseAdapter implements HookAdapter {
19
19
  constructor();
20
20
  readonly name = "Codex CLI";
@@ -18,6 +18,7 @@ import { resolve, dirname } from "node:path";
18
18
  import { fileURLToPath } from "node:url";
19
19
  import { homedir } from "node:os";
20
20
  import { BaseAdapter } from "../base.js";
21
+ import { buildNodeCommand, } from "../types.js";
21
22
  // ─────────────────────────────────────────────────────────
22
23
  // Adapter implementation
23
24
  // ─────────────────────────────────────────────────────────
@@ -155,7 +156,7 @@ export class CodexAdapter extends BaseAdapter {
155
156
  hooks: [
156
157
  {
157
158
  type: "command",
158
- command: `node ${pluginRoot}/hooks/pretooluse.mjs`,
159
+ command: buildNodeCommand(`${pluginRoot}/hooks/pretooluse.mjs`),
159
160
  },
160
161
  ],
161
162
  },
@@ -166,7 +167,7 @@ export class CodexAdapter extends BaseAdapter {
166
167
  hooks: [
167
168
  {
168
169
  type: "command",
169
- command: `node ${pluginRoot}/hooks/posttooluse.mjs`,
170
+ command: buildNodeCommand(`${pluginRoot}/hooks/posttooluse.mjs`),
170
171
  },
171
172
  ],
172
173
  },
@@ -177,7 +178,7 @@ export class CodexAdapter extends BaseAdapter {
177
178
  hooks: [
178
179
  {
179
180
  type: "command",
180
- command: `node ${pluginRoot}/hooks/sessionstart.mjs`,
181
+ command: buildNodeCommand(`${pluginRoot}/hooks/sessionstart.mjs`),
181
182
  },
182
183
  ],
183
184
  },
@@ -188,7 +189,7 @@ export class CodexAdapter extends BaseAdapter {
188
189
  hooks: [
189
190
  {
190
191
  type: "command",
191
- command: `node ${pluginRoot}/hooks/codex/userpromptsubmit.mjs`,
192
+ command: buildNodeCommand(`${pluginRoot}/hooks/codex/userpromptsubmit.mjs`),
192
193
  },
193
194
  ],
194
195
  },
@@ -199,7 +200,7 @@ export class CodexAdapter extends BaseAdapter {
199
200
  hooks: [
200
201
  {
201
202
  type: "command",
202
- command: `node ${pluginRoot}/hooks/codex/stop.mjs`,
203
+ command: buildNodeCommand(`${pluginRoot}/hooks/codex/stop.mjs`),
203
204
  },
204
205
  ],
205
206
  },
@@ -1,3 +1,4 @@
1
+ import { buildNodeCommand } from "../types.js";
1
2
  /**
2
3
  * adapters/gemini-cli/hooks — Gemini CLI hook definitions and matchers.
3
4
  *
@@ -65,7 +66,7 @@ export function isContextModeHook(entry, hookType) {
65
66
  export function buildHookCommand(hookType, pluginRoot) {
66
67
  const scriptName = HOOK_SCRIPTS[hookType];
67
68
  if (pluginRoot && scriptName) {
68
- return `node "${pluginRoot}/hooks/${scriptName}"`;
69
+ return buildNodeCommand(`${pluginRoot}/hooks/${scriptName}`);
69
70
  }
70
71
  return `context-mode hook gemini-cli ${hookType.toLowerCase()}`;
71
72
  }
@@ -1,3 +1,4 @@
1
+ import { buildNodeCommand } from "../types.js";
1
2
  /**
2
3
  * adapters/jetbrains-copilot/hooks — JetBrains Copilot hook definitions and matchers.
3
4
  *
@@ -76,7 +77,7 @@ export function buildHookCommand(hookType, pluginRoot) {
76
77
  throw new Error(`No script defined for hook type: ${hookType}`);
77
78
  }
78
79
  if (pluginRoot) {
79
- return `node "${pluginRoot}/hooks/jetbrains-copilot/${scriptName}"`;
80
+ return buildNodeCommand(`${pluginRoot}/hooks/jetbrains-copilot/${scriptName}`);
80
81
  }
81
82
  return `context-mode hook jetbrains-copilot ${hookType.toLowerCase()}`;
82
83
  }
@@ -1,3 +1,4 @@
1
+ import { buildNodeCommand } from "../types.js";
1
2
  /**
2
3
  * adapters/kiro/hooks — Kiro CLI hook definitions and matchers.
3
4
  *
@@ -64,7 +65,7 @@ export function isContextModeHook(entry, hookType) {
64
65
  export function buildHookCommand(hookType, pluginRoot) {
65
66
  const scriptName = HOOK_SCRIPTS[hookType];
66
67
  if (pluginRoot && scriptName) {
67
- return `node "${pluginRoot}/hooks/kiro/${scriptName}"`;
68
+ return buildNodeCommand(`${pluginRoot}/hooks/kiro/${scriptName}`);
68
69
  }
69
70
  return `context-mode hook kiro ${hookType.toLowerCase()}`;
70
71
  }
@@ -13,7 +13,7 @@
13
13
  * - 12 hook events (superset of Claude's 5, but context-mode uses the shared 5)
14
14
  */
15
15
  import { ClaudeCodeBaseAdapter, type ClaudeCodeWireInput } from "../claude-code-base.js";
16
- import type { HookAdapter, HookParadigm, PlatformCapabilities, DiagnosticResult, HookRegistration } from "../types.js";
16
+ import { type HookAdapter, type HookParadigm, type PlatformCapabilities, type DiagnosticResult, type HookRegistration } from "../types.js";
17
17
  export declare class QwenCodeAdapter extends ClaudeCodeBaseAdapter implements HookAdapter {
18
18
  constructor();
19
19
  readonly name = "Qwen Code";
@@ -16,6 +16,7 @@ import { readFileSync, existsSync, } from "node:fs";
16
16
  import { resolve, join } from "node:path";
17
17
  import { homedir } from "node:os";
18
18
  import { ClaudeCodeBaseAdapter } from "../claude-code-base.js";
19
+ import { buildNodeCommand, } from "../types.js";
19
20
  // ─────────────────────────────────────────────────────────
20
21
  // Adapter implementation
21
22
  // ─────────────────────────────────────────────────────────
@@ -57,7 +58,7 @@ export class QwenCodeAdapter extends ClaudeCodeBaseAdapter {
57
58
  {
58
59
  matcher: preToolUseMatcher,
59
60
  hooks: [
60
- { type: "command", command: `node ${pluginRoot}/hooks/pretooluse.mjs` },
61
+ { type: "command", command: buildNodeCommand(`${pluginRoot}/hooks/pretooluse.mjs`) },
61
62
  ],
62
63
  },
63
64
  ],
@@ -65,7 +66,7 @@ export class QwenCodeAdapter extends ClaudeCodeBaseAdapter {
65
66
  {
66
67
  matcher: "run_shell_command|read_file|write_file|edit|glob|grep_search|todo_write|agent|ask_user_question|mcp__",
67
68
  hooks: [
68
- { type: "command", command: `node ${pluginRoot}/hooks/posttooluse.mjs` },
69
+ { type: "command", command: buildNodeCommand(`${pluginRoot}/hooks/posttooluse.mjs`) },
69
70
  ],
70
71
  },
71
72
  ],
@@ -73,7 +74,7 @@ export class QwenCodeAdapter extends ClaudeCodeBaseAdapter {
73
74
  {
74
75
  matcher: "",
75
76
  hooks: [
76
- { type: "command", command: `node ${pluginRoot}/hooks/sessionstart.mjs` },
77
+ { type: "command", command: buildNodeCommand(`${pluginRoot}/hooks/sessionstart.mjs`) },
77
78
  ],
78
79
  },
79
80
  ],
@@ -81,7 +82,7 @@ export class QwenCodeAdapter extends ClaudeCodeBaseAdapter {
81
82
  {
82
83
  matcher: "",
83
84
  hooks: [
84
- { type: "command", command: `node ${pluginRoot}/hooks/precompact.mjs` },
85
+ { type: "command", command: buildNodeCommand(`${pluginRoot}/hooks/precompact.mjs`) },
85
86
  ],
86
87
  },
87
88
  ],
@@ -89,7 +90,7 @@ export class QwenCodeAdapter extends ClaudeCodeBaseAdapter {
89
90
  {
90
91
  matcher: "",
91
92
  hooks: [
92
- { type: "command", command: `node ${pluginRoot}/hooks/userpromptsubmit.mjs` },
93
+ { type: "command", command: buildNodeCommand(`${pluginRoot}/hooks/userpromptsubmit.mjs`) },
93
94
  ],
94
95
  },
95
96
  ],
@@ -210,11 +211,13 @@ export class QwenCodeAdapter extends ClaudeCodeBaseAdapter {
210
211
  return commands.every((h) => {
211
212
  if (!h.command)
212
213
  return true;
213
- // Extract path from "node /path/to/script.mjs" format
214
- const match = h.command.match(/node\s+"?([^"]+\.mjs)"?/);
215
- if (!match)
214
+ // Extract path from both new ("nodePath" "scriptPath.mjs") and legacy (node .../script.mjs) formats
215
+ const newFmt = h.command.match(/"[^"]+"\s+"([^"]+\.mjs)"/);
216
+ const legacyFmt = h.command.match(/node\s+"?([^"]+\.mjs)"?/);
217
+ const scriptMatch = newFmt || legacyFmt;
218
+ if (!scriptMatch)
216
219
  return true; // CLI dispatcher format, always valid
217
- return existsSync(match[1]);
220
+ return existsSync(scriptMatch[1]);
218
221
  });
219
222
  });
220
223
  const removed = entries.length - filtered.length;
@@ -245,7 +248,7 @@ export class QwenCodeAdapter extends ClaudeCodeBaseAdapter {
245
248
  for (const { name, script, matcher } of hookTypes) {
246
249
  const entry = {
247
250
  matcher,
248
- hooks: [{ type: "command", command: `node ${pluginRoot}/hooks/${script}` }],
251
+ hooks: [{ type: "command", command: buildNodeCommand(`${pluginRoot}/hooks/${script}`) }],
249
252
  };
250
253
  const existing = hooks[name];
251
254
  if (existing && Array.isArray(existing)) {
@@ -192,6 +192,19 @@ export interface DiagnosticResult {
192
192
  /** Suggested fix command (if applicable). */
193
193
  fix?: string;
194
194
  }
195
+ /**
196
+ * Build a cross-platform `node <script>` command string.
197
+ *
198
+ * Fixes two Windows bugs:
199
+ * #369 — Bare `node` fails on Windows Git Bash (MSYS) because PATH
200
+ * resolution is unreliable. Uses `process.execPath` instead.
201
+ * #372 — MSYS rewrites absolute paths on non-C: drives (e.g.
202
+ * `C:\Users\...` → `D:\c\Users\...`). Forward slashes +
203
+ * double-quoting prevents the translation.
204
+ *
205
+ * Safe on macOS/Linux — quoting and forward slashes are no-ops there.
206
+ */
207
+ export declare function buildNodeCommand(scriptPath: string): string;
195
208
  /** Supported platform identifiers. */
196
209
  export type PlatformId = "claude-code" | "gemini-cli" | "opencode" | "kilo" | "openclaw" | "codex" | "vscode-copilot" | "jetbrains-copilot" | "cursor" | "antigravity" | "kiro" | "pi" | "zed" | "qwen-code" | "unknown";
197
210
  /** Detection signal used to identify which platform is running. */
@@ -10,4 +10,26 @@
10
10
  * The MCP server layer is 100% portable and needs no adapter.
11
11
  * Only the hook layer requires platform-specific adapters.
12
12
  */
13
- export {};
13
+ // ─────────────────────────────────────────────────────────
14
+ // Platform detection
15
+ // ─────────────────────────────────────────────────────────
16
+ // ─────────────────────────────────────────────────────────
17
+ // Cross-platform command helpers (#369, #372)
18
+ // ─────────────────────────────────────────────────────────
19
+ /**
20
+ * Build a cross-platform `node <script>` command string.
21
+ *
22
+ * Fixes two Windows bugs:
23
+ * #369 — Bare `node` fails on Windows Git Bash (MSYS) because PATH
24
+ * resolution is unreliable. Uses `process.execPath` instead.
25
+ * #372 — MSYS rewrites absolute paths on non-C: drives (e.g.
26
+ * `C:\Users\...` → `D:\c\Users\...`). Forward slashes +
27
+ * double-quoting prevents the translation.
28
+ *
29
+ * Safe on macOS/Linux — quoting and forward slashes are no-ops there.
30
+ */
31
+ export function buildNodeCommand(scriptPath) {
32
+ const nodePath = process.execPath.replace(/\\/g, "/");
33
+ const safePath = scriptPath.replace(/\\/g, "/");
34
+ return `"${nodePath}" "${safePath}"`;
35
+ }
@@ -1,3 +1,4 @@
1
+ import { buildNodeCommand } from "../types.js";
1
2
  /**
2
3
  * adapters/vscode-copilot/hooks — VS Code Copilot hook definitions and matchers.
3
4
  *
@@ -76,7 +77,7 @@ export function buildHookCommand(hookType, pluginRoot) {
76
77
  throw new Error(`No script defined for hook type: ${hookType}`);
77
78
  }
78
79
  if (pluginRoot) {
79
- return `node "${pluginRoot}/hooks/${scriptName}"`;
80
+ return buildNodeCommand(`${pluginRoot}/hooks/${scriptName}`);
80
81
  }
81
82
  return `context-mode hook vscode-copilot ${hookType.toLowerCase()}`;
82
83
  }
@@ -37,6 +37,7 @@ import { OpenClawSessionDB } from "./adapters/openclaw/session-db.js";
37
37
  import { extractEvents, extractUserEvents } from "./session/extract.js";
38
38
  import { buildResumeSnapshot } from "./session/snapshot.js";
39
39
  import { WorkspaceRouter } from "./openclaw/workspace-router.js";
40
+ import { buildNodeCommand } from "./adapters/types.js";
40
41
  /** Plugin config schema for OpenClaw validation. */
41
42
  const configSchema = {
42
43
  type: "object",
@@ -437,7 +438,7 @@ export default {
437
438
  const bundlePath = resolve(_latestPluginRoot, "cli.bundle.mjs");
438
439
  const fallbackPath = resolve(_latestPluginRoot, "build", "cli.js");
439
440
  const cliPath = existsSync(bundlePath) ? bundlePath : fallbackPath;
440
- const cmd = `node "${cliPath}" doctor`;
441
+ const cmd = `${buildNodeCommand(cliPath)} doctor`;
441
442
  return {
442
443
  text: [
443
444
  "## ctx-doctor",
@@ -458,7 +459,7 @@ export default {
458
459
  const bundlePath = resolve(_latestPluginRoot, "cli.bundle.mjs");
459
460
  const fallbackPath = resolve(_latestPluginRoot, "build", "cli.js");
460
461
  const cliPath = existsSync(bundlePath) ? bundlePath : fallbackPath;
461
- const cmd = `node "${cliPath}" upgrade`;
462
+ const cmd = `${buildNodeCommand(cliPath)} upgrade`;
462
463
  return {
463
464
  text: [
464
465
  "## ctx-upgrade",
@@ -242,7 +242,7 @@ export default function piExtension(pi) {
242
242
  // Mark resume as consumed so it is not re-injected
243
243
  db.markResumeConsumed(_sessionId);
244
244
  // Build memory context from recent high-priority events
245
- const allEvents = db.getEvents(_sessionId, { minPriority: 2, limit: 50 });
245
+ const allEvents = db.getEvents(_sessionId, { minPriority: 3, limit: 50 });
246
246
  let memoryContext = "";
247
247
  if (allEvents.length > 0) {
248
248
  const memoryLines = ["<active_memory>"];
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Auto-memory search — searches CLAUDE.md and MEMORY.md files for
3
+ * persisted decisions, preferences, and context from prior sessions.
4
+ *
5
+ * Returns results in a format compatible with the unified search pipeline.
6
+ */
7
+ export interface AutoMemoryResult {
8
+ title: string;
9
+ content: string;
10
+ source: string;
11
+ origin: "auto-memory";
12
+ timestamp?: string;
13
+ }
14
+ /**
15
+ * Search auto-memory files (CLAUDE.md, MEMORY.md, user identity files)
16
+ * for content matching any of the given queries.
17
+ *
18
+ * Scans:
19
+ * 1. Project-level: <projectDir>/CLAUDE.md
20
+ * 2. User-level: <configDir>/CLAUDE.md
21
+ * 3. User memory: <configDir>/memory/*.md
22
+ *
23
+ * @param queries Array of search terms
24
+ * @param limit Max results to return
25
+ * @param projectDir Project directory path
26
+ * @param configDir Config directory (e.g. ~/.claude)
27
+ * @returns Matching auto-memory results
28
+ */
29
+ export declare function searchAutoMemory(queries: string[], limit?: number, projectDir?: string, configDir?: string): AutoMemoryResult[];
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Auto-memory search — searches CLAUDE.md and MEMORY.md files for
3
+ * persisted decisions, preferences, and context from prior sessions.
4
+ *
5
+ * Returns results in a format compatible with the unified search pipeline.
6
+ */
7
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { homedir } from "node:os";
10
+ const DEBUG = process.env.DEBUG?.includes("context-mode");
11
+ /**
12
+ * Search auto-memory files (CLAUDE.md, MEMORY.md, user identity files)
13
+ * for content matching any of the given queries.
14
+ *
15
+ * Scans:
16
+ * 1. Project-level: <projectDir>/CLAUDE.md
17
+ * 2. User-level: <configDir>/CLAUDE.md
18
+ * 3. User memory: <configDir>/memory/*.md
19
+ *
20
+ * @param queries Array of search terms
21
+ * @param limit Max results to return
22
+ * @param projectDir Project directory path
23
+ * @param configDir Config directory (e.g. ~/.claude)
24
+ * @returns Matching auto-memory results
25
+ */
26
+ export function searchAutoMemory(queries, limit = 5, projectDir, configDir) {
27
+ const results = [];
28
+ const effectiveConfigDir = configDir || join(homedir(), ".claude");
29
+ // Collect candidate files
30
+ const candidates = [];
31
+ // 1. Project-level CLAUDE.md
32
+ if (projectDir) {
33
+ const projectClaude = join(projectDir, "CLAUDE.md");
34
+ if (existsSync(projectClaude)) {
35
+ candidates.push({ path: projectClaude, label: "project/CLAUDE.md" });
36
+ }
37
+ }
38
+ // 2. User-level CLAUDE.md
39
+ const userClaude = join(effectiveConfigDir, "CLAUDE.md");
40
+ if (existsSync(userClaude)) {
41
+ candidates.push({ path: userClaude, label: "user/CLAUDE.md" });
42
+ }
43
+ // 3. User memory directory
44
+ const memoryDir = join(effectiveConfigDir, "memory");
45
+ if (existsSync(memoryDir)) {
46
+ try {
47
+ const files = readdirSync(memoryDir).filter(f => f.endsWith(".md"));
48
+ for (const file of files) {
49
+ candidates.push({
50
+ path: join(memoryDir, file),
51
+ label: `memory/${file}`,
52
+ });
53
+ }
54
+ }
55
+ catch (e) {
56
+ if (DEBUG)
57
+ process.stderr.write(`[ctx] auto-memory dir scan failed: ${e}\n`);
58
+ }
59
+ }
60
+ // Search each candidate file for matching queries
61
+ for (const candidate of candidates) {
62
+ if (results.length >= limit)
63
+ break;
64
+ try {
65
+ // Skip files larger than 1MB to avoid memory issues
66
+ try {
67
+ if (statSync(candidate.path).size > 1_000_000)
68
+ continue;
69
+ }
70
+ catch {
71
+ continue;
72
+ }
73
+ const content = readFileSync(candidate.path, "utf-8");
74
+ const contentLower = content.toLowerCase();
75
+ for (const query of queries) {
76
+ if (results.length >= limit)
77
+ break;
78
+ const queryLower = query.toLowerCase();
79
+ // Split query into terms, match if any term is found
80
+ const terms = queryLower.split(/\s+/).filter(t => t.length >= 3);
81
+ const matched = terms.some(term => {
82
+ try {
83
+ return new RegExp(`\\b${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, "i").test(content);
84
+ }
85
+ catch {
86
+ return contentLower.includes(term); // fallback for invalid regex
87
+ }
88
+ });
89
+ if (matched) {
90
+ // Extract a relevant section around the first match
91
+ const firstTermIdx = terms.reduce((best, term) => {
92
+ const idx = contentLower.indexOf(term);
93
+ return idx >= 0 && (best < 0 || idx < best) ? idx : best;
94
+ }, -1);
95
+ let start = Math.max(0, firstTermIdx - 200);
96
+ let end = Math.min(content.length, firstTermIdx + 500);
97
+ const prevBlank = content.lastIndexOf("\n\n", start);
98
+ const nextBlank = content.indexOf("\n\n", end);
99
+ if (prevBlank >= 0)
100
+ start = prevBlank + 2;
101
+ if (nextBlank >= 0)
102
+ end = nextBlank;
103
+ const snippet = content.slice(start, end).trim();
104
+ results.push({
105
+ title: `[auto-memory] ${candidate.label}`,
106
+ content: snippet,
107
+ source: candidate.label,
108
+ origin: "auto-memory",
109
+ timestamp: statSync(candidate.path).mtime.toISOString(),
110
+ });
111
+ break; // one result per file per query batch
112
+ }
113
+ }
114
+ }
115
+ catch (e) {
116
+ if (DEBUG)
117
+ process.stderr.write(`[ctx] auto-memory file read failed: ${e}\n`);
118
+ }
119
+ }
120
+ return results.slice(0, limit);
121
+ }