context-mode 0.9.21 → 1.0.0
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/hooks/hooks.json +46 -4
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +4 -4
- package/README.md +377 -191
- package/build/adapters/claude-code/config.d.ts +8 -0
- package/build/adapters/claude-code/config.js +8 -0
- package/build/adapters/claude-code/hooks.d.ts +53 -0
- package/build/adapters/claude-code/hooks.js +88 -0
- package/build/adapters/claude-code/index.d.ts +50 -0
- package/build/adapters/claude-code/index.js +523 -0
- package/build/adapters/codex/config.d.ts +8 -0
- package/build/adapters/codex/config.js +8 -0
- package/build/adapters/codex/hooks.d.ts +21 -0
- package/build/adapters/codex/hooks.js +27 -0
- package/build/adapters/codex/index.d.ts +44 -0
- package/build/adapters/codex/index.js +223 -0
- package/build/adapters/detect.d.ts +26 -0
- package/build/adapters/detect.js +131 -0
- package/build/adapters/gemini-cli/config.d.ts +8 -0
- package/build/adapters/gemini-cli/config.js +8 -0
- package/build/adapters/gemini-cli/hooks.d.ts +44 -0
- package/build/adapters/gemini-cli/hooks.js +64 -0
- package/build/adapters/gemini-cli/index.d.ts +57 -0
- package/build/adapters/gemini-cli/index.js +468 -0
- package/build/adapters/opencode/config.d.ts +8 -0
- package/build/adapters/opencode/config.js +8 -0
- package/build/adapters/opencode/hooks.d.ts +38 -0
- package/build/adapters/opencode/hooks.js +50 -0
- package/build/adapters/opencode/index.d.ts +52 -0
- package/build/adapters/opencode/index.js +386 -0
- package/build/adapters/types.d.ts +218 -0
- package/build/adapters/types.js +13 -0
- package/build/adapters/vscode-copilot/config.d.ts +8 -0
- package/build/adapters/vscode-copilot/config.js +8 -0
- package/build/adapters/vscode-copilot/hooks.d.ts +49 -0
- package/build/adapters/vscode-copilot/hooks.js +76 -0
- package/build/adapters/vscode-copilot/index.d.ts +58 -0
- package/build/adapters/vscode-copilot/index.js +512 -0
- package/build/cli.d.ts +9 -6
- package/build/cli.js +133 -423
- package/build/db-base.d.ts +84 -0
- package/build/db-base.js +128 -0
- package/build/executor.d.ts +6 -7
- package/build/executor.js +111 -51
- package/build/opencode-plugin.d.ts +37 -0
- package/build/opencode-plugin.js +118 -0
- package/build/runtime.js +1 -1
- package/build/server.js +436 -117
- package/build/session/db.d.ts +110 -0
- package/build/session/db.js +285 -0
- package/build/session/extract.d.ts +51 -0
- package/build/session/extract.js +407 -0
- package/build/session/snapshot.d.ts +70 -0
- package/build/session/snapshot.js +309 -0
- package/build/store.d.ts +4 -22
- package/build/store.js +67 -55
- package/build/truncate.d.ts +59 -0
- package/build/truncate.js +157 -0
- package/build/types.d.ts +101 -0
- package/build/types.js +20 -0
- package/configs/claude-code/CLAUDE.md +62 -0
- package/configs/codex/AGENTS.md +58 -0
- package/configs/codex/config.toml +5 -0
- package/configs/gemini-cli/GEMINI.md +58 -0
- package/configs/gemini-cli/mcp.json +7 -0
- package/configs/gemini-cli/settings.json +49 -0
- package/configs/opencode/AGENTS.md +58 -0
- package/configs/opencode/opencode.json +10 -0
- package/configs/vscode-copilot/copilot-instructions.md +58 -0
- package/configs/vscode-copilot/hooks.json +16 -0
- package/configs/vscode-copilot/mcp.json +8 -0
- package/hooks/core/formatters.mjs +86 -0
- package/hooks/core/routing.mjs +262 -0
- package/hooks/core/stdin.mjs +19 -0
- package/hooks/formatters/claude-code.mjs +57 -0
- package/hooks/formatters/gemini-cli.mjs +55 -0
- package/hooks/formatters/vscode-copilot.mjs +55 -0
- package/hooks/gemini-cli/aftertool.mjs +58 -0
- package/hooks/gemini-cli/beforetool.mjs +25 -0
- package/hooks/gemini-cli/precompress.mjs +51 -0
- package/hooks/gemini-cli/sessionstart.mjs +117 -0
- package/hooks/hooks.json +46 -4
- package/hooks/posttooluse.mjs +53 -0
- package/hooks/precompact.mjs +55 -0
- package/hooks/pretooluse.mjs +23 -266
- package/hooks/routing-block.mjs +19 -6
- package/hooks/session-directive.mjs +353 -0
- package/hooks/session-helpers.mjs +112 -0
- package/hooks/sessionstart.mjs +123 -16
- package/hooks/userpromptsubmit.mjs +58 -0
- package/hooks/vscode-copilot/posttooluse.mjs +58 -0
- package/hooks/vscode-copilot/precompact.mjs +51 -0
- package/hooks/vscode-copilot/pretooluse.mjs +25 -0
- package/hooks/vscode-copilot/sessionstart.mjs +115 -0
- package/package.json +20 -17
- package/skills/context-mode/SKILL.md +49 -49
- package/skills/{doctor → ctx-doctor}/SKILL.md +3 -3
- package/skills/{stats → ctx-stats}/SKILL.md +3 -3
- package/skills/{upgrade → ctx-upgrade}/SKILL.md +3 -3
- package/start.mjs +47 -0
- package/hooks/pretooluse.sh +0 -147
- package/server.bundle.mjs +0 -341
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/vscode-copilot/hooks — VS Code Copilot hook definitions and matchers.
|
|
3
|
+
*
|
|
4
|
+
* Defines the hook types, matchers, and registration format specific to
|
|
5
|
+
* VS Code Copilot's hook system. This module is used by:
|
|
6
|
+
* - CLI setup/upgrade commands (to configure hooks)
|
|
7
|
+
* - Doctor command (to validate hook configuration)
|
|
8
|
+
* - Hook config generation
|
|
9
|
+
*
|
|
10
|
+
* VS Code Copilot hook system reference:
|
|
11
|
+
* - Hooks are registered in .github/hooks/*.json
|
|
12
|
+
* - Hook names: PreToolUse, PostToolUse, PreCompact, SessionStart (PascalCase)
|
|
13
|
+
* - Additional hooks: Stop, SubagentStart, SubagentStop (unique to VS Code)
|
|
14
|
+
* - CRITICAL: matchers are parsed but IGNORED (all hooks fire on all tools)
|
|
15
|
+
* - Input: JSON on stdin
|
|
16
|
+
* - Output: JSON on stdout (or empty for passthrough)
|
|
17
|
+
* - Preview status — API may change
|
|
18
|
+
*/
|
|
19
|
+
// ─────────────────────────────────────────────────────────
|
|
20
|
+
// Hook type constants
|
|
21
|
+
// ─────────────────────────────────────────────────────────
|
|
22
|
+
/** VS Code Copilot hook types. */
|
|
23
|
+
export const HOOK_TYPES = {
|
|
24
|
+
PRE_TOOL_USE: "PreToolUse",
|
|
25
|
+
POST_TOOL_USE: "PostToolUse",
|
|
26
|
+
PRE_COMPACT: "PreCompact",
|
|
27
|
+
SESSION_START: "SessionStart",
|
|
28
|
+
// Additional hooks unique to VS Code Copilot
|
|
29
|
+
STOP: "Stop",
|
|
30
|
+
SUBAGENT_START: "SubagentStart",
|
|
31
|
+
SUBAGENT_STOP: "SubagentStop",
|
|
32
|
+
};
|
|
33
|
+
// ─────────────────────────────────────────────────────────
|
|
34
|
+
// Hook script file names
|
|
35
|
+
// ─────────────────────────────────────────────────────────
|
|
36
|
+
/** Map of hook types to their script file names. */
|
|
37
|
+
export const HOOK_SCRIPTS = {
|
|
38
|
+
[HOOK_TYPES.PRE_TOOL_USE]: "pretooluse.mjs",
|
|
39
|
+
[HOOK_TYPES.POST_TOOL_USE]: "posttooluse.mjs",
|
|
40
|
+
[HOOK_TYPES.PRE_COMPACT]: "precompact.mjs",
|
|
41
|
+
[HOOK_TYPES.SESSION_START]: "sessionstart.mjs",
|
|
42
|
+
};
|
|
43
|
+
// ─────────────────────────────────────────────────────────
|
|
44
|
+
// Hook validation
|
|
45
|
+
// ─────────────────────────────────────────────────────────
|
|
46
|
+
/** Required hooks that must be configured for context-mode to function. */
|
|
47
|
+
export const REQUIRED_HOOKS = [
|
|
48
|
+
HOOK_TYPES.PRE_TOOL_USE,
|
|
49
|
+
HOOK_TYPES.SESSION_START,
|
|
50
|
+
];
|
|
51
|
+
/** Optional hooks that enhance functionality but aren't critical. */
|
|
52
|
+
export const OPTIONAL_HOOKS = [
|
|
53
|
+
HOOK_TYPES.POST_TOOL_USE,
|
|
54
|
+
HOOK_TYPES.PRE_COMPACT,
|
|
55
|
+
];
|
|
56
|
+
/**
|
|
57
|
+
* Check if a hook entry points to a context-mode hook script.
|
|
58
|
+
*/
|
|
59
|
+
export function isContextModeHook(entry, hookType) {
|
|
60
|
+
const scriptName = HOOK_SCRIPTS[hookType];
|
|
61
|
+
if (!scriptName)
|
|
62
|
+
return false;
|
|
63
|
+
return (entry.hooks?.some((h) => h.command?.includes(scriptName)) ?? false);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Build the hook command string for a given hook type.
|
|
67
|
+
* Uses the CLI dispatcher: `context-mode hook vscode-copilot <event>`
|
|
68
|
+
* Requires global install: `npm install -g context-mode`
|
|
69
|
+
*/
|
|
70
|
+
export function buildHookCommand(hookType) {
|
|
71
|
+
const scriptName = HOOK_SCRIPTS[hookType];
|
|
72
|
+
if (!scriptName) {
|
|
73
|
+
throw new Error(`No script defined for hook type: ${hookType}`);
|
|
74
|
+
}
|
|
75
|
+
return `context-mode hook vscode-copilot ${hookType.toLowerCase()}`;
|
|
76
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/vscode-copilot — VS Code Copilot platform adapter.
|
|
3
|
+
*
|
|
4
|
+
* Implements HookAdapter for VS Code Copilot's JSON stdin/stdout hook paradigm.
|
|
5
|
+
*
|
|
6
|
+
* VS Code Copilot hook specifics:
|
|
7
|
+
* - I/O: JSON on stdin, JSON on stdout (same paradigm as Claude Code)
|
|
8
|
+
* - Hook names: PreToolUse, PostToolUse, PreCompact, SessionStart (PascalCase)
|
|
9
|
+
* - Additional hooks: Stop, SubagentStart, SubagentStop (unique to VS Code)
|
|
10
|
+
* - Arg modification: `updatedInput` in hookSpecificOutput wrapper (NOT flat)
|
|
11
|
+
* - Blocking: `permissionDecision: "deny"` (same as Claude Code)
|
|
12
|
+
* - Output modification: `additionalContext` in hookSpecificOutput,
|
|
13
|
+
* `decision: "block"` + reason
|
|
14
|
+
* - Tool input fields: tool_name, tool_input (snake_case, same as Claude Code)
|
|
15
|
+
* - But tool input PROPERTY names are camelCase (filePath not file_path)
|
|
16
|
+
* - Session ID: sessionId (camelCase, NOT session_id)
|
|
17
|
+
* - MCP tool prefix: f1e_ (not mcp__server__tool)
|
|
18
|
+
* - CRITICAL: matchers are parsed but IGNORED (all hooks fire on all tools)
|
|
19
|
+
* - Config: .github/hooks/*.json (primary), also reads .claude/settings.json
|
|
20
|
+
* - Env detection: VSCODE_PID, TERM_PROGRAM=vscode
|
|
21
|
+
* - Session dir: ~/.vscode/context-mode/sessions/ (fallback)
|
|
22
|
+
* - Preview status — API may change
|
|
23
|
+
*/
|
|
24
|
+
import type { HookAdapter, HookParadigm, PlatformCapabilities, DiagnosticResult, PreToolUseEvent, PostToolUseEvent, PreCompactEvent, SessionStartEvent, PreToolUseResponse, PostToolUseResponse, PreCompactResponse, SessionStartResponse, HookRegistration, RoutingInstructionsConfig } from "../types.js";
|
|
25
|
+
export declare class VSCodeCopilotAdapter implements HookAdapter {
|
|
26
|
+
readonly name = "VS Code Copilot";
|
|
27
|
+
readonly paradigm: HookParadigm;
|
|
28
|
+
readonly capabilities: PlatformCapabilities;
|
|
29
|
+
parsePreToolUseInput(raw: unknown): PreToolUseEvent;
|
|
30
|
+
parsePostToolUseInput(raw: unknown): PostToolUseEvent;
|
|
31
|
+
parsePreCompactInput(raw: unknown): PreCompactEvent;
|
|
32
|
+
parseSessionStartInput(raw: unknown): SessionStartEvent;
|
|
33
|
+
formatPreToolUseResponse(response: PreToolUseResponse): unknown;
|
|
34
|
+
formatPostToolUseResponse(response: PostToolUseResponse): unknown;
|
|
35
|
+
formatPreCompactResponse(response: PreCompactResponse): unknown;
|
|
36
|
+
formatSessionStartResponse(response: SessionStartResponse): unknown;
|
|
37
|
+
getSettingsPath(): string;
|
|
38
|
+
getSessionDir(): string;
|
|
39
|
+
getSessionDBPath(projectDir: string): string;
|
|
40
|
+
getSessionEventsPath(projectDir: string): string;
|
|
41
|
+
generateHookConfig(_pluginRoot: string): HookRegistration;
|
|
42
|
+
readSettings(): Record<string, unknown> | null;
|
|
43
|
+
writeSettings(settings: Record<string, unknown>): void;
|
|
44
|
+
validateHooks(pluginRoot: string): DiagnosticResult[];
|
|
45
|
+
checkPluginRegistration(): DiagnosticResult;
|
|
46
|
+
getInstalledVersion(): string;
|
|
47
|
+
configureAllHooks(_pluginRoot: string): string[];
|
|
48
|
+
backupSettings(): string | null;
|
|
49
|
+
setHookPermissions(pluginRoot: string): string[];
|
|
50
|
+
updatePluginRegistry(_pluginRoot: string, _version: string): void;
|
|
51
|
+
getRoutingInstructionsConfig(): RoutingInstructionsConfig;
|
|
52
|
+
writeRoutingInstructions(projectDir: string, pluginRoot: string): string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Extract session ID from VS Code Copilot hook input.
|
|
55
|
+
* VS Code Copilot uses camelCase sessionId (NOT session_id).
|
|
56
|
+
*/
|
|
57
|
+
private extractSessionId;
|
|
58
|
+
}
|
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/vscode-copilot — VS Code Copilot platform adapter.
|
|
3
|
+
*
|
|
4
|
+
* Implements HookAdapter for VS Code Copilot's JSON stdin/stdout hook paradigm.
|
|
5
|
+
*
|
|
6
|
+
* VS Code Copilot hook specifics:
|
|
7
|
+
* - I/O: JSON on stdin, JSON on stdout (same paradigm as Claude Code)
|
|
8
|
+
* - Hook names: PreToolUse, PostToolUse, PreCompact, SessionStart (PascalCase)
|
|
9
|
+
* - Additional hooks: Stop, SubagentStart, SubagentStop (unique to VS Code)
|
|
10
|
+
* - Arg modification: `updatedInput` in hookSpecificOutput wrapper (NOT flat)
|
|
11
|
+
* - Blocking: `permissionDecision: "deny"` (same as Claude Code)
|
|
12
|
+
* - Output modification: `additionalContext` in hookSpecificOutput,
|
|
13
|
+
* `decision: "block"` + reason
|
|
14
|
+
* - Tool input fields: tool_name, tool_input (snake_case, same as Claude Code)
|
|
15
|
+
* - But tool input PROPERTY names are camelCase (filePath not file_path)
|
|
16
|
+
* - Session ID: sessionId (camelCase, NOT session_id)
|
|
17
|
+
* - MCP tool prefix: f1e_ (not mcp__server__tool)
|
|
18
|
+
* - CRITICAL: matchers are parsed but IGNORED (all hooks fire on all tools)
|
|
19
|
+
* - Config: .github/hooks/*.json (primary), also reads .claude/settings.json
|
|
20
|
+
* - Env detection: VSCODE_PID, TERM_PROGRAM=vscode
|
|
21
|
+
* - Session dir: ~/.vscode/context-mode/sessions/ (fallback)
|
|
22
|
+
* - Preview status — API may change
|
|
23
|
+
*/
|
|
24
|
+
import { createHash } from "node:crypto";
|
|
25
|
+
import { readFileSync, writeFileSync, mkdirSync, copyFileSync, accessSync, existsSync, chmodSync, constants, } from "node:fs";
|
|
26
|
+
import { resolve, join } from "node:path";
|
|
27
|
+
import { homedir } from "node:os";
|
|
28
|
+
// ─────────────────────────────────────────────────────────
|
|
29
|
+
// Hook constants (re-exported from hooks.ts)
|
|
30
|
+
// ─────────────────────────────────────────────────────────
|
|
31
|
+
import { HOOK_TYPES as VSCODE_HOOK_NAMES, HOOK_SCRIPTS as VSCODE_HOOK_SCRIPTS, } from "./hooks.js";
|
|
32
|
+
// ─────────────────────────────────────────────────────────
|
|
33
|
+
// Adapter implementation
|
|
34
|
+
// ─────────────────────────────────────────────────────────
|
|
35
|
+
export class VSCodeCopilotAdapter {
|
|
36
|
+
name = "VS Code Copilot";
|
|
37
|
+
paradigm = "json-stdio";
|
|
38
|
+
capabilities = {
|
|
39
|
+
preToolUse: true,
|
|
40
|
+
postToolUse: true,
|
|
41
|
+
preCompact: true,
|
|
42
|
+
sessionStart: true,
|
|
43
|
+
canModifyArgs: true,
|
|
44
|
+
canModifyOutput: true,
|
|
45
|
+
canInjectSessionContext: true,
|
|
46
|
+
};
|
|
47
|
+
// ── Input parsing ──────────────────────────────────────
|
|
48
|
+
parsePreToolUseInput(raw) {
|
|
49
|
+
const input = raw;
|
|
50
|
+
return {
|
|
51
|
+
toolName: input.tool_name ?? "",
|
|
52
|
+
toolInput: input.tool_input ?? {},
|
|
53
|
+
sessionId: this.extractSessionId(input),
|
|
54
|
+
projectDir: process.env.CLAUDE_PROJECT_DIR || process.cwd(),
|
|
55
|
+
raw,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
parsePostToolUseInput(raw) {
|
|
59
|
+
const input = raw;
|
|
60
|
+
return {
|
|
61
|
+
toolName: input.tool_name ?? "",
|
|
62
|
+
toolInput: input.tool_input ?? {},
|
|
63
|
+
toolOutput: input.tool_output,
|
|
64
|
+
isError: input.is_error,
|
|
65
|
+
sessionId: this.extractSessionId(input),
|
|
66
|
+
projectDir: process.env.CLAUDE_PROJECT_DIR || process.cwd(),
|
|
67
|
+
raw,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
parsePreCompactInput(raw) {
|
|
71
|
+
const input = raw;
|
|
72
|
+
return {
|
|
73
|
+
sessionId: this.extractSessionId(input),
|
|
74
|
+
projectDir: process.env.CLAUDE_PROJECT_DIR || process.cwd(),
|
|
75
|
+
raw,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
parseSessionStartInput(raw) {
|
|
79
|
+
const input = raw;
|
|
80
|
+
const rawSource = input.source ?? "startup";
|
|
81
|
+
let source;
|
|
82
|
+
switch (rawSource) {
|
|
83
|
+
case "compact":
|
|
84
|
+
source = "compact";
|
|
85
|
+
break;
|
|
86
|
+
case "resume":
|
|
87
|
+
source = "resume";
|
|
88
|
+
break;
|
|
89
|
+
case "clear":
|
|
90
|
+
source = "clear";
|
|
91
|
+
break;
|
|
92
|
+
default:
|
|
93
|
+
source = "startup";
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
sessionId: this.extractSessionId(input),
|
|
97
|
+
source,
|
|
98
|
+
projectDir: process.env.CLAUDE_PROJECT_DIR || process.cwd(),
|
|
99
|
+
raw,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// ── Response formatting ────────────────────────────────
|
|
103
|
+
formatPreToolUseResponse(response) {
|
|
104
|
+
if (response.decision === "deny") {
|
|
105
|
+
return {
|
|
106
|
+
permissionDecision: "deny",
|
|
107
|
+
reason: response.reason ?? "Blocked by context-mode hook",
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (response.decision === "modify" && response.updatedInput) {
|
|
111
|
+
// VS Code Copilot: updatedInput is wrapped in hookSpecificOutput
|
|
112
|
+
return {
|
|
113
|
+
hookSpecificOutput: {
|
|
114
|
+
hookEventName: VSCODE_HOOK_NAMES.PRE_TOOL_USE,
|
|
115
|
+
updatedInput: response.updatedInput,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (response.decision === "context" && response.additionalContext) {
|
|
120
|
+
// VS Code Copilot: inject additionalContext via hookSpecificOutput
|
|
121
|
+
return {
|
|
122
|
+
hookSpecificOutput: {
|
|
123
|
+
hookEventName: VSCODE_HOOK_NAMES.PRE_TOOL_USE,
|
|
124
|
+
additionalContext: response.additionalContext,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (response.decision === "ask") {
|
|
129
|
+
// VS Code Copilot: use deny to force user attention (no native "ask")
|
|
130
|
+
return {
|
|
131
|
+
permissionDecision: "deny",
|
|
132
|
+
reason: response.reason ?? "Action requires user confirmation (security policy)",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// "allow" — return undefined for passthrough
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
formatPostToolUseResponse(response) {
|
|
139
|
+
if (response.updatedOutput) {
|
|
140
|
+
// VS Code Copilot: decision "block" + reason for output replacement
|
|
141
|
+
return {
|
|
142
|
+
hookSpecificOutput: {
|
|
143
|
+
hookEventName: VSCODE_HOOK_NAMES.POST_TOOL_USE,
|
|
144
|
+
decision: "block",
|
|
145
|
+
reason: response.updatedOutput,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (response.additionalContext) {
|
|
150
|
+
return {
|
|
151
|
+
hookSpecificOutput: {
|
|
152
|
+
hookEventName: VSCODE_HOOK_NAMES.POST_TOOL_USE,
|
|
153
|
+
additionalContext: response.additionalContext,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
formatPreCompactResponse(response) {
|
|
160
|
+
// VS Code Copilot: stdout content on exit 0 is injected as context
|
|
161
|
+
return response.context ?? "";
|
|
162
|
+
}
|
|
163
|
+
formatSessionStartResponse(response) {
|
|
164
|
+
// VS Code Copilot: stdout content is injected as additional context
|
|
165
|
+
return response.context ?? "";
|
|
166
|
+
}
|
|
167
|
+
// ── Configuration ──────────────────────────────────────
|
|
168
|
+
getSettingsPath() {
|
|
169
|
+
// VS Code Copilot primarily uses .github/hooks/*.json
|
|
170
|
+
// but also reads .claude/settings.json
|
|
171
|
+
return resolve(".github", "hooks", "context-mode.json");
|
|
172
|
+
}
|
|
173
|
+
getSessionDir() {
|
|
174
|
+
// Prefer .github/context-mode/sessions/ if .github exists,
|
|
175
|
+
// otherwise fall back to ~/.vscode/context-mode/sessions/
|
|
176
|
+
const githubDir = resolve(".github", "context-mode", "sessions");
|
|
177
|
+
const fallbackDir = join(homedir(), ".vscode", "context-mode", "sessions");
|
|
178
|
+
const dir = existsSync(resolve(".github")) ? githubDir : fallbackDir;
|
|
179
|
+
mkdirSync(dir, { recursive: true });
|
|
180
|
+
return dir;
|
|
181
|
+
}
|
|
182
|
+
getSessionDBPath(projectDir) {
|
|
183
|
+
const hash = createHash("sha256")
|
|
184
|
+
.update(projectDir)
|
|
185
|
+
.digest("hex")
|
|
186
|
+
.slice(0, 16);
|
|
187
|
+
return join(this.getSessionDir(), `${hash}.db`);
|
|
188
|
+
}
|
|
189
|
+
getSessionEventsPath(projectDir) {
|
|
190
|
+
const hash = createHash("sha256")
|
|
191
|
+
.update(projectDir)
|
|
192
|
+
.digest("hex")
|
|
193
|
+
.slice(0, 16);
|
|
194
|
+
return join(this.getSessionDir(), `${hash}-events.md`);
|
|
195
|
+
}
|
|
196
|
+
generateHookConfig(_pluginRoot) {
|
|
197
|
+
return {
|
|
198
|
+
[VSCODE_HOOK_NAMES.PRE_TOOL_USE]: [
|
|
199
|
+
{
|
|
200
|
+
matcher: "",
|
|
201
|
+
hooks: [
|
|
202
|
+
{
|
|
203
|
+
type: "command",
|
|
204
|
+
command: `context-mode hook vscode-copilot ${VSCODE_HOOK_NAMES.PRE_TOOL_USE.toLowerCase()}`,
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
[VSCODE_HOOK_NAMES.POST_TOOL_USE]: [
|
|
210
|
+
{
|
|
211
|
+
matcher: "",
|
|
212
|
+
hooks: [
|
|
213
|
+
{
|
|
214
|
+
type: "command",
|
|
215
|
+
command: `context-mode hook vscode-copilot ${VSCODE_HOOK_NAMES.POST_TOOL_USE.toLowerCase()}`,
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
[VSCODE_HOOK_NAMES.PRE_COMPACT]: [
|
|
221
|
+
{
|
|
222
|
+
matcher: "",
|
|
223
|
+
hooks: [
|
|
224
|
+
{
|
|
225
|
+
type: "command",
|
|
226
|
+
command: `context-mode hook vscode-copilot ${VSCODE_HOOK_NAMES.PRE_COMPACT.toLowerCase()}`,
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
[VSCODE_HOOK_NAMES.SESSION_START]: [
|
|
232
|
+
{
|
|
233
|
+
matcher: "",
|
|
234
|
+
hooks: [
|
|
235
|
+
{
|
|
236
|
+
type: "command",
|
|
237
|
+
command: `context-mode hook vscode-copilot ${VSCODE_HOOK_NAMES.SESSION_START.toLowerCase()}`,
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
readSettings() {
|
|
245
|
+
// Try .github/hooks/context-mode.json first, then .claude/settings.json
|
|
246
|
+
const paths = [
|
|
247
|
+
this.getSettingsPath(),
|
|
248
|
+
resolve(".claude", "settings.json"),
|
|
249
|
+
];
|
|
250
|
+
for (const configPath of paths) {
|
|
251
|
+
try {
|
|
252
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
253
|
+
return JSON.parse(raw);
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
writeSettings(settings) {
|
|
262
|
+
const configPath = this.getSettingsPath();
|
|
263
|
+
const dir = resolve(".github", "hooks");
|
|
264
|
+
mkdirSync(dir, { recursive: true });
|
|
265
|
+
writeFileSync(configPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
266
|
+
}
|
|
267
|
+
// ── Diagnostics (doctor) ─────────────────────────────────
|
|
268
|
+
validateHooks(pluginRoot) {
|
|
269
|
+
const results = [];
|
|
270
|
+
// Check .github/hooks/ directory for hook JSON files
|
|
271
|
+
const hooksDir = resolve(".github", "hooks");
|
|
272
|
+
try {
|
|
273
|
+
accessSync(hooksDir, constants.R_OK);
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
results.push({
|
|
277
|
+
check: "Hooks directory",
|
|
278
|
+
status: "fail",
|
|
279
|
+
message: ".github/hooks/ directory not found",
|
|
280
|
+
fix: "context-mode upgrade",
|
|
281
|
+
});
|
|
282
|
+
return results;
|
|
283
|
+
}
|
|
284
|
+
// Check for context-mode hook config
|
|
285
|
+
const hookConfigPath = resolve(hooksDir, "context-mode.json");
|
|
286
|
+
try {
|
|
287
|
+
const raw = readFileSync(hookConfigPath, "utf-8");
|
|
288
|
+
const config = JSON.parse(raw);
|
|
289
|
+
const hooks = config.hooks;
|
|
290
|
+
// Check PreToolUse
|
|
291
|
+
if (hooks?.[VSCODE_HOOK_NAMES.PRE_TOOL_USE]) {
|
|
292
|
+
results.push({
|
|
293
|
+
check: "PreToolUse hook",
|
|
294
|
+
status: "pass",
|
|
295
|
+
message: "PreToolUse hook configured in context-mode.json",
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
results.push({
|
|
300
|
+
check: "PreToolUse hook",
|
|
301
|
+
status: "fail",
|
|
302
|
+
message: "PreToolUse not found in context-mode.json",
|
|
303
|
+
fix: "context-mode upgrade",
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
// Check SessionStart
|
|
307
|
+
if (hooks?.[VSCODE_HOOK_NAMES.SESSION_START]) {
|
|
308
|
+
results.push({
|
|
309
|
+
check: "SessionStart hook",
|
|
310
|
+
status: "pass",
|
|
311
|
+
message: "SessionStart hook configured in context-mode.json",
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
results.push({
|
|
316
|
+
check: "SessionStart hook",
|
|
317
|
+
status: "fail",
|
|
318
|
+
message: "SessionStart not found in context-mode.json",
|
|
319
|
+
fix: "context-mode upgrade",
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
results.push({
|
|
325
|
+
check: "Hook configuration",
|
|
326
|
+
status: "fail",
|
|
327
|
+
message: "Could not read .github/hooks/context-mode.json",
|
|
328
|
+
fix: "context-mode upgrade",
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
// Warn about preview status
|
|
332
|
+
results.push({
|
|
333
|
+
check: "API stability",
|
|
334
|
+
status: "warn",
|
|
335
|
+
message: "VS Code Copilot hooks are in preview — API may change without notice",
|
|
336
|
+
});
|
|
337
|
+
// Warn about matcher behavior
|
|
338
|
+
results.push({
|
|
339
|
+
check: "Matcher support",
|
|
340
|
+
status: "warn",
|
|
341
|
+
message: "Matchers are parsed but IGNORED — all hooks fire on all tools",
|
|
342
|
+
});
|
|
343
|
+
return results;
|
|
344
|
+
}
|
|
345
|
+
checkPluginRegistration() {
|
|
346
|
+
// Check MCP config in .vscode/mcp.json
|
|
347
|
+
try {
|
|
348
|
+
const mcpConfigPath = resolve(".vscode", "mcp.json");
|
|
349
|
+
const raw = readFileSync(mcpConfigPath, "utf-8");
|
|
350
|
+
const config = JSON.parse(raw);
|
|
351
|
+
const servers = config.servers;
|
|
352
|
+
if (servers) {
|
|
353
|
+
const hasPlugin = Object.keys(servers).some((k) => k.includes("context-mode"));
|
|
354
|
+
if (hasPlugin) {
|
|
355
|
+
return {
|
|
356
|
+
check: "MCP registration",
|
|
357
|
+
status: "pass",
|
|
358
|
+
message: "context-mode found in .vscode/mcp.json",
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
check: "MCP registration",
|
|
364
|
+
status: "fail",
|
|
365
|
+
message: "context-mode not found in .vscode/mcp.json",
|
|
366
|
+
fix: "Add context-mode server to .vscode/mcp.json",
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
return {
|
|
371
|
+
check: "MCP registration",
|
|
372
|
+
status: "warn",
|
|
373
|
+
message: "Could not read .vscode/mcp.json",
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
getInstalledVersion() {
|
|
378
|
+
// Check VS Code extensions for context-mode
|
|
379
|
+
const extensionDirs = [
|
|
380
|
+
join(homedir(), ".vscode", "extensions"),
|
|
381
|
+
join(homedir(), ".vscode-insiders", "extensions"),
|
|
382
|
+
];
|
|
383
|
+
for (const extDir of extensionDirs) {
|
|
384
|
+
try {
|
|
385
|
+
const entries = readFileSync(join(extDir, "extensions.json"), "utf-8");
|
|
386
|
+
const exts = JSON.parse(entries);
|
|
387
|
+
const contextMode = exts.find((e) => typeof e.identifier === "object" &&
|
|
388
|
+
e.identifier !== null &&
|
|
389
|
+
e.identifier.id?.toString().includes("context-mode"));
|
|
390
|
+
if (contextMode && typeof contextMode.version === "string") {
|
|
391
|
+
return contextMode.version;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return "not installed";
|
|
399
|
+
}
|
|
400
|
+
// ── Upgrade ────────────────────────────────────────────
|
|
401
|
+
configureAllHooks(_pluginRoot) {
|
|
402
|
+
const changes = [];
|
|
403
|
+
const hookConfig = { hooks: {} };
|
|
404
|
+
const hooks = hookConfig.hooks;
|
|
405
|
+
const hookTypes = [
|
|
406
|
+
VSCODE_HOOK_NAMES.PRE_TOOL_USE,
|
|
407
|
+
VSCODE_HOOK_NAMES.POST_TOOL_USE,
|
|
408
|
+
VSCODE_HOOK_NAMES.PRE_COMPACT,
|
|
409
|
+
VSCODE_HOOK_NAMES.SESSION_START,
|
|
410
|
+
];
|
|
411
|
+
for (const hookType of hookTypes) {
|
|
412
|
+
const script = VSCODE_HOOK_SCRIPTS[hookType];
|
|
413
|
+
if (!script)
|
|
414
|
+
continue;
|
|
415
|
+
hooks[hookType] = [
|
|
416
|
+
{
|
|
417
|
+
matcher: "",
|
|
418
|
+
hooks: [
|
|
419
|
+
{
|
|
420
|
+
type: "command",
|
|
421
|
+
command: `context-mode hook vscode-copilot ${hookType.toLowerCase()}`,
|
|
422
|
+
},
|
|
423
|
+
],
|
|
424
|
+
},
|
|
425
|
+
];
|
|
426
|
+
changes.push(`Configured ${hookType} hook`);
|
|
427
|
+
}
|
|
428
|
+
// Write to .github/hooks/context-mode.json
|
|
429
|
+
const outputDir = resolve(".github", "hooks");
|
|
430
|
+
mkdirSync(outputDir, { recursive: true });
|
|
431
|
+
const outputPath = resolve(outputDir, "context-mode.json");
|
|
432
|
+
writeFileSync(outputPath, JSON.stringify(hookConfig, null, 2) + "\n", "utf-8");
|
|
433
|
+
changes.push(`Wrote hook config to ${outputPath}`);
|
|
434
|
+
return changes;
|
|
435
|
+
}
|
|
436
|
+
backupSettings() {
|
|
437
|
+
const settingsPath = this.getSettingsPath();
|
|
438
|
+
try {
|
|
439
|
+
accessSync(settingsPath, constants.R_OK);
|
|
440
|
+
const backupPath = settingsPath + ".bak";
|
|
441
|
+
copyFileSync(settingsPath, backupPath);
|
|
442
|
+
return backupPath;
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
setHookPermissions(pluginRoot) {
|
|
449
|
+
const set = [];
|
|
450
|
+
const hooksDir = join(pluginRoot, "hooks", "vscode-copilot");
|
|
451
|
+
for (const scriptName of Object.values(VSCODE_HOOK_SCRIPTS)) {
|
|
452
|
+
const scriptPath = resolve(hooksDir, scriptName);
|
|
453
|
+
try {
|
|
454
|
+
accessSync(scriptPath, constants.R_OK);
|
|
455
|
+
chmodSync(scriptPath, 0o755);
|
|
456
|
+
set.push(scriptPath);
|
|
457
|
+
}
|
|
458
|
+
catch {
|
|
459
|
+
/* skip missing scripts */
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return set;
|
|
463
|
+
}
|
|
464
|
+
updatePluginRegistry(_pluginRoot, _version) {
|
|
465
|
+
// VS Code manages extensions through its own marketplace/extension system.
|
|
466
|
+
// No manual registry update needed.
|
|
467
|
+
}
|
|
468
|
+
// ── Routing Instructions (soft enforcement) ────────────
|
|
469
|
+
getRoutingInstructionsConfig() {
|
|
470
|
+
return {
|
|
471
|
+
fileName: "copilot-instructions.md",
|
|
472
|
+
globalPath: "", // VS Code Copilot uses org-level, not global file
|
|
473
|
+
projectRelativePath: join(".github", "copilot-instructions.md"),
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
writeRoutingInstructions(projectDir, pluginRoot) {
|
|
477
|
+
const config = this.getRoutingInstructionsConfig();
|
|
478
|
+
const targetPath = resolve(projectDir, config.projectRelativePath);
|
|
479
|
+
const sourcePath = resolve(pluginRoot, "configs", "vscode-copilot", config.fileName);
|
|
480
|
+
try {
|
|
481
|
+
const content = readFileSync(sourcePath, "utf-8");
|
|
482
|
+
// Ensure .github directory exists
|
|
483
|
+
mkdirSync(resolve(projectDir, ".github"), { recursive: true });
|
|
484
|
+
try {
|
|
485
|
+
const existing = readFileSync(targetPath, "utf-8");
|
|
486
|
+
if (existing.includes("context-mode"))
|
|
487
|
+
return null;
|
|
488
|
+
writeFileSync(targetPath, existing.trimEnd() + "\n\n" + content, "utf-8");
|
|
489
|
+
return targetPath;
|
|
490
|
+
}
|
|
491
|
+
catch {
|
|
492
|
+
writeFileSync(targetPath, content, "utf-8");
|
|
493
|
+
return targetPath;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
// ── Internal helpers ───────────────────────────────────
|
|
501
|
+
/**
|
|
502
|
+
* Extract session ID from VS Code Copilot hook input.
|
|
503
|
+
* VS Code Copilot uses camelCase sessionId (NOT session_id).
|
|
504
|
+
*/
|
|
505
|
+
extractSessionId(input) {
|
|
506
|
+
if (input.sessionId)
|
|
507
|
+
return input.sessionId;
|
|
508
|
+
if (process.env.VSCODE_PID)
|
|
509
|
+
return `vscode-${process.env.VSCODE_PID}`;
|
|
510
|
+
return `pid-${process.ppid}`;
|
|
511
|
+
}
|
|
512
|
+
}
|
package/build/cli.d.ts
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
* context-mode CLI
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* context-mode
|
|
7
|
-
* context-mode
|
|
8
|
-
* context-mode
|
|
9
|
-
* context-mode
|
|
10
|
-
*
|
|
6
|
+
* context-mode → Start MCP server (stdio)
|
|
7
|
+
* context-mode doctor → Diagnose runtime issues, hooks, FTS5, version
|
|
8
|
+
* context-mode upgrade → Fix hooks, permissions, and settings
|
|
9
|
+
* context-mode hook <platform> <event> → Dispatch a hook script (used by platform hook configs)
|
|
10
|
+
*
|
|
11
|
+
* Platform auto-detection: CLI detects which platform is running
|
|
12
|
+
* (Claude Code, Gemini CLI, OpenCode, etc.) and uses the appropriate adapter.
|
|
11
13
|
*/
|
|
12
|
-
|
|
14
|
+
/** Normalize Windows backslash paths to forward slashes for Bash (MSYS2) compatibility. */
|
|
15
|
+
export declare function toUnixPath(p: string): string;
|