context-mode 1.0.29 → 1.0.30
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/build/server.js +70 -5
- package/build/store.d.ts +12 -0
- package/build/store.js +59 -3
- package/cli.bundle.mjs +101 -101
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server.bundle.mjs +79 -79
|
@@ -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.30"
|
|
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.30",
|
|
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.30",
|
|
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.30",
|
|
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.30",
|
|
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/build/server.js
CHANGED
|
@@ -3,13 +3,13 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
|
-
import { existsSync, unlinkSync, readdirSync, readFileSync, rmSync } from "node:fs";
|
|
6
|
+
import { existsSync, unlinkSync, readdirSync, readFileSync, rmSync, mkdirSync } from "node:fs";
|
|
7
7
|
import { join, dirname, resolve } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import { homedir, tmpdir } from "node:os";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
import { PolyglotExecutor } from "./executor.js";
|
|
12
|
-
import { ContentStore, cleanupStaleDBs } from "./store.js";
|
|
12
|
+
import { ContentStore, cleanupStaleDBs, cleanupStaleContentDBs } from "./store.js";
|
|
13
13
|
import { readBashPolicies, evaluateCommandDenyOnly, extractShellCommands, readToolDenyPatterns, evaluateFilePath, } from "./security.js";
|
|
14
14
|
import { detectRuntimes, getRuntimeSummary, getAvailableLanguages, hasBunRuntime, } from "./runtime.js";
|
|
15
15
|
import { classifyNonZeroExit } from "./exit-classify.js";
|
|
@@ -73,9 +73,74 @@ function maybeIndexSessionEvents(store) {
|
|
|
73
73
|
}
|
|
74
74
|
catch { /* best-effort — session continuity never blocks tools */ }
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Compute a per-project persistent path for the ContentStore.
|
|
78
|
+
* Uses SHA256 of the project dir (normalized for Windows) to avoid collisions.
|
|
79
|
+
*/
|
|
80
|
+
function getStorePath() {
|
|
81
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR
|
|
82
|
+
|| process.env.GEMINI_PROJECT_DIR
|
|
83
|
+
|| process.env.OPENCLAW_PROJECT_DIR
|
|
84
|
+
|| process.cwd();
|
|
85
|
+
const normalized = projectDir.replace(/\\/g, "/");
|
|
86
|
+
const hash = createHash("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
87
|
+
const dir = join(homedir(), ".context-mode", "content");
|
|
88
|
+
mkdirSync(dir, { recursive: true });
|
|
89
|
+
return join(dir, `${hash}.db`);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Detect fresh vs --continue session.
|
|
93
|
+
* SessionStart hook writes {hash}.cleanup on "startup", deletes it on "resume".
|
|
94
|
+
* Flag exists → fresh start → delete old store. Flag missing → continue → keep store.
|
|
95
|
+
* Uses same hash as getStorePath() so they stay in sync.
|
|
96
|
+
*/
|
|
97
|
+
function isFreshStart() {
|
|
98
|
+
try {
|
|
99
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR
|
|
100
|
+
|| process.env.GEMINI_PROJECT_DIR
|
|
101
|
+
|| process.env.OPENCLAW_PROJECT_DIR
|
|
102
|
+
|| process.cwd();
|
|
103
|
+
const normalized = projectDir.replace(/\\/g, "/");
|
|
104
|
+
const hash = createHash("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
105
|
+
// Check all platform config dirs for cleanup flag
|
|
106
|
+
const configDirs = [".claude", ".gemini", ".cursor", ".kiro", ".config/opencode", ".openclaw"];
|
|
107
|
+
for (const configDir of configDirs) {
|
|
108
|
+
const sessionsDir = join(homedir(), configDir, "context-mode", "sessions");
|
|
109
|
+
// Check with and without worktree suffix
|
|
110
|
+
const files = existsSync(sessionsDir) ? readdirSync(sessionsDir) : [];
|
|
111
|
+
if (files.some(f => f.startsWith(hash) && f.endsWith(".cleanup"))) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false; // default: persist (safer than deleting)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
76
121
|
function getStore() {
|
|
77
|
-
if (!_store)
|
|
78
|
-
|
|
122
|
+
if (!_store) {
|
|
123
|
+
const dbPath = getStorePath();
|
|
124
|
+
// Fresh session: delete old store DB for clean slate
|
|
125
|
+
if (isFreshStart()) {
|
|
126
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
127
|
+
try {
|
|
128
|
+
unlinkSync(dbPath + suffix);
|
|
129
|
+
}
|
|
130
|
+
catch { /* may not exist */ }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
_store = new ContentStore(dbPath);
|
|
134
|
+
// One-time startup cleanup: remove stale content DBs (>14 days)
|
|
135
|
+
try {
|
|
136
|
+
const contentDir = join(homedir(), ".context-mode", "content");
|
|
137
|
+
cleanupStaleContentDBs(contentDir, 14);
|
|
138
|
+
_store.cleanupStaleSources(14);
|
|
139
|
+
}
|
|
140
|
+
catch { /* best-effort */ }
|
|
141
|
+
// Also clean old PID-based DBs from migration
|
|
142
|
+
cleanupStaleDBs();
|
|
143
|
+
}
|
|
79
144
|
maybeIndexSessionEvents(_store);
|
|
80
145
|
return _store;
|
|
81
146
|
}
|
|
@@ -1527,7 +1592,7 @@ async function main() {
|
|
|
1527
1592
|
const shutdown = () => {
|
|
1528
1593
|
executor.cleanupBackgrounded();
|
|
1529
1594
|
if (_store)
|
|
1530
|
-
_store.
|
|
1595
|
+
_store.close(); // persist DB for --continue sessions
|
|
1531
1596
|
};
|
|
1532
1597
|
const gracefulShutdown = async () => {
|
|
1533
1598
|
shutdown();
|
package/build/store.d.ts
CHANGED
|
@@ -13,6 +13,11 @@ export type { IndexResult, SearchResult, StoreStats } from "./types.js";
|
|
|
13
13
|
* Remove stale DB files from previous sessions whose processes no longer exist.
|
|
14
14
|
*/
|
|
15
15
|
export declare function cleanupStaleDBs(): number;
|
|
16
|
+
/**
|
|
17
|
+
* Clean up stale per-project content store DBs older than maxAgeDays.
|
|
18
|
+
* Scans the given directory for *.db files and checks mtime.
|
|
19
|
+
*/
|
|
20
|
+
export declare function cleanupStaleContentDBs(contentDir: string, maxAgeDays: number): number;
|
|
16
21
|
export declare class ContentStore {
|
|
17
22
|
#private;
|
|
18
23
|
constructor(dbPath?: string);
|
|
@@ -58,5 +63,12 @@ export declare class ContentStore {
|
|
|
58
63
|
getChunksBySource(sourceId: number): SearchResult[];
|
|
59
64
|
getDistinctiveTerms(sourceId: number, maxTerms?: number): string[];
|
|
60
65
|
getStats(): StoreStats;
|
|
66
|
+
/**
|
|
67
|
+
* Delete sources (and their chunks) older than maxAgeDays.
|
|
68
|
+
* Returns count of deleted sources.
|
|
69
|
+
*/
|
|
70
|
+
cleanupStaleSources(maxAgeDays: number): number;
|
|
71
|
+
/** Get DB file size in bytes. */
|
|
72
|
+
getDBSizeBytes(): number;
|
|
61
73
|
close(): void;
|
|
62
74
|
}
|
package/build/store.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* Use for documentation, API references, and any content where
|
|
8
8
|
* you need EXACT text later — not summaries.
|
|
9
9
|
*/
|
|
10
|
-
import { loadDatabase, applyWALPragmas } from "./db-base.js";
|
|
11
|
-
import { readFileSync, readdirSync, unlinkSync } from "node:fs";
|
|
10
|
+
import { loadDatabase, applyWALPragmas, closeDB } from "./db-base.js";
|
|
11
|
+
import { readFileSync, readdirSync, unlinkSync, existsSync, statSync } from "node:fs";
|
|
12
12
|
import { tmpdir } from "node:os";
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
// ─────────────────────────────────────────────────────────
|
|
@@ -116,6 +116,37 @@ export function cleanupStaleDBs() {
|
|
|
116
116
|
catch { /* ignore readdir errors */ }
|
|
117
117
|
return cleaned;
|
|
118
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Clean up stale per-project content store DBs older than maxAgeDays.
|
|
121
|
+
* Scans the given directory for *.db files and checks mtime.
|
|
122
|
+
*/
|
|
123
|
+
export function cleanupStaleContentDBs(contentDir, maxAgeDays) {
|
|
124
|
+
let cleaned = 0;
|
|
125
|
+
try {
|
|
126
|
+
if (!existsSync(contentDir))
|
|
127
|
+
return 0;
|
|
128
|
+
const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
|
|
129
|
+
const files = readdirSync(contentDir).filter(f => f.endsWith(".db"));
|
|
130
|
+
for (const file of files) {
|
|
131
|
+
try {
|
|
132
|
+
const filePath = join(contentDir, file);
|
|
133
|
+
const mtime = statSync(filePath).mtimeMs;
|
|
134
|
+
if (mtime < cutoff) {
|
|
135
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
136
|
+
try {
|
|
137
|
+
unlinkSync(filePath + suffix);
|
|
138
|
+
}
|
|
139
|
+
catch { /* ignore */ }
|
|
140
|
+
}
|
|
141
|
+
cleaned++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch { /* ignore per-file errors */ }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch { /* ignore readdir errors */ }
|
|
148
|
+
return cleaned;
|
|
149
|
+
}
|
|
119
150
|
// ── Proximity helpers (pure functions) ──
|
|
120
151
|
/** Find all positions of a term in text. */
|
|
121
152
|
function findAllPositions(text, term) {
|
|
@@ -719,8 +750,33 @@ export class ContentStore {
|
|
|
719
750
|
};
|
|
720
751
|
}
|
|
721
752
|
// ── Cleanup ──
|
|
753
|
+
/**
|
|
754
|
+
* Delete sources (and their chunks) older than maxAgeDays.
|
|
755
|
+
* Returns count of deleted sources.
|
|
756
|
+
*/
|
|
757
|
+
cleanupStaleSources(maxAgeDays) {
|
|
758
|
+
const deleteChunks = this.#db.prepare("DELETE FROM chunks WHERE source_id IN (SELECT id FROM sources WHERE datetime(indexed_at) < datetime('now', '-' || ? || ' days'))");
|
|
759
|
+
const deleteChunksTrigram = this.#db.prepare("DELETE FROM chunks_trigram WHERE source_id IN (SELECT id FROM sources WHERE datetime(indexed_at) < datetime('now', '-' || ? || ' days'))");
|
|
760
|
+
const deleteSources = this.#db.prepare("DELETE FROM sources WHERE datetime(indexed_at) < datetime('now', '-' || ? || ' days')");
|
|
761
|
+
const cleanup = this.#db.transaction((days) => {
|
|
762
|
+
deleteChunks.run(days);
|
|
763
|
+
deleteChunksTrigram.run(days);
|
|
764
|
+
return deleteSources.run(days);
|
|
765
|
+
});
|
|
766
|
+
const info = cleanup(maxAgeDays);
|
|
767
|
+
return info.changes;
|
|
768
|
+
}
|
|
769
|
+
/** Get DB file size in bytes. */
|
|
770
|
+
getDBSizeBytes() {
|
|
771
|
+
try {
|
|
772
|
+
return statSync(this.#dbPath).size;
|
|
773
|
+
}
|
|
774
|
+
catch {
|
|
775
|
+
return 0;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
722
778
|
close() {
|
|
723
|
-
this.#db
|
|
779
|
+
closeDB(this.#db); // WAL checkpoint before close — important for persistent DBs
|
|
724
780
|
}
|
|
725
781
|
// ── Vocabulary Extraction ──
|
|
726
782
|
#extractAndStoreVocabulary(content) {
|