dsh-context-mode 0.5.1 → 0.5.3

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/README.md CHANGED
@@ -1,9 +1,27 @@
1
1
  # context-mode for DSH
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/dsh-context-mode)](https://www.npmjs.com/package/dsh-context-mode)
4
+ [![license](https://img.shields.io/npm/l/dsh-context-mode)](./LICENSE)
5
+
3
6
  `dsh-context-mode` exposes a self-contained context-mode server as native
4
7
  DeepSeek Harness tools. It registers the full `ctx_*` catalog at runtime and
5
8
  adds model-facing routing guidance, so dsh-TUI can use sandboxed execution,
6
- indexing, FTS5 retrieval, and web fetching without a second MCP client.
9
+ indexing, FTS5 retrieval, and web fetching without a second MCP client. It also
10
+ registers the eight user-invocable skills from the upstream package, so the
11
+ DSH skill surface includes `/context-mode`, `/ctx-index`, `/ctx-purge`,
12
+ `/ctx-search`, and `/ctx-upgrade`; the diagnostics, analytics, and statistics
13
+ skills are off by default with their tools.
14
+
15
+ > **Part of a pair.** This is the *tool* half: the `ctx_*` tools, plus the
16
+ > archiver that files each compaction's transcript into the knowledge base.
17
+ > Its companion
18
+ > [`dsh-context-mode-compaction`](https://github.com/52sujiu/dsh-context-mode-compaction)
19
+ > is the *strategy* half: it replaces DSH's compaction engine so a checkpoint
20
+ > rebuilds the conversation instead of only summarizing it.
21
+ >
22
+ > **Install both.** Without the strategy package, the archive is still written
23
+ > but no checkpoint points at it; without this one, checkpoints point at an
24
+ > archive nothing fills.
7
25
 
8
26
  The engine is vendored: `vendor/context-mode/` carries the source and this
9
27
  package builds its own `server.bundle.mjs` from it. There is **no `context-mode`
@@ -30,8 +48,17 @@ Elastic License 2.0.
30
48
  dsh plugin --profile dsh-tui add dsh-context-mode
31
49
  ```
32
50
 
33
- Restart the profile after installation. Removing the package removes its profile
34
- row as well:
51
+ To get the full pair this package plus the compaction strategy that turns a
52
+ checkpoint into a searchable transcript — add both and run the wiring step:
53
+
54
+ ```sh
55
+ dsh plugin --profile dsh-tui add dsh-context-mode
56
+ dsh plugin --profile dsh-tui add dsh-context-mode-compaction
57
+ npx dsh-context-mode-compaction # point the preset's compaction row here
58
+ ```
59
+
60
+ Restart the profile afterwards either way. Removing a package removes its
61
+ profile row as well:
35
62
 
36
63
  ```sh
37
64
  dsh plugin --profile dsh-tui remove dsh-context-mode
@@ -51,6 +78,7 @@ The default patch starts the bridge with:
51
78
  - the current working directory as the project directory
52
79
  - `~/.dsh/context-mode` as the isolated database root
53
80
  - a 60-second initialize and tool-catalog timeout
81
+ - `disabledTools: [ctx_doctor, ctx_insight, ctx_stats]`
54
82
 
55
83
  Override the row's `config` in a profile patch when needed:
56
84
 
@@ -62,8 +90,20 @@ Override the row's `config` in a profile patch when needed:
62
90
  projectDir: /absolute/path/to/worktree
63
91
  storageDir: /absolute/path/to/context-mode-data
64
92
  handshakeTimeoutMs: 120000
93
+ disabledTools: [] # opt the maintenance tools back in
65
94
  ```
66
95
 
96
+ ### Why some tools are off by default
97
+
98
+ Every registered tool ships its full schema on every request, so a tool nobody
99
+ calls is a permanent token tax. `ctx_doctor`, `ctx_insight`, and `ctx_stats`
100
+ report on context-mode itself rather than on the user's work — a human runs
101
+ them deliberately. Their skills are suppressed with them, since a skill that
102
+ teaches a model to call an unregistered tool helps nobody.
103
+
104
+ The tools behind `disabledTools` stay in the vendored engine; only their DSH
105
+ registration is skipped. Set `disabledTools: []` to restore all eleven.
106
+
67
107
  `serverPath` is available for local development and pinned deployments. It may
68
108
  be an absolute path or a path relative to the profile process directory.
69
109
 
@@ -77,10 +117,12 @@ when the Cordis plugin is disposed.
77
117
  Context-mode analysis, indexing, search, and diagnostics tools are marked concurrency-safe so independent model tool calls may overlap. `ctx_insight`, `ctx_purge`, and `ctx_upgrade` remain exclusive because they open external UI or mutate installation and stored data.
78
118
  `ctx_batch_execute` additionally parallelizes its own command batch through its `concurrency` parameter.
79
119
 
80
- All eleven `ctx_*` capabilities are exposed on DSH — `ctx_execute`,
81
- `ctx_execute_file`, `ctx_batch_execute`, `ctx_fetch_and_index`, `ctx_index`,
82
- `ctx_search`, `ctx_stats`, `ctx_doctor`, `ctx_upgrade`, `ctx_purge`, and
83
- `ctx_insight`. Each registered tool carries DSH-specific routing guidance in its
120
+ Eight of the eleven `ctx_*` capabilities are exposed on DSH by default
121
+ `ctx_execute`, `ctx_execute_file`, `ctx_batch_execute`, `ctx_fetch_and_index`,
122
+ `ctx_index`, `ctx_search`, `ctx_upgrade`, and `ctx_purge`. `ctx_stats`,
123
+ `ctx_doctor`, and `ctx_insight` are held back by `disabledTools` (see
124
+ [Configuration](#configuration)). Each registered tool carries DSH-specific
125
+ routing guidance in its
84
126
  description, and the plugin injects a routing section plus a bundled
85
127
  `context-mode` skill so the model reaches for these tools by default.
86
128
 
@@ -24,6 +24,15 @@ export interface Config {
24
24
  * `ctx_search` can still reach what the compaction summary drops.
25
25
  */
26
26
  precompact?: boolean;
27
+ /**
28
+ * Tool names to keep out of the model's catalog and the skill list.
29
+ *
30
+ * Every registered tool ships its full schema on every request, so a tool
31
+ * nobody calls is a permanent token tax. Defaults to the maintenance-only
32
+ * tools: diagnostics, analytics, and statistics are things a human runs
33
+ * deliberately, not something the model should reach for mid-task.
34
+ */
35
+ disabledTools?: string[];
27
36
  }
28
37
  export declare const Config: Schemastery<Config>;
29
38
  export declare const ROUTING_TEXT: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAYlD,eAAO,MAAM,IAAI,qBAAqB,CAAA;AAEtC,qDAAqD;AACrD,MAAM,WAAW,MAAM;IACrB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,+DAA+D;IAC/D,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,eAAO,MAAM,MAAM,EAAE,WAAW,CAAC,MAAM,CAOrC,CAAA;AAWF,eAAO,MAAM,YAAY,QAMb,CAAA;AA2CZ,+EAA+E;AAC/E,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,MAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoF5E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAYlD,eAAO,MAAM,IAAI,qBAAqB,CAAA;AAEtC,qDAAqD;AACrD,MAAM,WAAW,MAAM;IACrB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,+DAA+D;IAC/D,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;CACzB;AAWD,eAAO,MAAM,MAAM,EAAE,WAAW,CAAC,MAAM,CAQrC,CAAA;AAWF,eAAO,MAAM,YAAY,QAMb,CAAA;AA6EZ,+EAA+E;AAC/E,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,MAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAsF5E"}
@@ -17,6 +17,14 @@ import { installPrecompactArchive } from './precompact.js';
17
17
  import { installBashRoutingGuard } from './routing.js';
18
18
  import { installSessionMemory } from './session-memory.js';
19
19
  export const name = 'dsh-context-mode';
20
+ /**
21
+ * Maintenance-only tools that are off unless a deployment opts back in.
22
+ *
23
+ * `ctx_doctor`, `ctx_insight`, and `ctx_stats` report on context-mode itself;
24
+ * the model has never needed them to do the user's work, and each one's schema
25
+ * is carried on every request.
26
+ */
27
+ const DEFAULT_DISABLED_TOOLS = ['ctx_doctor', 'ctx_insight', 'ctx_stats'];
20
28
  export const Config = z.object({
21
29
  enabled: z.boolean().default(true),
22
30
  serverPath: z.string().default(''),
@@ -24,6 +32,7 @@ export const Config = z.object({
24
32
  storageDir: z.string().default(''),
25
33
  handshakeTimeoutMs: z.number().step(1).min(1_000).default(60_000),
26
34
  precompact: z.boolean().default(true),
35
+ disabledTools: z.array(z.string()).default([...DEFAULT_DISABLED_TOOLS]),
27
36
  });
28
37
  const OUTPUT_SCHEMA = {
29
38
  type: 'object',
@@ -58,14 +67,47 @@ const EXCLUSIVE_CONTEXT_TOOLS = new Set([
58
67
  'ctx_purge',
59
68
  'ctx_upgrade',
60
69
  ]);
61
- const BUNDLED_SKILL = {
62
- name: 'context-mode',
63
- description: 'Route large, inspectable, or data-heavy work through ctx_execute, ctx_execute_file, ctx_batch_execute, ctx_fetch_and_index, ctx_index, and ctx_search instead of raw Bash, web calls, or large output.',
64
- whenToUse: 'Use automatically for logs, tests, build output, git history or diffs, API responses, web docs, dependency audits, recursive listings, structured data, snapshots, or any output that may exceed 20 lines. Keep native Read/Edit for exact text needed to edit files.',
65
- path: 'skills/context-mode/SKILL.md',
70
+ const BUNDLED_SKILLS = [
71
+ {
72
+ name: 'context-mode',
73
+ description: 'Route large, inspectable, or data-heavy work through ctx_* tools instead of raw Bash, web calls, or large output.',
74
+ whenToUse: 'Use automatically for logs, tests, builds, git history, API responses, web docs, dependency audits, structured data, or output that may exceed 20 lines.',
75
+ },
76
+ {
77
+ name: 'ctx-doctor',
78
+ description: 'Run context-mode diagnostics for runtimes, storage, bridge health, and registration.',
79
+ },
80
+ {
81
+ name: 'ctx-index',
82
+ description: 'Index a local file or directory into the persistent context-mode knowledge base.',
83
+ },
84
+ {
85
+ name: 'ctx-insight',
86
+ description: 'Open the hosted context-mode Insight analytics dashboard.',
87
+ },
88
+ {
89
+ name: 'ctx-purge',
90
+ description: 'Permanently purge context-mode indexed content with an explicit scope and confirmation.',
91
+ },
92
+ {
93
+ name: 'ctx-search',
94
+ description: 'Search previously indexed project content and session memory.',
95
+ },
96
+ {
97
+ name: 'ctx-stats',
98
+ description: 'Show context-mode token consumption, savings ratio, and per-tool statistics.',
99
+ },
100
+ {
101
+ name: 'ctx-upgrade',
102
+ description: 'Upgrade context-mode and report the resulting installation checklist.',
103
+ },
104
+ ].map(skill => ({
105
+ ...skill,
106
+ path: `skills/${skill.name}/SKILL.md`,
66
107
  provider: name,
67
108
  source: 'bundled',
68
- };
109
+ invocation: { modelInvocable: true, userInvocable: true },
110
+ }));
69
111
  /** Register the plugin and bridge context-mode's MCP tool catalog into DSH. */
70
112
  export async function apply(ctx, config = {}) {
71
113
  const resolved = {
@@ -75,6 +117,7 @@ export async function apply(ctx, config = {}) {
75
117
  storageDir: config.storageDir?.trim() || join(homedir(), '.dsh', 'context-mode'),
76
118
  handshakeTimeoutMs: config.handshakeTimeoutMs ?? 60_000,
77
119
  precompact: config.precompact ?? true,
120
+ disabledTools: new Set(config.disabledTools ?? DEFAULT_DISABLED_TOOLS),
78
121
  };
79
122
  if (!resolved.enabled)
80
123
  return;
@@ -100,7 +143,7 @@ export async function apply(ctx, config = {}) {
100
143
  disposers.push(memoryDisposer);
101
144
  const precompactDisposer = installPrecompactArchive(ctx, () => client, { enabled: resolved.precompact });
102
145
  disposers.push(precompactDisposer);
103
- const skillDisposer = registerBundledSkill(ctx);
146
+ const skillDisposer = registerBundledSkills(ctx, resolved.disabledTools);
104
147
  if (skillDisposer !== undefined)
105
148
  disposers.push(skillDisposer);
106
149
  try {
@@ -123,6 +166,8 @@ export async function apply(ctx, config = {}) {
123
166
  for (const tool of catalog) {
124
167
  if (disposed)
125
168
  return;
169
+ if (resolved.disabledTools.has(tool.name))
170
+ continue;
126
171
  try {
127
172
  disposers.push(tools.register(toDefinition(tool, bridge)));
128
173
  }
@@ -146,18 +191,25 @@ export async function apply(ctx, config = {}) {
146
191
  ctx.logger.warn(`dsh-context-mode: bridge unavailable; plugin is inactive (${errorMessage(error)})`);
147
192
  }
148
193
  }
149
- function registerBundledSkill(ctx) {
194
+ function registerBundledSkills(ctx, disabledTools) {
150
195
  const skills = ctx.get('skills', false);
151
196
  if (skills === undefined)
152
197
  return undefined;
153
- try {
154
- const content = readFileSync(new URL('../../skills/context-mode/SKILL.md', import.meta.url), 'utf8');
155
- return skills.register({ ...BUNDLED_SKILL, content });
156
- }
157
- catch (error) {
158
- ctx.logger.warn(`dsh-context-mode: bundled skill unavailable (${errorMessage(error)})`);
159
- return undefined;
198
+ const disposers = [];
199
+ for (const skill of BUNDLED_SKILLS) {
200
+ // A skill whose tool is not registered would only teach the model to call
201
+ // something that is not there.
202
+ if (skill.name !== name && disabledTools.has(skill.name.replace(/-/g, '_')))
203
+ continue;
204
+ try {
205
+ const content = readFileSync(new URL(`../../${skill.path}`, import.meta.url), 'utf8');
206
+ disposers.push(skills.register({ ...skill, content }));
207
+ }
208
+ catch (error) {
209
+ ctx.logger.warn(`dsh-context-mode: bundled skill unavailable (${skill.name}: ${errorMessage(error)})`);
210
+ }
160
211
  }
212
+ return disposers.length === 0 ? undefined : () => disposers.forEach(dispose => dispose());
161
213
  }
162
214
  function toDefinition(tool, client) {
163
215
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context-mode",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Expose context-mode MCP tools as native DeepSeek Harness tools",
5
5
  "keywords": [
6
6
  "dsh",
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: ctx-doctor
3
+ description: |
4
+ Run context-mode diagnostics. Checks runtimes, hooks, FTS5,
5
+ plugin registration, npm and marketplace versions.
6
+ Trigger: /context-mode:ctx-doctor
7
+ user-invocable: true
8
+ ---
9
+
10
+ # Context Mode Doctor
11
+
12
+ Run diagnostics and display results directly in the conversation.
13
+
14
+ ## Instructions
15
+
16
+ 1. Call the `ctx_doctor` MCP tool directly. It runs all checks server-side and returns a plain-text status report.
17
+ 2. Display the results verbatim — they are already formatted with plain-text status prefixes: `[OK]` PASS, `[FAIL]` FAIL, `[WARN]` WARN. Renderer-safe (no markdown task-list syntax) for cross-client compatibility (e.g., Z.ai GLM).
18
+ 3. **Fallback** (only if MCP tool call fails): Derive the **plugin root** from this skill's base directory (go up 2 levels — remove `/skills/ctx-doctor`), then run with Bash:
19
+ ```
20
+ CLI="<PLUGIN_ROOT>/cli.bundle.mjs"; [ ! -f "$CLI" ] && CLI="<PLUGIN_ROOT>/build/cli.js"; node "$CLI" doctor
21
+ ```
22
+ Re-display results verbatim with the same `[OK]`/`[FAIL]`/`[WARN]` prefixes.
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: ctx-index
3
+ description: |
4
+ Index a local file or directory into context-mode's persistent FTS5 knowledge base
5
+ so future ctx_search calls can retrieve focused snippets without rereading raw files.
6
+ Trigger: /context-mode:ctx-index
7
+ user-invocable: true
8
+ ---
9
+
10
+ # Context Mode Index
11
+
12
+ Index local project content for later search.
13
+
14
+ ## Instructions
15
+
16
+ 1. Prefer the `ctx_index` MCP tool when it is available.
17
+ 2. Ask for a path only if the user did not provide one and the current project root is ambiguous.
18
+ 3. Use `path`, not large inline `content`, so file bytes do not enter the conversation.
19
+ 4. For repository indexing, pass conservative bounds and a clear source label:
20
+
21
+ ```javascript
22
+ ctx_index({
23
+ path: ".",
24
+ source: "project:<name>",
25
+ maxDepth: 5,
26
+ maxFiles: 200
27
+ })
28
+ ```
29
+
30
+ 5. If MCP tools are unavailable, fall back to the CLI:
31
+
32
+ ```bash
33
+ context-mode index . --source project:<name>
34
+ ```
35
+
36
+ 6. Report the indexed source label, file count or section count, and the matching search command:
37
+
38
+ ```javascript
39
+ ctx_search({ source: "project:<name>", queries: ["..."] })
40
+ ```
41
+
42
+ ## Safety
43
+
44
+ - Do not index dependency directories, build outputs, secrets, or generated artifacts.
45
+ - Prefer `--exclude` or `exclude` for project-specific noisy paths.
46
+ - For broad repos, ask the user before raising `maxFiles` above 500.
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: ctx-insight
3
+ description: |
4
+ Open the context-mode Insight dashboard in your default browser.
5
+ Insight is the hosted analytics layer for AI-assisted engineering teams —
6
+ per-engineer productive rate, retry waste, blocker detection, role-narrowed views.
7
+ Trigger: /context-mode:ctx-insight
8
+ user-invocable: true
9
+ ---
10
+
11
+ # Context Mode Insight
12
+
13
+ Open the hosted Insight dashboard in the user's default browser.
14
+
15
+ ## Instructions
16
+
17
+ 1. Call the `ctx_insight` MCP tool (no parameters). It opens
18
+ <https://context-mode.com/insight> in the default browser and returns a
19
+ confirmation line.
20
+ 2. Display the tool's output to the user.
21
+ 3. Tell the user:
22
+ - "Insight opened at https://context-mode.com/insight"
23
+ - The landing page at context-mode.com/insight is the single source of truth for sign-in and pricing details.
24
+ - If the browser did not open automatically, share the URL so they can open it manually.
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: ctx-purge
3
+ description: |
4
+ Purge the context-mode knowledge base. Permanently deletes all indexed content
5
+ and resets session stats. This is destructive and cannot be undone.
6
+ Trigger: /context-mode:ctx-purge
7
+ user-invocable: true
8
+ ---
9
+
10
+ # Context Mode Purge
11
+
12
+ Permanently deletes session data for this project. Two scopes are supported (issue #520):
13
+
14
+ - **Project scope** (`scope: "project"`): wipes EVERYTHING — knowledge base, all session DB rows for every session, events markdown, and stats.
15
+ - **Session scope** (`sessionId: "<id>"` or `scope: "session"`): wipes ONLY the matching session's rows + FTS5 chunks. Sibling sessions, project stats, and the FTS5 store file are preserved.
16
+
17
+ ## Instructions
18
+
19
+ 1. **Decide the scope first** with the user:
20
+ - "Wipe just one session?" → ask for the `sessionId`.
21
+ - "Wipe the whole project?" → confirm scope:'project' (this is the destructive, irreversible default).
22
+ 2. **Warn the user about scope:'project'**. Everything will be deleted:
23
+ - FTS5 knowledge base (all indexed content from `ctx_index`, `ctx_fetch_and_index`, `ctx_batch_execute`)
24
+ - Session events DB (analytics, metadata, resume snapshots) for ALL sessions in the project
25
+ - Session events markdown file
26
+ - In-memory session stats + persisted stats file
27
+ 3. Call the `mcp__context-mode__ctx_purge` MCP tool with the chosen parameters:
28
+ - Scoped: `{ confirm: true, sessionId: "<id>" }` — implies scope:'session'.
29
+ - Project: `{ confirm: true, scope: "project" }` — explicit destructive form.
30
+ - Bare `{ confirm: true }` still works but emits a deprecation warning. Prefer the explicit forms.
31
+ 4. Report the result to the user — the response lists exactly what was deleted and (for scoped purges) confirms that other sessions and project stats were preserved.
32
+
33
+ ## Schema rules
34
+
35
+ - `confirm: true` is always required.
36
+ - `sessionId` and `scope: "project"` together is REJECTED as ambiguous (the sessionId implies session scope; combining with project scope contradicts intent).
37
+ - `scope: "session"` without `sessionId` throws — sessionId is required.
38
+
39
+ ## When to Use
40
+
41
+ - **Scoped (per-session)**: scratch acceptance scenarios, drill replays, isolating a polluted session without losing the main working session's stats.
42
+ - **Project**: KB contains stale or incorrect content polluting search results, switching between unrelated projects in the same session, completely fresh start.
43
+
44
+ ## Important
45
+
46
+ - `ctx_purge` is the **only** way to delete session data. No other mechanism exists.
47
+ - `ctx_stats` is read-only — shows statistics only.
48
+ - `/clear` and `/compact` do NOT affect any context-mode data.
49
+ - There is no undo. Re-index content if you need it again.
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: ctx-search
3
+ description: |
4
+ Search context-mode's persistent FTS5 knowledge base for previously indexed
5
+ local project content, documentation, or session memory.
6
+ Trigger: /context-mode:ctx-search
7
+ user-invocable: true
8
+ ---
9
+
10
+ # Context Mode Search
11
+
12
+ Search indexed content without rereading raw sources into conversation context.
13
+
14
+ ## Instructions
15
+
16
+ 1. Prefer the `ctx_search` MCP tool when it is available.
17
+ 2. Batch all related questions in one `queries` array.
18
+ 3. Scope with `source` when the user names a project or indexed label.
19
+ 4. Use short, specific queries of two to four technical terms.
20
+
21
+ ```javascript
22
+ ctx_search({
23
+ source: "project:<name>",
24
+ queries: ["authentication middleware", "token refresh"],
25
+ limit: 5
26
+ })
27
+ ```
28
+
29
+ 5. If MCP tools are unavailable, fall back to the CLI:
30
+
31
+ ```bash
32
+ context-mode search "authentication middleware" --source project:<name> --limit 5
33
+ ```
34
+
35
+ 6. If the index is empty, tell the user to run `/context-mode:ctx-index` or `context-mode index <path>` first.
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: ctx-stats
3
+ description: |
4
+ Show how much context window context-mode saved this session.
5
+ Displays token consumption, context savings ratio, and per-tool breakdown.
6
+ Read-only — shows stats only, no reset capability.
7
+ To wipe the knowledge base entirely, use ctx_purge instead.
8
+ Trigger: /context-mode:ctx-stats
9
+ user-invocable: true
10
+ ---
11
+
12
+ # Context Mode Stats
13
+
14
+ Show context savings for the current session.
15
+
16
+ ## Instructions
17
+
18
+ 1. Call the `mcp__context-mode__ctx_stats` MCP tool (no parameters needed).
19
+ 2. **CRITICAL**: You MUST copy-paste the ENTIRE tool output as markdown text directly into your response message. Do NOT summarize, do NOT collapse, do NOT paraphrase. The user must see the full tables without pressing ctrl+o. Copy every line exactly as returned by the tool.
20
+ 3. After the full output, add ONE sentence highlighting the key savings metric, e.g.:
21
+ - "context-mode saved **12.4x** — 92% of data stayed in sandbox."
22
+ - If no data yet: "No context-mode calls yet this session."
23
+
24
+ ## Purge
25
+
26
+ - **`ctx_purge(confirm: true)`** — Permanently deletes all indexed content from the knowledge base. Use `/context-mode:ctx-purge` for this.
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: ctx-upgrade
3
+ description: |
4
+ Update context-mode from GitHub and fix hooks/settings.
5
+ Pulls latest, builds, installs, updates npm global, configures hooks.
6
+ Trigger: /context-mode:ctx-upgrade
7
+ user-invocable: true
8
+ ---
9
+
10
+ # Context Mode Upgrade
11
+
12
+ Pull latest from GitHub and reinstall the plugin.
13
+
14
+ ## Instructions
15
+
16
+ 1. Call the `ctx_upgrade` MCP tool directly. It returns a shell command to execute.
17
+ 2. Run the returned command using your shell execution tool (Bash, shell_execute, etc.).
18
+ 3. Display results as a markdown checklist:
19
+ ```
20
+ ## context-mode upgrade
21
+ - [x] Pulled latest from GitHub
22
+ - [x] Built and installed v1.0.39
23
+ - [x] Hooks configured
24
+ - [x] Doctor: all checks PASS
25
+ ```
26
+ Use `[x]` for success, `[ ]` for failure. Show actual version numbers.
27
+ 4. Tell the user to **restart their session** to pick up the new version.
28
+ 5. **Fallback** (only if MCP tool call fails): Derive the **plugin root** from this skill's base directory (go up 2 levels — remove `/skills/ctx-upgrade`), then run with Bash:
29
+ ```
30
+ CLI="<PLUGIN_ROOT>/cli.bundle.mjs"; [ ! -f "$CLI" ] && CLI="<PLUGIN_ROOT>/build/cli.js"; node "$CLI" upgrade
31
+ ```
@@ -582,7 +582,7 @@ ${s}`}}import{execFileSync as WN}from"node:child_process";function GN(){if(proce
582
582
  WHERE tool IN ('ctx_search', 'ctx_fetch_and_index')`).get();m?.bytes&&(a+=Number(m.bytes))}catch{}}}finally{h.close()}}catch{}}let u=0;t.sessionId&&t.contentDbPath&&(u=UD(t.sessionId,t.contentDbPath,{loadDatabase:t.loadDatabase}),i+=u);let l=Math.floor((o+i+c)/4);return{eventDataBytes:o,bytesAvoided:i,bytesReturned:a,snapshotBytes:c,contentBytes:u,totalSavedTokens:l}}function am(t){let e=pc({worktreeHash:t.worktreeHash,sessionsDir:t.sessionsDir}),r=pc({sessionId:t.sessionId,worktreeHash:t.worktreeHash,sessionsDir:t.sessionsDir,contentDbPath:t.contentDbPath}),n=r.bytesReturned,s=e.bytesAvoided+e.bytesReturned,o=Math.max(0,s-n);return{eventDataBytes:e.eventDataBytes,bytesAvoided:o,bytesReturned:n,snapshotBytes:e.snapshotBytes,contentBytes:r.contentBytes,totalSavedTokens:Math.floor((e.eventDataBytes+o+e.snapshotBytes)/4)}}var qD={minEvents:100,minProjects:5,recencyMs:30*864e5,minAvgBytes:50};function BD(t,e,r){let n={name:t.name,eventCount:0,sessionCount:0,dataBytes:0,rescueBytes:0,contentBytes:0,uuidConvs:0,projectDirs:[],firstMs:Number.POSITIVE_INFINITY,lastMs:0,isReal:!1};if(!_r(t.sessionsDir))return n;let s=[];try{s=ks(t.sessionsDir).filter(l=>l.endsWith(".db"))}catch{return n}if(s.length===0)return n;let o=null;try{o=e()}catch{return n}if(!o)return n;let i=new Set,a=new Set;for(let l of s){let d=vt(t.sessionsDir,l);try{let f=new o(d,{readonly:!0});try{let h=f.prepare("SELECT COUNT(*) AS cnt, COALESCE(SUM(LENGTH(data)), 0) AS bytes FROM session_events").get();h&&(n.eventCount+=Number(h.cnt??0),n.dataBytes+=Number(h.bytes??0));try{let p=f.prepare("SELECT COUNT(*) AS cnt FROM session_meta").get();n.sessionCount+=Number(p?.cnt??0)}catch{}try{let p=f.prepare("SELECT COALESCE(SUM(length(snapshot)), 0) AS bytes FROM session_resume WHERE consumed = 1").get();p?.bytes&&(n.rescueBytes+=Number(p.bytes))}catch{}try{let p=f.prepare("SELECT MIN(created_at) AS mn, MAX(created_at) AS mx FROM session_events").get();if(p?.mn){let m=Date.parse(p.mn+(p.mn.endsWith("Z")?"":"Z"));Number.isFinite(m)&&m<n.firstMs&&(n.firstMs=m)}if(p?.mx){let m=Date.parse(p.mx+(p.mx.endsWith("Z")?"":"Z"));Number.isFinite(m)&&m>n.lastMs&&(n.lastMs=m)}}catch{}try{let p=f.prepare("SELECT DISTINCT project_dir AS p FROM session_events WHERE project_dir != ''").all();for(let m of p)m.p&&i.add(m.p)}catch{}try{let p=f.prepare("SELECT DISTINCT session_id AS s FROM session_events").all();for(let m of p)m.s&&a.add(m.s)}catch{}}finally{f.close()}}catch{}}n.projectDirs=Array.from(i),n.uuidConvs=a.size;let c=n.eventCount>0?n.dataBytes/n.eventCount:0,u=n.lastMs>0&&r.nowMs-n.lastMs<=r.recencyMs;return n.isReal=n.eventCount>=r.minEvents&&i.size>=r.minProjects&&u&&c>=r.minAvgBytes,n}function gc(t){let e=FD({home:t?.home}),r=t?.loadDatabase??He,n={...qD,...t?.filter??{},nowMs:t?.filter?.nowMs??Date.now()},s=[],o=0,i=0,a=0;for(let c of e){if(!_r(c.sessionsDir))continue;let u=BD(c,r,n);s.push(u),o+=u.eventCount,i+=u.sessionCount,a+=u.dataBytes+u.rescueBytes}return{totalEvents:o,totalSessions:i,totalBytes:a,perAdapter:s}}var SS={project:"What you're building",feedback:"How you work",user:"Who you are",reference:"Where to look",memory:"Long-term context",other:"Other notes"},HD={"claude-code":"Claude Code","gemini-cli":"Gemini CLI",antigravity:"Antigravity","antigravity-cli":"Antigravity CLI",openclaw:"Openclaw",codex:"Codex CLI",cursor:"Cursor","vscode-copilot":"VS Code Copilot","copilot-cli":"GitHub Copilot CLI",kiro:"Kiro",pi:"Pi",omp:"OMP","qwen-code":"Qwen Code",kilo:"Kilo",opencode:"OpenCode",zed:"Zed","jetbrains-copilot":"JetBrains"};function fc(t){return HD[t]??t}function Ve(t){if(!Number.isFinite(t)||t<=0)return"0 B";if(t<1024)return`${Math.round(t)} B`;let e=t/1024;if(e<1024)return e<100?`${e.toFixed(1)} KB`:`${Math.round(e)} KB`;let r=e/1024;if(r<1024)return r<100?`${r.toFixed(1)} MB`:`${Math.round(r)} MB`;let n=r/1024;return n<100?`${n.toFixed(2)} GB`:`${n.toFixed(1)} GB`}function VD(t){let e=parseFloat(t);if(isNaN(e)||e<1)return"< 1 min";if(e<60)return`${Math.round(e)} min`;let r=Math.floor(e/60),n=Math.round(e%60);return n>0?`${r}h ${n}m`:`${r}h`}function lc(t){if(!t)return!1;try{return Intl.DateTimeFormat.supportedLocalesOf(t).length===0?!1:(new Intl.DateTimeFormat(t),!0)}catch{return!1}}function WD(){let t=process.env??{},e=t.CONTEXT_MODE_LOCALE??"";if(e&&!lc(e)&&(e=""),!e){if(process.platform==="darwin"){try{let n=zD("defaults",["read","-g","AppleLocale"],{encoding:"utf8",timeout:500}).trim();n&&(e=n.replace(/_/g,"-"))}catch{}e&&!lc(e)&&(e="")}if(!e&&(t.LC_TIME||t.LANG)){let n=(t.LC_TIME||t.LANG||"").split(".")[0];n&&(e=n.replace(/_/g,"-")),e&&!lc(e)&&(e="")}if(!e)try{e=new Intl.DateTimeFormat().resolvedOptions().locale}catch{e="en-US"}}let r=t.CONTEXT_MODE_TZ??"";if(!r)try{r=new Intl.DateTimeFormat().resolvedOptions().timeZone}catch{r="UTC"}return lc(e)||(e="en-US"),{locale:e,tz:r||"UTC"}}function pS(t){let e=hc();return e?t===e?"~":t.startsWith(e+LD)?"~"+t.slice(e.length):t:t}function GD(t,e,r){if(!Number.isFinite(e)||e<=0)return[];let n=e*ni(),s=(y,b=2)=>y.toFixed(b),o=Math.round(n/20),i=(n/200).toFixed(1),a=Math.round(n/73.67),c=Math.round(n*10),u=r>0?Math.round(n*10/r*365):0,l=(e*3/1e6).toFixed(2),d=(e*2.5/1e6).toFixed(2),f=(e*1.25/1e6).toFixed(2),h=(e*1/1e6).toFixed(2),p=process.env.PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN!==void 0,m=process.env.PI_CONTEXT_MODE_MODEL_ID,g=[];return p&&m?g.push(` $${s(n)} of ${m} tokens your team didn't burn.`):p?g.push(` $${s(n)} of tokens your team didn't burn.`):g.push(` $${s(n)} of Opus 4.7 tokens your team didn't burn.`),g.push(` context-mode kept ${Ve(t)} out of context \u2014 that's ${o} months of Cursor Pro paid for itself.`),c>0&&u>0&&(g.push(""),g.push(` Scale across a 10-dev team and that's ~$${u.toLocaleString("en-US")}/year saved.`)),p||(g.push(""),g.push(" (Opus rates shown for context. On cheaper models the dollar number drops; the savings ratio holds.)")),g}function KD(t){let{conversation:e,lifetime:r,multiAdapter:n,realBytes:s,cwd:o,locale:i,tz:a,now:c,version:u,latestVersion:l}=t,d=[],f=e.events*yS,h=Math.round((e.snapshotBytes??0)/4),p=f+h,m=s?.conversation?.totalSavedTokens??0,g=Math.max(p,m),y=(r?.totalEvents??0)*yS,b=Math.round((r?.rescueBytes??0)/4),_=y+b,x=s?.lifetime?.totalSavedTokens??0,w=Math.max(_,x),I=s?.lifetime?.bytesReturned??0,R=s?.lifetime?.bytesAvoided??0,M=I+R>0?Math.max(1,Math.floor(I/4)):Math.max(1,Math.round(w*.02)),z=n?.totalBytes&&n.totalBytes>0?n.totalBytes:w*4,W=s?.conversation?s.conversation.eventDataBytes+s.conversation.bytesAvoided+s.conversation.snapshotBytes:g*4,$=e.daysAlive>=1?`${e.daysAlive.toFixed(1)} days alive \xB7 still going`:`${Math.max(1,Math.round(e.daysAlive*24))} hr alive \xB7 still going`,T=r?.firstEventMs??n?.perAdapter?.[0]?.firstMs??0,F=T>0?Math.max(1,Math.round((c-T)/864e5)):0,de=n?.totalSessions??r?.totalSessions??1,nt=n?.perAdapter.filter(Pe=>Pe.isReal).length??0,ir;if(n&&nt>=2)ir=`across ${nt} AI tools`;else if(n&&nt===1){let Pe=n.perAdapter.find(Ct=>Ct.isReal);ir=`in ${Pe?fc(Pe.name):"Claude Code"}`}else ir="in Claude Code";F>0?d.push(` Across ${F} days you ran ${qt(de)} conversations ${ir}.`):d.push(` You ran ${qt(de)} conversations ${ir}.`);let Ac=F>0?z/F:0;d.push(` context-mode kept ${Ve(z)} out of your context window \u2014 about ${Ve(Ac)} every single day.`),d.push(""),d.push(""),d.push(" \u2500\u2500\u2500 1. Where you are now \u2500\u2500\u2500"),d.push("");let Em=e.firstEventMs&&e.firstEventMs>0?fS(e.firstEventMs,i,a):"";if(Em?d.push(` This conversation started ${Em} in ${pS(o)}.`):d.push(` This conversation lives in ${pS(o)}.`),d.push(` ${$}.`),e.snapshotsConsumed>0&&e.snapshotBytes>0){let Pe=e.lastRescueMs&&e.lastRescueMs>0?fS(e.lastRescueMs,i,a):"",Ct=Math.round(e.snapshotBytes/1024);Pe?d.push(` On ${Pe}, /compact fired \u2014 ${Ct} KB rescued from snapshot.`):d.push(` /compact fired \u2014 ${Ct} KB rescued from snapshot.`),d.push(" Without that, you'd be re-explaining everything to a blank model right now.")}d.push("");let km=s?.conversation,Tm=km?.bytesAvoided??0,Nc=km?.bytesReturned??0;if(Tm+Nc===0)d.push(" No measurable redirect activity captured yet \u2014 bars will appear once context-mode diverts its first payload."),d.push("");else{let Pe=Tm+Nc,Ct=Math.max(1,Nc),It=Math.max(1,Math.floor(Pe/4)),ar=Math.max(1,Math.floor(Ct/4)),Dc=Ur(It,It,32),XS=Ur(ar,It,32),YS=(1-ar/It)*100,QS=Math.max(1,Math.round(It/ar));d.push(` Without context-mode ${Ve(Pe).padStart(8)} ${Dc} ${qt(It).padStart(7)} tokens`),d.push(` With context-mode ${Ve(Ct).padStart(8)} ${XS} ${qt(ar).padStart(7)} tokens`),d.push(` ${YS.toFixed(1)}% kept out of context \xB7 your AI ran ${QS}\xD7 longer before /compact fired`),d.push("")}if(e.byDay&&e.byDay.length>0){let Pe=e.lastEventMs&&e.firstEventMs?Math.max(1,Math.round((e.lastEventMs-e.firstEventMs)/864e5)+1):e.byDay.length;d.push(` How that ${Ve(W)} built up \u2014 ${Pe} days, ${e.byDay.length} active:`),d.push(""),d.push(...XD(e.byDay,i,a))}d.push(""),d.push(""),d.push(" \u2500\u2500\u2500 2. What this chat captured (used when you --continue or /resume here) \u2500\u2500\u2500"),d.push("");let WS=e.byCategory.reduce((Pe,Ct)=>Pe+Ct.count,0).toLocaleString(i);d.push(` ${WS} things \u2014 files, errors, decisions, agent runs:`),d.push("");let GS=e.byCategory[0]?.count??1;for(let Pe of e.byCategory)d.push(` ${Pe.label.padEnd(26)} ${String(Pe.count).padStart(5)} ${Ur(Pe.count,GS,28)}`);d.push(""),d.push(""),d.push(" \u2500\u2500\u2500 3. The scope, getting wider \u2500\u2500\u2500"),d.push("");let $m=e.firstEventMs&&e.firstEventMs>0?new Intl.DateTimeFormat(i,{timeZone:a,year:"numeric",month:"short",day:"numeric"}).format(new Date(e.firstEventMs)):"",Rm=T>0?new Intl.DateTimeFormat(i,{timeZone:a,year:"numeric",month:"short",day:"numeric"}).format(new Date(T)):"",Pm=r?.distinctProjects??0,KS=r?.totalEvents??n?.totalEvents??0;if(d.push(` This chat: ${Ve(W)} kept out \xB7 ${e.events.toLocaleString(i)} captures${$m?` \xB7 started ${$m}`:""}.`),d.push(` All your work: ${Ve(z)} kept out \xB7 ${KS.toLocaleString(i)} captures across ${Pm} project${Pm===1?"":"s"}${Rm?` \xB7 since ${Rm}`:""}.`),d.push(""),d.push(""),d.push(" \u2500\u2500\u2500 4. The bottom line \u2500\u2500\u2500"),d.push(""),d.push(...GD(z,w,F)),d.push(""),d.push(""),d.push(" \u2500\u2500\u2500 5. What context-mode learned about how you work \u2500\u2500\u2500"),d.push(""),r&&r.autoMemoryCount>0){d.push(` ${r.autoMemoryCount} preferences picked up across ${r.autoMemoryProjects} project${r.autoMemoryProjects===1?"":"s"}:`);let Pe=Object.entries(r.autoMemoryByPrefix).sort((It,ar)=>ar[1]-It[1]),Ct=Pe.length>0?Pe[0][1]:1;for(let[It,ar]of Pe){let Dc=SS[It]??It;d.push(` ${Dc.padEnd(26)} ${String(ar).padStart(2)} ${Ur(ar,Ct,20)}`)}}else d.push(" No preferences learned yet \u2014 context-mode picks them up automatically.");d.push(""),d.push(""),d.push(" Your AI talks less, remembers more, costs less."),d.push(` Locale ${i} \xB7 timezone ${a} \xB7 pricing examples for illustration only.`),d.push("");let JS=u?`v${u}`:"context-mode";return d.push(` ${JS}`),u&&l&&l!=="unknown"&&bS(l,u)&&d.push(` Update available: v${u} -> v${l} | ctx_upgrade`),JD(d)}function JD(t){let e=[],r=0;for(let n of t)n===""?(r++,r<=2&&e.push(n)):(r=0,e.push(n));for(;e.length>0&&e[e.length-1]==="";)e.pop();return e}function XD(t,e,r){if(t.length===0)return[];let n=[...t].sort((f,h)=>f.ms-h.ms),s=n[0],o=n[n.length-1],i=Math.max(1,o.ms-s.ms),a=n[0];for(let f of n)f.count>a.count&&(a=f);let c=56,u=Array.from({length:c},()=>"\u2500");for(let f of n){let h=Math.round((f.ms-s.ms)/i*(c-1)),p="\u25CF";f===a&&(p="\u2588"),(f.rescueBytes??0)>0&&(p="\u25C6"),u[h]=p}let l=f=>{let h=new Intl.DateTimeFormat(e,{timeZone:r,month:"short",day:"numeric"}).formatToParts(new Date(f)),p=(h.find(g=>g.type==="month")?.value??"").toLowerCase(),m=h.find(g=>g.type==="day")?.value??"";return`${p} ${m}`},d=[];d.push(` ${l(s.ms)} ${u.join("")} ${l(o.ms)}`),d.push("");for(let f of n){let h=l(f.ms).padEnd(7),p=`${f.count} captures`,m=f===a?" \u2190 peak":"",g=(f.rescueBytes??0)>0?` \u25C6 /compact rescued ${Math.round((f.rescueBytes??0)/1024)} KB`:"";d.push(` ${h} ${p}${m}${g}`)}return d.push(""),d.push(" \u25CF active day \u2588 peak day \u25C6 /compact rescue"),d}function fS(t,e,r){if(!Number.isFinite(t)||t<=0)return"";let n=new Date(t);if(Number.isNaN(n.getTime()))return"";let s=new Intl.DateTimeFormat(e,{timeZone:r,year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",hour12:!1}).formatToParts(n),o=d=>s.find(f=>f.type===d)?.value??"",i=o("day"),a=o("month"),c=o("year"),u=o("hour"),l=o("minute");return u==="24"&&(u="00"),`${i} ${a} ${c} at ${u}:${l} (${r})`}function qt(t){return t>=1e6?`${(t/1e6).toFixed(1)}M`:t>=1e3?`${(t/1e3).toFixed(1)}K`:String(t)}function ni(){let t=process.env.PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN;if(t!==void 0&&t!==""){let e=Number(t);if(Number.isFinite(e)&&e>0)return e}return 5/1e6}var eB=5/1e6;function mc(t){return`$${((Number.isFinite(t)&&t>0?t:0)*ni()).toFixed(2)}`}function Ur(t,e,r=40){if(e<=0)return"\u2591".repeat(r);let n=Math.max(1,Math.round(t/e*r));return"\u2588".repeat(Math.min(n,r))+"\u2591".repeat(Math.max(0,r-n))}function mS(t,e){let r=e?.sessionTokensSaved??0;if(t.total_events===0&&(e?.lifetime?.totalEvents??0)===0&&r===0&&(e?.multiAdapter?.totalEvents??0)===0)return[];let n=e?.topN??Number.POSITIVE_INFINITY,s=[];s.push("");let o=e?.multiAdapter,i=o?.perAdapter.filter(m=>m.isReal).length??0,a=o?.totalEvents??e?.lifetime?.totalEvents??t.total_events,c=o?.totalSessions??e?.lifetime?.totalSessions??t.session_count,u=e?.lifetime?.distinctProjects;if(a>0&&u&&u>0){let m=i>=2?" everywhere":"";s.push(` All your work${m} \xB7 ${qt(a)} events captured across ${u} project${u===1?"":"s"} \xB7 ${qt(c)} conversations`)}else{s.push("Persistent memory \u2713 preserved across compact, restart & upgrade");let m=c===0&&r>0?1:c,g=m===1?"1 session":`${qt(m)} sessions`,y=a*256+r;s.push(` ${qt(a)} events \xB7 ${g} \xB7 ~${mc(y)} saved lifetime`)}s.push("");let l=e?.lifetime?.categoryCounts,d;l&&Object.keys(l).length>0?d=Object.entries(l).filter(([,m])=>m>0).map(([m,g])=>({category:m,count:g,label:dc[m]||m})).sort((m,g)=>g.count-m.count):d=(t.by_category??[]).filter(m=>m&&m.count>0);let f=d.slice(0,n),h=f.length>0?f[0].count:1;for(let m of f)s.push(` ${m.label.padEnd(26)} ${String(m.count).padStart(5)} ${Ur(m.count,h,30)}`);let p=Math.max(0,d.length-n);return p>0&&s.push(` ... ${p} more categor${p===1?"y":"ies"}`),s}function hS(t){if(!t||t.autoMemoryCount===0)return[];let e=[];e.push(""),e.push(` Preferences learned \xB7 ${t.autoMemoryCount} across ${t.autoMemoryProjects} project${t.autoMemoryProjects===1?"":"s"}`);let r=Object.entries(t.autoMemoryByPrefix).sort((s,o)=>o[1]-s[1]).slice(0,6),n=r.length>0?r[0][1]:1;for(let[s,o]of r){let i=SS[s]??s;e.push(` ${i.padEnd(26)} ${String(o).padStart(2)} ${Ur(o,n,20)}`)}return e}function gS(t,e){let r=[],n=mc(t),s=(e?.totalEvents??0)*256+t,o=mc(s);return r.push(""),r.push("\u2500".repeat(65)),r.push("Your AI talks less, remembers more, costs less."),r.push(`${n} this session \xB7 ${o} lifetime`),r.push("\u2500".repeat(65)),r}var yS=256;function _S(t){if(!t)return[];let e=[],r=[];for(let s of t.perAdapter)(s.isReal?e:r).push(s);if(e.length===0&&r.length===0)return[];let n=[];if(e.length>0){n.push(""),n.push("Where it came from (tools you actually used \u2014 fixtures + probes filtered):"),n.push("");let s=16,o=10,i=10,a=16;n.push(` ${"Tool".padEnd(s)}${"Captures".padStart(o)}${"Indexed".padStart(i)}${"Total kept out".padStart(a)}`);let c=[...e].sort((u,l)=>l.dataBytes+l.rescueBytes-(u.dataBytes+u.rescueBytes));for(let u of c){let l=u.dataBytes+u.rescueBytes,d=u.eventCount>0?qt(u.eventCount):"\u2014",f=Ve(u.dataBytes),h=Ve(l);n.push(` ${fc(u.name).padEnd(s)}${d.padStart(o)}${f.padStart(i)}${h.padStart(a)}`)}}if(r.length>0){e.length>0&&n.push("");let s=r.map(o=>fc(o.name)).join(", ");n.push(` Skipped (${r.length}): ${s}`),n.push(" These adapters have DBs on disk but only test fixtures, dev skeletons,"),n.push(" or detection probes \u2014 no real chat activity.")}return n}function yc(t,e,r,n){let s=[],o=VD(t.session.uptime_min),i=n?.lifetime,a=n?.mcpUsage,c=n?.conversation,u=n?.realBytes,l=n?.multiAdapter,d=l?.perAdapter.filter(I=>I.isReal).length??0;if(l&&d>0){let I=l.totalSessions||i?.totalSessions||0,R=i?.firstEventMs??0,M=R>0?Math.max(1,Math.round((Date.now()-R)/864e5)):0,z=M>0?`Across ${M} day${M===1?"":"s"} `:"",W=I>0?`you ran ${qt(I)} conversation${I===1?"":"s"} `:"you ran ",$;if(d>=2)$=`across ${d} AI tools`;else{let T=l.perAdapter.find(F=>F.isReal);$=`in ${T?fc(T.name):"Claude Code"}`}s.push(`${z}${W}${$}.`),s.push("")}if(c&&c.events>0){s.length>0&&(s.length=0);let I=WD(),R=n?.cwd??process.cwd(),M=n?.now??Date.now(),z=n?.locale??I.locale,W=n?.tz??I.tz;return s.push(...KD({conversation:c,lifetime:i,multiAdapter:l,realBytes:u,cwd:R,locale:z,tz:W,now:M,version:e,latestVersion:r})),s.join(`
583
583
  `)}let f=t.savings.kept_out+(t.cache?t.cache.bytes_saved:0),h=t.savings.total_bytes_returned,p=t.savings.total_calls,m=f+h,g=m>0?f/m*100:0,y=Math.round(f/4),b=h>0?Math.max(1,Math.round(m/Math.max(h,1))):0;if(f===0){s.push(`context-mode ${o} ${p} calls`),s.push(""),p===0?s.push("No tool calls yet. Use batch_execute or execute to start saving tokens."):s.push(`${Ve(h)} entered context | 0 tokens saved`),s.push(...mS(t.projectMemory,{lifetime:i,multiAdapter:l,sessionTokensSaved:0})),s.push(..._S(l)),s.push(...hS(i)),s.push(...gS(0,i)),s.push("");let I=e?`v${e}`:"context-mode";return s.push(I),e&&r&&r!=="unknown"&&bS(r,e)&&s.push(`Update available: v${e} -> v${r} | ctx_upgrade`),s.join(`
584
584
  `)}s.push(`${qt(y)} tokens saved \xB7 ${g.toFixed(1)}% reduction \xB7 ${o} \xB7 ~${mc(y)} saved (Opus)`),s.push(""),s.push(`Without context-mode |${Ur(m,m)}| ${Ve(m)}`),s.push(`With context-mode |${Ur(h,m)}| ${Ve(h)}`),s.push(""),b>=2?s.push(`${Ve(f)} kept out of your conversation \u2014 ${b}\xD7 longer sessions before compact.`):s.push(`${Ve(f)} kept out of your conversation. Never entered context.`),s.push("");let _=[`${p} calls`];t.cache&&t.cache.hits>0&&_.push(`${t.cache.hits} cache hits (+${Ve(t.cache.bytes_saved)})`),s.push(_.join(" \xB7 "));let x=t.savings.by_tool.filter(I=>I.calls>0);if(x.length>=2){s.push("");let I=x.map(R=>{let M=R.context_kb*1024,z=g<100?M/(1-g/100):M,W=Math.max(0,z-M);return{...R,returnedBytes:M,estimatedSaved:W}}).sort((R,M)=>M.estimatedSaved-R.estimatedSaved);for(let R of I){let M=R.tool.length>22?R.tool.slice(0,19)+"...":R.tool;s.push(` ${M.padEnd(22)} ${String(R.calls).padStart(4)} calls ${Ve(R.estimatedSaved).padStart(8)} saved`)}}if(a&&a.length>0){let I=a.filter(R=>R.median_concurrency!=null&&(R.max_concurrency??1)>1);if(I.length>0){s.push(""),s.push("Parallel I/O \u2713 one call did the work of many \u2014 faster runs, lower bill, same answer.");for(let R of I){let M=R.tool_name.replace(/^mcp__.*?__/,"");s.push(` ${M.padEnd(22)} ${R.calls} batches \xB7 ${R.median_concurrency} typical, ${R.max_concurrency} peak`)}}}s.push(...mS(t.projectMemory,{lifetime:i,multiAdapter:l,sessionTokensSaved:y})),s.push(..._S(l)),s.push(...hS(i)),s.push(...gS(y,i)),s.push("");let w=e?`v${e}`:"context-mode";return s.push(w),e&&r&&r!=="unknown"&&r!==e&&s.push(`Update available: v${e} -> v${r} | ctx_upgrade`),s.join(`
585
- `)}var xc=Os(sz(import.meta.url)),sr=(()=>{let t="0.5.0";if(t!==void 0&&t.trim().length>0)return t.trim();for(let e of["../../package.json","../package.json","./package.json"]){let r=rt(xc,e);if(Ie(r))try{return JSON.parse(oi(r,"utf8")).version}catch{}}return"unknown"})(),Ic=process.env.CONTEXT_MODE_UPSTREAM_CHECK==="1";function LS(){return Ie(rt(xc,"package.json"))?xc:Os(xc)}function cz(t){try{let e=process.platform==="win32"?kc("cmd.exe",["/d","/s","/c","codex plugin list"],{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:5e3}):kc("codex",["plugin","list"],{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:5e3});if(e.status!==0)return t;let r=Lf(String(e.stdout));if(r&&Ie(rt(r,".codex-plugin","hooks.json")))return r}catch{}return t}function jS(t){let e=LS();return t==="codex"?cz(e):e}process.env.CONTEXT_MODE_EMBEDDED_PLUGIN_TOOLS!=="1"&&(process.on("unhandledRejection",t=>{process.stderr.write(`[context-mode] unhandledRejection: ${t}
585
+ `)}var xc=Os(sz(import.meta.url)),sr=(()=>{let t="0.5.2";if(t!==void 0&&t.trim().length>0)return t.trim();for(let e of["../../package.json","../package.json","./package.json"]){let r=rt(xc,e);if(Ie(r))try{return JSON.parse(oi(r,"utf8")).version}catch{}}return"unknown"})(),Ic=process.env.CONTEXT_MODE_UPSTREAM_CHECK==="1";function LS(){return Ie(rt(xc,"package.json"))?xc:Os(xc)}function cz(t){try{let e=process.platform==="win32"?kc("cmd.exe",["/d","/s","/c","codex plugin list"],{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:5e3}):kc("codex",["plugin","list"],{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:5e3});if(e.status!==0)return t;let r=Lf(String(e.stdout));if(r&&Ie(rt(r,".codex-plugin","hooks.json")))return r}catch{}return t}function jS(t){let e=LS();return t==="codex"?cz(e):e}process.env.CONTEXT_MODE_EMBEDDED_PLUGIN_TOOLS!=="1"&&(process.on("unhandledRejection",t=>{process.stderr.write(`[context-mode] unhandledRejection: ${t}
586
586
  `)}),process.on("uncaughtException",t=>{try{YD(2,`[context-mode] uncaughtException: ${t?.message??t}
587
587
  `)}finally{process.exit(1)}}));var An=Va(),Sc=Hv(An),Se=new Ua({name:"context-mode",version:sr}),uz=[];function lz(t={}){if((t.embedded??process.env.CONTEXT_MODE_EMBEDDED_PLUGIN_TOOLS)==="1")return!1;let r=t.platform??mt().platform;if(r!=="opencode"&&r!=="kilo")return!1;let n=t.settings??dz(r);return pz(n)&&fz(n)}function dz(t){let e=t==="kilo"?"kilo":"opencode",r=[rt(`${e}.json`),rt(`${e}.jsonc`),rt(`.${e}`,`${e}.json`),rt(`.${e}`,`${e}.jsonc`),gt(Is(),".config",e,`${e}.json`),gt(Is(),".config",e,`${e}.jsonc`)];for(let n of r)try{if(!Ie(n))continue;return JSON.parse(uS(oi(n,"utf8")))}catch{}return null}function pz(t){let e=t?.plugin;return Array.isArray(e)&&e.some(r=>typeof r=="string"&&r.includes("context-mode"))}function fz(t){let e=t?.mcp;return!!(e&&typeof e=="object"&&!Array.isArray(e)&&Object.prototype.hasOwnProperty.call(e,"context-mode"))}var FS=lz(),pm=!1;function mz(t={}){if(pm)return;pm=!0;let e=t.write??(n=>{process.stderr.write(n)}),r=t.platform??"opencode/kilo";e(`[context-mode] ctx_* tools/list intentionally empty on this MCP child: legacy mcp.context-mode block coexists with plugin: ["context-mode"] in ${r}.json \u2014 plugin-native tools are the supported path (#623). Run \`context-mode upgrade\` to remove the legacy block (preserves other MCP servers).
588
588
  `)}function ZB(){pm=!1}function hz(t=Se){t.server.registerCapabilities({tools:{listChanged:!1}}),t.server.setRequestHandler(mn,async()=>({tools:[]}))}var gz=Se.registerTool.bind(Se);Se.registerTool=(...t)=>{let[e,r,n]=t;if(FS){mz();return}let s=yz(e,n);return uz.push({name:e,config:r,handler:s}),t[2]=s,gz(...t)};function yz(t,e){return async r=>{$x();try{return await e(r)}catch(n){let s=Ez(n);if(s)try{return V(t,s)}catch(o){if(o instanceof Zr)return s;throw o}throw n}finally{Rx()}}}FS&&process.env.CONTEXT_MODE_EMBEDDED_PLUGIN_TOOLS!=="1"&&hz(Se);var _m=new az;async function UB(t,e){let r=typeof t=="string"?{projectDir:t}:t;return _m.run(r,e)}Se.server.registerCapabilities({prompts:{listChanged:!1},resources:{listChanged:!1}});Se.server.setRequestHandler(Yn,async()=>({prompts:[]}));Se.server.setRequestHandler(Jn,async()=>({resources:[]}));Se.server.setRequestHandler(Xn,async()=>({resourceTemplates:[]}));function fm(t){if(Array.isArray(t))return t.map(fm);if(t===null||typeof t!="object")return t;let e={};for(let[r,n]of Object.entries(t))if(r!=="additionalProperties"){if(r==="const"){e.enum=[n];continue}e[r]=fm(n)}return e}function _z(t=Se){try{let r=t.server._requestHandlers?.get("tools/list");if(typeof r!="function")return;t.server.setRequestHandler(mn,async(n,s)=>{let o=await r(n,s);if(o&&Array.isArray(o.tools)){for(let i of o.tools)if(!(!i||i.inputSchema==null))try{i.inputSchema=fm(i.inputSchema)}catch{}}return o})}catch{}}var ii=new qo({runtimes:An,projectRoot:()=>ht()}),Oc=gt(ym(),`cm-fs-preload-${process.pid}.js`);Ec(Oc,`(function(){var __cm_fs=0;process.on('exit',function(){if(__cm_fs>0)try{process.stderr.write('__CM_FS__:'+__cm_fs+'\\n')}catch(e){}});try{var f=require('fs');var ors=f.readFileSync;f.readFileSync=function(){var r=ors.apply(this,arguments);if(Buffer.isBuffer(r))__cm_fs+=r.length;else if(typeof r==='string')__cm_fs+=Buffer.byteLength(r);return r;};}catch(e){}})();