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.
Files changed (48) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.openclaw-plugin/openclaw.plugin.json +1 -1
  4. package/.openclaw-plugin/package.json +1 -1
  5. package/README.md +22 -18
  6. package/build/adapters/claude-code/index.js +26 -9
  7. package/build/adapters/opencode/index.js +5 -5
  8. package/build/cli.js +92 -12
  9. package/build/server.js +45 -3
  10. package/build/session/analytics.d.ts +7 -0
  11. package/build/session/analytics.js +75 -15
  12. package/build/session/db.d.ts +3 -1
  13. package/build/session/persist-tool-calls.d.ts +54 -0
  14. package/build/session/persist-tool-calls.js +105 -0
  15. package/build/session/project-attribution.d.ts +1 -1
  16. package/cli.bundle.mjs +123 -122
  17. package/hooks/ensure-deps.mjs +28 -12
  18. package/hooks/posttooluse.mjs +90 -80
  19. package/hooks/precompact.mjs +56 -46
  20. package/hooks/pretooluse.mjs +161 -167
  21. package/hooks/routing-block.mjs +2 -2
  22. package/hooks/run-hook.mjs +82 -0
  23. package/hooks/session-db.bundle.mjs +2 -2
  24. package/hooks/sessionstart.mjs +187 -155
  25. package/hooks/userpromptsubmit.mjs +69 -58
  26. package/openclaw.plugin.json +1 -1
  27. package/package.json +2 -1
  28. package/scripts/heal-better-sqlite3.mjs +108 -0
  29. package/scripts/postinstall.mjs +27 -0
  30. package/server.bundle.mjs +88 -88
  31. package/skills/UPSTREAM-CREDITS.md +51 -0
  32. package/skills/context-mode-ops/SKILL.md +147 -0
  33. package/skills/diagnose/SKILL.md +122 -0
  34. package/skills/diagnose/scripts/hitl-loop.template.sh +41 -0
  35. package/skills/grill-me/SKILL.md +15 -0
  36. package/skills/grill-with-docs/ADR-FORMAT.md +47 -0
  37. package/skills/grill-with-docs/CONTEXT-FORMAT.md +77 -0
  38. package/skills/grill-with-docs/SKILL.md +93 -0
  39. package/skills/improve-codebase-architecture/DEEPENING.md +37 -0
  40. package/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +44 -0
  41. package/skills/improve-codebase-architecture/LANGUAGE.md +53 -0
  42. package/skills/improve-codebase-architecture/SKILL.md +76 -0
  43. package/skills/tdd/SKILL.md +114 -0
  44. package/skills/tdd/deep-modules.md +33 -0
  45. package/skills/tdd/interface-design.md +31 -0
  46. package/skills/tdd/mocking.md +59 -0
  47. package/skills/tdd/refactoring.md +10 -0
  48. 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.107"
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.107",
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.107",
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.107",
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.107",
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. OpenCode lacks a SessionStart hook, so the model needs an `AGENTS.md` file for routing awareness:
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's SessionStart hook is not yet available ([#14808](https://github.com/sst/opencode/issues/14808)), so startup/resume session restore is not supported. Compaction recovery works fully via the plugin.
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 and lacks SessionStart, so the model needs an `AGENTS.md` file for routing awareness:
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 availability depends on KiloCode's implementation.
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 4 hooks working together:
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 | -- | -- | -- | -- | -- | -- | -- | 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 | -- | -- | -- | Plugin | Yes | -- | -- | -- | ✓ (via session_start event) |
933
- | | **Session completeness** | **Full** | **High** | **High** | **High** | **Partial** | **High** | **High** | **High** | **Partial** | **--** | **Partial** | **--** | **High** |
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**, 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.
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 `--continue`.
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** — Partial. The TypeScript plugin captures PostToolUse events via `tool.execute.after`, but SessionStart is not yet available ([#14808](https://github.com/sst/opencode/issues/14808)). Events are stored but not automatically restored after compaction.
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** — Partial. Shares the same plugin architecture as OpenCode via the OpenCodeAdapter. The TypeScript plugin captures PostToolUse events via `tool.execute.after`, but SessionStart availability depends on KiloCode's implementation. Events are stored but may not be automatically restored after compaction.
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 | -- | -- | -- | Plugin | Yes | -- | -- | -- | Yes (extension) |
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`, and `experimental.session.compacting`, providing the same routing enforcement and session continuity as shell-based hooks. SessionStart is not yet available ([#14808](https://github.com/sst/opencode/issues/14808)), but compaction recovery works via the plugin's compacting hook.
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 depend on KiloCode's implementation of the plugin interface.
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
- // Remove ALL existing context-mode hooks from settings.json hooks.json
312
- // is the source of truth. Keeping them causes duplicate concurrent hook
313
- // processes (one from settings.json, one from hooks.json), which triggers
314
- // "non-blocking hook error" warnings on every tool call.
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
- const filtered = entries.filter((entry) => !isAnyContextModeHook(entry));
320
- const removed = entries.length - filtered.length;
321
- if (removed > 0) {
322
- hooks[hookType] = filtered;
323
- changes.push(`Removed ${removed} duplicate ${hookType} hook(s) — covered by plugin hooks.json`);
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: false,
43
+ sessionStart: true,
44
44
  canModifyArgs: true,
45
45
  canModifyOutput: true, // with TUI bug caveat for bash (#13575)
46
- canInjectSessionContext: false,
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
- // Warn about SessionStart limitation
313
+ // Note: SessionStart handled via experimental.chat.system.transform surrogate
314
314
  results.push({
315
315
  check: "SessionStart hook",
316
- status: "warn",
317
- message: `SessionStart not supported in ${this.name} (see issues #14808, #5409)`,
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
- p.log.error(color.red("FTS5 / better-sqlite3: FAIL") +
386
- ` ${message}` +
387
- color.dim("\n Try: npm rebuild better-sqlite3"));
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 resolved absolute path (fixes #132)
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: [resolve(pluginRoot, "start.mjs")],
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
- const scriptPath = resolve(getPluginRoot(), "bin", "statusline.mjs");
777
- if (!existsSync(scriptPath)) {
778
- process.stderr.write(`statusline script missing: ${scriptPath}\n`);
779
- process.exit(1);
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((err) => {
783
- process.stderr.write(`statusline failed: ${err?.message ?? err}\n`);
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. The b392c2f concurrency refactor
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