context-mode 1.0.88 → 1.0.90

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 (132) 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 +184 -60
  6. package/build/adapters/antigravity/index.d.ts +3 -5
  7. package/build/adapters/antigravity/index.js +7 -35
  8. package/build/adapters/base.d.ts +27 -0
  9. package/build/adapters/base.js +59 -0
  10. package/build/adapters/claude-code/index.d.ts +9 -25
  11. package/build/adapters/claude-code/index.js +27 -141
  12. package/build/adapters/claude-code-base.d.ts +49 -0
  13. package/build/adapters/claude-code-base.js +113 -0
  14. package/build/adapters/client-map.js +5 -0
  15. package/build/adapters/codex/hooks.d.ts +21 -14
  16. package/build/adapters/codex/hooks.js +22 -15
  17. package/build/adapters/codex/index.d.ts +6 -10
  18. package/build/adapters/codex/index.js +13 -43
  19. package/build/adapters/copilot-base.d.ts +78 -0
  20. package/build/adapters/copilot-base.js +281 -0
  21. package/build/adapters/cursor/index.d.ts +3 -5
  22. package/build/adapters/cursor/index.js +6 -34
  23. package/build/adapters/detect.d.ts +7 -0
  24. package/build/adapters/detect.js +57 -56
  25. package/build/adapters/gemini-cli/index.d.ts +3 -5
  26. package/build/adapters/gemini-cli/index.js +7 -35
  27. package/build/adapters/jetbrains-copilot/config.d.ts +8 -0
  28. package/build/adapters/jetbrains-copilot/config.js +8 -0
  29. package/build/adapters/jetbrains-copilot/hooks.d.ts +51 -0
  30. package/build/adapters/jetbrains-copilot/hooks.js +82 -0
  31. package/build/adapters/jetbrains-copilot/index.d.ts +24 -0
  32. package/build/adapters/jetbrains-copilot/index.js +119 -0
  33. package/build/adapters/kiro/hooks.d.ts +14 -0
  34. package/build/adapters/kiro/hooks.js +23 -0
  35. package/build/adapters/kiro/index.d.ts +3 -5
  36. package/build/adapters/kiro/index.js +10 -38
  37. package/build/adapters/openclaw/index.d.ts +3 -4
  38. package/build/adapters/openclaw/index.js +6 -22
  39. package/build/adapters/opencode/index.d.ts +2 -3
  40. package/build/adapters/opencode/index.js +5 -16
  41. package/build/adapters/qwen-code/index.d.ts +39 -0
  42. package/build/adapters/qwen-code/index.js +199 -0
  43. package/build/adapters/types.d.ts +1 -1
  44. package/build/adapters/vscode-copilot/index.d.ts +16 -46
  45. package/build/adapters/vscode-copilot/index.js +29 -320
  46. package/build/adapters/zed/index.d.ts +3 -5
  47. package/build/adapters/zed/index.js +7 -35
  48. package/build/cli.js +113 -47
  49. package/build/lifecycle.d.ts +23 -0
  50. package/build/lifecycle.js +54 -13
  51. package/build/opencode-plugin.d.ts +19 -7
  52. package/build/opencode-plugin.js +19 -7
  53. package/build/pi-extension.js +24 -7
  54. package/build/runtime.js +24 -9
  55. package/build/security.d.ts +17 -1
  56. package/build/security.js +40 -6
  57. package/build/server.js +129 -21
  58. package/build/session/analytics.d.ts +8 -7
  59. package/build/session/analytics.js +95 -75
  60. package/build/session/db.d.ts +10 -1
  61. package/build/session/db.js +67 -8
  62. package/build/session/extract.js +10 -2
  63. package/build/session/project-attribution.d.ts +73 -0
  64. package/build/session/project-attribution.js +231 -0
  65. package/build/store.d.ts +7 -0
  66. package/build/store.js +117 -18
  67. package/build/truncate.d.ts +6 -0
  68. package/build/truncate.js +51 -29
  69. package/build/types.d.ts +8 -0
  70. package/cli.bundle.mjs +157 -136
  71. package/configs/antigravity/GEMINI.md +31 -36
  72. package/configs/claude-code/CLAUDE.md +31 -37
  73. package/configs/codex/AGENTS.md +35 -49
  74. package/configs/cursor/context-mode.mdc +24 -25
  75. package/configs/gemini-cli/GEMINI.md +30 -36
  76. package/configs/jetbrains-copilot/copilot-instructions.md +59 -0
  77. package/configs/jetbrains-copilot/hooks.json +16 -0
  78. package/configs/jetbrains-copilot/mcp.json +8 -0
  79. package/configs/kilo/AGENTS.md +30 -36
  80. package/configs/kiro/KIRO.md +30 -36
  81. package/configs/kiro/agent.json +1 -1
  82. package/configs/openclaw/AGENTS.md +30 -36
  83. package/configs/opencode/AGENTS.md +30 -36
  84. package/configs/pi/AGENTS.md +31 -36
  85. package/configs/qwen-code/QWEN.md +63 -0
  86. package/configs/vscode-copilot/copilot-instructions.md +30 -36
  87. package/configs/zed/AGENTS.md +31 -36
  88. package/hooks/codex/posttooluse.mjs +7 -7
  89. package/hooks/codex/pretooluse.mjs +3 -3
  90. package/hooks/codex/sessionstart.mjs +2 -1
  91. package/hooks/core/formatters.mjs +24 -0
  92. package/hooks/core/routing.mjs +40 -15
  93. package/hooks/core/tool-naming.mjs +2 -0
  94. package/hooks/cursor/posttooluse.mjs +7 -7
  95. package/hooks/cursor/pretooluse.mjs +3 -3
  96. package/hooks/cursor/sessionstart.mjs +2 -1
  97. package/hooks/cursor/stop.mjs +2 -2
  98. package/hooks/ensure-deps.mjs +22 -10
  99. package/hooks/gemini-cli/aftertool.mjs +8 -8
  100. package/hooks/gemini-cli/beforetool.mjs +3 -2
  101. package/hooks/gemini-cli/precompress.mjs +2 -2
  102. package/hooks/gemini-cli/sessionstart.mjs +12 -4
  103. package/hooks/jetbrains-copilot/posttooluse.mjs +61 -0
  104. package/hooks/jetbrains-copilot/precompact.mjs +54 -0
  105. package/hooks/jetbrains-copilot/pretooluse.mjs +27 -0
  106. package/hooks/jetbrains-copilot/sessionstart.mjs +119 -0
  107. package/hooks/kiro/posttooluse.mjs +6 -7
  108. package/hooks/kiro/pretooluse.mjs +3 -2
  109. package/hooks/posttooluse.mjs +8 -8
  110. package/hooks/precompact.mjs +3 -4
  111. package/hooks/pretooluse.mjs +43 -20
  112. package/hooks/routing-block.mjs +35 -33
  113. package/hooks/session-attribution.bundle.mjs +1 -0
  114. package/hooks/session-db.bundle.mjs +27 -8
  115. package/hooks/session-extract.bundle.mjs +2 -1
  116. package/hooks/session-helpers.mjs +44 -3
  117. package/hooks/session-loaders.mjs +37 -0
  118. package/hooks/session-snapshot.bundle.mjs +14 -14
  119. package/hooks/sessionstart.mjs +5 -5
  120. package/hooks/userpromptsubmit.mjs +26 -9
  121. package/hooks/vscode-copilot/posttooluse.mjs +8 -8
  122. package/hooks/vscode-copilot/precompact.mjs +2 -2
  123. package/hooks/vscode-copilot/pretooluse.mjs +3 -2
  124. package/hooks/vscode-copilot/sessionstart.mjs +2 -2
  125. package/insight/server.mjs +262 -32
  126. package/insight/src/lib/api.ts +2 -1
  127. package/insight/src/routes/index.tsx +16 -3
  128. package/insight/src/routes/search.tsx +1 -1
  129. package/openclaw.plugin.json +1 -1
  130. package/package.json +11 -2
  131. package/server.bundle.mjs +117 -99
  132. package/skills/ctx-insight/SKILL.md +1 -1
