context-mode 1.0.89 → 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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/README.md +184 -60
- package/build/adapters/antigravity/index.d.ts +3 -5
- package/build/adapters/antigravity/index.js +7 -35
- package/build/adapters/base.d.ts +27 -0
- package/build/adapters/base.js +59 -0
- package/build/adapters/claude-code/index.d.ts +9 -25
- package/build/adapters/claude-code/index.js +12 -140
- package/build/adapters/claude-code-base.d.ts +49 -0
- package/build/adapters/claude-code-base.js +113 -0
- package/build/adapters/client-map.js +5 -0
- package/build/adapters/codex/hooks.d.ts +21 -14
- package/build/adapters/codex/hooks.js +22 -15
- package/build/adapters/codex/index.d.ts +6 -10
- package/build/adapters/codex/index.js +13 -43
- package/build/adapters/copilot-base.d.ts +78 -0
- package/build/adapters/copilot-base.js +281 -0
- package/build/adapters/cursor/index.d.ts +3 -5
- package/build/adapters/cursor/index.js +6 -34
- package/build/adapters/detect.d.ts +7 -0
- package/build/adapters/detect.js +57 -56
- package/build/adapters/gemini-cli/index.d.ts +3 -5
- package/build/adapters/gemini-cli/index.js +7 -35
- package/build/adapters/jetbrains-copilot/config.d.ts +8 -0
- package/build/adapters/jetbrains-copilot/config.js +8 -0
- package/build/adapters/jetbrains-copilot/hooks.d.ts +51 -0
- package/build/adapters/jetbrains-copilot/hooks.js +82 -0
- package/build/adapters/jetbrains-copilot/index.d.ts +24 -0
- package/build/adapters/jetbrains-copilot/index.js +119 -0
- package/build/adapters/kiro/hooks.d.ts +14 -0
- package/build/adapters/kiro/hooks.js +23 -0
- package/build/adapters/kiro/index.d.ts +3 -5
- package/build/adapters/kiro/index.js +10 -38
- package/build/adapters/openclaw/index.d.ts +3 -4
- package/build/adapters/openclaw/index.js +6 -22
- package/build/adapters/opencode/index.d.ts +2 -3
- package/build/adapters/opencode/index.js +5 -16
- package/build/adapters/qwen-code/index.d.ts +39 -0
- package/build/adapters/qwen-code/index.js +199 -0
- package/build/adapters/types.d.ts +1 -1
- package/build/adapters/vscode-copilot/index.d.ts +16 -46
- package/build/adapters/vscode-copilot/index.js +29 -320
- package/build/adapters/zed/index.d.ts +3 -5
- package/build/adapters/zed/index.js +7 -35
- package/build/cli.js +13 -0
- package/build/lifecycle.d.ts +23 -0
- package/build/lifecycle.js +54 -13
- package/build/opencode-plugin.d.ts +19 -7
- package/build/opencode-plugin.js +19 -7
- package/build/runtime.js +24 -9
- package/build/security.d.ts +17 -1
- package/build/security.js +40 -6
- package/build/server.js +41 -9
- package/build/session/analytics.d.ts +8 -7
- package/build/session/analytics.js +95 -75
- package/build/session/db.d.ts +10 -1
- package/build/session/db.js +67 -8
- package/build/session/extract.js +10 -2
- package/build/session/project-attribution.d.ts +73 -0
- package/build/session/project-attribution.js +231 -0
- package/build/store.d.ts +4 -0
- package/build/store.js +58 -9
- package/build/types.d.ts +8 -0
- package/cli.bundle.mjs +135 -121
- package/configs/antigravity/GEMINI.md +31 -36
- package/configs/claude-code/CLAUDE.md +31 -37
- package/configs/codex/AGENTS.md +35 -49
- package/configs/cursor/context-mode.mdc +24 -25
- package/configs/gemini-cli/GEMINI.md +30 -36
- package/configs/jetbrains-copilot/copilot-instructions.md +59 -0
- package/configs/jetbrains-copilot/hooks.json +16 -0
- package/configs/jetbrains-copilot/mcp.json +8 -0
- package/configs/kilo/AGENTS.md +30 -36
- package/configs/kiro/KIRO.md +30 -36
- package/configs/kiro/agent.json +1 -1
- package/configs/openclaw/AGENTS.md +30 -36
- package/configs/opencode/AGENTS.md +30 -36
- package/configs/pi/AGENTS.md +31 -36
- package/configs/qwen-code/QWEN.md +63 -0
- package/configs/vscode-copilot/copilot-instructions.md +30 -36
- package/configs/zed/AGENTS.md +31 -36
- package/hooks/codex/posttooluse.mjs +7 -7
- package/hooks/codex/pretooluse.mjs +3 -3
- package/hooks/codex/sessionstart.mjs +2 -1
- package/hooks/core/formatters.mjs +24 -0
- package/hooks/core/routing.mjs +40 -15
- package/hooks/core/tool-naming.mjs +2 -0
- package/hooks/cursor/posttooluse.mjs +7 -7
- package/hooks/cursor/pretooluse.mjs +3 -3
- package/hooks/cursor/sessionstart.mjs +2 -1
- package/hooks/cursor/stop.mjs +2 -2
- package/hooks/ensure-deps.mjs +22 -10
- package/hooks/gemini-cli/aftertool.mjs +8 -8
- package/hooks/gemini-cli/beforetool.mjs +3 -2
- package/hooks/gemini-cli/precompress.mjs +2 -2
- package/hooks/gemini-cli/sessionstart.mjs +12 -4
- package/hooks/jetbrains-copilot/posttooluse.mjs +61 -0
- package/hooks/jetbrains-copilot/precompact.mjs +54 -0
- package/hooks/jetbrains-copilot/pretooluse.mjs +27 -0
- package/hooks/jetbrains-copilot/sessionstart.mjs +119 -0
- package/hooks/kiro/posttooluse.mjs +6 -7
- package/hooks/kiro/pretooluse.mjs +3 -2
- package/hooks/posttooluse.mjs +8 -8
- package/hooks/precompact.mjs +3 -4
- package/hooks/pretooluse.mjs +5 -4
- package/hooks/routing-block.mjs +35 -33
- package/hooks/session-attribution.bundle.mjs +1 -0
- package/hooks/session-db.bundle.mjs +27 -8
- package/hooks/session-extract.bundle.mjs +2 -1
- package/hooks/session-helpers.mjs +44 -3
- package/hooks/session-loaders.mjs +37 -0
- package/hooks/sessionstart.mjs +5 -5
- package/hooks/userpromptsubmit.mjs +26 -9
- package/hooks/vscode-copilot/posttooluse.mjs +8 -8
- package/hooks/vscode-copilot/precompact.mjs +2 -2
- package/hooks/vscode-copilot/pretooluse.mjs +3 -2
- package/hooks/vscode-copilot/sessionstart.mjs +2 -2
- package/insight/server.mjs +237 -25
- package/insight/src/lib/api.ts +2 -1
- package/insight/src/routes/index.tsx +16 -3
- package/insight/src/routes/search.tsx +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +11 -2
- package/server.bundle.mjs +94 -80
- package/skills/ctx-insight/SKILL.md +1 -1
|
@@ -1,65 +1,59 @@
|
|
|
1
1
|
# context-mode — MANDATORY routing rules
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
|
9
|
+
## BLOCKED — do NOT attempt
|
|
10
10
|
|
|
11
11
|
### curl / wget — BLOCKED
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
|
22
|
+
## REDIRECTED — use sandbox
|
|
28
23
|
|
|
29
24
|
### Terminal / run_in_terminal (>20 lines output)
|
|
30
|
-
Terminal
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32
|
+
Use `ctx_execute(language: "shell", code: "grep ...")` in sandbox.
|
|
41
33
|
|
|
42
|
-
## Tool selection
|
|
34
|
+
## Tool selection
|
|
43
35
|
|
|
44
|
-
1. **GATHER**: `ctx_batch_execute(commands, queries)` —
|
|
45
|
-
2. **FOLLOW-UP**: `ctx_search(queries: ["q1", "q2", ...])` —
|
|
46
|
-
3. **PROCESSING**: `ctx_execute(language, code)` | `ctx_execute_file(path, language, code)` —
|
|
47
|
-
4. **WEB**: `ctx_fetch_and_index(url, source)` then `ctx_search(queries)` —
|
|
48
|
-
5. **INDEX**: `ctx_index(content, source)` —
|
|
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
|
|
42
|
+
## Output
|
|
51
43
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
61
|
-
| `ctx doctor` | Call
|
|
62
|
-
| `ctx upgrade` | Call
|
|
63
|
-
| `ctx purge` | Call
|
|
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
|
|
59
|
+
After /clear or /compact: knowledge base and session stats preserved. Use `ctx purge` to start fresh.
|
package/configs/zed/AGENTS.md
CHANGED
|
@@ -1,65 +1,60 @@
|
|
|
1
1
|
# context-mode — MANDATORY routing rules
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
|
9
|
+
## BLOCKED — do NOT use
|
|
10
10
|
|
|
11
11
|
### curl / wget — FORBIDDEN
|
|
12
|
-
Do NOT use `curl
|
|
13
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
|
23
|
+
## REDIRECTED — use sandbox
|
|
28
24
|
|
|
29
25
|
### Shell (>20 lines output)
|
|
30
|
-
Shell
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
+
Use `mcp:context-mode:ctx_execute(language: "shell", code: "grep ...")` in sandbox.
|
|
41
34
|
|
|
42
|
-
## Tool selection
|
|
35
|
+
## Tool selection
|
|
43
36
|
|
|
44
|
-
1. **GATHER**: `mcp:context-mode:ctx_batch_execute(commands, queries)` —
|
|
45
|
-
2. **FOLLOW-UP**: `mcp:context-mode:ctx_search(queries: ["q1", "q2", ...])` —
|
|
46
|
-
3. **PROCESSING**: `mcp:context-mode:ctx_execute(language, code)` | `mcp:context-mode:ctx_execute_file(path, language, code)` —
|
|
47
|
-
4. **WEB**: `mcp:context-mode:ctx_fetch_and_index(url, source)` then `mcp:context-mode:ctx_search(queries)` —
|
|
48
|
-
5. **INDEX**: `mcp:context-mode:ctx_index(content, source)` —
|
|
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
|
|
43
|
+
## Output
|
|
51
44
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
61
|
-
| `ctx doctor` | Call
|
|
62
|
-
| `ctx upgrade` | Call
|
|
63
|
-
| `ctx purge` | Call
|
|
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
|
|
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 =
|
|
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
|
-
|
|
47
|
-
|
|
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 =
|
|
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 =
|
|
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: {
|
package/hooks/core/routing.mjs
CHANGED
|
@@ -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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
51
|
-
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
62
|
-
|
|
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 =
|
|
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 =
|
|
41
|
+
const input = parseStdin(raw);
|
|
41
42
|
const source = input.source ?? input.trigger ?? "startup";
|
|
42
43
|
const projectDir = getInputProjectDir(input, CURSOR_OPTS);
|
|
43
44
|
|
package/hooks/cursor/stop.mjs
CHANGED
|
@@ -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 =
|
|
22
|
+
const input = parseStdin(raw);
|
|
23
23
|
const projectDir = getInputProjectDir(input, CURSOR_OPTS);
|
|
24
24
|
|
|
25
25
|
if (projectDir && !process.env.CURSOR_CWD) {
|
package/hooks/ensure-deps.mjs
CHANGED
|
@@ -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
|
}
|