@senoldogann/context-manager 0.1.30 → 0.2.0
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 +14 -2
- package/bin/ccm.js +58 -15
- package/package.json +2 -2
- package/test/installer.test.js +69 -0
package/README.md
CHANGED
|
@@ -104,13 +104,14 @@ CCM uses a **Local-First** architecture:
|
|
|
104
104
|
|
|
105
105
|
### Environment Variables
|
|
106
106
|
|
|
107
|
-
Create `~/.ccm/.env
|
|
107
|
+
Create `~/.ccm/.env` (or start from the repository's `.env.example`):
|
|
108
108
|
|
|
109
109
|
```ini
|
|
110
110
|
# Local (Recommended)
|
|
111
111
|
EMBEDDING_PROVIDER=ollama
|
|
112
112
|
EMBEDDING_HOST=http://127.0.0.1:11434
|
|
113
113
|
EMBEDDING_MODEL=mxbai-embed-large
|
|
114
|
+
EMBEDDING_API_KEY=ollama
|
|
114
115
|
|
|
115
116
|
# Cloud (Optional)
|
|
116
117
|
EMBEDDING_PROVIDER=openai
|
|
@@ -139,6 +140,12 @@ CCM_EMBED_DATA_FILES=0
|
|
|
139
140
|
CCM_ALLOW_UNVERIFIED_BINARIES=0
|
|
140
141
|
```
|
|
141
142
|
|
|
143
|
+
Advanced overrides:
|
|
144
|
+
- `CCM_PROJECT_ROOT` pins the default project root used by the wrapper and MCP fallback engine.
|
|
145
|
+
- `CCM_DB_PATH` overrides the default MCP vector DB location.
|
|
146
|
+
- `.env.example` in the repository contains chunking, batch-size, hybrid-weight, and compatibility-alias settings such as `OPENAI_API_KEY`, `CCM_SKIP_CHECKSUM`, `CCM_MCP_REQUIRE_ALLOWED_ROOTS`, `CCM_EMBED_DATA`, and `EMBEDDING_DISABLED`.
|
|
147
|
+
- Hybrid scoring defaults and tuning notes live in [`docs/hybrid-ranking.md`](https://github.com/senoldogann/LLM-Context-Manager/blob/main/docs/hybrid-ranking.md).
|
|
148
|
+
|
|
142
149
|
**Production Tip:** Set `CCM_ALLOWED_ROOTS` and enable `CCM_REQUIRE_ALLOWED_ROOTS=1` to restrict MCP access.
|
|
143
150
|
|
|
144
151
|
---
|
|
@@ -169,7 +176,7 @@ This package handles:
|
|
|
169
176
|
- The npm wrapper verifies checksums before using downloaded binaries
|
|
170
177
|
- Redirects are restricted to approved GitHub release hosts
|
|
171
178
|
- Release builds run for Linux, macOS, and Windows before assets are attached
|
|
172
|
-
- npm
|
|
179
|
+
- npm publication is a manual step from `npm/` after the GitHub Release assets are attached
|
|
173
180
|
- The same smoke path is documented here and in the main repository README
|
|
174
181
|
|
|
175
182
|
### ✅ Binary Integrity
|
|
@@ -190,6 +197,11 @@ Enable `CCM_EMBED_DATA_FILES=1` to include them in semantic search.
|
|
|
190
197
|
|
|
191
198
|
## 📝 Changelog
|
|
192
199
|
|
|
200
|
+
### v0.1.31
|
|
201
|
+
- ✅ `index_project` is idempotent again and ignores CCM's own generated index files
|
|
202
|
+
- ✅ `search_code` falls back cleanly when embeddings are disabled
|
|
203
|
+
- ✅ Added a real-world Guardian MCP end-to-end test for all 9 tools
|
|
204
|
+
|
|
193
205
|
### v0.1.27
|
|
194
206
|
- ✅ `search_code` now returns node IDs and location metadata, with configurable limits
|
|
195
207
|
- ✅ New `find_nodes` MCP tool for graph node discovery before `read_graph`
|
package/bin/ccm.js
CHANGED
|
@@ -37,9 +37,12 @@ let checksumCache = null;
|
|
|
37
37
|
|
|
38
38
|
const MCP_SERVER_NAME = 'context-manager';
|
|
39
39
|
const MCP_COMMAND = 'npx';
|
|
40
|
-
const MCP_ARGS = ['-y',
|
|
40
|
+
const MCP_ARGS = ['-y', `@senoldogann/context-manager@${VERSION}`, 'mcp'];
|
|
41
41
|
const MCP_ENV = {
|
|
42
|
-
RUST_LOG: 'info'
|
|
42
|
+
RUST_LOG: 'info',
|
|
43
|
+
CCM_PROJECT_ROOT: process.cwd(),
|
|
44
|
+
CCM_ALLOWED_ROOTS: process.cwd(),
|
|
45
|
+
CCM_REQUIRE_ALLOWED_ROOTS: '1'
|
|
43
46
|
};
|
|
44
47
|
const ALLOWED_REDIRECT_HOSTS = new Set([
|
|
45
48
|
'github.com',
|
|
@@ -114,12 +117,15 @@ function installJsonConfig(configPath, mcpConfig) {
|
|
|
114
117
|
|
|
115
118
|
let config = { mcpServers: {} };
|
|
116
119
|
if (fs.existsSync(configPath)) {
|
|
120
|
+
const backupPath = `${configPath}.bak`;
|
|
121
|
+
fs.copyFileSync(configPath, backupPath);
|
|
117
122
|
try {
|
|
118
123
|
const content = fs.readFileSync(configPath, 'utf8');
|
|
119
124
|
config = JSON.parse(content);
|
|
120
|
-
fs.copyFileSync(configPath, `${configPath}.bak`);
|
|
121
125
|
} catch (e) {
|
|
122
|
-
|
|
126
|
+
throw new Error(
|
|
127
|
+
`Could not parse ${configPath}. The original file was preserved and copied to ${backupPath}.`
|
|
128
|
+
);
|
|
123
129
|
}
|
|
124
130
|
}
|
|
125
131
|
|
|
@@ -128,11 +134,27 @@ function installJsonConfig(configPath, mcpConfig) {
|
|
|
128
134
|
}
|
|
129
135
|
|
|
130
136
|
config.mcpServers[MCP_SERVER_NAME] = mcpConfig;
|
|
131
|
-
|
|
137
|
+
writeJsonAtomic(configPath, config);
|
|
132
138
|
console.log(`[CCM] ✓ Successfully updated: ${configPath}`);
|
|
133
139
|
return true;
|
|
134
140
|
}
|
|
135
141
|
|
|
142
|
+
function writeJsonAtomic(configPath, value) {
|
|
143
|
+
const tempPath = `${configPath}.${process.pid}.${Date.now()}.tmp`;
|
|
144
|
+
try {
|
|
145
|
+
fs.writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, {
|
|
146
|
+
encoding: 'utf8',
|
|
147
|
+
mode: 0o600
|
|
148
|
+
});
|
|
149
|
+
fs.renameSync(tempPath, configPath);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (fs.existsSync(tempPath)) {
|
|
152
|
+
fs.unlinkSync(tempPath);
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
136
158
|
function installCodexConfig() {
|
|
137
159
|
const listResult = spawnSync('codex', ['mcp', 'list', '--json'], {
|
|
138
160
|
encoding: 'utf8'
|
|
@@ -157,17 +179,26 @@ function installCodexConfig() {
|
|
|
157
179
|
|
|
158
180
|
const existing = existingServers.find((server) => server.name === MCP_SERVER_NAME);
|
|
159
181
|
if (existing) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
if (removeResult.status !== 0) {
|
|
165
|
-
console.warn(`[CCM] Codex MCP removal failed: ${removeResult.stderr.trim()}`);
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
182
|
+
console.log('[CCM] Codex MCP entry already exists; leaving it unchanged.');
|
|
183
|
+
return true;
|
|
168
184
|
}
|
|
169
185
|
|
|
170
|
-
const addArgs = [
|
|
186
|
+
const addArgs = [
|
|
187
|
+
'mcp',
|
|
188
|
+
'add',
|
|
189
|
+
MCP_SERVER_NAME,
|
|
190
|
+
'--env',
|
|
191
|
+
'RUST_LOG=info',
|
|
192
|
+
'--env',
|
|
193
|
+
`CCM_PROJECT_ROOT=${process.cwd()}`,
|
|
194
|
+
'--env',
|
|
195
|
+
`CCM_ALLOWED_ROOTS=${process.cwd()}`,
|
|
196
|
+
'--env',
|
|
197
|
+
'CCM_REQUIRE_ALLOWED_ROOTS=1',
|
|
198
|
+
'--',
|
|
199
|
+
MCP_COMMAND,
|
|
200
|
+
...MCP_ARGS
|
|
201
|
+
];
|
|
171
202
|
const addResult = spawnSync('codex', addArgs, {
|
|
172
203
|
encoding: 'utf8'
|
|
173
204
|
});
|
|
@@ -415,4 +446,16 @@ async function main() {
|
|
|
415
446
|
}
|
|
416
447
|
}
|
|
417
448
|
|
|
418
|
-
main
|
|
449
|
+
if (require.main === module) {
|
|
450
|
+
main();
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
module.exports = {
|
|
454
|
+
MCP_ARGS,
|
|
455
|
+
MCP_ENV,
|
|
456
|
+
installJsonConfig,
|
|
457
|
+
parseChecksums,
|
|
458
|
+
resolveRedirectUrl,
|
|
459
|
+
verifyChecksum,
|
|
460
|
+
writeJsonAtomic
|
|
461
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@senoldogann/context-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "LLM Context Manager MCP Server & CLI wrapper using npx",
|
|
5
5
|
"repository": "https://github.com/senoldogann/LLM-Context-Manager",
|
|
6
6
|
"homepage": "https://github.com/senoldogann/LLM-Context-Manager",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"ccm-mcp": "bin/ccm.js"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
|
-
"test": "
|
|
16
|
+
"test": "node --test test/*.test.js"
|
|
17
17
|
},
|
|
18
18
|
"keywords": [
|
|
19
19
|
"mcp",
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const assert = require('node:assert/strict');
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const os = require('node:os');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const test = require('node:test');
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
MCP_ARGS,
|
|
9
|
+
MCP_ENV,
|
|
10
|
+
installJsonConfig,
|
|
11
|
+
writeJsonAtomic
|
|
12
|
+
} = require('../bin/ccm.js');
|
|
13
|
+
|
|
14
|
+
function tempConfig() {
|
|
15
|
+
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'ccm-installer-'));
|
|
16
|
+
return {
|
|
17
|
+
directory,
|
|
18
|
+
configPath: path.join(directory, 'mcp.json')
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test('installer merges MCP config and preserves existing settings', () => {
|
|
23
|
+
const { configPath } = tempConfig();
|
|
24
|
+
fs.writeFileSync(
|
|
25
|
+
configPath,
|
|
26
|
+
JSON.stringify({ theme: 'dark', mcpServers: { existing: { command: 'safe' } } })
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const changed = installJsonConfig(configPath, {
|
|
30
|
+
command: 'npx',
|
|
31
|
+
args: MCP_ARGS,
|
|
32
|
+
env: MCP_ENV
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
assert.equal(changed, true);
|
|
36
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
37
|
+
assert.equal(config.theme, 'dark');
|
|
38
|
+
assert.equal(config.mcpServers.existing.command, 'safe');
|
|
39
|
+
assert.equal(config.mcpServers['context-manager'].env.CCM_REQUIRE_ALLOWED_ROOTS, '1');
|
|
40
|
+
assert.ok(fs.existsSync(`${configPath}.bak`));
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('installer never overwrites malformed JSON', () => {
|
|
44
|
+
const { configPath } = tempConfig();
|
|
45
|
+
const malformed = '{"mcpServers":';
|
|
46
|
+
fs.writeFileSync(configPath, malformed);
|
|
47
|
+
|
|
48
|
+
assert.throws(
|
|
49
|
+
() => installJsonConfig(configPath, { command: 'npx', args: [], env: {} }),
|
|
50
|
+
/Could not parse/
|
|
51
|
+
);
|
|
52
|
+
assert.equal(fs.readFileSync(configPath, 'utf8'), malformed);
|
|
53
|
+
assert.equal(fs.readFileSync(`${configPath}.bak`, 'utf8'), malformed);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('atomic writer leaves valid JSON and no temporary file', () => {
|
|
57
|
+
const { directory, configPath } = tempConfig();
|
|
58
|
+
writeJsonAtomic(configPath, { ok: true });
|
|
59
|
+
|
|
60
|
+
assert.deepEqual(JSON.parse(fs.readFileSync(configPath, 'utf8')), { ok: true });
|
|
61
|
+
assert.deepEqual(fs.readdirSync(directory), ['mcp.json']);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('generated MCP command pins package version and narrows project access', () => {
|
|
65
|
+
assert.match(MCP_ARGS[1], /^@senoldogann\/context-manager@\d+\.\d+\.\d+$/);
|
|
66
|
+
assert.equal(MCP_ENV.CCM_REQUIRE_ALLOWED_ROOTS, '1');
|
|
67
|
+
assert.equal(MCP_ENV.CCM_ALLOWED_ROOTS, process.cwd());
|
|
68
|
+
assert.equal(MCP_ENV.CCM_PROJECT_ROOT, process.cwd());
|
|
69
|
+
});
|