context-mode 1.0.5 → 1.0.7
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/build/adapters/claude-code/hooks.d.ts +3 -3
- package/build/adapters/claude-code/hooks.js +7 -3
- package/build/adapters/claude-code/index.js +1 -1
- package/build/adapters/detect.d.ts +6 -7
- package/build/adapters/detect.js +23 -24
- package/build/adapters/gemini-cli/hooks.d.ts +3 -3
- package/build/adapters/gemini-cli/hooks.js +7 -3
- package/build/adapters/gemini-cli/index.d.ts +2 -2
- package/build/adapters/gemini-cli/index.js +8 -8
- package/build/adapters/types.d.ts +1 -1
- package/build/adapters/vscode-copilot/hooks.d.ts +3 -3
- package/build/adapters/vscode-copilot/hooks.js +6 -3
- package/build/adapters/vscode-copilot/index.d.ts +2 -2
- package/build/adapters/vscode-copilot/index.js +8 -8
- package/build/cli.js +2 -2
- package/build/exit-classify.d.ts +19 -0
- package/build/exit-classify.js +12 -0
- package/build/server.js +29 -10
- package/build/session/extract.js +11 -5
- package/build/session/snapshot.d.ts +5 -0
- package/build/session/snapshot.js +28 -2
- package/hooks/gemini-cli/aftertool.mjs +3 -3
- package/hooks/gemini-cli/precompress.mjs +3 -3
- package/hooks/gemini-cli/sessionstart.mjs +4 -4
- package/hooks/posttooluse.mjs +3 -3
- package/hooks/precompact.mjs +3 -3
- package/hooks/sessionstart.mjs +4 -4
- package/hooks/userpromptsubmit.mjs +3 -3
- package/hooks/vscode-copilot/posttooluse.mjs +3 -3
- package/hooks/vscode-copilot/precompact.mjs +3 -3
- package/hooks/vscode-copilot/sessionstart.mjs +5 -4
- package/package.json +5 -4
- package/server.bundle.mjs +1 -1
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Claude Code plugins by Mert Koseoğlu",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.7"
|
|
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.
|
|
16
|
+
"version": "1.0.7",
|
|
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.
|
|
3
|
+
"version": "1.0.7",
|
|
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",
|
|
@@ -49,7 +49,7 @@ export declare function isContextModeHook(entry: {
|
|
|
49
49
|
}, hookType: HookType): boolean;
|
|
50
50
|
/**
|
|
51
51
|
* Build the hook command string for a given hook type.
|
|
52
|
-
* Uses
|
|
53
|
-
*
|
|
52
|
+
* Uses absolute node path to avoid PATH issues (homebrew, nvm, volta, etc.).
|
|
53
|
+
* Falls back to CLI dispatcher if pluginRoot is not provided.
|
|
54
54
|
*/
|
|
55
|
-
export declare function buildHookCommand(hookType: HookType): string;
|
|
55
|
+
export declare function buildHookCommand(hookType: HookType, pluginRoot?: string): string;
|
|
@@ -83,9 +83,13 @@ export function isContextModeHook(entry, hookType) {
|
|
|
83
83
|
}
|
|
84
84
|
/**
|
|
85
85
|
* Build the hook command string for a given hook type.
|
|
86
|
-
* Uses
|
|
87
|
-
*
|
|
86
|
+
* Uses absolute node path to avoid PATH issues (homebrew, nvm, volta, etc.).
|
|
87
|
+
* Falls back to CLI dispatcher if pluginRoot is not provided.
|
|
88
88
|
*/
|
|
89
|
-
export function buildHookCommand(hookType) {
|
|
89
|
+
export function buildHookCommand(hookType, pluginRoot) {
|
|
90
|
+
if (pluginRoot) {
|
|
91
|
+
const scriptName = HOOK_SCRIPTS[hookType];
|
|
92
|
+
return `node "${pluginRoot}/hooks/${scriptName}"`;
|
|
93
|
+
}
|
|
90
94
|
return `context-mode hook claude-code ${hookType.toLowerCase()}`;
|
|
91
95
|
}
|
|
@@ -372,7 +372,7 @@ export class ClaudeCodeAdapter {
|
|
|
372
372
|
HOOK_TYPES.SESSION_START,
|
|
373
373
|
];
|
|
374
374
|
for (const hookType of hookTypes) {
|
|
375
|
-
const command = buildHookCommand(hookType);
|
|
375
|
+
const command = buildHookCommand(hookType, pluginRoot);
|
|
376
376
|
if (hookType === HOOK_TYPES.PRE_TOOL_USE) {
|
|
377
377
|
const entry = {
|
|
378
378
|
matcher: PRE_TOOL_USE_MATCHER_PATTERN,
|
|
@@ -6,13 +6,12 @@
|
|
|
6
6
|
* 2. Config directory existence (medium confidence)
|
|
7
7
|
* 3. Fallback to Claude Code (low confidence — most common)
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* - Claude Code:
|
|
11
|
-
* - Gemini CLI:
|
|
12
|
-
* - OpenCode:
|
|
13
|
-
* -
|
|
14
|
-
* - VS Code:
|
|
15
|
-
* - Cursor: CURSOR_*, ~/.cursor/
|
|
9
|
+
* Verified env vars per platform (from source code audit):
|
|
10
|
+
* - Claude Code: CLAUDE_PROJECT_DIR, CLAUDE_SESSION_ID | ~/.claude/
|
|
11
|
+
* - Gemini CLI: GEMINI_PROJECT_DIR (hooks), GEMINI_CLI (MCP) | ~/.gemini/
|
|
12
|
+
* - OpenCode: OPENCODE, OPENCODE_PID | ~/.config/opencode/
|
|
13
|
+
* - Codex CLI: CODEX_CI, CODEX_THREAD_ID | ~/.codex/
|
|
14
|
+
* - VS Code Copilot: VSCODE_PID, VSCODE_CWD | ~/.vscode/
|
|
16
15
|
*/
|
|
17
16
|
import type { PlatformId, DetectionSignal, HookAdapter } from "./types.js";
|
|
18
17
|
/**
|
package/build/adapters/detect.js
CHANGED
|
@@ -6,13 +6,12 @@
|
|
|
6
6
|
* 2. Config directory existence (medium confidence)
|
|
7
7
|
* 3. Fallback to Claude Code (low confidence — most common)
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* - Claude Code:
|
|
11
|
-
* - Gemini CLI:
|
|
12
|
-
* - OpenCode:
|
|
13
|
-
* -
|
|
14
|
-
* - VS Code:
|
|
15
|
-
* - Cursor: CURSOR_*, ~/.cursor/
|
|
9
|
+
* Verified env vars per platform (from source code audit):
|
|
10
|
+
* - Claude Code: CLAUDE_PROJECT_DIR, CLAUDE_SESSION_ID | ~/.claude/
|
|
11
|
+
* - Gemini CLI: GEMINI_PROJECT_DIR (hooks), GEMINI_CLI (MCP) | ~/.gemini/
|
|
12
|
+
* - OpenCode: OPENCODE, OPENCODE_PID | ~/.config/opencode/
|
|
13
|
+
* - Codex CLI: CODEX_CI, CODEX_THREAD_ID | ~/.codex/
|
|
14
|
+
* - VS Code Copilot: VSCODE_PID, VSCODE_CWD | ~/.vscode/
|
|
16
15
|
*/
|
|
17
16
|
import { existsSync } from "node:fs";
|
|
18
17
|
import { resolve } from "node:path";
|
|
@@ -29,25 +28,25 @@ export function detectPlatform() {
|
|
|
29
28
|
reason: "CLAUDE_PROJECT_DIR or CLAUDE_SESSION_ID env var set",
|
|
30
29
|
};
|
|
31
30
|
}
|
|
32
|
-
if (process.env.GEMINI_PROJECT_DIR || process.env.
|
|
31
|
+
if (process.env.GEMINI_PROJECT_DIR || process.env.GEMINI_CLI) {
|
|
33
32
|
return {
|
|
34
33
|
platform: "gemini-cli",
|
|
35
34
|
confidence: "high",
|
|
36
|
-
reason: "GEMINI_PROJECT_DIR or
|
|
35
|
+
reason: "GEMINI_PROJECT_DIR or GEMINI_CLI env var set",
|
|
37
36
|
};
|
|
38
37
|
}
|
|
39
|
-
if (process.env.
|
|
38
|
+
if (process.env.OPENCODE || process.env.OPENCODE_PID) {
|
|
40
39
|
return {
|
|
41
40
|
platform: "opencode",
|
|
42
41
|
confidence: "high",
|
|
43
|
-
reason: "
|
|
42
|
+
reason: "OPENCODE or OPENCODE_PID env var set",
|
|
44
43
|
};
|
|
45
44
|
}
|
|
46
|
-
if (process.env.
|
|
45
|
+
if (process.env.CODEX_CI || process.env.CODEX_THREAD_ID) {
|
|
47
46
|
return {
|
|
48
|
-
platform: "
|
|
47
|
+
platform: "codex",
|
|
49
48
|
confidence: "high",
|
|
50
|
-
reason: "
|
|
49
|
+
reason: "CODEX_CI or CODEX_THREAD_ID env var set",
|
|
51
50
|
};
|
|
52
51
|
}
|
|
53
52
|
if (process.env.VSCODE_PID || process.env.VSCODE_CWD) {
|
|
@@ -57,13 +56,6 @@ export function detectPlatform() {
|
|
|
57
56
|
reason: "VSCODE_PID or VSCODE_CWD env var set",
|
|
58
57
|
};
|
|
59
58
|
}
|
|
60
|
-
if (process.env.CURSOR_SESSION_ID || process.env.CURSOR_TRACE_ID) {
|
|
61
|
-
return {
|
|
62
|
-
platform: "cursor",
|
|
63
|
-
confidence: "high",
|
|
64
|
-
reason: "CURSOR_SESSION_ID or CURSOR_TRACE_ID env var set",
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
59
|
// ── Medium confidence: config directory existence ──────
|
|
68
60
|
const home = homedir();
|
|
69
61
|
if (existsSync(resolve(home, ".claude"))) {
|
|
@@ -80,11 +72,18 @@ export function detectPlatform() {
|
|
|
80
72
|
reason: "~/.gemini/ directory exists",
|
|
81
73
|
};
|
|
82
74
|
}
|
|
83
|
-
if (existsSync(resolve(home, ".
|
|
75
|
+
if (existsSync(resolve(home, ".codex"))) {
|
|
84
76
|
return {
|
|
85
|
-
platform: "
|
|
77
|
+
platform: "codex",
|
|
78
|
+
confidence: "medium",
|
|
79
|
+
reason: "~/.codex/ directory exists",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (existsSync(resolve(home, ".config", "opencode"))) {
|
|
83
|
+
return {
|
|
84
|
+
platform: "opencode",
|
|
86
85
|
confidence: "medium",
|
|
87
|
-
reason: "~/.
|
|
86
|
+
reason: "~/.config/opencode/ directory exists",
|
|
88
87
|
};
|
|
89
88
|
}
|
|
90
89
|
// ── Low confidence: fallback ───────────────────────────
|
|
@@ -40,7 +40,7 @@ export declare function isContextModeHook(entry: {
|
|
|
40
40
|
}, hookType: HookType): boolean;
|
|
41
41
|
/**
|
|
42
42
|
* Build the hook command string for a given hook type.
|
|
43
|
-
* Uses
|
|
44
|
-
*
|
|
43
|
+
* Uses absolute node path to avoid PATH issues (homebrew, nvm, volta, etc.).
|
|
44
|
+
* Falls back to CLI dispatcher if pluginRoot is not provided.
|
|
45
45
|
*/
|
|
46
|
-
export declare function buildHookCommand(hookType: HookType): string;
|
|
46
|
+
export declare function buildHookCommand(hookType: HookType, pluginRoot?: string): string;
|
|
@@ -59,9 +59,13 @@ export function isContextModeHook(entry, hookType) {
|
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
61
|
* Build the hook command string for a given hook type.
|
|
62
|
-
* Uses
|
|
63
|
-
*
|
|
62
|
+
* Uses absolute node path to avoid PATH issues (homebrew, nvm, volta, etc.).
|
|
63
|
+
* Falls back to CLI dispatcher if pluginRoot is not provided.
|
|
64
64
|
*/
|
|
65
|
-
export function buildHookCommand(hookType) {
|
|
65
|
+
export function buildHookCommand(hookType, pluginRoot) {
|
|
66
|
+
const scriptName = HOOK_SCRIPTS[hookType];
|
|
67
|
+
if (pluginRoot && scriptName) {
|
|
68
|
+
return `node "${pluginRoot}/hooks/${scriptName}"`;
|
|
69
|
+
}
|
|
66
70
|
return `context-mode hook gemini-cli ${hookType.toLowerCase()}`;
|
|
67
71
|
}
|
|
@@ -35,13 +35,13 @@ export declare class GeminiCLIAdapter implements HookAdapter {
|
|
|
35
35
|
getSessionDir(): string;
|
|
36
36
|
getSessionDBPath(projectDir: string): string;
|
|
37
37
|
getSessionEventsPath(projectDir: string): string;
|
|
38
|
-
generateHookConfig(
|
|
38
|
+
generateHookConfig(pluginRoot: string): HookRegistration;
|
|
39
39
|
readSettings(): Record<string, unknown> | null;
|
|
40
40
|
writeSettings(settings: Record<string, unknown>): void;
|
|
41
41
|
validateHooks(pluginRoot: string): DiagnosticResult[];
|
|
42
42
|
checkPluginRegistration(): DiagnosticResult;
|
|
43
43
|
getInstalledVersion(): string;
|
|
44
|
-
configureAllHooks(
|
|
44
|
+
configureAllHooks(pluginRoot: string): string[];
|
|
45
45
|
backupSettings(): string | null;
|
|
46
46
|
setHookPermissions(pluginRoot: string): string[];
|
|
47
47
|
updatePluginRegistry(pluginRoot: string, version: string): void;
|
|
@@ -25,7 +25,7 @@ import { homedir } from "node:os";
|
|
|
25
25
|
// ─────────────────────────────────────────────────────────
|
|
26
26
|
// Hook constants (re-exported from hooks.ts)
|
|
27
27
|
// ─────────────────────────────────────────────────────────
|
|
28
|
-
import { HOOK_TYPES as GEMINI_HOOK_NAMES, HOOK_SCRIPTS as GEMINI_HOOK_SCRIPTS, } from "./hooks.js";
|
|
28
|
+
import { HOOK_TYPES as GEMINI_HOOK_NAMES, HOOK_SCRIPTS as GEMINI_HOOK_SCRIPTS, buildHookCommand as buildGeminiHookCommand, } from "./hooks.js";
|
|
29
29
|
// ─────────────────────────────────────────────────────────
|
|
30
30
|
// Adapter implementation
|
|
31
31
|
// ─────────────────────────────────────────────────────────
|
|
@@ -176,7 +176,7 @@ export class GeminiCLIAdapter {
|
|
|
176
176
|
.slice(0, 16);
|
|
177
177
|
return join(this.getSessionDir(), `${hash}-events.md`);
|
|
178
178
|
}
|
|
179
|
-
generateHookConfig(
|
|
179
|
+
generateHookConfig(pluginRoot) {
|
|
180
180
|
return {
|
|
181
181
|
[GEMINI_HOOK_NAMES.BEFORE_TOOL]: [
|
|
182
182
|
{
|
|
@@ -184,7 +184,7 @@ export class GeminiCLIAdapter {
|
|
|
184
184
|
hooks: [
|
|
185
185
|
{
|
|
186
186
|
type: "command",
|
|
187
|
-
command:
|
|
187
|
+
command: buildGeminiHookCommand(GEMINI_HOOK_NAMES.BEFORE_TOOL, pluginRoot),
|
|
188
188
|
},
|
|
189
189
|
],
|
|
190
190
|
},
|
|
@@ -195,7 +195,7 @@ export class GeminiCLIAdapter {
|
|
|
195
195
|
hooks: [
|
|
196
196
|
{
|
|
197
197
|
type: "command",
|
|
198
|
-
command:
|
|
198
|
+
command: buildGeminiHookCommand(GEMINI_HOOK_NAMES.AFTER_TOOL, pluginRoot),
|
|
199
199
|
},
|
|
200
200
|
],
|
|
201
201
|
},
|
|
@@ -206,7 +206,7 @@ export class GeminiCLIAdapter {
|
|
|
206
206
|
hooks: [
|
|
207
207
|
{
|
|
208
208
|
type: "command",
|
|
209
|
-
command:
|
|
209
|
+
command: buildGeminiHookCommand(GEMINI_HOOK_NAMES.PRE_COMPRESS, pluginRoot),
|
|
210
210
|
},
|
|
211
211
|
],
|
|
212
212
|
},
|
|
@@ -217,7 +217,7 @@ export class GeminiCLIAdapter {
|
|
|
217
217
|
hooks: [
|
|
218
218
|
{
|
|
219
219
|
type: "command",
|
|
220
|
-
command:
|
|
220
|
+
command: buildGeminiHookCommand(GEMINI_HOOK_NAMES.SESSION_START, pluginRoot),
|
|
221
221
|
},
|
|
222
222
|
],
|
|
223
223
|
},
|
|
@@ -339,7 +339,7 @@ export class GeminiCLIAdapter {
|
|
|
339
339
|
return "not installed";
|
|
340
340
|
}
|
|
341
341
|
// ── Upgrade ────────────────────────────────────────────
|
|
342
|
-
configureAllHooks(
|
|
342
|
+
configureAllHooks(pluginRoot) {
|
|
343
343
|
const settings = this.readSettings() ?? {};
|
|
344
344
|
const hooks = (settings.hooks ?? {});
|
|
345
345
|
const changes = [];
|
|
@@ -348,7 +348,7 @@ export class GeminiCLIAdapter {
|
|
|
348
348
|
{ name: GEMINI_HOOK_NAMES.SESSION_START },
|
|
349
349
|
];
|
|
350
350
|
for (const config of hookConfigs) {
|
|
351
|
-
const command =
|
|
351
|
+
const command = buildGeminiHookCommand(config.name, pluginRoot);
|
|
352
352
|
const entry = {
|
|
353
353
|
matcher: "",
|
|
354
354
|
hooks: [{ type: "command", command }],
|
|
@@ -206,7 +206,7 @@ export interface DiagnosticResult {
|
|
|
206
206
|
fix?: string;
|
|
207
207
|
}
|
|
208
208
|
/** Supported platform identifiers. */
|
|
209
|
-
export type PlatformId = "claude-code" | "gemini-cli" | "opencode" | "codex" | "
|
|
209
|
+
export type PlatformId = "claude-code" | "gemini-cli" | "opencode" | "codex" | "vscode-copilot" | "unknown";
|
|
210
210
|
/** Detection signal used to identify which platform is running. */
|
|
211
211
|
export interface DetectionSignal {
|
|
212
212
|
/** Platform identifier. */
|
|
@@ -45,7 +45,7 @@ export declare function isContextModeHook(entry: {
|
|
|
45
45
|
}, hookType: HookType): boolean;
|
|
46
46
|
/**
|
|
47
47
|
* Build the hook command string for a given hook type.
|
|
48
|
-
* Uses
|
|
49
|
-
*
|
|
48
|
+
* Uses absolute node path to avoid PATH issues (homebrew, nvm, volta, etc.).
|
|
49
|
+
* Falls back to CLI dispatcher if pluginRoot is not provided.
|
|
50
50
|
*/
|
|
51
|
-
export declare function buildHookCommand(hookType: HookType): string;
|
|
51
|
+
export declare function buildHookCommand(hookType: HookType, pluginRoot?: string): string;
|
|
@@ -67,13 +67,16 @@ export function isContextModeHook(entry, hookType) {
|
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
69
|
* Build the hook command string for a given hook type.
|
|
70
|
-
* Uses
|
|
71
|
-
*
|
|
70
|
+
* Uses absolute node path to avoid PATH issues (homebrew, nvm, volta, etc.).
|
|
71
|
+
* Falls back to CLI dispatcher if pluginRoot is not provided.
|
|
72
72
|
*/
|
|
73
|
-
export function buildHookCommand(hookType) {
|
|
73
|
+
export function buildHookCommand(hookType, pluginRoot) {
|
|
74
74
|
const scriptName = HOOK_SCRIPTS[hookType];
|
|
75
75
|
if (!scriptName) {
|
|
76
76
|
throw new Error(`No script defined for hook type: ${hookType}`);
|
|
77
77
|
}
|
|
78
|
+
if (pluginRoot) {
|
|
79
|
+
return `node "${pluginRoot}/hooks/${scriptName}"`;
|
|
80
|
+
}
|
|
78
81
|
return `context-mode hook vscode-copilot ${hookType.toLowerCase()}`;
|
|
79
82
|
}
|
|
@@ -38,13 +38,13 @@ export declare class VSCodeCopilotAdapter implements HookAdapter {
|
|
|
38
38
|
getSessionDir(): string;
|
|
39
39
|
getSessionDBPath(projectDir: string): string;
|
|
40
40
|
getSessionEventsPath(projectDir: string): string;
|
|
41
|
-
generateHookConfig(
|
|
41
|
+
generateHookConfig(pluginRoot: string): HookRegistration;
|
|
42
42
|
readSettings(): Record<string, unknown> | null;
|
|
43
43
|
writeSettings(settings: Record<string, unknown>): void;
|
|
44
44
|
validateHooks(pluginRoot: string): DiagnosticResult[];
|
|
45
45
|
checkPluginRegistration(): DiagnosticResult;
|
|
46
46
|
getInstalledVersion(): string;
|
|
47
|
-
configureAllHooks(
|
|
47
|
+
configureAllHooks(pluginRoot: string): string[];
|
|
48
48
|
backupSettings(): string | null;
|
|
49
49
|
setHookPermissions(pluginRoot: string): string[];
|
|
50
50
|
updatePluginRegistry(_pluginRoot: string, _version: string): void;
|
|
@@ -28,7 +28,7 @@ import { homedir } from "node:os";
|
|
|
28
28
|
// ─────────────────────────────────────────────────────────
|
|
29
29
|
// Hook constants (re-exported from hooks.ts)
|
|
30
30
|
// ─────────────────────────────────────────────────────────
|
|
31
|
-
import { HOOK_TYPES as VSCODE_HOOK_NAMES, HOOK_SCRIPTS as VSCODE_HOOK_SCRIPTS, } from "./hooks.js";
|
|
31
|
+
import { HOOK_TYPES as VSCODE_HOOK_NAMES, HOOK_SCRIPTS as VSCODE_HOOK_SCRIPTS, buildHookCommand as buildVSCodeHookCommand, } from "./hooks.js";
|
|
32
32
|
// ─────────────────────────────────────────────────────────
|
|
33
33
|
// Adapter implementation
|
|
34
34
|
// ─────────────────────────────────────────────────────────
|
|
@@ -193,7 +193,7 @@ export class VSCodeCopilotAdapter {
|
|
|
193
193
|
.slice(0, 16);
|
|
194
194
|
return join(this.getSessionDir(), `${hash}-events.md`);
|
|
195
195
|
}
|
|
196
|
-
generateHookConfig(
|
|
196
|
+
generateHookConfig(pluginRoot) {
|
|
197
197
|
return {
|
|
198
198
|
[VSCODE_HOOK_NAMES.PRE_TOOL_USE]: [
|
|
199
199
|
{
|
|
@@ -201,7 +201,7 @@ export class VSCodeCopilotAdapter {
|
|
|
201
201
|
hooks: [
|
|
202
202
|
{
|
|
203
203
|
type: "command",
|
|
204
|
-
command:
|
|
204
|
+
command: buildVSCodeHookCommand(VSCODE_HOOK_NAMES.PRE_TOOL_USE, pluginRoot),
|
|
205
205
|
},
|
|
206
206
|
],
|
|
207
207
|
},
|
|
@@ -212,7 +212,7 @@ export class VSCodeCopilotAdapter {
|
|
|
212
212
|
hooks: [
|
|
213
213
|
{
|
|
214
214
|
type: "command",
|
|
215
|
-
command:
|
|
215
|
+
command: buildVSCodeHookCommand(VSCODE_HOOK_NAMES.POST_TOOL_USE, pluginRoot),
|
|
216
216
|
},
|
|
217
217
|
],
|
|
218
218
|
},
|
|
@@ -223,7 +223,7 @@ export class VSCodeCopilotAdapter {
|
|
|
223
223
|
hooks: [
|
|
224
224
|
{
|
|
225
225
|
type: "command",
|
|
226
|
-
command:
|
|
226
|
+
command: buildVSCodeHookCommand(VSCODE_HOOK_NAMES.PRE_COMPACT, pluginRoot),
|
|
227
227
|
},
|
|
228
228
|
],
|
|
229
229
|
},
|
|
@@ -234,7 +234,7 @@ export class VSCodeCopilotAdapter {
|
|
|
234
234
|
hooks: [
|
|
235
235
|
{
|
|
236
236
|
type: "command",
|
|
237
|
-
command:
|
|
237
|
+
command: buildVSCodeHookCommand(VSCODE_HOOK_NAMES.SESSION_START, pluginRoot),
|
|
238
238
|
},
|
|
239
239
|
],
|
|
240
240
|
},
|
|
@@ -398,7 +398,7 @@ export class VSCodeCopilotAdapter {
|
|
|
398
398
|
return "not installed";
|
|
399
399
|
}
|
|
400
400
|
// ── Upgrade ────────────────────────────────────────────
|
|
401
|
-
configureAllHooks(
|
|
401
|
+
configureAllHooks(pluginRoot) {
|
|
402
402
|
const changes = [];
|
|
403
403
|
const hookConfig = { hooks: {} };
|
|
404
404
|
const hooks = hookConfig.hooks;
|
|
@@ -418,7 +418,7 @@ export class VSCodeCopilotAdapter {
|
|
|
418
418
|
hooks: [
|
|
419
419
|
{
|
|
420
420
|
type: "command",
|
|
421
|
-
command:
|
|
421
|
+
command: buildVSCodeHookCommand(hookType, pluginRoot),
|
|
422
422
|
},
|
|
423
423
|
],
|
|
424
424
|
},
|
package/build/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ import { execSync } from "node:child_process";
|
|
|
17
17
|
import { readFileSync, cpSync, accessSync, readdirSync, rmSync, closeSync, openSync, constants } from "node:fs";
|
|
18
18
|
import { resolve, dirname, join } from "node:path";
|
|
19
19
|
import { tmpdir, devNull } from "node:os";
|
|
20
|
-
import { fileURLToPath } from "node:url";
|
|
20
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
21
21
|
import { detectRuntimes, getRuntimeSummary, hasBunRuntime, getAvailableLanguages, } from "./runtime.js";
|
|
22
22
|
// ── Adapter imports ──────────────────────────────────────
|
|
23
23
|
import { detectPlatform, getAdapter } from "./adapters/detect.js";
|
|
@@ -62,7 +62,7 @@ async function hookDispatch(platform, event) {
|
|
|
62
62
|
process.exit(1);
|
|
63
63
|
}
|
|
64
64
|
const pluginRoot = getPluginRoot();
|
|
65
|
-
await import(join(pluginRoot, scriptPath));
|
|
65
|
+
await import(pathToFileURL(join(pluginRoot, scriptPath)).href);
|
|
66
66
|
}
|
|
67
67
|
/* -------------------------------------------------------
|
|
68
68
|
* Entry point
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classify non-zero exit codes for ctx_execute / ctx_execute_file.
|
|
3
|
+
*
|
|
4
|
+
* Shell commands like `grep` exit 1 for "no matches" — not a real error.
|
|
5
|
+
* We treat exit code 1 as a soft failure when:
|
|
6
|
+
* - language is "shell"
|
|
7
|
+
* - exit code is exactly 1
|
|
8
|
+
* - stdout has non-whitespace content
|
|
9
|
+
*/
|
|
10
|
+
export interface ExitClassification {
|
|
11
|
+
isError: boolean;
|
|
12
|
+
output: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function classifyNonZeroExit(params: {
|
|
15
|
+
language: string;
|
|
16
|
+
exitCode: number;
|
|
17
|
+
stdout: string;
|
|
18
|
+
stderr: string;
|
|
19
|
+
}): ExitClassification;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function classifyNonZeroExit(params) {
|
|
2
|
+
const { language, exitCode, stdout, stderr } = params;
|
|
3
|
+
const isSoftFail = language === "shell" &&
|
|
4
|
+
exitCode === 1 &&
|
|
5
|
+
stdout.trim().length > 0;
|
|
6
|
+
return {
|
|
7
|
+
isError: !isSoftFail,
|
|
8
|
+
output: isSoftFail
|
|
9
|
+
? stdout
|
|
10
|
+
: `Exit code: ${exitCode}\n\nstdout:\n${stdout}\n\nstderr:\n${stderr}`,
|
|
11
|
+
};
|
|
12
|
+
}
|
package/build/server.js
CHANGED
|
@@ -4,7 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
6
|
import { existsSync, unlinkSync, readdirSync, readFileSync, rmSync } from "node:fs";
|
|
7
|
-
import { join, dirname } from "node:path";
|
|
7
|
+
import { join, dirname, resolve } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import { homedir, tmpdir } from "node:os";
|
|
10
10
|
import { z } from "zod";
|
|
@@ -12,7 +12,8 @@ import { PolyglotExecutor } from "./executor.js";
|
|
|
12
12
|
import { ContentStore, cleanupStaleDBs } from "./store.js";
|
|
13
13
|
import { readBashPolicies, evaluateCommandDenyOnly, extractShellCommands, readToolDenyPatterns, evaluateFilePath, } from "./security.js";
|
|
14
14
|
import { detectRuntimes, getRuntimeSummary, getAvailableLanguages, hasBunRuntime, } from "./runtime.js";
|
|
15
|
-
|
|
15
|
+
import { classifyNonZeroExit } from "./exit-classify.js";
|
|
16
|
+
const VERSION = "1.0.7";
|
|
16
17
|
// Prevent silent server death from unhandled async errors
|
|
17
18
|
process.on("unhandledRejection", (err) => {
|
|
18
19
|
process.stderr.write(`[context-mode] unhandledRejection: ${err}\n`);
|
|
@@ -420,21 +421,23 @@ __cm_main().catch(e=>{console.error(e);process.exitCode=1});${background ? '\nse
|
|
|
420
421
|
});
|
|
421
422
|
}
|
|
422
423
|
if (result.exitCode !== 0) {
|
|
423
|
-
const output =
|
|
424
|
+
const { isError, output } = classifyNonZeroExit({
|
|
425
|
+
language, exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr,
|
|
426
|
+
});
|
|
424
427
|
if (intent && intent.trim().length > 0 && Buffer.byteLength(output) > INTENT_SEARCH_THRESHOLD) {
|
|
425
428
|
trackIndexed(Buffer.byteLength(output));
|
|
426
429
|
return trackResponse("ctx_execute", {
|
|
427
430
|
content: [
|
|
428
|
-
{ type: "text", text: intentSearch(output, intent, `execute:${language}:error`) },
|
|
431
|
+
{ type: "text", text: intentSearch(output, intent, isError ? `execute:${language}:error` : `execute:${language}`) },
|
|
429
432
|
],
|
|
430
|
-
isError
|
|
433
|
+
isError,
|
|
431
434
|
});
|
|
432
435
|
}
|
|
433
436
|
return trackResponse("ctx_execute", {
|
|
434
437
|
content: [
|
|
435
438
|
{ type: "text", text: output },
|
|
436
439
|
],
|
|
437
|
-
isError
|
|
440
|
+
isError,
|
|
438
441
|
});
|
|
439
442
|
}
|
|
440
443
|
const stdout = result.stdout || "(no output)";
|
|
@@ -598,21 +601,23 @@ server.registerTool("ctx_execute_file", {
|
|
|
598
601
|
});
|
|
599
602
|
}
|
|
600
603
|
if (result.exitCode !== 0) {
|
|
601
|
-
const
|
|
604
|
+
const { isError, output } = classifyNonZeroExit({
|
|
605
|
+
language, exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr,
|
|
606
|
+
});
|
|
602
607
|
if (intent && intent.trim().length > 0 && Buffer.byteLength(output) > INTENT_SEARCH_THRESHOLD) {
|
|
603
608
|
trackIndexed(Buffer.byteLength(output));
|
|
604
609
|
return trackResponse("ctx_execute_file", {
|
|
605
610
|
content: [
|
|
606
|
-
{ type: "text", text: intentSearch(output, intent, `file:${path}:error`) },
|
|
611
|
+
{ type: "text", text: intentSearch(output, intent, isError ? `file:${path}:error` : `file:${path}`) },
|
|
607
612
|
],
|
|
608
|
-
isError
|
|
613
|
+
isError,
|
|
609
614
|
});
|
|
610
615
|
}
|
|
611
616
|
return trackResponse("ctx_execute_file", {
|
|
612
617
|
content: [
|
|
613
618
|
{ type: "text", text: output },
|
|
614
619
|
],
|
|
615
|
-
isError
|
|
620
|
+
isError,
|
|
616
621
|
});
|
|
617
622
|
}
|
|
618
623
|
const stdout = result.stdout || "(no output)";
|
|
@@ -1455,6 +1460,20 @@ async function main() {
|
|
|
1455
1460
|
process.on("SIGTERM", () => { shutdown(); process.exit(0); });
|
|
1456
1461
|
const transport = new StdioServerTransport();
|
|
1457
1462
|
await server.connect(transport);
|
|
1463
|
+
// Write routing instructions for hookless platforms (e.g. Codex CLI)
|
|
1464
|
+
try {
|
|
1465
|
+
const { detectPlatform, getAdapter } = await import("./adapters/detect.js");
|
|
1466
|
+
const signal = detectPlatform();
|
|
1467
|
+
const adapter = await getAdapter(signal.platform);
|
|
1468
|
+
if (!adapter.capabilities.sessionStart) {
|
|
1469
|
+
const pluginRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
1470
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR ?? process.env.CODEX_HOME ?? process.cwd();
|
|
1471
|
+
const written = adapter.writeRoutingInstructions(projectDir, pluginRoot);
|
|
1472
|
+
if (written)
|
|
1473
|
+
console.error(`Wrote routing instructions: ${written}`);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
catch { /* best effort — don't block server startup */ }
|
|
1458
1477
|
console.error(`Context Mode MCP server v${VERSION} running on stdio`);
|
|
1459
1478
|
console.error(`Detected runtimes:\n${getRuntimeSummary(runtimes)}`);
|
|
1460
1479
|
if (!hasBunRuntime()) {
|
package/build/session/extract.js
CHANGED
|
@@ -244,17 +244,23 @@ function extractSkill(input) {
|
|
|
244
244
|
}
|
|
245
245
|
/**
|
|
246
246
|
* Category 9: subagent
|
|
247
|
-
* Agent tool calls
|
|
247
|
+
* Agent tool calls — tracks both launch and completion.
|
|
248
|
+
* When tool_response is present, the agent has completed and the result
|
|
249
|
+
* is captured at higher priority (P2) so it survives budget trimming.
|
|
248
250
|
*/
|
|
249
251
|
function extractSubagent(input) {
|
|
250
252
|
if (input.tool_name !== "Agent")
|
|
251
253
|
return [];
|
|
252
|
-
const prompt = String(input.tool_input["prompt"] ?? input.tool_input["description"] ?? "");
|
|
254
|
+
const prompt = truncate(String(input.tool_input["prompt"] ?? input.tool_input["description"] ?? ""), 200);
|
|
255
|
+
const response = input.tool_response ? truncate(String(input.tool_response), 300) : "";
|
|
256
|
+
const isCompleted = response.length > 0;
|
|
253
257
|
return [{
|
|
254
|
-
type: "
|
|
258
|
+
type: isCompleted ? "subagent_completed" : "subagent_launched",
|
|
255
259
|
category: "subagent",
|
|
256
|
-
data:
|
|
257
|
-
|
|
260
|
+
data: isCompleted
|
|
261
|
+
? truncate(`[completed] ${prompt} → ${response}`, 300)
|
|
262
|
+
: truncate(`[launched] ${prompt}`, 300),
|
|
263
|
+
priority: isCompleted ? 2 : 3,
|
|
258
264
|
}];
|
|
259
265
|
}
|
|
260
266
|
/**
|
|
@@ -57,6 +57,11 @@ export declare function renderErrors(errorEvents: StoredEvent[]): string;
|
|
|
57
57
|
* Render <intent> from the most recent intent event.
|
|
58
58
|
*/
|
|
59
59
|
export declare function renderIntent(intentEvent: StoredEvent): string;
|
|
60
|
+
/**
|
|
61
|
+
* Render <subagents> from subagent events.
|
|
62
|
+
* Shows agent dispatch status (launched/completed) and result summaries.
|
|
63
|
+
*/
|
|
64
|
+
export declare function renderSubagents(subagentEvents: StoredEvent[]): string;
|
|
60
65
|
/**
|
|
61
66
|
* Render <mcp_tools> from MCP tool call events.
|
|
62
67
|
* Deduplicates by tool name, shows usage count.
|
|
@@ -190,6 +190,23 @@ export function renderErrors(errorEvents) {
|
|
|
190
190
|
export function renderIntent(intentEvent) {
|
|
191
191
|
return ` <intent mode="${escapeXML(intentEvent.data)}">${escapeXML(truncateString(intentEvent.data, 100))}</intent>`;
|
|
192
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Render <subagents> from subagent events.
|
|
195
|
+
* Shows agent dispatch status (launched/completed) and result summaries.
|
|
196
|
+
*/
|
|
197
|
+
export function renderSubagents(subagentEvents) {
|
|
198
|
+
if (subagentEvents.length === 0)
|
|
199
|
+
return "";
|
|
200
|
+
const lines = [" <subagents>"];
|
|
201
|
+
for (const ev of subagentEvents) {
|
|
202
|
+
const status = ev.type === "subagent_completed" ? "completed"
|
|
203
|
+
: ev.type === "subagent_launched" ? "launched"
|
|
204
|
+
: "unknown";
|
|
205
|
+
lines.push(` <agent status="${status}">${escapeXML(truncateString(ev.data, 200))}</agent>`);
|
|
206
|
+
}
|
|
207
|
+
lines.push(" </subagents>");
|
|
208
|
+
return lines.join("\n");
|
|
209
|
+
}
|
|
193
210
|
/**
|
|
194
211
|
* Render <mcp_tools> from MCP tool call events.
|
|
195
212
|
* Deduplicates by tool name, shows usage count.
|
|
@@ -297,7 +314,7 @@ export function buildResumeSnapshot(events, opts) {
|
|
|
297
314
|
const rules = renderRules(ruleEvents);
|
|
298
315
|
if (rules)
|
|
299
316
|
p1Sections.push(rules);
|
|
300
|
-
// P2 sections (35% budget): decisions, environment, errors_resolved
|
|
317
|
+
// P2 sections (35% budget): decisions, environment, errors_resolved, completed subagents
|
|
301
318
|
const p2Sections = [];
|
|
302
319
|
const decisions = renderDecisions(decisionEvents);
|
|
303
320
|
if (decisions)
|
|
@@ -310,7 +327,12 @@ export function buildResumeSnapshot(events, opts) {
|
|
|
310
327
|
const errors = renderErrors(errorEvents);
|
|
311
328
|
if (errors)
|
|
312
329
|
p2Sections.push(errors);
|
|
313
|
-
//
|
|
330
|
+
// Completed subagents are P2 — their results must survive budget trimming
|
|
331
|
+
const completedSubagents = subagentEvents.filter(e => e.type === "subagent_completed");
|
|
332
|
+
const subagentsP2 = renderSubagents(completedSubagents);
|
|
333
|
+
if (subagentsP2)
|
|
334
|
+
p2Sections.push(subagentsP2);
|
|
335
|
+
// P3-P4 sections (15% budget): intent, mcp_tools, launched subagents
|
|
314
336
|
const p3Sections = [];
|
|
315
337
|
if (intentEvents.length > 0) {
|
|
316
338
|
const lastIntent = intentEvents[intentEvents.length - 1];
|
|
@@ -319,6 +341,10 @@ export function buildResumeSnapshot(events, opts) {
|
|
|
319
341
|
const mcpTools = renderMcpTools(mcpEvents);
|
|
320
342
|
if (mcpTools)
|
|
321
343
|
p3Sections.push(mcpTools);
|
|
344
|
+
const launchedSubagents = subagentEvents.filter(e => e.type === "subagent_launched");
|
|
345
|
+
const subagentsP3 = renderSubagents(launchedSubagents);
|
|
346
|
+
if (subagentsP3)
|
|
347
|
+
p3Sections.push(subagentsP3);
|
|
322
348
|
// ── Assemble with budget trimming ──
|
|
323
349
|
const header = `<session_resume compact_count="${compactCount}" events_captured="${events.length}" generated_at="${now}">`;
|
|
324
350
|
const footer = `</session_resume>`;
|
|
@@ -13,7 +13,7 @@ import { readStdin, getSessionId, getSessionDBPath, getProjectDir, GEMINI_OPTS }
|
|
|
13
13
|
import { appendFileSync } from "node:fs";
|
|
14
14
|
import { join, dirname } from "node:path";
|
|
15
15
|
import { homedir } from "node:os";
|
|
16
|
-
import { fileURLToPath } from "node:url";
|
|
16
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
17
17
|
|
|
18
18
|
const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
|
|
19
19
|
const PKG_SESSION = join(HOOK_DIR, "..", "..", "build", "session");
|
|
@@ -26,8 +26,8 @@ try {
|
|
|
26
26
|
|
|
27
27
|
appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] CALL: ${input.tool_name}\n`);
|
|
28
28
|
|
|
29
|
-
const { extractEvents } = await import(join(PKG_SESSION, "extract.js"));
|
|
30
|
-
const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
|
|
29
|
+
const { extractEvents } = await import(pathToFileURL(join(PKG_SESSION, "extract.js")).href);
|
|
30
|
+
const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
|
|
31
31
|
|
|
32
32
|
const dbPath = getSessionDBPath(OPTS);
|
|
33
33
|
const db = new SessionDB({ dbPath });
|
|
@@ -12,7 +12,7 @@ import { readStdin, getSessionId, getSessionDBPath, GEMINI_OPTS } from "../sessi
|
|
|
12
12
|
import { appendFileSync } from "node:fs";
|
|
13
13
|
import { join, dirname } from "node:path";
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
|
-
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
16
16
|
|
|
17
17
|
const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
|
|
18
18
|
const PKG_SESSION = join(HOOK_DIR, "..", "..", "build", "session");
|
|
@@ -23,8 +23,8 @@ try {
|
|
|
23
23
|
const raw = await readStdin();
|
|
24
24
|
const input = JSON.parse(raw);
|
|
25
25
|
|
|
26
|
-
const { buildResumeSnapshot } = await import(join(PKG_SESSION, "snapshot.js"));
|
|
27
|
-
const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
|
|
26
|
+
const { buildResumeSnapshot } = await import(pathToFileURL(join(PKG_SESSION, "snapshot.js")).href);
|
|
27
|
+
const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
|
|
28
28
|
|
|
29
29
|
const dbPath = getSessionDBPath(OPTS);
|
|
30
30
|
const db = new SessionDB({ dbPath });
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
import { join, dirname } from "node:path";
|
|
20
20
|
import { readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
21
21
|
import { homedir } from "node:os";
|
|
22
|
-
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
23
23
|
|
|
24
24
|
const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
|
|
25
25
|
const PKG_SESSION = join(HOOK_DIR, "..", "..", "build", "session");
|
|
@@ -33,7 +33,7 @@ try {
|
|
|
33
33
|
const source = input.source ?? "startup";
|
|
34
34
|
|
|
35
35
|
if (source === "compact") {
|
|
36
|
-
const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
|
|
36
|
+
const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
|
|
37
37
|
const dbPath = getSessionDBPath(OPTS);
|
|
38
38
|
const db = new SessionDB({ dbPath });
|
|
39
39
|
const sessionId = getSessionId(input, OPTS);
|
|
@@ -53,7 +53,7 @@ try {
|
|
|
53
53
|
} else if (source === "resume") {
|
|
54
54
|
try { unlinkSync(getCleanupFlagPath(OPTS)); } catch { /* no flag */ }
|
|
55
55
|
|
|
56
|
-
const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
|
|
56
|
+
const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
|
|
57
57
|
const dbPath = getSessionDBPath(OPTS);
|
|
58
58
|
const db = new SessionDB({ dbPath });
|
|
59
59
|
|
|
@@ -65,7 +65,7 @@ try {
|
|
|
65
65
|
|
|
66
66
|
db.close();
|
|
67
67
|
} else if (source === "startup") {
|
|
68
|
-
const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
|
|
68
|
+
const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
|
|
69
69
|
const dbPath = getSessionDBPath(OPTS);
|
|
70
70
|
const db = new SessionDB({ dbPath });
|
|
71
71
|
try { unlinkSync(getSessionEventsPath(OPTS)); } catch { /* no stale file */ }
|
package/hooks/posttooluse.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import "./suppress-stderr.mjs";
|
|
|
11
11
|
|
|
12
12
|
import { readStdin, getSessionId, getSessionDBPath } from "./session-helpers.mjs";
|
|
13
13
|
import { join, dirname } from "node:path";
|
|
14
|
-
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
15
15
|
|
|
16
16
|
// Resolve absolute path for imports — relative dynamic imports can fail
|
|
17
17
|
// when Claude Code invokes hooks from a different working directory.
|
|
@@ -22,8 +22,8 @@ try {
|
|
|
22
22
|
const raw = await readStdin();
|
|
23
23
|
const input = JSON.parse(raw);
|
|
24
24
|
|
|
25
|
-
const { extractEvents } = await import(join(PKG_SESSION, "extract.js"));
|
|
26
|
-
const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
|
|
25
|
+
const { extractEvents } = await import(pathToFileURL(join(PKG_SESSION, "extract.js")).href);
|
|
26
|
+
const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
|
|
27
27
|
|
|
28
28
|
const dbPath = getSessionDBPath();
|
|
29
29
|
const db = new SessionDB({ dbPath });
|
package/hooks/precompact.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import { readStdin, getSessionId, getSessionDBPath } from "./session-helpers.mjs
|
|
|
12
12
|
import { appendFileSync } from "node:fs";
|
|
13
13
|
import { join, dirname } from "node:path";
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
|
-
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
16
16
|
|
|
17
17
|
// Resolve absolute path for imports
|
|
18
18
|
const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
|
|
@@ -23,8 +23,8 @@ try {
|
|
|
23
23
|
const raw = await readStdin();
|
|
24
24
|
const input = JSON.parse(raw);
|
|
25
25
|
|
|
26
|
-
const { buildResumeSnapshot } = await import(join(PKG_SESSION, "snapshot.js"));
|
|
27
|
-
const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
|
|
26
|
+
const { buildResumeSnapshot } = await import(pathToFileURL(join(PKG_SESSION, "snapshot.js")).href);
|
|
27
|
+
const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
|
|
28
28
|
|
|
29
29
|
const dbPath = getSessionDBPath();
|
|
30
30
|
const db = new SessionDB({ dbPath });
|
package/hooks/sessionstart.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import { ROUTING_BLOCK } from "./routing-block.mjs";
|
|
|
18
18
|
import { readStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath } from "./session-helpers.mjs";
|
|
19
19
|
import { writeSessionEventsFile, buildSessionDirective, getAllProjectEvents } from "./session-directive.mjs";
|
|
20
20
|
import { join, dirname } from "node:path";
|
|
21
|
-
import { fileURLToPath } from "node:url";
|
|
21
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
22
22
|
import { readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
23
23
|
import { homedir } from "node:os";
|
|
24
24
|
|
|
@@ -35,7 +35,7 @@ try {
|
|
|
35
35
|
|
|
36
36
|
if (source === "compact") {
|
|
37
37
|
// Session was compacted — write events to file for auto-indexing, inject directive only
|
|
38
|
-
const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
|
|
38
|
+
const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
|
|
39
39
|
const dbPath = getSessionDBPath();
|
|
40
40
|
const db = new SessionDB({ dbPath });
|
|
41
41
|
const sessionId = getSessionId(input);
|
|
@@ -56,7 +56,7 @@ try {
|
|
|
56
56
|
// User used --continue — clear cleanup flag so startup doesn't wipe data
|
|
57
57
|
try { unlinkSync(getCleanupFlagPath()); } catch { /* no flag */ }
|
|
58
58
|
|
|
59
|
-
const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
|
|
59
|
+
const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
|
|
60
60
|
const dbPath = getSessionDBPath();
|
|
61
61
|
const db = new SessionDB({ dbPath });
|
|
62
62
|
|
|
@@ -69,7 +69,7 @@ try {
|
|
|
69
69
|
db.close();
|
|
70
70
|
} else if (source === "startup") {
|
|
71
71
|
// Fresh session (no --continue) — clean slate, capture CLAUDE.md rules.
|
|
72
|
-
const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
|
|
72
|
+
const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
|
|
73
73
|
const dbPath = getSessionDBPath();
|
|
74
74
|
const db = new SessionDB({ dbPath });
|
|
75
75
|
try { unlinkSync(getSessionEventsPath()); } catch { /* no stale file */ }
|
|
@@ -11,7 +11,7 @@ import "./suppress-stderr.mjs";
|
|
|
11
11
|
|
|
12
12
|
import { readStdin, getSessionId, getSessionDBPath } from "./session-helpers.mjs";
|
|
13
13
|
import { join, dirname } from "node:path";
|
|
14
|
-
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
15
15
|
|
|
16
16
|
const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
|
|
17
17
|
const PKG_SESSION = join(HOOK_DIR, "..", "build", "session");
|
|
@@ -30,8 +30,8 @@ try {
|
|
|
30
30
|
|| trimmed.startsWith("<tool-result>");
|
|
31
31
|
|
|
32
32
|
if (trimmed.length > 0 && !isSystemMessage) {
|
|
33
|
-
const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
|
|
34
|
-
const { extractUserEvents } = await import(join(PKG_SESSION, "extract.js"));
|
|
33
|
+
const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
|
|
34
|
+
const { extractUserEvents } = await import(pathToFileURL(join(PKG_SESSION, "extract.js")).href);
|
|
35
35
|
const dbPath = getSessionDBPath();
|
|
36
36
|
const db = new SessionDB({ dbPath });
|
|
37
37
|
const sessionId = getSessionId(input);
|
|
@@ -12,8 +12,8 @@ import "../suppress-stderr.mjs";
|
|
|
12
12
|
import { readStdin, getSessionId, getSessionDBPath, getProjectDir, VSCODE_OPTS } from "../session-helpers.mjs";
|
|
13
13
|
import { appendFileSync } from "node:fs";
|
|
14
14
|
import { join, dirname } from "node:path";
|
|
15
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
15
16
|
import { homedir } from "node:os";
|
|
16
|
-
import { fileURLToPath } from "node:url";
|
|
17
17
|
|
|
18
18
|
const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
|
|
19
19
|
const PKG_SESSION = join(HOOK_DIR, "..", "..", "build", "session");
|
|
@@ -26,8 +26,8 @@ try {
|
|
|
26
26
|
|
|
27
27
|
appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] CALL: ${input.tool_name}\n`);
|
|
28
28
|
|
|
29
|
-
const { extractEvents } = await import(join(PKG_SESSION, "extract.js"));
|
|
30
|
-
const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
|
|
29
|
+
const { extractEvents } = await import(pathToFileURL(join(PKG_SESSION, "extract.js")).href);
|
|
30
|
+
const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
|
|
31
31
|
|
|
32
32
|
const dbPath = getSessionDBPath(OPTS);
|
|
33
33
|
const db = new SessionDB({ dbPath });
|
|
@@ -11,8 +11,8 @@ import "../suppress-stderr.mjs";
|
|
|
11
11
|
import { readStdin, getSessionId, getSessionDBPath, VSCODE_OPTS } from "../session-helpers.mjs";
|
|
12
12
|
import { appendFileSync } from "node:fs";
|
|
13
13
|
import { join, dirname } from "node:path";
|
|
14
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
14
15
|
import { homedir } from "node:os";
|
|
15
|
-
import { fileURLToPath } from "node:url";
|
|
16
16
|
|
|
17
17
|
const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
|
|
18
18
|
const PKG_SESSION = join(HOOK_DIR, "..", "..", "build", "session");
|
|
@@ -23,8 +23,8 @@ try {
|
|
|
23
23
|
const raw = await readStdin();
|
|
24
24
|
const input = JSON.parse(raw);
|
|
25
25
|
|
|
26
|
-
const { buildResumeSnapshot } = await import(join(PKG_SESSION, "snapshot.js"));
|
|
27
|
-
const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
|
|
26
|
+
const { buildResumeSnapshot } = await import(pathToFileURL(join(PKG_SESSION, "snapshot.js")).href);
|
|
27
|
+
const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
|
|
28
28
|
|
|
29
29
|
const dbPath = getSessionDBPath(OPTS);
|
|
30
30
|
const db = new SessionDB({ dbPath });
|
|
@@ -18,9 +18,10 @@ import {
|
|
|
18
18
|
} from "../session-helpers.mjs";
|
|
19
19
|
import { join } from "node:path";
|
|
20
20
|
import { readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
21
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
21
22
|
import { homedir } from "node:os";
|
|
22
23
|
|
|
23
|
-
const HOOK_DIR = new URL(".", import.meta.url)
|
|
24
|
+
const HOOK_DIR = fileURLToPath(new URL(".", import.meta.url));
|
|
24
25
|
const PKG_SESSION = join(HOOK_DIR, "..", "..", "build", "session");
|
|
25
26
|
const OPTS = VSCODE_OPTS;
|
|
26
27
|
|
|
@@ -32,7 +33,7 @@ try {
|
|
|
32
33
|
const source = input.source ?? "startup";
|
|
33
34
|
|
|
34
35
|
if (source === "compact") {
|
|
35
|
-
const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
|
|
36
|
+
const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
|
|
36
37
|
const dbPath = getSessionDBPath(OPTS);
|
|
37
38
|
const db = new SessionDB({ dbPath });
|
|
38
39
|
const sessionId = getSessionId(input, OPTS);
|
|
@@ -52,7 +53,7 @@ try {
|
|
|
52
53
|
} else if (source === "resume") {
|
|
53
54
|
try { unlinkSync(getCleanupFlagPath(OPTS)); } catch { /* no flag */ }
|
|
54
55
|
|
|
55
|
-
const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
|
|
56
|
+
const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
|
|
56
57
|
const dbPath = getSessionDBPath(OPTS);
|
|
57
58
|
const db = new SessionDB({ dbPath });
|
|
58
59
|
|
|
@@ -64,7 +65,7 @@ try {
|
|
|
64
65
|
|
|
65
66
|
db.close();
|
|
66
67
|
} else if (source === "startup") {
|
|
67
|
-
const { SessionDB } = await import(join(PKG_SESSION, "db.js"));
|
|
68
|
+
const { SessionDB } = await import(pathToFileURL(join(PKG_SESSION, "db.js")).href);
|
|
68
69
|
const dbPath = getSessionDBPath(OPTS);
|
|
69
70
|
const db = new SessionDB({ dbPath });
|
|
70
71
|
try { unlinkSync(getSessionEventsPath(OPTS)); } catch { /* no stale file */ }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP plugin that saves 98% of your context window. Works with Claude Code, Gemini CLI, VS Code Copilot, OpenCode, and Codex CLI. Sandboxed code execution, FTS5 knowledge base, and intent-driven search.",
|
|
6
6
|
"author": "Mert Koseoğlu",
|
|
@@ -26,10 +26,11 @@
|
|
|
26
26
|
},
|
|
27
27
|
"homepage": "https://github.com/mksglu/context-mode#readme",
|
|
28
28
|
"bugs": "https://github.com/mksglu/context-mode/issues",
|
|
29
|
-
"main": "./build/
|
|
29
|
+
"main": "./build/opencode-plugin.js",
|
|
30
30
|
"exports": {
|
|
31
|
-
".": "./build/
|
|
32
|
-
"./plugin": "./build/opencode-plugin.js"
|
|
31
|
+
".": "./build/opencode-plugin.js",
|
|
32
|
+
"./plugin": "./build/opencode-plugin.js",
|
|
33
|
+
"./cli": "./build/cli.js"
|
|
33
34
|
},
|
|
34
35
|
"bin": {
|
|
35
36
|
"context-mode": "./build/cli.js"
|
package/server.bundle.mjs
CHANGED
|
@@ -202,7 +202,7 @@ ${n}`}}};import{createRequire as YE}from"node:module";var dd=null;function $y(){
|
|
|
202
202
|
`);Buffer.byteLength(x)>r&&p.length>1&&(p.pop(),g(),p=[_])}g(),i=[]},u=0;for(;u<o.length;){let l=o[u];if(/^[-_*]{3,}\s*$/.test(l)){c(),u++;continue}let d=l.match(/^(#{1,4})\s+(.+)$/);if(d){c();let m=d[1].length,p=d[2].trim();for(;s.length>0&&s[s.length-1].level>=m;)s.pop();s.push({level:m,text:p}),a=p,i.push(l),u++;continue}let f=l.match(/^(`{3,})(.*)?$/);if(f){let m=f[1],p=[l];for(u++;u<o.length;){if(p.push(o[u]),o[u].startsWith(m)&&o[u].trim()===m){u++;break}u++}i.push(...p);continue}i.push(l),u++}return c(),n}#T(e,r){let n=e.split(/\n\s*\n/);if(n.length>=3&&n.length<=200&&n.every(c=>Buffer.byteLength(c)<5e3))return n.map((c,u)=>{let l=c.trim();return{title:l.split(`
|
|
203
203
|
`)[0].slice(0,80)||`Section ${u+1}`,content:l}}).filter(c=>c.content.length>0);let o=e.split(`
|
|
204
204
|
`);if(o.length<=r)return[{title:"Output",content:e}];let s=[],a=Math.max(r-2,1);for(let c=0;c<o.length;c+=a){let u=o.slice(c,c+r);if(u.length===0)break;let l=c+1,d=Math.min(c+u.length,o.length),f=u[0]?.trim().slice(0,80);s.push({title:f||`Lines ${l}-${d}`,content:u.join(`
|
|
205
|
-
`)})}return s}#b(e,r,n,o){let s=r.length>0?r.join(" > "):"(root)",i=JSON.stringify(e,null,2);if(Buffer.byteLength(i)<=o&&!(typeof e=="object"&&e!==null&&!Array.isArray(e)&&Object.values(e).some(c=>typeof c=="object"&&c!==null))){n.push({title:s,content:i,hasCode:!0});return}if(typeof e=="object"&&e!==null&&!Array.isArray(e)){let a=Object.entries(e);if(a.length>0){for(let[c,u]of a)this.#b(u,[...r,c],n,o);return}n.push({title:s,content:i,hasCode:!0});return}if(Array.isArray(e)){this.#P(e,r,n,o);return}n.push({title:s,content:i,hasCode:!1})}#E(e){if(e.length===0)return null;let r=e[0];if(typeof r!="object"||r===null||Array.isArray(r))return null;let n=["id","name","title","path","slug","key","label"],o=r;for(let s of n)if(s in o&&(typeof o[s]=="string"||typeof o[s]=="number"))return s;return null}#z(e,r,n,o,s){let i=e?`${e} > `:"";if(!s)return r===n?`${i}[${r}]`:`${i}[${r}-${n}]`;let a=c=>String(c[s]);return o.length===1?`${i}${a(o[0])}`:o.length<=3?i+o.map(a).join(", "):`${i}${a(o[0])}\u2026${a(o[o.length-1])}`}#P(e,r,n,o){let s=r.length>0?r.join(" > "):"(root)",i=this.#E(e),a=[],c=0,u=l=>{if(a.length===0)return;let d=this.#z(s,c,l,a,i);n.push({title:d,content:JSON.stringify(a,null,2),hasCode:!0})};for(let l=0;l<e.length;l++){a.push(e[l]);let d=JSON.stringify(a,null,2);Buffer.byteLength(d)>o&&a.length>1&&(a.pop(),u(l-1),a=[e[l]],c=l)}u(c+a.length-1)}#R(e,r){return e.length===0?r||"Untitled":e.map(n=>n.text).join(" > ")}};import{readFileSync as Iy}from"node:fs";import{resolve as bn}from"node:path";import{homedir as Oy}from"node:os";function Ny(t){let e=t.match(/^Bash\((.+)\)$/);return e?e[1]:null}function oz(t){let e=t.match(/^(\w+)\((.+)\)$/);return e?{tool:e[1],glob:e[2]}:null}function sz(t){return t.replace(/[.*+?^${}()|[\]\\\/\-]/g,"\\$&")}function Ry(t){return t.replace(/[.+?^${}()|[\]\\\/\-]/g,"\\$&").replace(/\*/g,".*")}function iz(t,e=!1){let r,n=t.indexOf(":");if(n!==-1){let o=t.slice(0,n),s=t.slice(n+1),i=sz(o),a=Ry(s);r=`^${i}(\\s${a})?$`}else r=`^${Ry(t)}$`;return new RegExp(r,e?"i":"")}function az(t,e=!1){let r="",n=0;for(;n<t.length;)t[n]==="*"&&t[n+1]==="*"?n+2<t.length&&t[n+2]==="/"?(r+="(.*/)?",n+=3):(r+=".*",n+=2):t[n]==="*"?(r+="[^/]*",n++):t[n]==="?"?(r+="[^/]",n++):(r+=t[n].replace(/[.+^${}()|[\]\\\/\-]/g,"\\$&"),n++);return new RegExp(`^${r}$`,e?"i":"")}function cz(t,e,r=!1){for(let n of e){let o=Ny(n);if(o&&iz(o,r).test(t))return n}return null}function uz(t){let e=[],r="",n=!1,o=!1,s=!1;for(let i=0;i<t.length;i++){let a=t[i],c=i>0?t[i-1]:"";a==="'"&&!o&&!s&&c!=="\\"?(n=!n,r+=a):a==='"'&&!n&&!s&&c!=="\\"?(o=!o,r+=a):a==="`"&&!n&&!o&&c!=="\\"?(s=!s,r+=a):!n&&!o&&!s?a===";"?(e.push(r.trim()),r=""):a==="|"&&t[i+1]==="|"||a==="&"&&t[i+1]==="&"?(e.push(r.trim()),r="",i++):a==="|"?(e.push(r.trim()),r=""):r+=a:r+=a}return r.trim()&&e.push(r.trim()),e.filter(i=>i.length>0)}function pd(t){let e;try{e=Iy(t,"utf-8")}catch{return null}let r;try{r=JSON.parse(e)}catch{return null}let n=r?.permissions;if(!n||typeof n!="object")return null;let o=s=>Array.isArray(s)?s.filter(i=>typeof i=="string"&&Ny(i)!==null):[];return{allow:o(n.allow),deny:o(n.deny),ask:o(n.ask)}}function fd(t,e){let r=[];if(t){let s=bn(t,".claude","settings.local.json"),i=pd(s);i&&r.push(i);let a=bn(t,".claude","settings.json"),c=pd(a);c&&r.push(c)}let n=e??bn(Oy(),".claude","settings.json"),o=pd(n);return o&&r.push(o),r}function Cy(t,e,r){let n=[],o=a=>{let c;try{c=Iy(a,"utf-8")}catch{return null}let u;try{u=JSON.parse(c)}catch{return null}let l=u?.permissions?.deny;if(!Array.isArray(l))return[];let d=[];for(let f of l){if(typeof f!="string")continue;let m=oz(f);m&&m.tool===t&&d.push(m.glob)}return d};if(e){let a=o(bn(e,".claude","settings.local.json"));a!==null&&n.push(a);let c=o(bn(e,".claude","settings.json"));c!==null&&n.push(c)}let s=r??bn(Oy(),".claude","settings.json"),i=o(s);return i!==null&&n.push(i),n}function md(t,e,r=process.platform==="win32"){let n=uz(t);for(let o of n)for(let s of e){let i=cz(o,s.deny,r);if(i)return{decision:"deny",matchedPattern:i}}return{decision:"allow"}}function Ay(t,e,r=process.platform==="win32"){let n=t.replace(/\\/g,"/");for(let o of e)for(let s of o)if(az(s,r).test(n))return{denied:!0,matchedPattern:s};return{denied:!1}}var lz={python:[/os\.system\(\s*(['"])(.*?)\1\s*\)/g,/subprocess\.(?:run|call|Popen|check_output|check_call)\(\s*(['"])(.*?)\1/g],javascript:[/exec(?:Sync|File|FileSync)?\(\s*(['"`])(.*?)\1/g,/spawn(?:Sync)?\(\s*(['"`])(.*?)\1/g],typescript:[/exec(?:Sync|File|FileSync)?\(\s*(['"`])(.*?)\1/g,/spawn(?:Sync)?\(\s*(['"`])(.*?)\1/g],ruby:[/system\(\s*(['"])(.*?)\1/g,/`(.*?)`/g],go:[/exec\.Command\(\s*(['"`])(.*?)\1/g],php:[/shell_exec\(\s*(['"`])(.*?)\1/g,/(?:^|[^.])exec\(\s*(['"`])(.*?)\1/g,/(?:^|[^.])system\(\s*(['"`])(.*?)\1/g,/passthru\(\s*(['"`])(.*?)\1/g,/proc_open\(\s*(['"`])(.*?)\1/g],rust:[/Command::new\(\s*(['"`])(.*?)\1/g]};function dz(t){let e=[],r=/subprocess\.(?:run|call|Popen|check_output|check_call)\(\s*\[([^\]]+)\]/g,n;for(;(n=r.exec(t))!==null;){let s=[...n[1].matchAll(/(['"])(.*?)\1/g)].map(i=>i[2]);s.length>0&&e.push(s.join(" "))}return e}function jy(t,e){let r=lz[e];if(!r&&e!=="python")return[];let n=[];if(r)for(let o of r){o.lastIndex=0;let s;for(;(s=o.exec(t))!==null;){let i=s[s.length-1];i&&n.push(i)}}return e==="python"&&n.push(...dz(t)),n}var Fy="1.0.
|
|
205
|
+
`)})}return s}#b(e,r,n,o){let s=r.length>0?r.join(" > "):"(root)",i=JSON.stringify(e,null,2);if(Buffer.byteLength(i)<=o&&!(typeof e=="object"&&e!==null&&!Array.isArray(e)&&Object.values(e).some(c=>typeof c=="object"&&c!==null))){n.push({title:s,content:i,hasCode:!0});return}if(typeof e=="object"&&e!==null&&!Array.isArray(e)){let a=Object.entries(e);if(a.length>0){for(let[c,u]of a)this.#b(u,[...r,c],n,o);return}n.push({title:s,content:i,hasCode:!0});return}if(Array.isArray(e)){this.#P(e,r,n,o);return}n.push({title:s,content:i,hasCode:!1})}#E(e){if(e.length===0)return null;let r=e[0];if(typeof r!="object"||r===null||Array.isArray(r))return null;let n=["id","name","title","path","slug","key","label"],o=r;for(let s of n)if(s in o&&(typeof o[s]=="string"||typeof o[s]=="number"))return s;return null}#z(e,r,n,o,s){let i=e?`${e} > `:"";if(!s)return r===n?`${i}[${r}]`:`${i}[${r}-${n}]`;let a=c=>String(c[s]);return o.length===1?`${i}${a(o[0])}`:o.length<=3?i+o.map(a).join(", "):`${i}${a(o[0])}\u2026${a(o[o.length-1])}`}#P(e,r,n,o){let s=r.length>0?r.join(" > "):"(root)",i=this.#E(e),a=[],c=0,u=l=>{if(a.length===0)return;let d=this.#z(s,c,l,a,i);n.push({title:d,content:JSON.stringify(a,null,2),hasCode:!0})};for(let l=0;l<e.length;l++){a.push(e[l]);let d=JSON.stringify(a,null,2);Buffer.byteLength(d)>o&&a.length>1&&(a.pop(),u(l-1),a=[e[l]],c=l)}u(c+a.length-1)}#R(e,r){return e.length===0?r||"Untitled":e.map(n=>n.text).join(" > ")}};import{readFileSync as Iy}from"node:fs";import{resolve as bn}from"node:path";import{homedir as Oy}from"node:os";function Ny(t){let e=t.match(/^Bash\((.+)\)$/);return e?e[1]:null}function oz(t){let e=t.match(/^(\w+)\((.+)\)$/);return e?{tool:e[1],glob:e[2]}:null}function sz(t){return t.replace(/[.*+?^${}()|[\]\\\/\-]/g,"\\$&")}function Ry(t){return t.replace(/[.+?^${}()|[\]\\\/\-]/g,"\\$&").replace(/\*/g,".*")}function iz(t,e=!1){let r,n=t.indexOf(":");if(n!==-1){let o=t.slice(0,n),s=t.slice(n+1),i=sz(o),a=Ry(s);r=`^${i}(\\s${a})?$`}else r=`^${Ry(t)}$`;return new RegExp(r,e?"i":"")}function az(t,e=!1){let r="",n=0;for(;n<t.length;)t[n]==="*"&&t[n+1]==="*"?n+2<t.length&&t[n+2]==="/"?(r+="(.*/)?",n+=3):(r+=".*",n+=2):t[n]==="*"?(r+="[^/]*",n++):t[n]==="?"?(r+="[^/]",n++):(r+=t[n].replace(/[.+^${}()|[\]\\\/\-]/g,"\\$&"),n++);return new RegExp(`^${r}$`,e?"i":"")}function cz(t,e,r=!1){for(let n of e){let o=Ny(n);if(o&&iz(o,r).test(t))return n}return null}function uz(t){let e=[],r="",n=!1,o=!1,s=!1;for(let i=0;i<t.length;i++){let a=t[i],c=i>0?t[i-1]:"";a==="'"&&!o&&!s&&c!=="\\"?(n=!n,r+=a):a==='"'&&!n&&!s&&c!=="\\"?(o=!o,r+=a):a==="`"&&!n&&!o&&c!=="\\"?(s=!s,r+=a):!n&&!o&&!s?a===";"?(e.push(r.trim()),r=""):a==="|"&&t[i+1]==="|"||a==="&"&&t[i+1]==="&"?(e.push(r.trim()),r="",i++):a==="|"?(e.push(r.trim()),r=""):r+=a:r+=a}return r.trim()&&e.push(r.trim()),e.filter(i=>i.length>0)}function pd(t){let e;try{e=Iy(t,"utf-8")}catch{return null}let r;try{r=JSON.parse(e)}catch{return null}let n=r?.permissions;if(!n||typeof n!="object")return null;let o=s=>Array.isArray(s)?s.filter(i=>typeof i=="string"&&Ny(i)!==null):[];return{allow:o(n.allow),deny:o(n.deny),ask:o(n.ask)}}function fd(t,e){let r=[];if(t){let s=bn(t,".claude","settings.local.json"),i=pd(s);i&&r.push(i);let a=bn(t,".claude","settings.json"),c=pd(a);c&&r.push(c)}let n=e??bn(Oy(),".claude","settings.json"),o=pd(n);return o&&r.push(o),r}function Cy(t,e,r){let n=[],o=a=>{let c;try{c=Iy(a,"utf-8")}catch{return null}let u;try{u=JSON.parse(c)}catch{return null}let l=u?.permissions?.deny;if(!Array.isArray(l))return[];let d=[];for(let f of l){if(typeof f!="string")continue;let m=oz(f);m&&m.tool===t&&d.push(m.glob)}return d};if(e){let a=o(bn(e,".claude","settings.local.json"));a!==null&&n.push(a);let c=o(bn(e,".claude","settings.json"));c!==null&&n.push(c)}let s=r??bn(Oy(),".claude","settings.json"),i=o(s);return i!==null&&n.push(i),n}function md(t,e,r=process.platform==="win32"){let n=uz(t);for(let o of n)for(let s of e){let i=cz(o,s.deny,r);if(i)return{decision:"deny",matchedPattern:i}}return{decision:"allow"}}function Ay(t,e,r=process.platform==="win32"){let n=t.replace(/\\/g,"/");for(let o of e)for(let s of o)if(az(s,r).test(n))return{denied:!0,matchedPattern:s};return{denied:!1}}var lz={python:[/os\.system\(\s*(['"])(.*?)\1\s*\)/g,/subprocess\.(?:run|call|Popen|check_output|check_call)\(\s*(['"])(.*?)\1/g],javascript:[/exec(?:Sync|File|FileSync)?\(\s*(['"`])(.*?)\1/g,/spawn(?:Sync)?\(\s*(['"`])(.*?)\1/g],typescript:[/exec(?:Sync|File|FileSync)?\(\s*(['"`])(.*?)\1/g,/spawn(?:Sync)?\(\s*(['"`])(.*?)\1/g],ruby:[/system\(\s*(['"])(.*?)\1/g,/`(.*?)`/g],go:[/exec\.Command\(\s*(['"`])(.*?)\1/g],php:[/shell_exec\(\s*(['"`])(.*?)\1/g,/(?:^|[^.])exec\(\s*(['"`])(.*?)\1/g,/(?:^|[^.])system\(\s*(['"`])(.*?)\1/g,/passthru\(\s*(['"`])(.*?)\1/g,/proc_open\(\s*(['"`])(.*?)\1/g],rust:[/Command::new\(\s*(['"`])(.*?)\1/g]};function dz(t){let e=[],r=/subprocess\.(?:run|call|Popen|check_output|check_call)\(\s*\[([^\]]+)\]/g,n;for(;(n=r.exec(t))!==null;){let s=[...n[1].matchAll(/(['"])(.*?)\1/g)].map(i=>i[2]);s.length>0&&e.push(s.join(" "))}return e}function jy(t,e){let r=lz[e];if(!r&&e!=="python")return[];let n=[];if(r)for(let o of r){o.lastIndex=0;let s;for(;(s=o.exec(t))!==null;){let i=s[s.length-1];i&&n.push(i)}}return e==="python"&&n.push(...dz(t)),n}var Fy="1.0.6";process.on("unhandledRejection",t=>{process.stderr.write(`[context-mode] unhandledRejection: ${t}
|
|
206
206
|
`)});process.on("uncaughtException",t=>{process.stderr.write(`[context-mode] uncaughtException: ${t?.message??t}
|
|
207
207
|
`)});var vd=xi(),_z=gy(vd),Et=new yi({name:"context-mode",version:Fy}),zo=new $i({runtimes:vd,projectRoot:process.env.CLAUDE_PROJECT_DIR}),$n=null;function vz(t){try{let e=ki(qy(),".claude","context-mode","sessions");if(!Dy(e))return;let r=mz(e).filter(n=>n.endsWith("-events.md"));for(let n of r){let o=ki(e,n);try{t.index({path:o,source:"session-events"}),fz(o)}catch{}}}catch{}}function Po(){return $n||($n=new wi),vz($n),$n}var De={calls:{},bytesReturned:{},bytesIndexed:0,bytesSandboxed:0,sessionStart:Date.now()};function W(t,e){let r=e.content.reduce((n,o)=>n+Buffer.byteLength(o.text),0);return De.calls[t]=(De.calls[t]||0)+1,De.bytesReturned[t]=(De.bytesReturned[t]||0)+r,e}function ir(t){De.bytesIndexed+=t}function xd(t,e){try{let r=fd(process.env.CLAUDE_PROJECT_DIR),n=md(t,r);if(n.decision==="deny")return W(e,{content:[{type:"text",text:`Command blocked by security policy: matches deny pattern ${n.matchedPattern}`}],isError:!0})}catch{}return null}function Uy(t,e,r){try{let n=jy(t,e);if(n.length===0)return null;let o=fd(process.env.CLAUDE_PROJECT_DIR);for(let s of n){let i=md(s,o);if(i.decision==="deny")return W(r,{content:[{type:"text",text:`Command blocked by security policy: embedded shell command "${s}" matches deny pattern ${i.matchedPattern}`}],isError:!0})}}catch{}return null}function xz(t,e){try{let r=Cy("Read",process.env.CLAUDE_PROJECT_DIR),n=Ay(t,r);if(n.denied)return W(e,{content:[{type:"text",text:`File access blocked by security policy: path matches Read deny pattern ${n.matchedPattern}`}],isError:!0})}catch{}return null}var bz=_z.join(", "),$z=ud()?" (Bun detected \u2014 JS/TS runs 3-5x faster)":"",wz="",kz="";function Sz(t){let e=[],r=0,n=0;for(;n<t.length;)if(t[n]===wz){for(e.push(r),n++;n<t.length&&t[n]!==kz;)r++,n++;n<t.length&&n++}else r++,n++;return e}function Vy(t,e,r=1500,n){if(t.length<=r)return t;let o=[];if(n)for(let u of Sz(n))o.push(u);if(o.length===0){let u=e.toLowerCase().split(/\s+/).filter(d=>d.length>2),l=t.toLowerCase();for(let d of u){let f=l.indexOf(d);for(;f!==-1;)o.push(f),f=l.indexOf(d,f+1)}}if(o.length===0)return t.slice(0,r)+`
|
|
208
208
|
\u2026`;o.sort((u,l)=>u-l);let s=300,i=[];for(let u of o){let l=Math.max(0,u-s),d=Math.min(t.length,u+s);i.length>0&&l<=i[i.length-1][1]?i[i.length-1][1]=d:i.push([l,d])}let a=[],c=0;for(let[u,l]of i){if(c>=r)break;let d=t.slice(u,Math.min(l,u+(r-c)));a.push((u>0?"\u2026":"")+d+(l<t.length?"\u2026":"")),c+=d.length}return a.join(`
|