context-mode 1.0.98 → 1.0.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/README.md +9 -7
- package/build/adapters/claude-code-base.js +4 -4
- package/build/adapters/codex/index.js +23 -1
- package/build/adapters/qwen-code/index.d.ts +1 -1
- package/build/adapters/qwen-code/index.js +110 -4
- package/build/cli.js +2 -0
- package/build/opencode-plugin.js +1 -1
- package/build/pi-extension.js +1 -1
- package/build/search/auto-memory.d.ts +29 -0
- package/build/search/auto-memory.js +121 -0
- package/build/search/unified.d.ts +41 -0
- package/build/search/unified.js +89 -0
- package/build/server.js +88 -40
- package/build/session/analytics.js +1 -1
- package/build/session/db.d.ts +17 -0
- package/build/session/db.js +28 -0
- package/build/session/extract.d.ts +4 -0
- package/build/session/extract.js +232 -1
- package/build/session/snapshot.js +31 -0
- package/build/store.js +118 -8
- package/build/types.d.ts +1 -0
- package/cli.bundle.mjs +260 -125
- package/configs/claude-code/CLAUDE.md +21 -1
- package/configs/codex/AGENTS.md +23 -2
- package/configs/codex/hooks.json +14 -0
- package/configs/cursor/context-mode.mdc +18 -1
- package/configs/gemini-cli/GEMINI.md +22 -1
- package/configs/jetbrains-copilot/copilot-instructions.md +22 -1
- package/configs/kilo/AGENTS.md +19 -2
- package/configs/kiro/KIRO.md +18 -1
- package/configs/openclaw/AGENTS.md +22 -2
- package/configs/opencode/AGENTS.md +18 -1
- package/configs/pi/AGENTS.md +18 -1
- package/configs/qwen-code/QWEN.md +38 -18
- package/configs/vscode-copilot/copilot-instructions.md +22 -1
- package/hooks/auto-injection.mjs +76 -0
- package/hooks/codex/stop.mjs +43 -0
- package/hooks/codex/userpromptsubmit.mjs +75 -0
- package/hooks/core/mcp-ready.mjs +7 -1
- package/hooks/posttooluse.mjs +50 -1
- package/hooks/precompact.mjs +9 -0
- package/hooks/pretooluse.mjs +27 -0
- package/hooks/routing-block.mjs +7 -1
- package/hooks/session-db.bundle.mjs +19 -13
- package/hooks/session-extract.bundle.mjs +2 -2
- package/hooks/session-snapshot.bundle.mjs +18 -17
- package/hooks/sessionstart.mjs +17 -0
- package/hooks/userpromptsubmit.mjs +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server.bundle.mjs +228 -93
- package/skills/context-mode-ops/agent-teams.md +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.100"
|
|
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.100",
|
|
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.100",
|
|
4
4
|
"description": "MCP server that saves 98% of your context window with session continuity. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and automatic state restore across compactions.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mert Koseoğlu",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "Context Mode",
|
|
4
4
|
"kind": "tool",
|
|
5
5
|
"description": "OpenClaw plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
|
|
6
|
-
"version": "1.0.
|
|
6
|
+
"version": "1.0.100",
|
|
7
7
|
"sandbox": {
|
|
8
8
|
"mode": "permissive",
|
|
9
9
|
"filesystem_access": "full",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.100",
|
|
4
4
|
"description": "OpenClaw plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mert Koseoğlu",
|
package/README.md
CHANGED
|
@@ -523,12 +523,14 @@ Full documentation: [`docs/adapters/openclaw.md`](docs/adapters/openclaw.md)
|
|
|
523
523
|
"hooks": {
|
|
524
524
|
"PreToolUse": [{ "matcher": "local_shell|shell|shell_command|exec_command|container.exec|Bash|Shell|grep_files|mcp__plugin_context-mode_context-mode__ctx_execute|mcp__plugin_context-mode_context-mode__ctx_execute_file|mcp__plugin_context-mode_context-mode__ctx_batch_execute", "hooks": [{ "type": "command", "command": "context-mode hook codex pretooluse" }] }],
|
|
525
525
|
"PostToolUse": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex posttooluse" }] }],
|
|
526
|
-
"SessionStart": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex sessionstart" }] }]
|
|
526
|
+
"SessionStart": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex sessionstart" }] }],
|
|
527
|
+
"UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex userpromptsubmit" }] }],
|
|
528
|
+
"Stop": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex stop" }] }]
|
|
527
529
|
}
|
|
528
530
|
}
|
|
529
531
|
```
|
|
530
532
|
|
|
531
|
-
`PreToolUse` enforces deny/block routing today and is prepared for input rewrites once Codex supports them. `PostToolUse` captures session events. `SessionStart` restores state after compaction.
|
|
533
|
+
`PreToolUse` enforces deny/block routing today and is prepared for input rewrites once Codex supports them. `PostToolUse` captures session events. `SessionStart` restores state after compaction. `UserPromptSubmit` captures user decisions and corrections. `Stop` records turn-end state.
|
|
532
534
|
|
|
533
535
|
> **Note:** Codex PreToolUse routing currently supports deny rules only (blocks dangerous commands). It still needs upstream `updatedInput` support before context-mode can rewrite tool input; track [openai/codex#18491](https://github.com/openai/codex/issues/18491). Context injection (`additionalContext`) is not supported in Codex PreToolUse — it works via PostToolUse and SessionStart instead. This is handled automatically.
|
|
534
536
|
|
|
@@ -912,12 +914,12 @@ Session continuity requires 4 hooks working together:
|
|
|
912
914
|
|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
|
913
915
|
| **PreToolUse** | Enforces sandbox routing before tool execution | Yes | -- | -- | -- | Yes | -- | -- | -- | Yes | -- | Yes | -- | ✓ (via tool_call event) |
|
|
914
916
|
| **PostToolUse** | Captures events after each tool call | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | -- | Yes | -- | ✓ (via tool_result event) |
|
|
915
|
-
| **UserPromptSubmit** | Captures user decisions and corrections | Yes | -- | -- | -- | -- | -- | -- | -- |
|
|
917
|
+
| **UserPromptSubmit** | Captures user decisions and corrections | Yes | -- | -- | -- | -- | -- | -- | -- | Yes | -- | -- | -- | -- |
|
|
916
918
|
| **PreCompact** | Builds snapshot before compaction | Yes | Yes | Yes | Yes | -- | Plugin | Plugin | Plugin | -- | -- | -- | -- | ✓ (via session_before_compact) |
|
|
917
919
|
| **SessionStart** | Restores state after compaction or resume | Yes | Yes | Yes | Yes | -- | -- | -- | Plugin | Yes | -- | -- | -- | ✓ (via session_start event) |
|
|
918
920
|
| | **Session completeness** | **Full** | **High** | **High** | **High** | **Partial** | **High** | **High** | **High** | **Partial** | **--** | **Partial** | **--** | **High** |
|
|
919
921
|
|
|
920
|
-
> **Note:** Full session continuity (capture + snapshot + restore) works on **Claude Code**, **Gemini CLI**, **VS Code Copilot**, and **JetBrains Copilot**. **OpenCode** provides **high** session continuity: it captures tool events and injects compaction snapshots via the plugin, but SessionStart is not yet available ([#14808](https://github.com/sst/opencode/issues/14808)), so startup/resume restore is not supported. **KiloCode** shares the same plugin architecture as OpenCode via the OpenCodeAdapter, so its continuity level depends on KiloCode's SessionStart support. **Cursor** captures tool events via `preToolUse`/`postToolUse`, but `sessionStart` is currently rejected by Cursor's validator ([forum report](https://forum.cursor.com/t/unknown-hook-type-sessionstart/149566)), so session restore after compaction is not available yet. **OpenClaw** uses native gateway plugin hooks (`api.on()`) for full session continuity. **Pi Coding Agent** provides high session continuity via extension hooks (`tool_call`, `tool_result`, `session_start`, `session_before_compact`). **Codex CLI** provides partial hook-based session tracking through PreToolUse, PostToolUse, and
|
|
922
|
+
> **Note:** Full session continuity (capture + snapshot + restore) works on **Claude Code**, **Gemini CLI**, **VS Code Copilot**, and **JetBrains Copilot**. **OpenCode** provides **high** session continuity: it captures tool events and injects compaction snapshots via the plugin, but SessionStart is not yet available ([#14808](https://github.com/sst/opencode/issues/14808)), so startup/resume restore is not supported. **KiloCode** shares the same plugin architecture as OpenCode via the OpenCodeAdapter, so its continuity level depends on KiloCode's SessionStart support. **Cursor** captures tool events via `preToolUse`/`postToolUse`, but `sessionStart` is currently rejected by Cursor's validator ([forum report](https://forum.cursor.com/t/unknown-hook-type-sessionstart/149566)), so session restore after compaction is not available yet. **OpenClaw** uses native gateway plugin hooks (`api.on()`) for full session continuity. **Pi Coding Agent** provides high session continuity via extension hooks (`tool_call`, `tool_result`, `session_start`, `session_before_compact`). **Codex CLI** provides partial hook-based session tracking through PreToolUse, PostToolUse, SessionStart, UserPromptSubmit, and Stop; MCP tools work. **Antigravity**, **Kiro**, and **Zed** have no hook support in the current release, so session tracking is not available.
|
|
921
923
|
|
|
922
924
|
<details>
|
|
923
925
|
<summary><strong>What gets captured</strong></summary>
|
|
@@ -979,7 +981,7 @@ After compaction, the model receives a **Session Guide** — a structured narrat
|
|
|
979
981
|
- **Session Intent** — mode classification (implement, investigate, review, discuss)
|
|
980
982
|
- **User Role** — behavioral directives set during the session
|
|
981
983
|
|
|
982
|
-
Detailed event data is also indexed into FTS5 for on-demand retrieval via `
|
|
984
|
+
Detailed event data is also indexed into FTS5 for on-demand retrieval via `ctx_search()`.
|
|
983
985
|
|
|
984
986
|
</details>
|
|
985
987
|
|
|
@@ -1002,7 +1004,7 @@ Detailed event data is also indexed into FTS5 for on-demand retrieval via `searc
|
|
|
1002
1004
|
|
|
1003
1005
|
**OpenClaw / Pi Agent** — High coverage. All tool lifecycle hooks (`after_tool_call`, `before_compaction`, `session_start`) fire via the native gateway plugin. User decisions aren't captured but file edits, git ops, errors, and tasks are fully tracked. Falls back to DB snapshot reconstruction if compaction hooks fail on older gateway versions. See [`docs/adapters/openclaw.md`](docs/adapters/openclaw.md).
|
|
1004
1006
|
|
|
1005
|
-
**Codex CLI** — MCP active, hooks stable. Hook scripts (PreToolUse, PostToolUse, SessionStart) are implemented and tested. PreToolUse deny routing works; input rewriting still depends on upstream `updatedInput` support ([openai/codex#18491](https://github.com/openai/codex/issues/18491)).
|
|
1007
|
+
**Codex CLI** — MCP active, hooks stable. Hook scripts (PreToolUse, PostToolUse, SessionStart, UserPromptSubmit, Stop) are implemented and tested. PreToolUse deny routing works; input rewriting still depends on upstream `updatedInput` support ([openai/codex#18491](https://github.com/openai/codex/issues/18491)).
|
|
1006
1008
|
|
|
1007
1009
|
**Antigravity** — No session support. No hooks, no event capture. Requires manually copying `GEMINI.md` to your project root. Auto-detected via MCP protocol handshake (`clientInfo.name`).
|
|
1008
1010
|
|
|
@@ -1035,7 +1037,7 @@ Detailed event data is also indexed into FTS5 for on-demand retrieval via `searc
|
|
|
1035
1037
|
>
|
|
1036
1038
|
> **OpenClaw** runs context-mode as a native gateway plugin targeting Pi Agent sessions. Hooks register via `api.on()` (tool/lifecycle) and `api.registerHook()` (commands). All tool interception and compaction hooks are supported. See [`docs/adapters/openclaw.md`](docs/adapters/openclaw.md).
|
|
1037
1039
|
>
|
|
1038
|
-
> **Codex CLI** hooks are stable. MCP tools work, and hook scripts activate through `~/.codex/hooks.json`. PreToolUse supports `permissionDecision: "deny"` only; input modification still needs upstream `updatedInput` support ([openai/codex#18491](https://github.com/openai/codex/issues/18491)). `additionalContext` is not supported in PreToolUse (context injection works via PostToolUse and SessionStart instead; the codex formatter handles this automatically). See the Codex install section for setup. **Antigravity** and **Zed** do not support hooks. They rely solely on manually-copied routing instruction files (`AGENTS.md` / `GEMINI.md`) for enforcement (~60% compliance). See each platform's install section for copy instructions. Antigravity and Zed are auto-detected via MCP protocol handshake — no manual platform configuration needed.
|
|
1040
|
+
> **Codex CLI** hooks are stable. MCP tools work, and hook scripts activate through `~/.codex/hooks.json`. PreToolUse supports `permissionDecision: "deny"` only; input modification still needs upstream `updatedInput` support ([openai/codex#18491](https://github.com/openai/codex/issues/18491)). `additionalContext` is not supported in PreToolUse (context injection works via PostToolUse and SessionStart instead; the codex formatter handles this automatically). UserPromptSubmit and Stop capture prompt and turn-end continuity events. See the Codex install section for setup. **Antigravity** and **Zed** do not support hooks. They rely solely on manually-copied routing instruction files (`AGENTS.md` / `GEMINI.md`) for enforcement (~60% compliance). See each platform's install section for copy instructions. Antigravity and Zed are auto-detected via MCP protocol handshake — no manual platform configuration needed.
|
|
1039
1041
|
>
|
|
1040
1042
|
> **Kiro** supports native `preToolUse` and `postToolUse` hooks for routing enforcement and tool event capture. `agentSpawn` (SessionStart equivalent) and `stop` are not yet wired. Requires manually copying `KIRO.md` to your project root. Kiro is auto-detected via MCP protocol handshake (`clientInfo.name`).
|
|
1041
1043
|
>
|
|
@@ -26,7 +26,7 @@ export class ClaudeCodeBaseAdapter extends BaseAdapter {
|
|
|
26
26
|
toolName: input.tool_name ?? "",
|
|
27
27
|
toolInput: input.tool_input ?? {},
|
|
28
28
|
sessionId: this.extractSessionId(input),
|
|
29
|
-
projectDir: process.env[this.projectDirEnvVar],
|
|
29
|
+
projectDir: process.env[this.projectDirEnvVar] ?? process.cwd(),
|
|
30
30
|
raw,
|
|
31
31
|
};
|
|
32
32
|
}
|
|
@@ -38,7 +38,7 @@ export class ClaudeCodeBaseAdapter extends BaseAdapter {
|
|
|
38
38
|
toolOutput: input.tool_output,
|
|
39
39
|
isError: input.is_error,
|
|
40
40
|
sessionId: this.extractSessionId(input),
|
|
41
|
-
projectDir: process.env[this.projectDirEnvVar],
|
|
41
|
+
projectDir: process.env[this.projectDirEnvVar] ?? process.cwd(),
|
|
42
42
|
raw,
|
|
43
43
|
};
|
|
44
44
|
}
|
|
@@ -46,7 +46,7 @@ export class ClaudeCodeBaseAdapter extends BaseAdapter {
|
|
|
46
46
|
const input = raw;
|
|
47
47
|
return {
|
|
48
48
|
sessionId: this.extractSessionId(input),
|
|
49
|
-
projectDir: process.env[this.projectDirEnvVar],
|
|
49
|
+
projectDir: process.env[this.projectDirEnvVar] ?? process.cwd(),
|
|
50
50
|
raw,
|
|
51
51
|
};
|
|
52
52
|
}
|
|
@@ -70,7 +70,7 @@ export class ClaudeCodeBaseAdapter extends BaseAdapter {
|
|
|
70
70
|
return {
|
|
71
71
|
sessionId: this.extractSessionId(input),
|
|
72
72
|
source,
|
|
73
|
-
projectDir: process.env[this.projectDirEnvVar],
|
|
73
|
+
projectDir: process.env[this.projectDirEnvVar] ?? process.cwd(),
|
|
74
74
|
raw,
|
|
75
75
|
};
|
|
76
76
|
}
|
|
@@ -182,6 +182,28 @@ export class CodexAdapter extends BaseAdapter {
|
|
|
182
182
|
],
|
|
183
183
|
},
|
|
184
184
|
],
|
|
185
|
+
UserPromptSubmit: [
|
|
186
|
+
{
|
|
187
|
+
matcher: "",
|
|
188
|
+
hooks: [
|
|
189
|
+
{
|
|
190
|
+
type: "command",
|
|
191
|
+
command: `node ${pluginRoot}/hooks/codex/userpromptsubmit.mjs`,
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
Stop: [
|
|
197
|
+
{
|
|
198
|
+
matcher: "",
|
|
199
|
+
hooks: [
|
|
200
|
+
{
|
|
201
|
+
type: "command",
|
|
202
|
+
command: `node ${pluginRoot}/hooks/codex/stop.mjs`,
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
],
|
|
185
207
|
};
|
|
186
208
|
}
|
|
187
209
|
readSettings() {
|
|
@@ -208,7 +230,7 @@ export class CodexAdapter extends BaseAdapter {
|
|
|
208
230
|
{
|
|
209
231
|
check: "Hook support",
|
|
210
232
|
status: "pass",
|
|
211
|
-
message: "Codex CLI hooks are stable. Configure ~/.codex/hooks.json for PreToolUse, PostToolUse, and
|
|
233
|
+
message: "Codex CLI hooks are stable. Configure ~/.codex/hooks.json for PreToolUse, PostToolUse, SessionStart, UserPromptSubmit, and Stop.",
|
|
212
234
|
},
|
|
213
235
|
];
|
|
214
236
|
}
|
|
@@ -27,7 +27,7 @@ export declare class QwenCodeAdapter extends ClaudeCodeBaseAdapter implements Ho
|
|
|
27
27
|
validateHooks(_pluginRoot: string): DiagnosticResult[];
|
|
28
28
|
checkPluginRegistration(): DiagnosticResult;
|
|
29
29
|
getInstalledVersion(): string;
|
|
30
|
-
configureAllHooks(
|
|
30
|
+
configureAllHooks(pluginRoot: string): string[];
|
|
31
31
|
setHookPermissions(_pluginRoot: string): string[];
|
|
32
32
|
updatePluginRegistry(_pluginRoot: string, _version: string): void;
|
|
33
33
|
getRoutingInstructionsConfig(): {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - MCP clientInfo: qwen-cli-mcp-client-* (pattern)
|
|
13
13
|
* - 12 hook events (superset of Claude's 5, but context-mode uses the shared 5)
|
|
14
14
|
*/
|
|
15
|
-
import { readFileSync, } from "node:fs";
|
|
15
|
+
import { readFileSync, existsSync, } from "node:fs";
|
|
16
16
|
import { resolve, join } from "node:path";
|
|
17
17
|
import { homedir } from "node:os";
|
|
18
18
|
import { ClaudeCodeBaseAdapter } from "../claude-code-base.js";
|
|
@@ -63,7 +63,7 @@ export class QwenCodeAdapter extends ClaudeCodeBaseAdapter {
|
|
|
63
63
|
],
|
|
64
64
|
PostToolUse: [
|
|
65
65
|
{
|
|
66
|
-
matcher: "",
|
|
66
|
+
matcher: "run_shell_command|read_file|write_file|edit|glob|grep_search|todo_write|agent|ask_user_question|mcp__",
|
|
67
67
|
hooks: [
|
|
68
68
|
{ type: "command", command: `node ${pluginRoot}/hooks/posttooluse.mjs` },
|
|
69
69
|
],
|
|
@@ -162,10 +162,116 @@ export class QwenCodeAdapter extends ClaudeCodeBaseAdapter {
|
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
getInstalledVersion() {
|
|
165
|
+
const settings = this.readSettings();
|
|
166
|
+
if (!settings)
|
|
167
|
+
return "not installed";
|
|
168
|
+
const hooks = settings.hooks;
|
|
169
|
+
if (!hooks)
|
|
170
|
+
return "not installed";
|
|
171
|
+
// Check if any hook type has context-mode scripts configured
|
|
172
|
+
const contextModeScripts = [
|
|
173
|
+
"pretooluse.mjs",
|
|
174
|
+
"posttooluse.mjs",
|
|
175
|
+
"precompact.mjs",
|
|
176
|
+
"sessionstart.mjs",
|
|
177
|
+
"userpromptsubmit.mjs",
|
|
178
|
+
];
|
|
179
|
+
for (const [, entries] of Object.entries(hooks)) {
|
|
180
|
+
if (!Array.isArray(entries))
|
|
181
|
+
continue;
|
|
182
|
+
for (const entry of entries) {
|
|
183
|
+
const e = entry;
|
|
184
|
+
if (e.hooks?.some((h) => h.command && contextModeScripts.some((s) => h.command.includes(s)))) {
|
|
185
|
+
return "installed (hooks configured)";
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
165
189
|
return "not installed";
|
|
166
190
|
}
|
|
167
|
-
configureAllHooks(
|
|
168
|
-
|
|
191
|
+
configureAllHooks(pluginRoot) {
|
|
192
|
+
const settings = this.readSettings() ?? {};
|
|
193
|
+
const hooks = (settings.hooks ?? {});
|
|
194
|
+
const changes = [];
|
|
195
|
+
// ── Phase 1: Clean stale context-mode hooks ──────────
|
|
196
|
+
// After an upgrade, settings.json may contain hardcoded paths
|
|
197
|
+
// pointing to deleted version directories. Remove those.
|
|
198
|
+
for (const hookType of Object.keys(hooks)) {
|
|
199
|
+
const entries = hooks[hookType];
|
|
200
|
+
if (!Array.isArray(entries))
|
|
201
|
+
continue;
|
|
202
|
+
const filtered = entries.filter((entry) => {
|
|
203
|
+
const e = entry;
|
|
204
|
+
const commands = e.hooks ?? [];
|
|
205
|
+
// Preserve entries that are not context-mode hooks
|
|
206
|
+
const isContextMode = commands.some((h) => h.command && /context-mode|pretooluse|posttooluse|precompact|sessionstart|userpromptsubmit/i.test(h.command));
|
|
207
|
+
if (!isContextMode)
|
|
208
|
+
return true;
|
|
209
|
+
// For context-mode hooks, check if referenced script files exist
|
|
210
|
+
return commands.every((h) => {
|
|
211
|
+
if (!h.command)
|
|
212
|
+
return true;
|
|
213
|
+
// Extract path from "node /path/to/script.mjs" format
|
|
214
|
+
const match = h.command.match(/node\s+"?([^"]+\.mjs)"?/);
|
|
215
|
+
if (!match)
|
|
216
|
+
return true; // CLI dispatcher format, always valid
|
|
217
|
+
return existsSync(match[1]);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
const removed = entries.length - filtered.length;
|
|
221
|
+
if (removed > 0) {
|
|
222
|
+
hooks[hookType] = filtered;
|
|
223
|
+
changes.push(`Removed ${removed} stale ${hookType} hook(s)`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// ── Phase 2: Register fresh hooks ────────────────────
|
|
227
|
+
const hookTypes = [
|
|
228
|
+
{
|
|
229
|
+
name: "PreToolUse",
|
|
230
|
+
script: "pretooluse.mjs",
|
|
231
|
+
matcher: [
|
|
232
|
+
"run_shell_command", "read_file", "read_many_files", "grep_search",
|
|
233
|
+
"web_fetch", "agent",
|
|
234
|
+
"mcp__plugin_context-mode_context-mode__ctx_execute",
|
|
235
|
+
"mcp__plugin_context-mode_context-mode__ctx_execute_file",
|
|
236
|
+
"mcp__plugin_context-mode_context-mode__ctx_batch_execute",
|
|
237
|
+
].join("|"),
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: "SessionStart",
|
|
241
|
+
script: "sessionstart.mjs",
|
|
242
|
+
matcher: "",
|
|
243
|
+
},
|
|
244
|
+
];
|
|
245
|
+
for (const { name, script, matcher } of hookTypes) {
|
|
246
|
+
const entry = {
|
|
247
|
+
matcher,
|
|
248
|
+
hooks: [{ type: "command", command: `node ${pluginRoot}/hooks/${script}` }],
|
|
249
|
+
};
|
|
250
|
+
const existing = hooks[name];
|
|
251
|
+
if (existing && Array.isArray(existing)) {
|
|
252
|
+
// Replace existing context-mode entry or append
|
|
253
|
+
const idx = existing.findIndex((e) => {
|
|
254
|
+
const typed = e;
|
|
255
|
+
return typed.hooks?.some((h) => h.command?.includes(script)) ?? false;
|
|
256
|
+
});
|
|
257
|
+
if (idx >= 0) {
|
|
258
|
+
existing[idx] = entry;
|
|
259
|
+
changes.push(`Updated ${name} hook`);
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
existing.push(entry);
|
|
263
|
+
changes.push(`Added ${name} hook`);
|
|
264
|
+
}
|
|
265
|
+
hooks[name] = existing;
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
hooks[name] = [entry];
|
|
269
|
+
changes.push(`Created ${name} hooks`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
settings.hooks = hooks;
|
|
273
|
+
this.writeSettings(settings);
|
|
274
|
+
return changes;
|
|
169
275
|
}
|
|
170
276
|
setHookPermissions(_pluginRoot) {
|
|
171
277
|
return [];
|
package/build/cli.js
CHANGED
|
@@ -55,6 +55,8 @@ const HOOK_MAP = {
|
|
|
55
55
|
pretooluse: "hooks/codex/pretooluse.mjs",
|
|
56
56
|
posttooluse: "hooks/codex/posttooluse.mjs",
|
|
57
57
|
sessionstart: "hooks/codex/sessionstart.mjs",
|
|
58
|
+
userpromptsubmit: "hooks/codex/userpromptsubmit.mjs",
|
|
59
|
+
stop: "hooks/codex/stop.mjs",
|
|
58
60
|
},
|
|
59
61
|
"kiro": {
|
|
60
62
|
pretooluse: "hooks/kiro/pretooluse.mjs",
|
package/build/opencode-plugin.js
CHANGED
|
@@ -27,7 +27,7 @@ import { buildResumeSnapshot } from "./session/snapshot.js";
|
|
|
27
27
|
import { OpenCodeAdapter } from "./adapters/opencode/index.js";
|
|
28
28
|
// ── Helpers ───────────────────────────────────────────────
|
|
29
29
|
function getPlatform() {
|
|
30
|
-
return process.env.
|
|
30
|
+
return process.env.KILO_PID ? "kilo" : "opencode";
|
|
31
31
|
}
|
|
32
32
|
// ── Plugin Factory ────────────────────────────────────────
|
|
33
33
|
/**
|
package/build/pi-extension.js
CHANGED
|
@@ -242,7 +242,7 @@ export default function piExtension(pi) {
|
|
|
242
242
|
// Mark resume as consumed so it is not re-injected
|
|
243
243
|
db.markResumeConsumed(_sessionId);
|
|
244
244
|
// Build memory context from recent high-priority events
|
|
245
|
-
const allEvents = db.getEvents(_sessionId, { minPriority:
|
|
245
|
+
const allEvents = db.getEvents(_sessionId, { minPriority: 3, limit: 50 });
|
|
246
246
|
let memoryContext = "";
|
|
247
247
|
if (allEvents.length > 0) {
|
|
248
248
|
const memoryLines = ["<active_memory>"];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-memory search — searches CLAUDE.md and MEMORY.md files for
|
|
3
|
+
* persisted decisions, preferences, and context from prior sessions.
|
|
4
|
+
*
|
|
5
|
+
* Returns results in a format compatible with the unified search pipeline.
|
|
6
|
+
*/
|
|
7
|
+
export interface AutoMemoryResult {
|
|
8
|
+
title: string;
|
|
9
|
+
content: string;
|
|
10
|
+
source: string;
|
|
11
|
+
origin: "auto-memory";
|
|
12
|
+
timestamp?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Search auto-memory files (CLAUDE.md, MEMORY.md, user identity files)
|
|
16
|
+
* for content matching any of the given queries.
|
|
17
|
+
*
|
|
18
|
+
* Scans:
|
|
19
|
+
* 1. Project-level: <projectDir>/CLAUDE.md
|
|
20
|
+
* 2. User-level: <configDir>/CLAUDE.md
|
|
21
|
+
* 3. User memory: <configDir>/memory/*.md
|
|
22
|
+
*
|
|
23
|
+
* @param queries Array of search terms
|
|
24
|
+
* @param limit Max results to return
|
|
25
|
+
* @param projectDir Project directory path
|
|
26
|
+
* @param configDir Config directory (e.g. ~/.claude)
|
|
27
|
+
* @returns Matching auto-memory results
|
|
28
|
+
*/
|
|
29
|
+
export declare function searchAutoMemory(queries: string[], limit?: number, projectDir?: string, configDir?: string): AutoMemoryResult[];
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-memory search — searches CLAUDE.md and MEMORY.md files for
|
|
3
|
+
* persisted decisions, preferences, and context from prior sessions.
|
|
4
|
+
*
|
|
5
|
+
* Returns results in a format compatible with the unified search pipeline.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
const DEBUG = process.env.DEBUG?.includes("context-mode");
|
|
11
|
+
/**
|
|
12
|
+
* Search auto-memory files (CLAUDE.md, MEMORY.md, user identity files)
|
|
13
|
+
* for content matching any of the given queries.
|
|
14
|
+
*
|
|
15
|
+
* Scans:
|
|
16
|
+
* 1. Project-level: <projectDir>/CLAUDE.md
|
|
17
|
+
* 2. User-level: <configDir>/CLAUDE.md
|
|
18
|
+
* 3. User memory: <configDir>/memory/*.md
|
|
19
|
+
*
|
|
20
|
+
* @param queries Array of search terms
|
|
21
|
+
* @param limit Max results to return
|
|
22
|
+
* @param projectDir Project directory path
|
|
23
|
+
* @param configDir Config directory (e.g. ~/.claude)
|
|
24
|
+
* @returns Matching auto-memory results
|
|
25
|
+
*/
|
|
26
|
+
export function searchAutoMemory(queries, limit = 5, projectDir, configDir) {
|
|
27
|
+
const results = [];
|
|
28
|
+
const effectiveConfigDir = configDir || join(homedir(), ".claude");
|
|
29
|
+
// Collect candidate files
|
|
30
|
+
const candidates = [];
|
|
31
|
+
// 1. Project-level CLAUDE.md
|
|
32
|
+
if (projectDir) {
|
|
33
|
+
const projectClaude = join(projectDir, "CLAUDE.md");
|
|
34
|
+
if (existsSync(projectClaude)) {
|
|
35
|
+
candidates.push({ path: projectClaude, label: "project/CLAUDE.md" });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// 2. User-level CLAUDE.md
|
|
39
|
+
const userClaude = join(effectiveConfigDir, "CLAUDE.md");
|
|
40
|
+
if (existsSync(userClaude)) {
|
|
41
|
+
candidates.push({ path: userClaude, label: "user/CLAUDE.md" });
|
|
42
|
+
}
|
|
43
|
+
// 3. User memory directory
|
|
44
|
+
const memoryDir = join(effectiveConfigDir, "memory");
|
|
45
|
+
if (existsSync(memoryDir)) {
|
|
46
|
+
try {
|
|
47
|
+
const files = readdirSync(memoryDir).filter(f => f.endsWith(".md"));
|
|
48
|
+
for (const file of files) {
|
|
49
|
+
candidates.push({
|
|
50
|
+
path: join(memoryDir, file),
|
|
51
|
+
label: `memory/${file}`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
if (DEBUG)
|
|
57
|
+
process.stderr.write(`[ctx] auto-memory dir scan failed: ${e}\n`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Search each candidate file for matching queries
|
|
61
|
+
for (const candidate of candidates) {
|
|
62
|
+
if (results.length >= limit)
|
|
63
|
+
break;
|
|
64
|
+
try {
|
|
65
|
+
// Skip files larger than 1MB to avoid memory issues
|
|
66
|
+
try {
|
|
67
|
+
if (statSync(candidate.path).size > 1_000_000)
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const content = readFileSync(candidate.path, "utf-8");
|
|
74
|
+
const contentLower = content.toLowerCase();
|
|
75
|
+
for (const query of queries) {
|
|
76
|
+
if (results.length >= limit)
|
|
77
|
+
break;
|
|
78
|
+
const queryLower = query.toLowerCase();
|
|
79
|
+
// Split query into terms, match if any term is found
|
|
80
|
+
const terms = queryLower.split(/\s+/).filter(t => t.length >= 3);
|
|
81
|
+
const matched = terms.some(term => {
|
|
82
|
+
try {
|
|
83
|
+
return new RegExp(`\\b${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, "i").test(content);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return contentLower.includes(term); // fallback for invalid regex
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
if (matched) {
|
|
90
|
+
// Extract a relevant section around the first match
|
|
91
|
+
const firstTermIdx = terms.reduce((best, term) => {
|
|
92
|
+
const idx = contentLower.indexOf(term);
|
|
93
|
+
return idx >= 0 && (best < 0 || idx < best) ? idx : best;
|
|
94
|
+
}, -1);
|
|
95
|
+
let start = Math.max(0, firstTermIdx - 200);
|
|
96
|
+
let end = Math.min(content.length, firstTermIdx + 500);
|
|
97
|
+
const prevBlank = content.lastIndexOf("\n\n", start);
|
|
98
|
+
const nextBlank = content.indexOf("\n\n", end);
|
|
99
|
+
if (prevBlank >= 0)
|
|
100
|
+
start = prevBlank + 2;
|
|
101
|
+
if (nextBlank >= 0)
|
|
102
|
+
end = nextBlank;
|
|
103
|
+
const snippet = content.slice(start, end).trim();
|
|
104
|
+
results.push({
|
|
105
|
+
title: `[auto-memory] ${candidate.label}`,
|
|
106
|
+
content: snippet,
|
|
107
|
+
source: candidate.label,
|
|
108
|
+
origin: "auto-memory",
|
|
109
|
+
timestamp: statSync(candidate.path).mtime.toISOString(),
|
|
110
|
+
});
|
|
111
|
+
break; // one result per file per query batch
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
if (DEBUG)
|
|
117
|
+
process.stderr.write(`[ctx] auto-memory file read failed: ${e}\n`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return results.slice(0, limit);
|
|
121
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified multi-source search — merges ContentStore, SessionDB, and
|
|
3
|
+
* auto-memory results into a single ranked or chronological result set.
|
|
4
|
+
*
|
|
5
|
+
* Used by ctx_search when sort="timeline" to search across all sources,
|
|
6
|
+
* or sort="relevance" (default) for ContentStore-only BM25 search.
|
|
7
|
+
*/
|
|
8
|
+
import type { ContentStore } from "../store.js";
|
|
9
|
+
import type { SessionDB } from "../session/db.js";
|
|
10
|
+
export interface UnifiedSearchResult {
|
|
11
|
+
title: string;
|
|
12
|
+
content: string;
|
|
13
|
+
source: string;
|
|
14
|
+
origin: "current-session" | "prior-session" | "auto-memory";
|
|
15
|
+
timestamp?: string;
|
|
16
|
+
rank?: number;
|
|
17
|
+
matchLayer?: string;
|
|
18
|
+
highlighted?: string;
|
|
19
|
+
contentType?: "code" | "prose";
|
|
20
|
+
}
|
|
21
|
+
export interface SearchAllSourcesOpts {
|
|
22
|
+
query: string;
|
|
23
|
+
limit: number;
|
|
24
|
+
store: ContentStore;
|
|
25
|
+
sort?: "relevance" | "timeline";
|
|
26
|
+
source?: string;
|
|
27
|
+
contentType?: "code" | "prose";
|
|
28
|
+
sessionDB?: SessionDB | null;
|
|
29
|
+
projectDir?: string;
|
|
30
|
+
configDir?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Search across all available sources.
|
|
34
|
+
*
|
|
35
|
+
* - sort="relevance" (default): BM25-ranked results from ContentStore only.
|
|
36
|
+
* - sort="timeline": chronological merge of ContentStore + SessionDB + auto-memory.
|
|
37
|
+
*
|
|
38
|
+
* Errors in any single source are caught and logged — partial results
|
|
39
|
+
* are always returned.
|
|
40
|
+
*/
|
|
41
|
+
export declare function searchAllSources(opts: SearchAllSourcesOpts): UnifiedSearchResult[];
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified multi-source search — merges ContentStore, SessionDB, and
|
|
3
|
+
* auto-memory results into a single ranked or chronological result set.
|
|
4
|
+
*
|
|
5
|
+
* Used by ctx_search when sort="timeline" to search across all sources,
|
|
6
|
+
* or sort="relevance" (default) for ContentStore-only BM25 search.
|
|
7
|
+
*/
|
|
8
|
+
import { searchAutoMemory } from "./auto-memory.js";
|
|
9
|
+
const DEBUG = process.env.DEBUG?.includes("context-mode");
|
|
10
|
+
// ─────────────────────────────────────────────────────────
|
|
11
|
+
// Implementation
|
|
12
|
+
// ─────────────────────────────────────────────────────────
|
|
13
|
+
/**
|
|
14
|
+
* Search across all available sources.
|
|
15
|
+
*
|
|
16
|
+
* - sort="relevance" (default): BM25-ranked results from ContentStore only.
|
|
17
|
+
* - sort="timeline": chronological merge of ContentStore + SessionDB + auto-memory.
|
|
18
|
+
*
|
|
19
|
+
* Errors in any single source are caught and logged — partial results
|
|
20
|
+
* are always returned.
|
|
21
|
+
*/
|
|
22
|
+
export function searchAllSources(opts) {
|
|
23
|
+
const { query, limit, store, sort = "relevance", source, contentType, sessionDB, projectDir, configDir, } = opts;
|
|
24
|
+
const results = [];
|
|
25
|
+
// Capture session start time once — used as proxy for ContentStore items
|
|
26
|
+
// (we don't know exact indexing time, but all content is from current session)
|
|
27
|
+
const sessionStartTime = new Date().toISOString();
|
|
28
|
+
// ── Source 1: ContentStore (always, both modes) ──
|
|
29
|
+
try {
|
|
30
|
+
const storeResults = store.searchWithFallback(query, limit, source, contentType);
|
|
31
|
+
results.push(...storeResults.map((r) => ({
|
|
32
|
+
title: r.title,
|
|
33
|
+
content: r.content,
|
|
34
|
+
source: r.source,
|
|
35
|
+
origin: "current-session",
|
|
36
|
+
timestamp: r.timestamp || sessionStartTime,
|
|
37
|
+
rank: r.rank,
|
|
38
|
+
matchLayer: r.matchLayer,
|
|
39
|
+
highlighted: r.highlighted,
|
|
40
|
+
contentType: r.contentType,
|
|
41
|
+
})));
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
if (DEBUG)
|
|
45
|
+
process.stderr.write(`[ctx] ContentStore search failed: ${e}\n`);
|
|
46
|
+
}
|
|
47
|
+
// ── Sources 2+3: timeline mode only ──
|
|
48
|
+
if (sort === "timeline") {
|
|
49
|
+
// Source 2: SessionDB — prior session events
|
|
50
|
+
try {
|
|
51
|
+
if (sessionDB) {
|
|
52
|
+
const dbResults = sessionDB.searchEvents(query, limit, projectDir || "", source);
|
|
53
|
+
results.push(...dbResults.map((r) => ({
|
|
54
|
+
title: `[${r.category}] ${r.type}`,
|
|
55
|
+
content: r.data,
|
|
56
|
+
source: "prior-session",
|
|
57
|
+
origin: "prior-session",
|
|
58
|
+
timestamp: r.created_at,
|
|
59
|
+
})));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
if (DEBUG)
|
|
64
|
+
process.stderr.write(`[ctx] SessionDB search failed: ${e}\n`);
|
|
65
|
+
}
|
|
66
|
+
// Source 3: Auto-memory
|
|
67
|
+
try {
|
|
68
|
+
const memResults = searchAutoMemory([query], limit, projectDir, configDir);
|
|
69
|
+
results.push(...memResults);
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
if (DEBUG)
|
|
73
|
+
process.stderr.write(`[ctx] auto-memory search failed: ${e}\n`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// ── Normalize timestamps for consistent sorting ──
|
|
77
|
+
// SQLite datetime('now') → "YYYY-MM-DD HH:MM:SS" (no T, no Z)
|
|
78
|
+
// ISO → "YYYY-MM-DDTHH:MM:SS.sssZ"
|
|
79
|
+
for (const r of results) {
|
|
80
|
+
if (r.timestamp && !r.timestamp.includes("T")) {
|
|
81
|
+
r.timestamp = r.timestamp.replace(" ", "T") + "Z";
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// ── Sort ──
|
|
85
|
+
if (sort === "timeline") {
|
|
86
|
+
results.sort((a, b) => (a.timestamp || "").localeCompare(b.timestamp || ""));
|
|
87
|
+
}
|
|
88
|
+
return results.slice(0, limit);
|
|
89
|
+
}
|