@@ -1,65 +1,59 @@
1
1
  # context-mode — MANDATORY routing rules
2
2
 
3
- You have context-mode MCP tools available. These rules are NOT optional — they protect your context window from flooding. A single unrouted command can dump 56 KB into context and waste the entire session.
3
+ context-mode MCP tools available. Rules protect context window from flooding. One unrouted command dumps 56 KB into context.
4
4
 
5
5
  ## Think in Code — MANDATORY
6
6
 
7
- When you need to analyze, count, filter, compare, search, parse, transform, or process data: **write code** that does the work via `ctx_execute(language, code)` and `console.log()` only the answer. Do NOT read raw data into context to process mentally. Your role is to PROGRAM the analysis, not to COMPUTE it. Write robust, pure JavaScript — no npm dependencies, only Node.js built-ins (`fs`, `path`, `child_process`). Always use `try/catch`, handle `null`/`undefined`, and ensure compatibility with both Node.js and Bun. One script replaces ten tool calls and saves 100x context.
7
+ Analyze/count/filter/compare/search/parse/transform data: **write code** via `ctx_execute(language, code)`, `console.log()` only the answer. Do NOT read raw data into context. PROGRAM the analysis, not COMPUTE it. Pure JavaScript — Node.js built-ins only (`fs`, `path`, `child_process`). `try/catch`, handle `null`/`undefined`. One script replaces ten tool calls.
8
8
 
9
- ## BLOCKED commands — do NOT attempt these
9
+ ## BLOCKED — do NOT attempt
10
10
 
11
11
  ### curl / wget — BLOCKED
12
- Any terminal command containing `curl` or `wget` will be intercepted and blocked. Do NOT retry.
13
- Instead use:
14
- - `ctx_fetch_and_index(url, source)` to fetch and index web pages
15
- - `ctx_execute(language: "javascript", code: "const r = await fetch(...)")` to run HTTP calls in sandbox
12
+ Terminal `curl`/`wget` intercepted and blocked. Do NOT retry.
13
+ Use: `ctx_fetch_and_index(url, source)` or `ctx_execute(language: "javascript", code: "const r = await fetch(...)")`
16
14
 
17
15
  ### Inline HTTP — BLOCKED
18
- Any terminal command containing `fetch('http`, `requests.get(`, `requests.post(`, `http.get(`, or `http.request(` will be intercepted and blocked. Do NOT retry with terminal.
19
- Instead use:
20
- - `ctx_execute(language, code)` to run HTTP calls in sandbox — only stdout enters context
16
+ `fetch('http`, `requests.get(`, `requests.post(`, `http.get(`, `http.request(` intercepted. Do NOT retry.
17
+ Use: `ctx_execute(language, code)` — only stdout enters context
21
18
 
22
19
  ### WebFetch / fetch — BLOCKED
23
- Direct web fetching tools are blocked. Use the sandbox equivalent.
24
- Instead use:
25
- - `ctx_fetch_and_index(url, source)` then `ctx_search(queries)` to query the indexed content
20
+ Use: `ctx_fetch_and_index(url, source)` then `ctx_search(queries)`
26
21
 
27
- ## REDIRECTED tools — use sandbox equivalents
22
+ ## REDIRECTED — use sandbox
28
23
 
29
24
  ### Terminal / run_in_terminal (>20 lines output)
