context-mode 1.0.156 → 1.0.157
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/.codex-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/hooks/platform-bridge.mjs +133 -2
- package/hooks/session-loaders.mjs +1 -11
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Claude Code plugins by Mert Koseoğlu",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.157"
|
|
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.157",
|
|
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.157",
|
|
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",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.157",
|
|
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.157",
|
|
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.157",
|
|
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",
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import os from "node:os";
|
|
10
|
+
import { execSync } from "node:child_process";
|
|
10
11
|
|
|
11
12
|
const CACHE_TTL_MS = 60_000;
|
|
12
13
|
const FETCH_TIMEOUT_MS = 2_000;
|
|
@@ -110,6 +111,117 @@ export function buildUrl(cfg, _eventType) {
|
|
|
110
111
|
return `${cfg.platform_url}/events`;
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
// === Project identity resolution — worktree-invariant canonicalization ===
|
|
115
|
+
// Filesystem path is the wrong identifier for "project": git worktrees fork
|
|
116
|
+
// the path while keeping the same repo, monorepos collapse N packages into
|
|
117
|
+
// one umbrella, and forks of the same repo look like different projects.
|
|
118
|
+
// Resolve to a stable identity using:
|
|
119
|
+
// 1. Closest package.json `name` if it lives DEEPER than the .git root
|
|
120
|
+
// (monorepo sub-package — preserve granularity)
|
|
121
|
+
// 2. git config remote.origin.url, normalized
|
|
122
|
+
// (worktrees of one repo collapse to one identity)
|
|
123
|
+
// 3. Closest package.json `name` at any depth
|
|
124
|
+
// (local-only Node project)
|
|
125
|
+
// 4. basename(projectDir) (last resort)
|
|
126
|
+
const _projectIdentityCache = new Map();
|
|
127
|
+
|
|
128
|
+
function resolveProjectIdentity(projectDir) {
|
|
129
|
+
if (typeof projectDir !== "string" || !projectDir) return null;
|
|
130
|
+
if (_projectIdentityCache.has(projectDir)) return _projectIdentityCache.get(projectDir);
|
|
131
|
+
const id = computeProjectIdentity(projectDir);
|
|
132
|
+
_projectIdentityCache.set(projectDir, id);
|
|
133
|
+
return id;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function computeProjectIdentity(projectDir) {
|
|
137
|
+
let absoluteDir;
|
|
138
|
+
try {
|
|
139
|
+
absoluteDir = path.resolve(projectDir);
|
|
140
|
+
} catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const walked = walkUpFromDir(absoluteDir);
|
|
144
|
+
const pkg = walked.packageJson;
|
|
145
|
+
const gitTop = walked.gitToplevel;
|
|
146
|
+
|
|
147
|
+
// (1) Monorepo sub-package: package.json STRICTLY deeper than .git root.
|
|
148
|
+
if (pkg && gitTop && pkg.dir !== gitTop && pkg.dir.length > gitTop.length && pkg.name) {
|
|
149
|
+
return pkg.name;
|
|
150
|
+
}
|
|
151
|
+
// (2) Git remote URL.
|
|
152
|
+
const remote = gitTop ? readGitRemote(gitTop) : null;
|
|
153
|
+
if (remote) return normalizeRemoteUrl(remote);
|
|
154
|
+
// (3) Closest package.json (any depth).
|
|
155
|
+
if (pkg?.name) return pkg.name;
|
|
156
|
+
// (4) Basename.
|
|
157
|
+
return path.basename(absoluteDir);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function walkUpFromDir(start) {
|
|
161
|
+
let dir = start;
|
|
162
|
+
let pkg = null;
|
|
163
|
+
let gitTop = null;
|
|
164
|
+
// Safety: cap walk to 64 levels; real filesystems never hit this.
|
|
165
|
+
for (let i = 0; i < 64; i++) {
|
|
166
|
+
if (!pkg) {
|
|
167
|
+
const pkgPath = path.join(dir, "package.json");
|
|
168
|
+
if (fs.existsSync(pkgPath)) {
|
|
169
|
+
try {
|
|
170
|
+
const parsed = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
171
|
+
if (typeof parsed?.name === "string" && parsed.name.trim()) {
|
|
172
|
+
pkg = { dir, name: parsed.name.trim() };
|
|
173
|
+
}
|
|
174
|
+
} catch { /* malformed — skip silently */ }
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (!gitTop && fs.existsSync(path.join(dir, ".git"))) {
|
|
178
|
+
gitTop = dir;
|
|
179
|
+
}
|
|
180
|
+
if (pkg && gitTop) break;
|
|
181
|
+
const parent = path.dirname(dir);
|
|
182
|
+
if (parent === dir) break;
|
|
183
|
+
dir = parent;
|
|
184
|
+
}
|
|
185
|
+
return { packageJson: pkg, gitToplevel: gitTop };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function readGitRemote(gitTop) {
|
|
189
|
+
try {
|
|
190
|
+
const url = execSync("git config --get remote.origin.url", {
|
|
191
|
+
cwd: gitTop,
|
|
192
|
+
encoding: "utf8",
|
|
193
|
+
timeout: 500,
|
|
194
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
195
|
+
}).trim();
|
|
196
|
+
return url || null;
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Canonical wire shape: host/path, lowercased host, no scheme, no .git suffix,
|
|
203
|
+
// no embedded credentials. All clone-equivalents collapse to one identity.
|
|
204
|
+
function normalizeRemoteUrl(url) {
|
|
205
|
+
let u = String(url).trim();
|
|
206
|
+
// SSH form (git@host:org/repo) → host/org/repo
|
|
207
|
+
const sshMatch = u.match(/^[a-z0-9_-]+@([^:]+):(.+)$/i);
|
|
208
|
+
if (sshMatch) {
|
|
209
|
+
u = `${sshMatch[1]}/${sshMatch[2]}`;
|
|
210
|
+
} else {
|
|
211
|
+
// scheme://[user[:pass]@]host/path → host/path
|
|
212
|
+
u = u.replace(/^[a-z]+:\/\/(?:[^@/]+@)?/i, "");
|
|
213
|
+
}
|
|
214
|
+
u = u.replace(/\.git\/?$/i, "").replace(/\/+$/, "");
|
|
215
|
+
// Lowercase host segment only (paths can be case-sensitive)
|
|
216
|
+
const slash = u.indexOf("/");
|
|
217
|
+
if (slash > 0) {
|
|
218
|
+
u = u.slice(0, slash).toLowerCase() + u.slice(slash);
|
|
219
|
+
} else {
|
|
220
|
+
u = u.toLowerCase();
|
|
221
|
+
}
|
|
222
|
+
return u;
|
|
223
|
+
}
|
|
224
|
+
|
|
113
225
|
// === Privacy: secret + PII redaction ===
|
|
114
226
|
const SECRETS = [
|
|
115
227
|
/\b(?:ghp|gho|ghs|ghu|github_pat)_[A-Za-z0-9_]{20,}\b/g, // GitHub
|
|
@@ -157,7 +269,16 @@ export async function maybeForward(event, platform, opts = {}) {
|
|
|
157
269
|
const cfg = readConfig();
|
|
158
270
|
if (!cfg) return;
|
|
159
271
|
|
|
160
|
-
|
|
272
|
+
// Project identity must be resolved from the RAW projectDir — the resolver
|
|
273
|
+
// reads `git config` against the actual filesystem path. After sanitize,
|
|
274
|
+
// $HOME-normalization would break the lookup. We overlay the resolved id
|
|
275
|
+
// back onto the event so the sanitize/walk path sees the canonical value
|
|
276
|
+
// (URL or package name, which need no further normalization).
|
|
277
|
+
const resolvedProject = resolveProjectIdentity(event?.projectDir);
|
|
278
|
+
const eventWithProject = resolvedProject !== null
|
|
279
|
+
? { ...event, project: resolvedProject }
|
|
280
|
+
: event;
|
|
281
|
+
const ev = sanitizeEvent(eventWithProject);
|
|
161
282
|
const ctrl = new AbortController();
|
|
162
283
|
const t = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
|
|
163
284
|
|
|
@@ -204,6 +325,16 @@ export const _internal = {
|
|
|
204
325
|
sanitizeEvent,
|
|
205
326
|
privacyTransform,
|
|
206
327
|
configPath,
|
|
207
|
-
|
|
328
|
+
resolveProjectIdentity,
|
|
329
|
+
normalizeRemoteUrl,
|
|
330
|
+
walkUpFromDir,
|
|
331
|
+
resetState: () => {
|
|
332
|
+
_cache = null;
|
|
333
|
+
_cacheLoadedAt = 0;
|
|
334
|
+
_warned = false;
|
|
335
|
+
_fsLoads = 0;
|
|
336
|
+
_projectIdentityCache.clear();
|
|
337
|
+
},
|
|
208
338
|
get fsLoads() { return _fsLoads; },
|
|
339
|
+
get projectIdentityCacheSize() { return _projectIdentityCache.size; },
|
|
209
340
|
};
|
|
@@ -107,18 +107,8 @@ export function attributeAndInsertEvents(db, sessionId, events, input, projectDi
|
|
|
107
107
|
if (hasPlatformConfig()) {
|
|
108
108
|
const platform = detectPlatformFromEnv();
|
|
109
109
|
for (let i = 0; i < events.length; i++) {
|
|
110
|
-
const attr = attributions[i];
|
|
111
110
|
maybeForward(
|
|
112
|
-
{
|
|
113
|
-
...events[i],
|
|
114
|
-
...attr,
|
|
115
|
-
session_id: sessionId,
|
|
116
|
-
// Canonical alias — server reads `project` (snake-case shape on the wire);
|
|
117
|
-
// attribution-side stores `projectDir` (camelCase TS interface). Surfacing
|
|
118
|
-
// both keeps the wire shape stable without forcing the attribution module
|
|
119
|
-
// to change its public type.
|
|
120
|
-
project: attr?.projectDir,
|
|
121
|
-
},
|
|
111
|
+
{ ...events[i], ...attributions[i], session_id: sessionId },
|
|
122
112
|
platform,
|
|
123
113
|
);
|
|
124
114
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.157",
|
|
7
7
|
"sandbox": {
|
|
8
8
|
"mode": "permissive",
|
|
9
9
|
"filesystem_access": "full",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.157",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP plugin that saves 98% of your context window. Works with Claude Code, Gemini CLI, VS Code Copilot, OpenCode, and Codex CLI. Sandboxed code execution, FTS5 knowledge base, and intent-driven search.",
|
|
6
6
|
"author": "Mert Koseoğlu",
|