context-mode 1.0.54 → 1.0.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/README.md +20 -6
- package/build/adapters/antigravity/index.d.ts +1 -3
- package/build/adapters/antigravity/index.js +0 -30
- package/build/adapters/claude-code/index.d.ts +1 -3
- package/build/adapters/claude-code/index.js +15 -34
- package/build/adapters/codex/index.d.ts +1 -3
- package/build/adapters/codex/index.js +1 -31
- package/build/adapters/cursor/index.d.ts +1 -3
- package/build/adapters/cursor/index.js +0 -11
- package/build/adapters/gemini-cli/index.d.ts +1 -3
- package/build/adapters/gemini-cli/index.js +0 -30
- package/build/adapters/kiro/index.d.ts +1 -3
- package/build/adapters/kiro/index.js +0 -30
- package/build/adapters/openclaw/index.d.ts +1 -3
- package/build/adapters/openclaw/index.js +0 -38
- package/build/adapters/opencode/index.d.ts +1 -3
- package/build/adapters/opencode/index.js +15 -34
- package/build/adapters/types.d.ts +0 -13
- package/build/adapters/vscode-copilot/index.d.ts +1 -3
- package/build/adapters/vscode-copilot/index.js +0 -32
- package/build/adapters/zed/index.d.ts +1 -3
- package/build/adapters/zed/index.js +0 -30
- package/build/executor.d.ts +0 -1
- package/build/executor.js +26 -14
- package/build/openclaw-plugin.js +0 -30
- package/build/opencode-plugin.d.ts +1 -0
- package/build/opencode-plugin.js +1 -8
- package/build/server.js +82 -19
- package/build/truncate.d.ts +4 -17
- package/build/truncate.js +4 -52
- package/cli.bundle.mjs +110 -137
- package/hooks/ensure-deps.mjs +80 -2
- package/hooks/routing-block.mjs +12 -1
- package/hooks/session-helpers.mjs +13 -0
- package/hooks/session-snapshot.bundle.mjs +13 -13
- package/hooks/sessionstart.mjs +5 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server.bundle.mjs +83 -107
- package/skills/context-mode-ops/SKILL.md +111 -0
- package/skills/context-mode-ops/agent-teams.md +198 -0
- package/skills/context-mode-ops/communication.md +224 -0
- package/skills/context-mode-ops/release.md +199 -0
- package/skills/context-mode-ops/review-pr.md +269 -0
- package/skills/context-mode-ops/tdd.md +329 -0
- package/skills/context-mode-ops/triage-issue.md +218 -0
- package/skills/context-mode-ops/validation.md +238 -0
- package/start.mjs +5 -52
package/hooks/ensure-deps.mjs
CHANGED
|
@@ -8,13 +8,22 @@
|
|
|
8
8
|
* hook that needs native modules. Fast path: existsSync check (~0.1ms).
|
|
9
9
|
* Slow path: npm install (first run only, ~5-30s).
|
|
10
10
|
*
|
|
11
|
+
* Also handles ABI compatibility (#148, #203): when the current Node.js
|
|
12
|
+
* version differs from the one better-sqlite3 was compiled against,
|
|
13
|
+
* automatically swaps in a cached binary or rebuilds. This protects
|
|
14
|
+
* both the MCP server AND hooks from ABI mismatch crashes when users
|
|
15
|
+
* have multiple Node versions via mise/volta/fnm/nvm.
|
|
16
|
+
*
|
|
17
|
+
* @see https://github.com/mksglu/context-mode/issues/148
|
|
11
18
|
* @see https://github.com/mksglu/context-mode/issues/172
|
|
19
|
+
* @see https://github.com/mksglu/context-mode/issues/203
|
|
12
20
|
*/
|
|
13
21
|
|
|
14
|
-
import { existsSync } from "node:fs";
|
|
22
|
+
import { existsSync, copyFileSync } from "node:fs";
|
|
15
23
|
import { execSync } from "node:child_process";
|
|
16
24
|
import { resolve, dirname } from "node:path";
|
|
17
25
|
import { fileURLToPath } from "node:url";
|
|
26
|
+
import { createRequire } from "node:module";
|
|
18
27
|
|
|
19
28
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
29
|
const root = resolve(__dirname, "..");
|
|
@@ -23,7 +32,9 @@ const NATIVE_DEPS = ["better-sqlite3"];
|
|
|
23
32
|
|
|
24
33
|
export function ensureDeps() {
|
|
25
34
|
for (const pkg of NATIVE_DEPS) {
|
|
26
|
-
|
|
35
|
+
const pkgDir = resolve(root, "node_modules", pkg);
|
|
36
|
+
if (!existsSync(pkgDir)) {
|
|
37
|
+
// Package not installed at all
|
|
27
38
|
try {
|
|
28
39
|
execSync(`npm install ${pkg} --no-package-lock --no-save --silent`, {
|
|
29
40
|
cwd: root,
|
|
@@ -31,9 +42,76 @@ export function ensureDeps() {
|
|
|
31
42
|
timeout: 120000,
|
|
32
43
|
});
|
|
33
44
|
} catch { /* best effort — hook degrades gracefully without DB */ }
|
|
45
|
+
} else if (
|
|
46
|
+
!existsSync(resolve(pkgDir, "build", "Release")) &&
|
|
47
|
+
!existsSync(resolve(pkgDir, "prebuilds"))
|
|
48
|
+
) {
|
|
49
|
+
// Package installed but native binary missing (e.g., npm ignore-scripts=true)
|
|
50
|
+
try {
|
|
51
|
+
execSync(`npm rebuild ${pkg} --ignore-scripts=false`, {
|
|
52
|
+
cwd: root,
|
|
53
|
+
stdio: "pipe",
|
|
54
|
+
timeout: 120000,
|
|
55
|
+
});
|
|
56
|
+
} catch { /* best effort — hook degrades gracefully without DB */ }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* ABI-aware native binary caching for better-sqlite3 (#148, #203).
|
|
63
|
+
*
|
|
64
|
+
* Users with mise/asdf/volta/fnm may run sessions with different Node
|
|
65
|
+
* versions. Each ABI needs its own compiled binary — cache them
|
|
66
|
+
* side-by-side so switching Node versions doesn't require a rebuild
|
|
67
|
+
* every time.
|
|
68
|
+
*
|
|
69
|
+
* Flow:
|
|
70
|
+
* 1. Check if ABI-specific cache exists → swap in
|
|
71
|
+
* 2. Probe-load better-sqlite3 → if OK, cache current binary
|
|
72
|
+
* 3. If ABI mismatch → npm rebuild, then cache the new binary
|
|
73
|
+
*/
|
|
74
|
+
export function ensureNativeCompat(pluginRoot) {
|
|
75
|
+
try {
|
|
76
|
+
const abi = process.versions.modules;
|
|
77
|
+
const nativeDir = resolve(pluginRoot, "node_modules", "better-sqlite3", "build", "Release");
|
|
78
|
+
const binaryPath = resolve(nativeDir, "better_sqlite3.node");
|
|
79
|
+
const abiCachePath = resolve(nativeDir, `better_sqlite3.abi${abi}.node`);
|
|
80
|
+
|
|
81
|
+
if (!existsSync(nativeDir)) return;
|
|
82
|
+
|
|
83
|
+
// Fast path: cached binary for this ABI already exists
|
|
84
|
+
if (existsSync(abiCachePath)) {
|
|
85
|
+
copyFileSync(abiCachePath, binaryPath);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!existsSync(binaryPath)) return;
|
|
90
|
+
|
|
91
|
+
// Probe: try loading better-sqlite3 with current Node
|
|
92
|
+
try {
|
|
93
|
+
const req = createRequire(resolve(pluginRoot, "package.json"));
|
|
94
|
+
req("better-sqlite3");
|
|
95
|
+
// Load succeeded — cache the working binary for this ABI
|
|
96
|
+
copyFileSync(binaryPath, abiCachePath);
|
|
97
|
+
} catch (probeErr) {
|
|
98
|
+
if (probeErr?.message?.includes("NODE_MODULE_VERSION")) {
|
|
99
|
+
// ABI mismatch — rebuild for current Node version
|
|
100
|
+
execSync("npm rebuild better-sqlite3", {
|
|
101
|
+
cwd: pluginRoot,
|
|
102
|
+
stdio: "pipe",
|
|
103
|
+
timeout: 60000,
|
|
104
|
+
});
|
|
105
|
+
if (existsSync(binaryPath)) {
|
|
106
|
+
copyFileSync(binaryPath, abiCachePath);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
34
109
|
}
|
|
110
|
+
} catch {
|
|
111
|
+
/* best effort — caller will report the error on first DB access */
|
|
35
112
|
}
|
|
36
113
|
}
|
|
37
114
|
|
|
38
115
|
// Auto-run on import (like suppress-stderr.mjs)
|
|
39
116
|
ensureDeps();
|
|
117
|
+
ensureNativeCompat(root);
|
package/hooks/routing-block.mjs
CHANGED
|
@@ -35,12 +35,21 @@ export function createRoutingBlock(t) {
|
|
|
35
35
|
- DO NOT use Read for analysis (use execute_file). Read IS correct for files you intend to Edit.
|
|
36
36
|
- DO NOT use WebFetch (use ${t("ctx_fetch_and_index")} instead).
|
|
37
37
|
- Bash is ONLY for git/mkdir/rm/mv/navigation.
|
|
38
|
+
- DO NOT use ${t("ctx_execute")} or ${t("ctx_execute_file")} to create, modify, or overwrite files.
|
|
39
|
+
ctx_execute is for data analysis, log processing, and computation only.
|
|
38
40
|
</forbidden_actions>
|
|
39
41
|
|
|
42
|
+
<file_writing_policy>
|
|
43
|
+
ALWAYS use the native Write tool to create files and Edit tool to modify files.
|
|
44
|
+
NEVER use ${t("ctx_execute")}, ${t("ctx_execute_file")}, or Bash to write file content.
|
|
45
|
+
This applies to all file types: code, configs, plans, specs, YAML, JSON, markdown.
|
|
46
|
+
</file_writing_policy>
|
|
47
|
+
|
|
40
48
|
<output_constraints>
|
|
41
49
|
<word_limit>Keep your final response under 500 words.</word_limit>
|
|
42
50
|
<artifact_policy>
|
|
43
|
-
Write artifacts (code, configs, PRDs) to FILES. NEVER return them as inline text.
|
|
51
|
+
Write artifacts (code, configs, PRDs) to FILES using the native Write tool. NEVER return them as inline text.
|
|
52
|
+
Use Edit tool for modifications to existing files.
|
|
44
53
|
Return only: file path + 1-line description.
|
|
45
54
|
</artifact_policy>
|
|
46
55
|
<response_format>
|
|
@@ -61,6 +70,8 @@ export function createRoutingBlock(t) {
|
|
|
61
70
|
|
|
62
71
|
When the user says "ctx upgrade", "ctx-upgrade", "/ctx-upgrade", or asks to update context-mode:
|
|
63
72
|
→ Call the upgrade MCP tool, execute the returned shell command, display results as a checklist.
|
|
73
|
+
|
|
74
|
+
After /clear: call ${t("ctx_stats")}(reset: true) to reset session stats and search index.
|
|
64
75
|
</ctx_commands>
|
|
65
76
|
</context_window_protection>`;
|
|
66
77
|
}
|
|
@@ -170,3 +170,16 @@ export function getCleanupFlagPath(opts = CLAUDE_OPTS) {
|
|
|
170
170
|
mkdirSync(dir, { recursive: true });
|
|
171
171
|
return join(dir, `${hash}${getWorktreeSuffix()}.cleanup`);
|
|
172
172
|
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Return the per-project clear-stats flag path.
|
|
176
|
+
* Written by SessionStart hook on /clear, read by MCP server to reset stats.
|
|
177
|
+
* Path: ~/<configDir>/context-mode/sessions/<SHA256(projectDir)[:16]>.clear-stats
|
|
178
|
+
*/
|
|
179
|
+
export function getClearStatsFlagPath(opts = CLAUDE_OPTS) {
|
|
180
|
+
const projectDir = getProjectDir(opts);
|
|
181
|
+
const hash = createHash("sha256").update(projectDir).digest("hex").slice(0, 16);
|
|
182
|
+
const dir = join(homedir(), opts.configDir, "context-mode", "sessions");
|
|
183
|
+
mkdirSync(dir, { recursive: true });
|
|
184
|
+
return join(dir, `${hash}${getWorktreeSuffix()}.clear-stats`);
|
|
185
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
function
|
|
2
|
-
`)}function z(t){if(t.length===0)return"";let e=[],n={};for(let i of t)try{let r=JSON.parse(i.data);typeof r.subject=="string"?e.push(r.subject):typeof r.taskId=="string"&&typeof r.status=="string"&&(n[r.taskId]=r.status)}catch{}if(e.length===0)return"";let s=new Set(["completed","deleted","failed"]),c=Object.keys(n).sort((i,r)=>Number(i)-Number(r)),u=[];for(let i=0;i<e.length;i++){let r=c[i],f=r?n[r]??"pending":"pending";s.has(f)||u.push(e[i])}if(u.length===0)return"";let
|
|
3
|
-
`)}function
|
|
4
|
-
`)}function
|
|
5
|
-
`)}function
|
|
6
|
-
`)}function
|
|
7
|
-
`)}function
|
|
1
|
+
function l(t,e){return t.length<=e?t:t.slice(0,Math.max(0,e-3))+"..."}function a(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}var G=2048,J=10;function q(t){if(t.length===0)return"";let e=new Map;for(let u of t){let p=u.data,i=e.get(p);i||(i={ops:new Map,last:""},e.set(p,i));let r;u.type==="file_write"?r="write":u.type==="file_read"?r="read":u.type==="file_edit"?r="edit":r=u.type,i.ops.set(r,(i.ops.get(r)??0)+1),i.last=r}let s=Array.from(e.entries()).slice(-J),c=[" <active_files>"];for(let[u,{ops:p,last:i}]of s){let r=Array.from(p.entries()).map(([f,g])=>`${f}:${g}`).join(",");c.push(` <file path="${a(u)}" ops="${a(r)}" last="${a(i)}" />`)}return c.push(" </active_files>"),c.join(`
|
|
2
|
+
`)}function z(t){if(t.length===0)return"";let e=[],n={};for(let i of t)try{let r=JSON.parse(i.data);typeof r.subject=="string"?e.push(r.subject):typeof r.taskId=="string"&&typeof r.status=="string"&&(n[r.taskId]=r.status)}catch{}if(e.length===0)return"";let s=new Set(["completed","deleted","failed"]),c=Object.keys(n).sort((i,r)=>Number(i)-Number(r)),u=[];for(let i=0;i<e.length;i++){let r=c[i],f=r?n[r]??"pending":"pending";s.has(f)||u.push(e[i])}if(u.length===0)return"";let p=[" <task_state>"];for(let i of u)p.push(` - ${a(l(i,100))}`);return p.push(" </task_state>"),p.join(`
|
|
3
|
+
`)}function U(t){if(t.length===0)return"";let e=new Set,n=[" <rules>"];for(let s of t){let c=s.data;e.has(c)||(e.add(c),s.type==="rule_content"?n.push(` <rule_content>${a(l(s.data,400))}</rule_content>`):n.push(` - ${a(l(s.data,200))}`))}return n.push(" </rules>"),n.join(`
|
|
4
|
+
`)}function V(t){if(t.length===0)return"";let e=new Set,n=[" <decisions>"];for(let s of t){let c=s.data;e.has(c)||(e.add(c),n.push(` - ${a(l(s.data,200))}`))}return n.push(" </decisions>"),n.join(`
|
|
5
|
+
`)}function Y(t,e,n){let s=[];if(!t&&e.length===0&&!n)return"";s.push(" <environment>"),t&&s.push(` <cwd>${a(t.data)}</cwd>`),n&&s.push(` <git op="${a(n.data)}" />`);for(let c of e)s.push(` <env>${a(l(c.data,150))}</env>`);return s.push(" </environment>"),s.join(`
|
|
6
|
+
`)}function H(t){if(t.length===0)return"";let e=[" <errors_encountered>"];for(let n of t)e.push(` - ${a(l(n.data,150))}`);return e.push(" </errors_encountered>"),e.join(`
|
|
7
|
+
`)}function K(t){return` <intent mode="${a(t.data)}">${a(l(t.data,100))}</intent>`}function P(t){if(t.length===0)return"";let e=[" <subagents>"];for(let n of t){let s=n.type==="subagent_completed"?"completed":n.type==="subagent_launched"?"launched":"unknown";e.push(` <agent status="${s}">${a(l(n.data,200))}</agent>`)}return e.push(" </subagents>"),e.join(`
|
|
8
8
|
`)}function Q(t){if(t.length===0)return"";let e=new Map;for(let s of t){let c=s.data.split(":")[0].trim();e.set(c,(e.get(c)??0)+1)}let n=[" <mcp_tools>"];for(let[s,c]of e)n.push(` <tool name="${a(s)}" calls="${c}" />`);return n.push(" </mcp_tools>"),n.join(`
|
|
9
|
-
`)}function et(t,e){let n=e?.maxBytes??G,s=e?.compactCount??1,c=new Date().toISOString(),u=[],
|
|
10
|
-
`),y;if(
|
|
11
|
-
${
|
|
12
|
-
${
|
|
13
|
-
${
|
|
14
|
-
${
|
|
9
|
+
`)}function et(t,e){let n=e?.maxBytes??G,s=e?.compactCount??1,c=new Date().toISOString(),u=[],p=[],i=[],r=[],f=[],g=[],$=[],h=[],E=[],S=[],w=[],m=[];for(let o of t)switch(o.category){case"file":u.push(o);break;case"task":p.push(o);break;case"rule":i.push(o);break;case"decision":r.push(o);break;case"cwd":f.push(o);break;case"error":g.push(o);break;case"env":$.push(o);break;case"git":h.push(o);break;case"subagent":E.push(o);break;case"intent":S.push(o);break;case"mcp":w.push(o);break;case"plan":m.push(o);break}let b=[],x=q(u);x&&b.push(x);let B=z(p);B&&b.push(B);let j=U(i);j&&b.push(j);let d=[],I=V(r);I&&d.push(I);let R=f.length>0?f[f.length-1]:void 0,F=h.length>0?h[h.length-1]:void 0,L=Y(R,$,F);L&&d.push(L);let M=H(g);M&&d.push(M);let D=E.filter(o=>o.type==="subagent_completed"),O=P(D);O&&d.push(O),m.length>0&&m[m.length-1].type==="plan_enter"&&d.push(' <plan_mode status="active" />');let v=[];if(S.length>0){let o=S[S.length-1];v.push(K(o))}let T=Q(w);T&&v.push(T);let X=E.filter(o=>o.type==="subagent_launched"),A=P(X);A&&v.push(A);let _=`<session_resume compact_count="${s}" events_captured="${t.length}" generated_at="${c}">`,k="</session_resume>",C=[b,d,v];for(let o=C.length;o>=0;o--){let N=C.slice(0,o).flat().join(`
|
|
10
|
+
`),y;if(N?y=`${_}
|
|
11
|
+
${N}
|
|
12
|
+
${k}`:y=`${_}
|
|
13
|
+
${k}`,Buffer.byteLength(y)<=n)return y}return`${_}
|
|
14
|
+
${k}`}export{et as buildResumeSnapshot,q as renderActiveFiles,V as renderDecisions,Y as renderEnvironment,H as renderErrors,K as renderIntent,Q as renderMcpTools,U as renderRules,P as renderSubagents,z as renderTaskState};
|
package/hooks/sessionstart.mjs
CHANGED
|
@@ -19,7 +19,7 @@ import { createRoutingBlock } from "./routing-block.mjs";
|
|
|
19
19
|
import { createToolNamer } from "./core/tool-naming.mjs";
|
|
20
20
|
|
|
21
21
|
const ROUTING_BLOCK = createRoutingBlock(createToolNamer("claude-code"));
|
|
22
|
-
import { readStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath } from "./session-helpers.mjs";
|
|
22
|
+
import { readStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath, getClearStatsFlagPath } from "./session-helpers.mjs";
|
|
23
23
|
import { writeSessionEventsFile, buildSessionDirective, getSessionEvents, getLatestSessionEvents } from "./session-directive.mjs";
|
|
24
24
|
import { createSessionLoaders } from "./session-loaders.mjs";
|
|
25
25
|
import { join, dirname } from "node:path";
|
|
@@ -145,7 +145,10 @@ try {
|
|
|
145
145
|
}
|
|
146
146
|
} catch { /* best effort — never block session start */ }
|
|
147
147
|
}
|
|
148
|
-
// "clear" —
|
|
148
|
+
// "clear" — signal MCP server to reset session stats
|
|
149
|
+
if (source === "clear") {
|
|
150
|
+
try { writeFileSync(getClearStatsFlagPath(), String(Date.now())); } catch { /* best effort */ }
|
|
151
|
+
}
|
|
149
152
|
} catch (err) {
|
|
150
153
|
// Session continuity is best-effort — never block session start
|
|
151
154
|
try {
|
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.57",
|
|
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.57",
|
|
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",
|