30
- Terminal is ONLY for: `git`, `mkdir`, `rm`, `mv`, `cd`, `ls`, `npm install`, `pip install`, and other short-output commands.
31
- For everything else, use:
32
- - `ctx_batch_execute(commands, queries)` — run multiple commands + search in ONE call
33
- - `ctx_execute(language: "shell", code: "...")` — run in sandbox, only stdout enters context
25
+ Terminal ONLY for: `git`, `mkdir`, `rm`, `mv`, `cd`, `ls`, `npm install`, `pip install`.
26
+ Otherwise: `ctx_batch_execute(commands, queries)` or `ctx_execute(language: "shell", code: "...")`
34
27
 
35
28
  ### read_file (for analysis)
36
- If you are reading a file to **edit** it → read_file is correct (edit needs content in context).
37
- If you are reading to **analyze, explore, or summarize** → use `ctx_execute_file(path, language, code)` instead. Only your printed summary enters context.
29
+ Reading to **edit** → read_file correct. Reading to **analyze/explore/summarize** `ctx_execute_file(path, language, code)`.
38
30
 
39
31
  ### grep / search (large results)
40
- Search results can flood context. Use `ctx_execute(language: "shell", code: "grep ...")` to run searches in sandbox. Only your printed summary enters context.
32
+ Use `ctx_execute(language: "shell", code: "grep ...")` in sandbox.
41
33
 
42
- ## Tool selection hierarchy
34
+ ## Tool selection
43
35
 
44
- 1. **GATHER**: `ctx_batch_execute(commands, queries)` — Primary tool. Runs all commands, auto-indexes output, returns search results. ONE call replaces 30+ individual calls. Each command: `{label: "descriptive header", command: "..."}`. Label becomes FTS5 chunk title — descriptive labels improve search.
45
- 2. **FOLLOW-UP**: `ctx_search(queries: ["q1", "q2", ...])` — Query indexed content. Pass ALL questions as array in ONE call.
46
- 3. **PROCESSING**: `ctx_execute(language, code)` | `ctx_execute_file(path, language, code)` — Sandbox execution. Only stdout enters context.
47
- 4. **WEB**: `ctx_fetch_and_index(url, source)` then `ctx_search(queries)` — Fetch, chunk, index, query. Raw HTML never enters context.
48
- 5. **INDEX**: `ctx_index(content, source)` — Store content in FTS5 knowledge base for later search.
36
+ 1. **GATHER**: `ctx_batch_execute(commands, queries)` — runs all commands, auto-indexes, returns search. ONE call replaces 30+. Each command: `{label: "header", command: "..."}`.
37
+ 2. **FOLLOW-UP**: `ctx_search(queries: ["q1", "q2", ...])` — all questions as array, ONE call.
38
+ 3. **PROCESSING**: `ctx_execute(language, code)` | `ctx_execute_file(path, language, code)` — sandbox, only stdout enters context.
39
+ 4. **WEB**: `ctx_fetch_and_index(url, source)` then `ctx_search(queries)` — raw HTML never enters context.
40
+ 5. **INDEX**: `ctx_index(content, source)` — store in FTS5 for later search.
49
41
 
50
- ## Output constraints
42
+ ## Output
51
43
 
52
- - Keep responses under 500 words.
53
- - Write artifacts (code, configs, PRDs) to FILES never return them as inline text. Return only: file path + 1-line description.
54
- - When indexing content, use descriptive source labels so others can `ctx_search(source: "label")` later.
44
+ Terse like caveman. Technical substance exact. Only fluff die.
45
+ Drop: articles, filler (just/really/basically), pleasantries, hedging. Fragments OK. Short synonyms. Code unchanged.
46
+ Pattern: [thing] [action] [reason]. [next step]. Auto-expand for: security warnings, irreversible actions, user confusion.
47
+ Write artifacts to FILES — never inline. Return: file path + 1-line description.
48
+ Descriptive source labels for `ctx_search(source: "label")`.
55
49
 
56
50
  ## ctx commands
57
51
 
58
52
  | Command | Action |
59
53
  |---------|--------|
60
- | `ctx stats` | Call the `ctx_stats` MCP tool and display the full output verbatim |
61
- | `ctx doctor` | Call the `ctx_doctor` MCP tool, run the returned shell command, display as checklist |
62
- | `ctx upgrade` | Call the `ctx_upgrade` MCP tool, run the returned shell command, display as checklist |
63
- | `ctx purge` | Call the `ctx_purge` MCP tool with confirm: true. Warns before wiping the knowledge base. |
54
+ | `ctx stats` | Call `ctx_stats` MCP tool, display full output verbatim |
55
+ | `ctx doctor` | Call `ctx_doctor` MCP tool, run returned shell command, display as checklist |
56
+ | `ctx upgrade` | Call `ctx_upgrade` MCP tool, run returned shell command, display as checklist |
57
+ | `ctx purge` | Call `ctx_purge` MCP tool with confirm: true. Warns before wiping knowledge base. |
64
58
 
65
- After /clear or /compact: knowledge base and session stats are preserved. Use `ctx purge` if you want to start fresh.
59
+ After /clear or /compact: knowledge base and session stats preserved. Use `ctx purge` to start fresh.
@@ -1,65 +1,60 @@
1
1
  # context-mode — MANDATORY routing rules
2
2
 
3
- You have context-mode MCP tools available. These rules are NOT optional — they protect your context window from flooding. A single unrouted command can dump 56 KB into context and waste the entire session. Zed does NOT have hooks, so these instructions are your ONLY enforcement mechanism. Follow them strictly.
3
+ context-mode MCP tools available. Rules protect context window from flooding. One unrouted command dumps 56 KB into context. Zed has NO hooks these instructions are ONLY enforcement. Follow strictly.
4
4
 
5
5
  ## Think in Code — MANDATORY
6
6
 
