context-mode 1.0.107 → 1.0.109
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 +22 -18
- package/build/adapters/claude-code/index.js +26 -9
- package/build/adapters/opencode/index.js +5 -5
- package/build/cli.js +92 -12
- package/build/server.js +45 -3
- package/build/session/analytics.d.ts +7 -0
- package/build/session/analytics.js +75 -15
- package/build/session/db.d.ts +3 -1
- package/build/session/persist-tool-calls.d.ts +54 -0
- package/build/session/persist-tool-calls.js +105 -0
- package/build/session/project-attribution.d.ts +1 -1
- package/cli.bundle.mjs +123 -122
- package/hooks/ensure-deps.mjs +28 -12
- package/hooks/posttooluse.mjs +90 -80
- package/hooks/precompact.mjs +56 -46
- package/hooks/pretooluse.mjs +161 -167
- package/hooks/routing-block.mjs +2 -2
- package/hooks/run-hook.mjs +82 -0
- package/hooks/session-db.bundle.mjs +2 -2
- package/hooks/sessionstart.mjs +187 -155
- package/hooks/userpromptsubmit.mjs +69 -58
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/scripts/heal-better-sqlite3.mjs +108 -0
- package/scripts/postinstall.mjs +27 -0
- package/server.bundle.mjs +88 -88
- package/skills/UPSTREAM-CREDITS.md +51 -0
- package/skills/context-mode-ops/SKILL.md +147 -0
- package/skills/diagnose/SKILL.md +122 -0
- package/skills/diagnose/scripts/hitl-loop.template.sh +41 -0
- package/skills/grill-me/SKILL.md +15 -0
- package/skills/grill-with-docs/ADR-FORMAT.md +47 -0
- package/skills/grill-with-docs/CONTEXT-FORMAT.md +77 -0
- package/skills/grill-with-docs/SKILL.md +93 -0
- package/skills/improve-codebase-architecture/DEEPENING.md +37 -0
- package/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +44 -0
- package/skills/improve-codebase-architecture/LANGUAGE.md +53 -0
- package/skills/improve-codebase-architecture/SKILL.md +76 -0
- package/skills/tdd/SKILL.md +114 -0
- package/skills/tdd/deep-modules.md +33 -0
- package/skills/tdd/interface-design.md +31 -0
- package/skills/tdd/mocking.md +59 -0
- package/skills/tdd/refactoring.md +10 -0
- package/skills/tdd/tests.md +61 -0
|
@@ -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.109"
|
|
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.109",
|
|
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.109",
|
|
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.109",
|
|
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.109",
|
|
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
|
@@ -404,7 +404,7 @@ Full configs: [`configs/cursor/hooks.json`](configs/cursor/hooks.json) | [`confi
|
|
|
404
404
|
|
|
405
405
|
The `mcp` entry registers all 11 MCP tools. The `plugin` entry enables hooks — OpenCode calls the plugin's TypeScript functions directly before and after each tool execution, blocking dangerous commands and enforcing sandbox routing.
|
|
406
406
|
|
|
407
|
-
3. *(Optional)* Copy the routing rules file.
|
|
407
|
+
3. *(Optional)* Copy the routing rules file. The model needs an `AGENTS.md` file for routing awareness:
|
|
408
408
|
|
|
409
409
|
```bash
|
|
410
410
|
cp node_modules/context-mode/configs/opencode/AGENTS.md AGENTS.md
|
|
@@ -416,9 +416,9 @@ Full configs: [`configs/cursor/hooks.json`](configs/cursor/hooks.json) | [`confi
|
|
|
416
416
|
|
|
417
417
|
**Verify:** In the OpenCode session, type `ctx stats`. Context-mode tools should appear and respond.
|
|
418
418
|
|
|
419
|
-
**Routing:** Hooks enforce routing programmatically via `tool.execute.before` and `tool.execute.after`. The optional [`AGENTS.md`](configs/opencode/AGENTS.md) file provides routing instructions for model awareness. The `experimental.session.compacting` hook builds resume snapshots when the conversation compacts.
|
|
419
|
+
**Routing:** Hooks enforce routing programmatically via `tool.execute.before` and `tool.execute.after`. The optional [`AGENTS.md`](configs/opencode/AGENTS.md) file provides routing instructions for model awareness. The `experimental.session.compacting` hook builds resume snapshots when the conversation compacts. The `experimental.chat.system.transform` hook injects the routing block and prior-session snapshots at session start, enabling session continuity across restarts. The `chat.message` hook captures user prompts and decisions (UserPromptSubmit equivalent).
|
|
420
420
|
|
|
421
|
-
> **Note:** OpenCode
|
|
421
|
+
> **Note:** OpenCode lacks a real SessionStart hook ([#14808](https://github.com/sst/opencode/issues/14808), [#5409](https://github.com/sst/opencode/issues/5409)). The plugin uses `experimental.chat.system.transform` as a surrogate — it injects both the routing block and resume snapshots into the system prompt. User-prompt capture uses `chat.message` instead of the missing UserPromptSubmit hook. AGENTS.md/CLAUDE.md/CONTEXT.md rules are captured automatically on first hook fire per project.
|
|
422
422
|
|
|
423
423
|
Full configs: [`configs/opencode/opencode.json`](configs/opencode/opencode.json) | [`configs/opencode/AGENTS.md`](configs/opencode/AGENTS.md)
|
|
424
424
|
|
|
@@ -454,7 +454,7 @@ Full configs: [`configs/opencode/opencode.json`](configs/opencode/opencode.json)
|
|
|
454
454
|
|
|
455
455
|
The `mcp` entry registers all 11 MCP tools. The `plugin` entry enables hooks — KiloCode calls the plugin's TypeScript functions directly before and after each tool execution, blocking dangerous commands and enforcing sandbox routing.
|
|
456
456
|
|
|
457
|
-
3. *(Optional)* Copy the routing rules file. KiloCode shares the OpenCode plugin architecture
|
|
457
|
+
3. *(Optional)* Copy the routing rules file. KiloCode shares the OpenCode plugin architecture, so the model needs an `AGENTS.md` file for routing awareness:
|
|
458
458
|
|
|
459
459
|
```bash
|
|
460
460
|
cp node_modules/context-mode/configs/opencode/AGENTS.md AGENTS.md
|
|
@@ -464,9 +464,9 @@ Full configs: [`configs/opencode/opencode.json`](configs/opencode/opencode.json)
|
|
|
464
464
|
|
|
465
465
|
**Verify:** In the KiloCode session, type `ctx stats`. Context-mode tools should appear and respond.
|
|
466
466
|
|
|
467
|
-
**Routing:** Hooks enforce routing programmatically via `tool.execute.before` and `tool.execute.after`. The optional [`AGENTS.md`](configs/opencode/AGENTS.md) file provides routing instructions for model awareness. The `experimental.session.compacting` hook builds resume snapshots when the conversation compacts.
|
|
467
|
+
**Routing:** Hooks enforce routing programmatically via `tool.execute.before` and `tool.execute.after`. The optional [`AGENTS.md`](configs/opencode/AGENTS.md) file provides routing instructions for model awareness. The `experimental.session.compacting` hook builds resume snapshots when the conversation compacts. The `experimental.chat.system.transform` hook injects the routing block and prior-session snapshots at session start, enabling session continuity across restarts. The `chat.message` hook captures user prompts and decisions (UserPromptSubmit equivalent).
|
|
468
468
|
|
|
469
|
-
> **Note:** KiloCode shares the same plugin architecture as OpenCode, using the OpenCodeAdapter with platform-specific configuration paths (`kilo.json` instead of `opencode.json`, `~/.config/kilo/` instead of `~/.config/opencode/`). SessionStart hook
|
|
469
|
+
> **Note:** KiloCode shares the same plugin architecture as OpenCode, using the OpenCodeAdapter with platform-specific configuration paths (`kilo.json` instead of `opencode.json`, `~/.config/kilo/` instead of `~/.config/opencode/`). Like OpenCode, it lacks a real SessionStart hook — the plugin uses `experimental.chat.system.transform` as a surrogate. User-prompt capture uses `chat.message` instead of the missing UserPromptSubmit hook. AGENTS.md/CLAUDE.md/CONTEXT.md rules are captured automatically on first hook fire per project.
|
|
470
470
|
|
|
471
471
|
</details>
|
|
472
472
|
|
|
@@ -817,6 +817,8 @@ Context Mode uses [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) o
|
|
|
817
817
|
|
|
818
818
|
On older glibc systems (CentOS 7/8, RHEL 8, Debian 10), prebuilt binaries don't load and better-sqlite3 **automatically falls back to compiling from source** via `prebuild-install || node-gyp rebuild --release`. This requires a C++20 compiler (GCC 10+), Make, and Python with setuptools.
|
|
819
819
|
|
|
820
|
+
**Windows / missing binding self-heal:** if `better_sqlite3.node` ends up missing after install (e.g. `prebuild-install` not on cmd.exe PATH, no MSVC toolchain), the postinstall script and the runtime hook automatically re-fetch the prebuild and repair the binding — no manual `npm rebuild` needed (#408).
|
|
821
|
+
|
|
820
822
|
**CentOS 8 / RHEL 8** (glibc 2.28):
|
|
821
823
|
|
|
822
824
|
```bash
|
|
@@ -919,20 +921,22 @@ This means `--continue` sessions preserve indexed docs across restarts. No re-fe
|
|
|
919
921
|
|
|
920
922
|
When the context window fills up, the agent compacts the conversation — dropping older messages to make room. Without session tracking, the model forgets which files it was editing, what tasks are in progress, what errors were resolved, and what you last asked for.
|
|
921
923
|
|
|
922
|
-
Context Mode captures every meaningful event during your session and persists them in a per-project SQLite database. When the conversation compacts (or you resume with `--continue`), your working state is rebuilt automatically — the model continues from your last prompt without asking you to repeat anything.
|
|
924
|
+
Context Mode captures every meaningful event during your session and persists them in a per-project SQLite database. When the conversation compacts (or you resume with `--continue`, `--resume`, or `/resume`), your working state is rebuilt automatically — the model continues from your last prompt without asking you to repeat anything.
|
|
925
|
+
|
|
926
|
+
> Resuming a non-latest session via `/resume <picker>` works the same way: the SessionStart hook detects the empty live-event table for the freshly issued session id and falls back to the most recent unconsumed snapshot for the project (`session_resume` table). The picker selects the conversation; context-mode rehydrates the prior working state.
|
|
923
927
|
|
|
924
|
-
Session continuity requires
|
|
928
|
+
Session continuity requires 5 hooks working together:
|
|
925
929
|
|
|
926
930
|
| Hook | Role | Claude Code | Gemini CLI | VS Code Copilot | JetBrains Copilot | Cursor | OpenCode | KiloCode | OpenClaw | Codex CLI | Antigravity | Kiro | Zed | Pi |
|
|
927
931
|
|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
|
928
932
|
| **PreToolUse** | Enforces sandbox routing before tool execution | Yes | -- | -- | -- | Yes | -- | -- | -- | Yes | -- | Yes | -- | ✓ (via tool_call event) |
|
|
929
933
|
| **PostToolUse** | Captures events after each tool call | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | -- | Yes | -- | ✓ (via tool_result event) |
|
|
930
|
-
| **UserPromptSubmit** | Captures user decisions and corrections | Yes | -- | -- | -- | -- |
|
|
934
|
+
| **UserPromptSubmit** | Captures user decisions and corrections | Yes | -- | -- | -- | -- | Plugin (via chat.message) | Plugin (via chat.message) | -- | Yes | -- | -- | -- | -- |
|
|
931
935
|
| **PreCompact** | Builds snapshot before compaction | Yes | Yes | Yes | Yes | -- | Plugin | Plugin | Plugin | -- | -- | -- | -- | ✓ (via session_before_compact) |
|
|
932
|
-
| **SessionStart** | Restores state after compaction or resume | Yes | Yes | Yes | Yes | -- |
|
|
933
|
-
| | **Session completeness** | **Full** | **High** | **High** | **High** | **Partial** | **
|
|
936
|
+
| **SessionStart** | Restores state after compaction or resume | Yes | Yes | Yes | Yes | -- | ✓ (via experimental.chat.system.transform) | ✓ (via experimental.chat.system.transform) | Plugin | Yes | -- | -- | -- | ✓ (via session_start event) |
|
|
937
|
+
| | **Session completeness** | **Full** | **High** | **High** | **High** | **Partial** | **Full** | **Full** | **High** | **Partial** | **--** | **Partial** | **--** | **High** |
|
|
934
938
|
|
|
935
|
-
> **Note:** Full session continuity (capture + snapshot + restore) works on **Claude Code**, **Gemini CLI**, **VS Code Copilot**,
|
|
939
|
+
> **Note:** Full session continuity (capture + snapshot + restore) works on **Claude Code**, **Gemini CLI**, **VS Code Copilot**, **JetBrains Copilot**, **OpenCode**, and **KiloCode**. **OpenCode** and **KiloCode** use `experimental.chat.system.transform` as a SessionStart surrogate to inject the routing block and restore prior sessions, plus `chat.message` for user-prompt capture; full SessionStart hook support is not yet available ([#14808](https://github.com/sst/opencode/issues/14808), [#5409](https://github.com/sst/opencode/issues/5409)), but prior-session continuity and user-decision capture work fully. **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.
|
|
936
940
|
|
|
937
941
|
<details>
|
|
938
942
|
<summary><strong>What gets captured</strong></summary>
|
|
@@ -1015,7 +1019,7 @@ Detailed event data is also indexed into FTS5 for on-demand retrieval via `ctx_s
|
|
|
1015
1019
|
<details>
|
|
1016
1020
|
<summary><strong>Per-platform details</strong></summary>
|
|
1017
1021
|
|
|
1018
|
-
**Claude Code** — Full session support. All 5 hook types fire, capturing tool events, user decisions, building compaction snapshots, and restoring state after compaction or
|
|
1022
|
+
**Claude Code** — Full session support. All 5 hook types fire, capturing tool events, user decisions, building compaction snapshots, and restoring state after compaction, `--continue`, `--resume`, or `/resume`.
|
|
1019
1023
|
|
|
1020
1024
|
**Gemini CLI** — High coverage. PostToolUse (AfterTool), PreCompact (PreCompress), and SessionStart all fire. Missing UserPromptSubmit, so user decisions and corrections aren't captured — but file edits, git ops, errors, and tasks are fully tracked.
|
|
1021
1025
|
|
|
@@ -1025,9 +1029,9 @@ Detailed event data is also indexed into FTS5 for on-demand retrieval via `ctx_s
|
|
|
1025
1029
|
|
|
1026
1030
|
**Cursor** — Partial coverage. Native `preToolUse` and `postToolUse` hooks capture tool events. `sessionStart` is documented by Cursor but currently rejected by their validator, so session restore is not available. Routing instructions are delivered via MCP server startup instead.
|
|
1027
1031
|
|
|
1028
|
-
**OpenCode** —
|
|
1032
|
+
**OpenCode** — Full session support. The TypeScript plugin captures PostToolUse events via `tool.execute.after`, user prompts and decisions via `chat.message`, builds compaction snapshots via `experimental.session.compacting`, and restores prior sessions via `experimental.chat.system.transform` (SessionStart surrogate). Routing block is injected on first `chat.system.transform` per session. AGENTS.md/CLAUDE.md/CONTEXT.md rules are captured automatically on first hook fire.
|
|
1029
1033
|
|
|
1030
|
-
**KiloCode** —
|
|
1034
|
+
**KiloCode** — Full session support. Shares the same plugin architecture as OpenCode via the OpenCodeAdapter. The TypeScript plugin captures PostToolUse events via `tool.execute.after`, user prompts and decisions via `chat.message`, builds compaction snapshots via `experimental.session.compacting`, and restores prior sessions via `experimental.chat.system.transform` (SessionStart surrogate).
|
|
1031
1035
|
|
|
1032
1036
|
**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).
|
|
1033
1037
|
|
|
@@ -1050,7 +1054,7 @@ Detailed event data is also indexed into FTS5 for on-demand retrieval via `ctx_s
|
|
|
1050
1054
|
| MCP Server | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
|
1051
1055
|
| PreToolUse Hook | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | -- | Yes | -- | Yes (extension) |
|
|
1052
1056
|
| PostToolUse Hook | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | -- | Yes | -- | Yes (extension) |
|
|
1053
|
-
| SessionStart Hook | Yes | Yes | Yes | Yes | Yes | -- |
|
|
1057
|
+
| SessionStart Hook | Yes | Yes | Yes | Yes | Yes | -- | ✓ (via experimental.chat.system.transform) | ✓ (via experimental.chat.system.transform) | Plugin | Yes | -- | -- | -- | Yes (extension) |
|
|
1054
1058
|
| PreCompact Hook | Yes | Yes | Yes | Yes | Yes | -- | Plugin | Plugin | Plugin | -- | -- | -- | -- | Yes (extension) |
|
|
1055
1059
|
| Can Modify Args | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | -- | -- | -- | -- | Yes (extension) |
|
|
1056
1060
|
| Can Block Tools | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | -- | Yes | -- | Yes (extension) |
|
|
@@ -1058,9 +1062,9 @@ Detailed event data is also indexed into FTS5 for on-demand retrieval via `ctx_s
|
|
|
1058
1062
|
| Slash Commands | Yes | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
|
|
1059
1063
|
| Plugin Marketplace | Yes | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
|
|
1060
1064
|
|
|
1061
|
-
> **OpenCode** uses a TypeScript plugin paradigm — hooks run as in-process functions via `tool.execute.before`, `tool.execute.after`,
|
|
1065
|
+
> **OpenCode** uses a TypeScript plugin paradigm — hooks run as in-process functions via `tool.execute.before`, `tool.execute.after`, `experimental.session.compacting`, `experimental.chat.system.transform`, and `chat.message`, providing full routing enforcement, session continuity, and user-prompt capture. The `experimental.chat.system.transform` hook acts as a SessionStart surrogate to inject the routing block and restore prior sessions. The `chat.message` hook captures user prompts and decisions (UserPromptSubmit equivalent).
|
|
1062
1066
|
>
|
|
1063
|
-
> **KiloCode** shares the same TypeScript plugin architecture as OpenCode via the OpenCodeAdapter, with platform-specific configuration paths (`kilo.json` instead of `opencode.json`, `~/.config/kilo/` instead of `~/.config/opencode/`). Hook capabilities
|
|
1067
|
+
> **KiloCode** shares the same TypeScript plugin architecture as OpenCode via the OpenCodeAdapter, with platform-specific configuration paths (`kilo.json` instead of `opencode.json`, `~/.config/kilo/` instead of `~/.config/opencode/`). Hook capabilities match OpenCode, including SessionStart surrogate via `experimental.chat.system.transform` and user-prompt capture via `chat.message`.
|
|
1064
1068
|
>
|
|
1065
1069
|
> **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).
|
|
1066
1070
|
>
|
|
@@ -308,19 +308,36 @@ export class ClaudeCodeAdapter extends ClaudeCodeBaseAdapter {
|
|
|
308
308
|
if (pluginHooks) {
|
|
309
309
|
const allCovered = REQUIRED_HOOKS.every((ht) => this.checkHookType(undefined, pluginHooks, ht));
|
|
310
310
|
if (allCovered) {
|
|
311
|
-
//
|
|
312
|
-
// is the source of truth
|
|
313
|
-
//
|
|
314
|
-
//
|
|
311
|
+
// Strip ONLY the inner context-mode hook commands from each matcher entry —
|
|
312
|
+
// hooks.json is the source of truth for ctx-mode. User hooks co-located in
|
|
313
|
+
// the same matcher entry MUST be preserved (#415: entry-level filter wiped
|
|
314
|
+
// every co-located user hook). After stripping, prune entries whose `hooks`
|
|
315
|
+
// array becomes empty.
|
|
316
|
+
const ctxScriptNames = Object.values(HOOK_SCRIPTS);
|
|
317
|
+
const isCtxModeCommand = (cmd) => cmd != null &&
|
|
318
|
+
(ctxScriptNames.some((s) => cmd.includes(s)) ||
|
|
319
|
+
cmd.includes("context-mode hook"));
|
|
315
320
|
for (const hookType of Object.keys(hooks)) {
|
|
316
321
|
const entries = hooks[hookType];
|
|
317
322
|
if (!Array.isArray(entries))
|
|
318
323
|
continue;
|
|
319
|
-
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
hooks[
|
|
323
|
-
|
|
324
|
+
let totalRemoved = 0;
|
|
325
|
+
for (const entry of entries) {
|
|
326
|
+
const typedEntry = entry;
|
|
327
|
+
const innerHooks = typedEntry.hooks ?? [];
|
|
328
|
+
const before = innerHooks.length;
|
|
329
|
+
typedEntry.hooks = innerHooks.filter((h) => !isCtxModeCommand(h.command));
|
|
330
|
+
totalRemoved += before - typedEntry.hooks.length;
|
|
331
|
+
}
|
|
332
|
+
const pruned = entries.filter((e) => {
|
|
333
|
+
const ih = e.hooks;
|
|
334
|
+
return Array.isArray(ih) && ih.length > 0;
|
|
335
|
+
});
|
|
336
|
+
if (totalRemoved > 0 || pruned.length !== entries.length) {
|
|
337
|
+
hooks[hookType] = pruned;
|
|
338
|
+
if (totalRemoved > 0) {
|
|
339
|
+
changes.push(`Removed ${totalRemoved} duplicate ${hookType} hook(s) — covered by plugin hooks.json`);
|
|
340
|
+
}
|
|
324
341
|
}
|
|
325
342
|
}
|
|
326
343
|
settings.hooks = hooks;
|
|
@@ -40,10 +40,10 @@ export class OpenCodeAdapter extends BaseAdapter {
|
|
|
40
40
|
preToolUse: true,
|
|
41
41
|
postToolUse: true,
|
|
42
42
|
preCompact: true, // experimental
|
|
43
|
-
sessionStart:
|
|
43
|
+
sessionStart: true,
|
|
44
44
|
canModifyArgs: true,
|
|
45
45
|
canModifyOutput: true, // with TUI bug caveat for bash (#13575)
|
|
46
|
-
canInjectSessionContext:
|
|
46
|
+
canInjectSessionContext: true,
|
|
47
47
|
};
|
|
48
48
|
platform;
|
|
49
49
|
constructor(platform = "opencode") {
|
|
@@ -310,11 +310,11 @@ export class OpenCodeAdapter extends BaseAdapter {
|
|
|
310
310
|
fix: "context-mode upgrade",
|
|
311
311
|
});
|
|
312
312
|
}
|
|
313
|
-
//
|
|
313
|
+
// Note: SessionStart handled via experimental.chat.system.transform surrogate
|
|
314
314
|
results.push({
|
|
315
315
|
check: "SessionStart hook",
|
|
316
|
-
status: "
|
|
317
|
-
message: `SessionStart
|
|
316
|
+
status: "pass",
|
|
317
|
+
message: `SessionStart via experimental.chat.system.transform surrogate (native hook pending #14808, #5409)`,
|
|
318
318
|
});
|
|
319
319
|
return results;
|
|
320
320
|
}
|
package/build/cli.js
CHANGED
|
@@ -382,9 +382,26 @@ async function doctor() {
|
|
|
382
382
|
}
|
|
383
383
|
else {
|
|
384
384
|
criticalFails++;
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
385
|
+
// Detect better-sqlite3 native bindings-missing pattern (issue #408).
|
|
386
|
+
// The `bindings` package throws "Could not locate the bindings file"
|
|
387
|
+
// when better_sqlite3.node failed to install — typical on Windows
|
|
388
|
+
// when prebuild-install was not on PATH so install fell through to
|
|
389
|
+
// node-gyp without an MSVC toolchain.
|
|
390
|
+
const isBindingsMissing = /Could not locate the bindings file/i.test(message) ||
|
|
391
|
+
/bindings\.node/i.test(message) ||
|
|
392
|
+
/\bbindings\b/i.test(message);
|
|
393
|
+
if (isBindingsMissing && process.platform === "win32") {
|
|
394
|
+
p.log.error(color.red("FTS5 / better-sqlite3: FAIL") +
|
|
395
|
+
` — ${message}` +
|
|
396
|
+
color.dim("\n Root cause: prebuild-install was likely not on PATH, so install fell through to node-gyp without an MSVC toolchain (Windows)." +
|
|
397
|
+
"\n Try (primary): npm install better-sqlite3 # re-resolves the dep tree and re-links the prebuild-install bin shim to fetch a prebuilt binary" +
|
|
398
|
+
"\n Try (fallback): npm rebuild better-sqlite3"));
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
p.log.error(color.red("FTS5 / better-sqlite3: FAIL") +
|
|
402
|
+
` — ${message}` +
|
|
403
|
+
color.dim("\n Try: npm rebuild better-sqlite3"));
|
|
404
|
+
}
|
|
388
405
|
}
|
|
389
406
|
}
|
|
390
407
|
// Version check — adapter-aware
|
|
@@ -545,6 +562,36 @@ async function upgrade() {
|
|
|
545
562
|
let pluginRoot = getPluginRoot();
|
|
546
563
|
const changes = [];
|
|
547
564
|
const s = p.spinner();
|
|
565
|
+
// Step 0: Sync the marketplace clone (#418).
|
|
566
|
+
// Claude Code reads plugin metadata from ~/.claude/plugins/marketplaces/context-mode/.
|
|
567
|
+
// Without a git pull there, the marketplace stays pinned at the install-time
|
|
568
|
+
// commit and CC keeps reporting the old version even after our cache dir is
|
|
569
|
+
// updated — users then see "ctx-upgrade succeeded" but nothing actually
|
|
570
|
+
// changed at the plugin-system level.
|
|
571
|
+
const marketplaceDir = resolve(homedir(), ".claude", "plugins", "marketplaces", "context-mode");
|
|
572
|
+
if (existsSync(join(marketplaceDir, ".git"))) {
|
|
573
|
+
s.start("Syncing marketplace clone");
|
|
574
|
+
try {
|
|
575
|
+
// Preserve user dev edits (Mert-class users symlink the clone to a worktree).
|
|
576
|
+
const statusOut = execFileSync("git", ["-C", marketplaceDir, "status", "--porcelain"], { stdio: "pipe", encoding: "utf-8", timeout: 5000 });
|
|
577
|
+
if (statusOut.trim()) {
|
|
578
|
+
s.stop(color.yellow("Marketplace clone has local edits — skipping git pull"));
|
|
579
|
+
p.log.info(color.dim(` Run manually: git -C "${marketplaceDir}" stash && git pull --ff-only`));
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
execFileSync("git", ["-C", marketplaceDir, "fetch", "--tags", "origin"], { stdio: "pipe", timeout: 30000 });
|
|
583
|
+
execFileSync("git", ["-C", marketplaceDir, "reset", "--hard", "origin/HEAD"], { stdio: "pipe", timeout: 10000 });
|
|
584
|
+
s.stop(color.green("Marketplace clone synced"));
|
|
585
|
+
changes.push("Marketplace clone updated to upstream");
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
catch (err) {
|
|
589
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
590
|
+
s.stop(color.yellow("Marketplace sync skipped"));
|
|
591
|
+
p.log.warn(color.yellow("git refresh on marketplace failed") + ` — ${message}`);
|
|
592
|
+
p.log.info(color.dim(" Continuing — cache dir update will still happen."));
|
|
593
|
+
}
|
|
594
|
+
}
|
|
548
595
|
// Step 1: Pull latest from GitHub
|
|
549
596
|
p.log.step("Pulling latest from GitHub...");
|
|
550
597
|
const localVersion = getLocalVersion();
|
|
@@ -596,12 +643,16 @@ async function upgrade() {
|
|
|
596
643
|
}
|
|
597
644
|
catch { /* some files may not exist in source */ }
|
|
598
645
|
}
|
|
599
|
-
// Write .mcp.json with
|
|
646
|
+
// Write .mcp.json with CLAUDE_PLUGIN_ROOT placeholder (fixes #411).
|
|
647
|
+
// Absolute paths bake-in the current pluginRoot dir, which sessionstart.mjs
|
|
648
|
+
// (#181) deletes after upgrade — breaking MCP server resolution. The literal
|
|
649
|
+
// ${CLAUDE_PLUGIN_ROOT} placeholder is resolved by Claude at load-time and
|
|
650
|
+
// stays valid across version cleanups. Matches .claude-plugin/plugin.json.
|
|
600
651
|
const mcpConfig = {
|
|
601
652
|
mcpServers: {
|
|
602
653
|
"context-mode": {
|
|
603
654
|
command: "node",
|
|
604
|
-
args: [
|
|
655
|
+
args: ["${CLAUDE_PLUGIN_ROOT}/start.mjs"],
|
|
605
656
|
},
|
|
606
657
|
},
|
|
607
658
|
};
|
|
@@ -773,14 +824,43 @@ async function upgrade() {
|
|
|
773
824
|
* statusline — forward to bin/statusline.mjs
|
|
774
825
|
* ------------------------------------------------------- */
|
|
775
826
|
function statuslineForward() {
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
827
|
+
// Try multiple plugin-root candidates in priority order. After ctx-upgrade,
|
|
828
|
+
// getPluginRoot() can resolve to a cache dir that sessionstart.mjs (#181)
|
|
829
|
+
// already cleaned, leaving bin/statusline.mjs missing. Falling back to the
|
|
830
|
+
// marketplace clone (#418-synced, stable across upgrades) and to the path
|
|
831
|
+
// Claude Code itself loads from (installed_plugins.json) keeps the bar
|
|
832
|
+
// alive instead of silently going blank.
|
|
833
|
+
const candidates = [
|
|
834
|
+
resolve(getPluginRoot(), "bin", "statusline.mjs"),
|
|
835
|
+
resolve(homedir(), ".claude", "plugins", "marketplaces", "context-mode", "bin", "statusline.mjs"),
|
|
836
|
+
];
|
|
837
|
+
// installed_plugins.json may list one or more install paths CC actually
|
|
838
|
+
// loads from. Prefer those if they exist.
|
|
839
|
+
try {
|
|
840
|
+
const registryPath = resolve(homedir(), ".claude", "plugins", "installed_plugins.json");
|
|
841
|
+
if (existsSync(registryPath)) {
|
|
842
|
+
const registry = JSON.parse(readFileSync(registryPath, "utf-8"));
|
|
843
|
+
const entries = registry?.plugins?.["context-mode@context-mode"];
|
|
844
|
+
if (Array.isArray(entries)) {
|
|
845
|
+
for (const entry of entries) {
|
|
846
|
+
const installPath = entry?.installPath;
|
|
847
|
+
if (typeof installPath === "string" && installPath) {
|
|
848
|
+
candidates.push(resolve(installPath, "bin", "statusline.mjs"));
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
catch { /* registry malformed — fall through to other candidates */ }
|
|
855
|
+
const scriptPath = candidates.find((c) => existsSync(c));
|
|
856
|
+
if (!scriptPath) {
|
|
857
|
+
// Statusline output is the user-facing status bar; stderr surfaces visibly
|
|
858
|
+
// in some terminals. Exit silently — the bar simply stays empty until the
|
|
859
|
+
// next /ctx-upgrade or restart resolves the path.
|
|
860
|
+
process.exit(0);
|
|
780
861
|
}
|
|
781
862
|
// Re-exec via dynamic import so stdin/stdout are inherited cleanly.
|
|
782
|
-
import(pathToFileURL(scriptPath).href).catch((
|
|
783
|
-
process.
|
|
784
|
-
process.exit(1);
|
|
863
|
+
import(pathToFileURL(scriptPath).href).catch(() => {
|
|
864
|
+
process.exit(0);
|
|
785
865
|
});
|
|
786
866
|
}
|
package/build/server.js
CHANGED
|
@@ -19,6 +19,7 @@ import { detectRuntimes, getRuntimeSummary, getAvailableLanguages, hasBunRuntime
|
|
|
19
19
|
import { classifyNonZeroExit } from "./exit-classify.js";
|
|
20
20
|
import { startLifecycleGuard } from "./lifecycle.js";
|
|
21
21
|
import { getWorktreeSuffix, SessionDB } from "./session/db.js";
|
|
22
|
+
import { persistToolCallCounter, restoreSessionStats } from "./session/persist-tool-calls.js";
|
|
22
23
|
import { searchAllSources } from "./search/unified.js";
|
|
23
24
|
import { buildNodeCommand } from "./adapters/types.js";
|
|
24
25
|
import { detectPlatform, getSessionDirSegments } from "./adapters/detect.js";
|
|
@@ -169,6 +170,15 @@ function hashProjectDir() {
|
|
|
169
170
|
const normalized = projectDir.replace(/\\/g, "/");
|
|
170
171
|
return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
171
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Resolve the per-project SessionDB path the way 4742160 originally did
|
|
175
|
+
* for `persistToolCallCounter`. Centralized so the write-back, the
|
|
176
|
+
* restore-on-startup, and any future SessionDB consumer all hash to the
|
|
177
|
+
* same file under worktree isolation.
|
|
178
|
+
*/
|
|
179
|
+
function getSessionDbPath() {
|
|
180
|
+
return join(getSessionDir(), `${hashProjectDir()}${getWorktreeSuffix()}.db`);
|
|
181
|
+
}
|
|
172
182
|
/**
|
|
173
183
|
* Compute a per-project, per-platform persistent path for the ContentStore.
|
|
174
184
|
* Derives content dir from the adapter's session dir so each platform
|
|
@@ -351,10 +361,13 @@ function trackResponse(toolName, response) {
|
|
|
351
361
|
// Persist a sidecar JSON snapshot for the statusline — read at ~3-5 Hz by
|
|
352
362
|
// bin/statusline.mjs (and any external dashboard) so they don't have to
|
|
353
363
|
// open the SQLite database. Throttled inside persistStats() (500ms) so
|
|
354
|
-
// it's safe to call on every response.
|
|
355
|
-
// dropped the SessionDB tool-call counter (`persistToolCallCounter`); we
|
|
356
|
-
// keep persistStats here because the statusline depends on it.
|
|
364
|
+
// it's safe to call on every response.
|
|
357
365
|
persistStats();
|
|
366
|
+
// Persist to SessionDB so counters survive process restart, --continue,
|
|
367
|
+
// upgrade. Re-introduces the write path 4742160 added and b392c2f dropped.
|
|
368
|
+
// setImmediate keeps this off the response hot path; the helper itself
|
|
369
|
+
// is best-effort (never throws).
|
|
370
|
+
setImmediate(() => persistToolCallCounter(getSessionDbPath(), toolName, bytes));
|
|
358
371
|
return response;
|
|
359
372
|
}
|
|
360
373
|
function trackIndexed(bytes) {
|
|
@@ -2823,6 +2836,28 @@ async function main() {
|
|
|
2823
2836
|
}
|
|
2824
2837
|
}
|
|
2825
2838
|
catch { /* best effort — _detectedAdapter stays null, falls back to .claude */ }
|
|
2839
|
+
// Restore tool-call counters from SessionDB BEFORE the heartbeat fires
|
|
2840
|
+
// so the very first persistStats() carries the prior PID's totals into
|
|
2841
|
+
// the sidecar JSON the statusline reads. Otherwise `/ctx-upgrade` flashes
|
|
2842
|
+
// `0 calls / $0.00` until the user makes another MCP tool call. Wrapped
|
|
2843
|
+
// in try/catch — a stats-restore failure must never block server startup.
|
|
2844
|
+
try {
|
|
2845
|
+
const restored = restoreSessionStats(getSessionDbPath());
|
|
2846
|
+
if (restored) {
|
|
2847
|
+
for (const [tool, count] of Object.entries(restored.calls)) {
|
|
2848
|
+
sessionStats.calls[tool] = count;
|
|
2849
|
+
}
|
|
2850
|
+
for (const [tool, bytes] of Object.entries(restored.bytesReturned)) {
|
|
2851
|
+
sessionStats.bytesReturned[tool] = bytes;
|
|
2852
|
+
}
|
|
2853
|
+
// Anchor uptime_ms to the original session start so `/ctx-upgrade`
|
|
2854
|
+
// doesn't reset the "session age" the statusline shows.
|
|
2855
|
+
if (restored.sessionStart > 0) {
|
|
2856
|
+
sessionStats.sessionStart = restored.sessionStart;
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
catch { /* best effort — never block startup on a stats restore failure */ }
|
|
2826
2861
|
// Non-blocking version check — result stored for trackResponse warnings.
|
|
2827
2862
|
// First fetch at startup, then refresh every hour so long-running sessions
|
|
2828
2863
|
// (some users keep the MCP server alive 24h+) catch new releases without a
|
|
@@ -2834,6 +2869,13 @@ async function main() {
|
|
|
2834
2869
|
fetchLatestVersion().then(v => { if (v !== "unknown")
|
|
2835
2870
|
_latestVersion = v; });
|
|
2836
2871
|
}, 60 * 60 * 1000).unref();
|
|
2872
|
+
// Stats heartbeat — keep the statusline truthful while the user works in
|
|
2873
|
+
// tools other than MCP (Bash/Read/Edit during long sessions or post-/compact
|
|
2874
|
+
// pauses). Without this, stats.updated_at only advances on MCP tool calls,
|
|
2875
|
+
// so bin/statusline.mjs falsely flips to "stale — restart to resume saving"
|
|
2876
|
+
// even though the server is alive. Heartbeat refreshes updated_at every 60s;
|
|
2877
|
+
// statusline staleness threshold is 30min (cliff is 30 missed ticks away).
|
|
2878
|
+
setInterval(() => persistStats(), 60_000).unref();
|
|
2837
2879
|
console.error(`Context Mode MCP server v${VERSION} running on stdio`);
|
|
2838
2880
|
console.error(`Detected runtimes:\n${getRuntimeSummary(runtimes)}`);
|
|
2839
2881
|
if (!hasBunRuntime()) {
|
|
@@ -185,6 +185,13 @@ export interface LifetimeStats {
|
|
|
185
185
|
autoMemoryProjects: number;
|
|
186
186
|
/** Per-prefix breakdown of auto-memory files (user/feedback/project/...). */
|
|
187
187
|
autoMemoryByPrefix: Record<string, number>;
|
|
188
|
+
/**
|
|
189
|
+
* Per-category event counts aggregated across every SessionDB on disk.
|
|
190
|
+
* Keys are the raw category strings (file/cwd/rule/...) — the renderer
|
|
191
|
+
* looks them up against `categoryLabels` for display. Empty `{}` when no
|
|
192
|
+
* sidecar has any events. Optional for back-compat with older fixtures.
|
|
193
|
+
*/
|
|
194
|
+
categoryCounts: Record<string, number>;
|
|
188
195
|
}
|
|
189
196
|
/**
|
|
190
197
|
* Aggregate lifetime stats from all SessionDB files in `sessionsDir` and
|