7
- When you need to analyze, count, filter, compare, search, parse, transform, or process data: **write code** that does the work via `mcp:context-mode:ctx_execute(language, code)` and `console.log()` only the answer. Do NOT read raw data into context to process mentally. Your role is to PROGRAM the analysis, not to COMPUTE it. Write robust, pure JavaScript — no npm dependencies, only Node.js built-ins (`fs`, `path`, `child_process`). Always use `try/catch`, handle `null`/`undefined`, and ensure compatibility with both Node.js and Bun. One script replaces ten tool calls and saves 100x context.
7
+ Analyze/count/filter/compare/search/parse/transform data: **write code** via `mcp:context-mode:ctx_execute(language, code)`, `console.log()` only the answer. Do NOT read raw data into context. PROGRAM the analysis, not COMPUTE it. Pure JavaScript — Node.js built-ins only (`fs`, `path`, `child_process`). `try/catch`, handle `null`/`undefined`. One script replaces ten tool calls.
8
8
 
9
- ## BLOCKED commands — do NOT use these
9
+ ## BLOCKED — do NOT use
10
10
 
11
11
  ### curl / wget — FORBIDDEN
12
- Do NOT use `curl` or `wget` in any shell command. They dump raw HTTP responses directly into your context window.
13
- Instead use:
14
- - `mcp:context-mode:ctx_fetch_and_index(url, source)` to fetch and index web pages
15
- - `mcp:context-mode:ctx_execute(language: "javascript", code: "const r = await fetch(...)")` to run HTTP calls in sandbox
12
+ Do NOT use `curl`/`wget` in shell. Dumps raw HTTP into context.
13
+ Use: `mcp:context-mode:ctx_fetch_and_index(url, source)` or `mcp:context-mode:ctx_execute(language: "javascript", code: "const r = await fetch(...)")`
16
14
 
17
15
  ### Inline HTTP — FORBIDDEN
18
- Do NOT run inline HTTP calls via `node -e "fetch(..."`, `python -c "requests.get(..."`, or similar patterns. They bypass the sandbox and flood context.
19
- Instead use:
20
- - `mcp:context-mode:ctx_execute(language, code)` to run HTTP calls in sandbox — only stdout enters context
16
+ No `node -e "fetch(..."`, `python -c "requests.get(..."`. Bypasses sandbox.
17
+ Use: `mcp:context-mode:ctx_execute(language, code)` — only stdout enters context
21
18
 
22
19
  ### Direct web fetching — FORBIDDEN
23
- Do NOT use any direct URL fetching tool. Raw HTML can exceed 100 KB.
24
- Instead use:
25
- - `mcp:context-mode:ctx_fetch_and_index(url, source)` then `mcp:context-mode:ctx_search(queries)` to query the indexed content
20
+ Raw HTML can exceed 100 KB.
21
+ Use: `mcp:context-mode:ctx_fetch_and_index(url, source)` then `mcp:context-mode:ctx_search(queries)`
26
22
 
27
- ## REDIRECTED tools — use sandbox equivalents
23
+ ## REDIRECTED — use sandbox
28
24
 
29
25
  ### Shell (>20 lines output)
30
- Shell is ONLY for: `git`, `mkdir`, `rm`, `mv`, `cd`, `ls`, `npm install`, `pip install`, and other short-output commands.
31
- For everything else, use:
32
- - `mcp:context-mode:ctx_batch_execute(commands, queries)` — run multiple commands + search in ONE call
33
- - `mcp:context-mode:ctx_execute(language: "shell", code: "...")` — run in sandbox, only stdout enters context
26
+ Shell ONLY for: `git`, `mkdir`, `rm`, `mv`, `cd`, `ls`, `npm install`, `pip install`.
27
+ Otherwise: `mcp:context-mode:ctx_batch_execute(commands, queries)` or `mcp:context-mode:ctx_execute(language: "shell", code: "...")`
34
28
 
35
29
  ### File reading (for analysis)
36
- If you are reading a file to **edit** it → reading is correct (edit needs content in context).
37
- If you are reading to **analyze, explore, or summarize** → use `mcp:context-mode:ctx_execute_file(path, language, code)` instead. Only your printed summary enters context. The raw file stays in the sandbox.
30
+ Reading to **edit** → reading correct. Reading to **analyze/explore/summarize** `mcp:context-mode:ctx_execute_file(path, language, code)`.
38
31
 
39
32
  ### grep / search (large results)
40
- Search results can flood context. Use `mcp:context-mode:ctx_execute(language: "shell", code: "grep ...")` to run searches in sandbox. Only your printed summary enters context.
33
+ Use `mcp:context-mode:ctx_execute(language: "shell", code: "grep ...")` in sandbox.
41
34
 
42
- ## Tool selection hierarchy
35
+ ## Tool selection
43
36
 
44
- 1. **GATHER**: `mcp:context-mode:ctx_batch_execute(commands, queries)` — Primary tool. Runs all commands, auto-indexes output, returns search results. ONE call replaces 30+ individual calls. Each command: `{label: "descriptive header", command: "..."}`. Label becomes FTS5 chunk title — descriptive labels improve search.
45
- 2. **FOLLOW-UP**: `mcp:context-mode:ctx_search(queries: ["q1", "q2", ...])` — Query indexed content. Pass ALL questions as array in ONE call.
46
- 3. **PROCESSING**: `mcp:context-mode:ctx_execute(language, code)` | `mcp:context-mode:ctx_execute_file(path, language, code)` — Sandbox execution. Only stdout enters context.
47
- 4. **WEB**: `mcp:context-mode:ctx_fetch_and_index(url, source)` then `mcp:context-mode:ctx_search(queries)` — Fetch, chunk, index, query. Raw HTML never enters context.
48
- 5. **INDEX**: `mcp:context-mode:ctx_index(content, source)` — Store content in FTS5 knowledge base for later search.
37
+ 1. **GATHER**: `mcp:context-mode:ctx_batch_execute(commands, queries)` — runs all commands, auto-indexes, returns search. ONE call replaces 30+. Each command: `{label: "header", command: "..."}`.
38
+ 2. **FOLLOW-UP**: `mcp:context-mode:ctx_search(queries: ["q1", "q2", ...])` — all questions as array, ONE call.
39
+ 3. **PROCESSING**: `mcp:context-mode:ctx_execute(language, code)` | `mcp:context-mode:ctx_execute_file(path, language, code)` — sandbox, only stdout enters context.
40
+ 4. **WEB**: `mcp:context-mode:ctx_fetch_and_index(url, source)` then `mcp:context-mode:ctx_search(queries)` — raw HTML never enters context.
41
+ 5. **INDEX**: `mcp:context-mode:ctx_index(content, source)` — store in FTS5 for later search.
49
42
 
50
- ## Output constraints
43
+ ## Output
51
44
 
52
- - Keep responses under 500 words.
53
- - Write artifacts (code, configs, PRDs) to FILES never return them as inline text. Return only: file path + 1-line description.
54
- - When indexing content, use descriptive source labels so others can `search(source: "label")` later.
45
+ Terse like caveman. Technical substance exact. Only fluff die.
46
+ Drop: articles, filler (just/really/basically), pleasantries, hedging. Fragments OK. Short synonyms. Code unchanged.
47
+ Pattern: [thing] [action] [reason]. [next step]. Auto-expand for: security warnings, irreversible actions, user confusion.
48
+ Write artifacts to FILES — never inline. Return: file path + 1-line description.
49
+ Descriptive source labels for `search(source: "label")`.
55
50
 
56
51
  ## ctx commands
57
52
 
58
53
  | Command | Action |
59
54
  |---------|--------|
60
- | `ctx stats` | Call the `stats` MCP tool and display the full output verbatim |
61
- | `ctx doctor` | Call the `doctor` MCP tool, run the returned shell command, display as checklist |
62
- | `ctx upgrade` | Call the `upgrade` MCP tool, run the returned shell command, display as checklist |
63
- | `ctx purge` | Call the `mcp:context-mode:purge` MCP tool with confirm: true. Warns before wiping the knowledge base. |
55
+ | `ctx stats` | Call `stats` MCP tool, display full output verbatim |
56
+ | `ctx doctor` | Call `doctor` MCP tool, run returned shell command, display as checklist |
57
+ | `ctx upgrade` | Call `upgrade` MCP tool, run returned shell command, display as checklist |
58
+ | `ctx purge` | Call `mcp:context-mode:purge` MCP tool with confirm: true. Warns before wiping knowledge base. |
64
59
 
65
- After /clear or /compact: knowledge base and session stats are preserved. Use `ctx purge` if you want to start fresh.
60
+ After /clear or /compact: knowledge base and session stats preserved. Use `ctx purge` to start fresh.
@@ -5,13 +5,13 @@ import "../ensure-deps.mjs";
5
5
  * Codex CLI postToolUse hook — session event capture.
6
6
  */
7
7
 
8
- import { readStdin, getSessionId, getSessionDBPath, getInputProjectDir, CODEX_OPTS } from "../session-helpers.mjs";
9
- import { createSessionLoaders } from "../session-loaders.mjs";
8
+ import { readStdin, parseStdin, getSessionId, getSessionDBPath, getInputProjectDir, CODEX_OPTS } from "../session-helpers.mjs";
9
+ import { createSessionLoaders, attributeAndInsertEvents } from "../session-loaders.mjs";
10
10
  import { dirname } from "node:path";
11
11
  import { fileURLToPath } from "node:url";
12
12
 
13
13
  const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
14
- const { loadSessionDB, loadExtract } = createSessionLoaders(HOOK_DIR);
14
+ const { loadSessionDB, loadExtract, loadProjectAttribution } = createSessionLoaders(HOOK_DIR);
15
15
  const OPTS = CODEX_OPTS;
16
16
 
17
17
  function normalizeToolName(toolName) {
@@ -22,10 +22,11 @@ function normalizeToolName(toolName) {
22
22
 
23
23
  try {
24
24
  const raw = await readStdin();
25
- const input = JSON.parse(raw);
25
+ const input = parseStdin(raw);
26
26
  const projectDir = getInputProjectDir(input, OPTS);
27
27
 
28
28
  const { extractEvents } = await loadExtract();
29
+ const { resolveProjectAttributions } = await loadProjectAttribution();
29
30
  const { SessionDB } = await loadSessionDB();
30
31
 
31
32
  const dbPath = getSessionDBPath(OPTS);
@@ -43,9 +44,8 @@ try {
43
44
  };
44
45
 
45
46
  const events = extractEvents(normalizedInput);
46
- for (const event of events) {
47
- db.insertEvent(sessionId, event, "PostToolUse");
48
- }
47
+
48
+ attributeAndInsertEvents(db, sessionId, events, input, projectDir, "PostToolUse", resolveProjectAttributions);
49
49
 
50
50
  db.close();
51
51
  } catch {
@@ -10,7 +10,7 @@ import "../suppress-stderr.mjs";
10
10
 
11
11
  import { dirname, resolve } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
- import { readStdin, getInputProjectDir, CODEX_OPTS } from "../session-helpers.mjs";
13
+ import { readStdin, parseStdin, getInputProjectDir, getSessionId, CODEX_OPTS } from "../session-helpers.mjs";
14
14
  import { routePreToolUse, initSecurity } from "../core/routing.mjs";
15
15
  import { formatDecision } from "../core/formatters.mjs";
16
16
 
@@ -18,12 +18,12 @@ const __hookDir = dirname(fileURLToPath(import.meta.url));
18
18
  await initSecurity(resolve(__hookDir, "..", "..", "build"));
19
19
 
20
20
  const raw = await readStdin();
21
- const input = JSON.parse(raw);
21
+ const input = parseStdin(raw);
22
22
  const tool = input.tool_name ?? "";
23
23
  const toolInput = input.tool_input ?? {};
24
24
  const projectDir = getInputProjectDir(input, CODEX_OPTS);
25
25
 
26
- const decision = routePreToolUse(tool, toolInput, projectDir, "codex");
26
+ const decision = routePreToolUse(tool, toolInput, projectDir, "codex", getSessionId(input, CODEX_OPTS));
27
27
  const response = formatDecision("codex", decision);
28
28
  const output = response ?? {
29
29
  hookSpecificOutput: { hookEventName: "PreToolUse" },
@@ -18,6 +18,7 @@ import {
18
18
  } from "../session-directive.mjs";
19
19
  import {
20
20
  readStdin,
21
+ parseStdin,
21
22
  getSessionId,
22
23
  getSessionDBPath,
23
24
  getSessionEventsPath,
@@ -37,7 +38,7 @@ let additionalContext = ROUTING_BLOCK;
37
38
 
38
39
  try {
39
40
  const raw = await readStdin();
40
- const input = JSON.parse(raw);
41
+ const input = parseStdin(raw);
41
42
  const source = input.source ?? "startup";
42
43
  const projectDir = getInputProjectDir(input, CODEX_OPTS);
43
44
 
@@ -69,6 +69,30 @@ export const formatters = {
69
69
  }),
70
70
  },
71
71
 
72
+ "jetbrains-copilot": {
73
+ deny: (reason) => ({
74
+ permissionDecision: "deny",
75
+ permissionDecisionReason: reason,
76
+ }),
77
+ ask: () => ({
78
+ permissionDecision: "ask",
79
+ }),
80
+ modify: (updatedInput) => ({
81
+ hookSpecificOutput: {
82
+ hookEventName: "PreToolUse",
83
+ permissionDecision: "allow",
84
+ permissionDecisionReason: "Routed to context-mode sandbox",
85
+ updatedInput,
86
+ },
87
+ }),
88
+ context: (additionalContext) => ({
89
+ hookSpecificOutput: {
90
+ hookEventName: "PreToolUse",
91
+ additionalContext,
92
+ },
93
+ }),
94
+ },
95
+
72
96
  "codex": {
73
97
  deny: (reason) => ({
74
98
  hookSpecificOutput: {
@@ -36,23 +36,41 @@ import { resolve } from "node:path";
36
36
  // - In-memory Set for same-process (OpenCode ts-plugin, vitest)
37
37
  // - File-based markers with O_EXCL for cross-process atomicity
38
38
  // (Claude Code, Gemini, Cursor, VS Code Copilot)
39
- // Session scoped via process.ppid (= host PID, constant for session lifetime).
39
+ //
40
+ // Session identity is resolved in this order:
41
+ // 1. sessionId passed in by the caller (stable across hook invocations)
42
+ // 2. process.ppid fallback (works on macOS/Linux — host PID is stable)
43
+ //
44
+ // The ppid fallback is unreliable on Windows + Git Bash, where each hook
45
+ // invocation spawns a fresh bash.exe with a different PID (#298). Callers
46
+ // that have a stable session identifier (e.g. from the hook payload) should
47
+ // pass it to routePreToolUse so the marker directory stays consistent across
48
+ // invocations of the same logical session.
40
49
  const _guidanceShown = new Set();
41
- const _guidanceId = process.env.VITEST_WORKER_ID
42
- ? `${process.ppid}-w${process.env.VITEST_WORKER_ID}`
43
- : String(process.ppid);
44
- const _guidanceDir = resolve(tmpdir(), `context-mode-guidance-${_guidanceId}`);
45
50
 
46
- function guidanceOnce(type, content) {
51
+ function defaultGuidanceId() {
52
+ return process.env.VITEST_WORKER_ID
53
+ ? `${process.ppid}-w${process.env.VITEST_WORKER_ID}`
54
+ : String(process.ppid);
55
+ }
56
+
57
+ function guidanceDirFor(sessionId) {
58
+ const id = sessionId ? `s-${sessionId}` : defaultGuidanceId();
59
+ return resolve(tmpdir(), `context-mode-guidance-${id}`);
60
+ }
61
+
62
+ function guidanceOnce(type, content, sessionId) {
47
63
  // Fast path: in-memory (same process)
48
64
  if (_guidanceShown.has(type)) return null;
49
65
 
50
- // Ensure marker directory exists
51
- try { mkdirSync(_guidanceDir, { recursive: true }); } catch {}
66
+ // Resolve marker directory for this session (stable even on Windows/Git Bash
67
+ // where process.ppid shifts every invocation see #298).
68
+ const dir = guidanceDirFor(sessionId);
69
+ try { mkdirSync(dir, { recursive: true }); } catch {}
52
70
 
53
71
  // Atomic create-or-fail: O_CREAT | O_EXCL | O_WRONLY
54
72
  // First process to create the file wins; others get EEXIST.
55
- const marker = resolve(_guidanceDir, type);
73
+ const marker = resolve(dir, type);
56
74
  try {
57
75
  const fd = openSync(marker, fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_WRONLY);
58
76
  closeSync(fd);
@@ -66,9 +84,13 @@ function guidanceOnce(type, content) {
66
84
  return { action: "context", additionalContext: content };
67
85
  }
68
86
 
69
- export function resetGuidanceThrottle() {
87
+ export function resetGuidanceThrottle(sessionId) {
70
88
  _guidanceShown.clear();
71
- try { rmSync(_guidanceDir, { recursive: true, force: true }); } catch {}
89
+ // Clear ppid-based dir (legacy / fallback callers) and the sessionId dir if given
90
+ try { rmSync(guidanceDirFor(), { recursive: true, force: true }); } catch {}
91
+ if (sessionId) {
92
+ try { rmSync(guidanceDirFor(sessionId), { recursive: true, force: true }); } catch {}
93
+ }
72
94
  }
73
95
 
74
96
  /**
@@ -150,8 +172,11 @@ const TOOL_ALIASES = {
150
172
  * @param {object} toolInput - The tool input/parameters
151
173
  * @param {string} [projectDir] - Project directory for security policy lookup
152
174
  * @param {string} [platform="claude-code"] - Platform ID for tool name formatting
175
+ * @param {string} [sessionId] - Stable session identifier from hook payload. When
176
+ * provided, the guidance throttle uses it to scope marker files across hook
177
+ * invocations even when process.ppid shifts (Windows/Git Bash — see #298).
153
178
  */
154
- export function routePreToolUse(toolName, toolInput, projectDir, platform) {
179
+ export function routePreToolUse(toolName, toolInput, projectDir, platform, sessionId) {
155
180
  // Build platform-specific tool namer (defaults to claude-code for backward compat)
156
181
  const t = createToolNamer(platform || "claude-code");
157
182
 
@@ -272,17 +297,17 @@ export function routePreToolUse(toolName, toolInput, projectDir, platform) {
272
297
  }
273
298
 
274
299
  // allow all other Bash commands, but inject routing nudge (once per session)
275
- return guidanceOnce("bash", bashGuidance);
300
+ return guidanceOnce("bash", bashGuidance, sessionId);
276
301
  }
277
302
 
278
303
  // ─── Read: nudge toward execute_file (once per session) ───
279
304
  if (canonical === "Read") {
280
- return guidanceOnce("read", readGuidance);
305
+ return guidanceOnce("read", readGuidance, sessionId);
281
306
  }
282
307
 
283
308
  // ─── Grep: nudge toward execute (once per session) ───
284
309
  if (canonical === "Grep") {
285
- return guidanceOnce("grep", grepGuidance);
310
+ return guidanceOnce("grep", grepGuidance, sessionId);
286
311
  }
287
312
 
288
313
  // ─── WebFetch: deny + redirect to sandbox ───
@@ -22,12 +22,14 @@ const TOOL_PREFIXES = {
22
22
  "opencode": (tool) => `context-mode_${tool}`,
23
23
  "kilo": (tool) => `context-mode_${tool}`,
24
24
  "vscode-copilot": (tool) => `context-mode_${tool}`,
25
+ "jetbrains-copilot": (tool) => `context-mode_${tool}`,
25
26
  "kiro": (tool) => `@context-mode/${tool}`,
26
27
  "zed": (tool) => `mcp:context-mode:${tool}`,
27
28
  "cursor": (tool) => tool,
28
29
  "codex": (tool) => tool,
29
30
  "openclaw": (tool) => tool,
30
31
  "pi": (tool) => tool,
32
+ "qwen-code": (tool) => `mcp__context-mode__${tool}`,
31
33
  };
32
34
 
33
35
  /**
@@ -5,13 +5,13 @@ import "../ensure-deps.mjs";
5
5
  * Cursor postToolUse hook — session event capture.
6
6
  */
7
7
 
8
- import { readStdin, getSessionId, getSessionDBPath, getInputProjectDir, CURSOR_OPTS } from "../session-helpers.mjs";
8
+ import { readStdin, parseStdin, getSessionId, getSessionDBPath, getInputProjectDir, CURSOR_OPTS } from "../session-helpers.mjs";
9
9
  import { dirname } from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
- import { createSessionLoaders } from "../session-loaders.mjs";
11
+ import { createSessionLoaders, attributeAndInsertEvents } from "../session-loaders.mjs";
12
12
 
13
13
  const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
14
- const { loadSessionDB, loadExtract } = createSessionLoaders(HOOK_DIR);
14
+ const { loadSessionDB, loadExtract, loadProjectAttribution } = createSessionLoaders(HOOK_DIR);
15
15
  const OPTS = CURSOR_OPTS;
16
16
 
17
17
  function normalizeToolName(toolName) {
@@ -30,7 +30,7 @@ function normalizeToolName(toolName) {
30
30
 
31
31
  try {
32
32
  const raw = await readStdin();
33
- const input = JSON.parse(raw);
33
+ const input = parseStdin(raw);
34
34
  const projectDir = getInputProjectDir(input, CURSOR_OPTS);
35
35
 
36
36
  if (projectDir && !process.env.CURSOR_CWD) {
@@ -38,6 +38,7 @@ try {
38
38
  }
39
39
 
40
40
  const { extractEvents } = await loadExtract();
41
+ const { resolveProjectAttributions } = await loadProjectAttribution();
41
42
  const { SessionDB } = await loadSessionDB();
42
43
 
43
44
  const dbPath = getSessionDBPath(OPTS);
@@ -58,9 +59,8 @@ try {
58
59
  };
59
60
 
60
61
  const events = extractEvents(normalizedInput);
61
- for (const event of events) {
62
- db.insertEvent(sessionId, event, "PostToolUse");
63
- }
62
+
63
+ attributeAndInsertEvents(db, sessionId, events, input, projectDir, "PostToolUse", resolveProjectAttributions);
64
64
 
65
65
  db.close();
66
66
  } catch {
@@ -6,7 +6,7 @@ import "../suppress-stderr.mjs";
6
6
 
7
7
  import { dirname, resolve } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
- import { readStdin, getInputProjectDir, CURSOR_OPTS } from "../session-helpers.mjs";
9
+ import { readStdin, parseStdin, getInputProjectDir, getSessionId, CURSOR_OPTS } from "../session-helpers.mjs";
10
10
  import { routePreToolUse, initSecurity } from "../core/routing.mjs";
11
11
  import { formatDecision } from "../core/formatters.mjs";
12
12
 
@@ -14,12 +14,12 @@ const __hookDir = dirname(fileURLToPath(import.meta.url));
14
14
  await initSecurity(resolve(__hookDir, "..", "..", "build"));
15
15
 
16
16
  const raw = await readStdin();
17
- const input = JSON.parse(raw);
17
+ const input = parseStdin(raw);
18
18
  const tool = input.tool_name ?? "";
19
19
  const toolInput = input.tool_input ?? {};
20
20
  const projectDir = getInputProjectDir(input, CURSOR_OPTS);
21
21
 
22
- const decision = routePreToolUse(tool, toolInput, projectDir, "cursor");
22
+ const decision = routePreToolUse(tool, toolInput, projectDir, "cursor", getSessionId(input, CURSOR_OPTS));
23
23
  const response = formatDecision("cursor", decision);
24
24
  // Cursor treats empty stdout as an invalid hook response,
25
25
  // so even passthrough decisions must emit a syntactically valid no-op payload.
@@ -18,6 +18,7 @@ import {
18
18
  } from "../session-directive.mjs";
19
19
  import {
20
20
  readStdin,
21
+ parseStdin,
21
22
  getSessionId,
22
23
  getSessionDBPath,
23
24
  getSessionEventsPath,
@@ -37,7 +38,7 @@ let additionalContext = ROUTING_BLOCK;
37
38
 
38
39
  try {
39
40
  const raw = await readStdin();
40
- const input = JSON.parse(raw);
41
+ const input = parseStdin(raw);
41
42
  const source = input.source ?? input.trigger ?? "startup";
42
43
  const projectDir = getInputProjectDir(input, CURSOR_OPTS);
43
44
 
@@ -8,7 +8,7 @@ import "../ensure-deps.mjs";
8
8
  * Output: { "followup_message": "" } (empty = don't continue the loop)
9
9
  */
10
10
 
11
- import { readStdin, getSessionId, getSessionDBPath, getInputProjectDir, CURSOR_OPTS } from "../session-helpers.mjs";
11
+ import { readStdin, parseStdin, getSessionId, getSessionDBPath, getInputProjectDir, CURSOR_OPTS } from "../session-helpers.mjs";
12
12
  import { dirname } from "node:path";
13
13
  import { fileURLToPath } from "node:url";
14
14
  import { createSessionLoaders } from "../session-loaders.mjs";
@@ -19,7 +19,7 @@ const OPTS = CURSOR_OPTS;
19
19
 
20
20
  try {
21
21
  const raw = await readStdin();
22
- const input = JSON.parse(raw);
22
+ const input = parseStdin(raw);
23
23
  const projectDir = getInputProjectDir(input, CURSOR_OPTS);
24
24
 
25
25
  if (projectDir && !process.env.CURSOR_CWD) {
@@ -28,8 +28,24 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
28
28
  const root = resolve(__dirname, "..");
29
29
 
30
30
  const NATIVE_DEPS = ["better-sqlite3"];
31
+ const NATIVE_BINARIES = {
32
+ "better-sqlite3": ["build", "Release", "better_sqlite3.node"],
33
+ };
34
+
35
+ /**
36
+ * Check if the current runtime has built-in SQLite support, making
37
+ * better-sqlite3 unnecessary. Bun has bun:sqlite, Node >= 22.5 has node:sqlite.
38
+ * When true, skip the entire better-sqlite3 bootstrap to avoid SIGSEGV
39
+ * coredumps on Node v24 (#331) and unnecessary install overhead.
40
+ */
41
+ function hasModernSqlite() {
42
+ if (typeof globalThis.Bun !== "undefined") return true;
43
+ const [major, minor] = process.versions.node.split(".").map(Number);
44
+ return major > 22 || (major === 22 && minor >= 5);
45
+ }
31
46
 
32
47
  export function ensureDeps() {
48
+ if (hasModernSqlite()) return;
33
49
  for (const pkg of NATIVE_DEPS) {
34
50
  const pkgDir = resolve(root, "node_modules", pkg);
35
51
  if (!existsSync(pkgDir)) {
@@ -41,10 +57,7 @@ export function ensureDeps() {
41
57
  timeout: 120000,
42
58
  });
43
59
  } catch { /* best effort — hook degrades gracefully without DB */ }
44
- } else if (
45
- !existsSync(resolve(pkgDir, "build", "Release")) &&
46
- !existsSync(resolve(pkgDir, "prebuilds"))
47
- ) {
60
+ } else if (!existsSync(resolve(pkgDir, ...NATIVE_BINARIES[pkg]))) {
48
61
  // Package installed but native binary missing (e.g., npm ignore-scripts=true)
49
62
  try {
50
63
  execSync(`npm rebuild ${pkg} --ignore-scripts=false`, {
@@ -81,6 +94,7 @@ function probeNativeInChildProcess(pluginRoot) {
81
94
  }
82
95
 
83
96
  export function ensureNativeCompat(pluginRoot) {
97
+ if (hasModernSqlite()) return;
84
98
  try {
85
99
  const abi = process.versions.modules;
86
100
  const nativeDir = resolve(pluginRoot, "node_modules", "better-sqlite3", "build", "Release");
@@ -101,21 +115,19 @@ export function ensureNativeCompat(pluginRoot) {
101
115
  // Cached binary is stale/corrupt — fall through to rebuild
102
116
  }
103
117
 
104
- if (!existsSync(binaryPath)) return;
105
-
106
118
  // Probe: try loading better-sqlite3 with current Node
107
- if (probeNativeInChildProcess(pluginRoot)) {
119
+ if (existsSync(binaryPath) && probeNativeInChildProcess(pluginRoot)) {
108
120
  // Load succeeded — cache the working binary for this ABI
109
121
  copyFileSync(binaryPath, abiCachePath);
110
122
  } else {
111
- // ABI mismatch — rebuild for current Node version
112
- execSync("npm rebuild better-sqlite3", {
123
+ // ABI mismatch or missing native binary — rebuild for current Node version
124
+ execSync("npm rebuild better-sqlite3 --ignore-scripts=false", {
113
125
  cwd: pluginRoot,
114
126
  stdio: "pipe",
115
127
  timeout: 60000,
116
128
  });
117
129
  codesignBinary(binaryPath);
118
- if (existsSync(binaryPath)) {
130
+ if (existsSync(binaryPath) && probeNativeInChildProcess(pluginRoot)) {
119
131
  copyFileSync(binaryPath, abiCachePath);
120
132
  }
121
133
  }