@ornexus/neocortex 4.60.43 → 4.60.45

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.
Files changed (37) hide show
  1. package/dist/sbom.cdx.json +23 -23
  2. package/install.js +16 -1
  3. package/install.ps1 +57 -401
  4. package/install.sh +30 -37
  5. package/package.json +2 -2
  6. package/packages/client/dist/commands/invoke.js +39 -39
  7. package/packages/client/dist/errors/error-messages.js +2 -2
  8. package/packages/client/dist/evidence/collector.d.ts +2 -0
  9. package/packages/client/dist/evidence/collector.js +1 -1
  10. package/packages/client/dist/license/license-client.d.ts +11 -0
  11. package/packages/client/dist/license/license-client.js +1 -1
  12. package/packages/client/dist/runner/headless/run-yoloop.d.ts +9 -0
  13. package/packages/client/dist/runner/headless/run-yoloop.js +5 -3
  14. package/packages/client/dist/yoloop/index.d.ts +1 -1
  15. package/packages/client/dist/yoloop/invoke-hooks.js +10 -8
  16. package/packages/client/dist/yoloop/shared-yoloop-types.d.ts +61 -0
  17. package/packages/client/dist/yoloop/yoloop-client-state-store.d.ts +23 -0
  18. package/packages/client/dist/yoloop/yoloop-client-state-store.js +1 -1
  19. package/packages/shared/dist/invocation/index.js +9 -2
  20. package/targets-stubs/antigravity/gemini.md +1 -1
  21. package/targets-stubs/antigravity/install-antigravity.sh +10 -101
  22. package/targets-stubs/antigravity/skill/SKILL.md +1 -1
  23. package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
  24. package/targets-stubs/claude-code/neocortex-root.md +2 -2
  25. package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
  26. package/targets-stubs/claude-code/neocortex.md +2 -2
  27. package/targets-stubs/codex/AGENTS.md +1 -1
  28. package/targets-stubs/codex/install-codex.sh +4 -1
  29. package/targets-stubs/cursor/agent.md +2 -2
  30. package/targets-stubs/cursor/install-cursor.sh +4 -1
  31. package/targets-stubs/gemini-cli/agent.md +2 -2
  32. package/targets-stubs/gemini-cli/install-gemini.sh +4 -1
  33. package/targets-stubs/grok/install-grok.sh +4 -1
  34. package/targets-stubs/lib/mcp-merge.js +220 -20
  35. package/targets-stubs/opencode/neocortex-root.md +1 -1
  36. package/targets-stubs/vscode/install-vscode.sh +4 -1
  37. package/targets-stubs/vscode/neocortex.agent.md +2 -2
@@ -51,7 +51,10 @@ install_codex() {
51
51
  if [ -f "$codex_target/config-mcp.toml" ]; then
52
52
  local config_file="$codex_home/config.toml"
53
53
  if command -v node >/dev/null 2>&1; then
54
- node "$(dirname "$codex_target/config-mcp.toml")/../lib/mcp-merge.js" toml "$codex_target/config-mcp.toml" "$config_file" CODEX || true
54
+ node "$(dirname "$codex_target/config-mcp.toml")/../lib/mcp-merge.js" toml "$codex_target/config-mcp.toml" "$config_file" CODEX || {
55
+ echo "[CODEX] ERROR: MCP merge blocked; user config was preserved." >&2
56
+ return 1
57
+ }
55
58
  else
56
59
  echo "[CODEX] WARN: node not found; leaving existing $config_file untouched"
57
60
  echo "[CODEX] WARN: Stub available at: $codex_target/config-mcp.toml"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: neocortex
3
- description: "🧠 Neocortex v4.60.43 | OrNexus Team"
3
+ description: "🧠 Neocortex v4.60.45 | OrNexus Team"
4
4
  model: inherit
5
5
  readonly: false
6
6
  is_background: false
@@ -64,7 +64,7 @@ SEMPRE que este agente for invocado, imprima o banner abaixo como PRIMEIRO outpu
64
64
  ┌────────────────────────────────────────────────────────────┐
65
65
  │ │
66
66
  │ ####### N E O C O R T E X │
67
- │ ### ######## v4.60.43 │
67
+ │ ### ######## v4.60.45 │
68
68
  │ ######### ##### │
69
69
  │ ## ############## Development Orchestrator │
70
70
  │ ## ### ###### ## OrNexus Team │
@@ -42,7 +42,10 @@ install_cursor() {
42
42
 
43
43
  # 2. User-level additive merge MCP config (preserves user-configured servers)
44
44
  if [ -f "$cursor_target/mcp.json" ]; then
45
- _merge_cursor_mcp "$cursor_target/mcp.json" "${CURSOR_HOME:-$HOME/.cursor}/mcp.json" || true
45
+ _merge_cursor_mcp "$cursor_target/mcp.json" "${CURSOR_HOME:-$HOME/.cursor}/mcp.json" || {
46
+ echo "[CURSOR] ERROR: MCP merge blocked; user config was preserved." >&2
47
+ return 1
48
+ }
46
49
  fi
47
50
 
48
51
  echo "[CURSOR] Thin-client installation complete!"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: neocortex-root
3
- description: "🧠 Neocortex v4.60.43 | OrNexus Team"
3
+ description: "🧠 Neocortex v4.60.45 | OrNexus Team"
4
4
  kind: local
5
5
  tools:
6
6
  # File operations (Gemini CLI built-ins)
@@ -62,7 +62,7 @@ SEMPRE que este agente for invocado, imprima o banner abaixo como PRIMEIRO outpu
62
62
  ┌────────────────────────────────────────────────────────────┐
63
63
  │ │
64
64
  │ ####### N E O C O R T E X │
65
- │ ### ######## v4.60.43 │
65
+ │ ### ######## v4.60.45 │
66
66
  │ ######### ##### │
67
67
  │ ## ############## Development Orchestrator │
68
68
  │ ## ### ###### ## OrNexus Team │
@@ -63,7 +63,10 @@ install_gemini() {
63
63
 
64
64
  # 4. Additive merge MCP config into settings.json
65
65
  if [ -f "$gemini_target/settings-mcp.json" ]; then
66
- _merge_gemini_mcp "$gemini_target/settings-mcp.json" "$gemini_home/settings.json" || true
66
+ _merge_gemini_mcp "$gemini_target/settings-mcp.json" "$gemini_home/settings.json" || {
67
+ echo "[GEMINI] ERROR: MCP merge blocked; user config was preserved." >&2
68
+ return 1
69
+ }
67
70
  fi
68
71
 
69
72
  echo "[GEMINI] Thin-client installation complete!"
@@ -21,7 +21,10 @@ install_grok() {
21
21
  fi
22
22
 
23
23
  if command -v node >/dev/null 2>&1; then
24
- node "$source_dir/targets-stubs/lib/mcp-merge.js" toml "$grok_target/config-mcp.toml" "$grok_home/config.toml" GROK || true
24
+ node "$source_dir/targets-stubs/lib/mcp-merge.js" toml "$grok_target/config-mcp.toml" "$grok_home/config.toml" GROK || {
25
+ echo "[GROK] ERROR: MCP merge blocked; user config was preserved." >&2
26
+ return 1
27
+ }
25
28
  else
26
29
  echo "[GROK] WARN: node not found; leaving existing $grok_home/config.toml untouched"
27
30
  fi
@@ -6,6 +6,7 @@
6
6
 
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
+ const crypto = require('crypto');
9
10
 
10
11
  const MANAGED_SERVERS = [
11
12
  'playwright',
@@ -51,16 +52,168 @@ function stableJson(value) {
51
52
  return `${JSON.stringify(value, null, 2)}\n`;
52
53
  }
53
54
 
54
- function mergeJson(stubFile, destFile, rootKey, target) {
55
+ function ownershipPath(destFile) {
56
+ return `${destFile}.neocortex-ownership.json`;
57
+ }
58
+
59
+ function atomicWrite(file, content) {
60
+ ensureDir(file);
61
+ const temporary = `${file}.tmp.${process.pid}.${crypto.randomBytes(6).toString('hex')}`;
62
+ let fd;
63
+ try {
64
+ fd = fs.openSync(temporary, 'wx', 0o600);
65
+ fs.writeFileSync(fd, content, 'utf8');
66
+ fs.fsyncSync(fd);
67
+ } finally {
68
+ if (fd !== undefined) fs.closeSync(fd);
69
+ }
70
+ try {
71
+ fs.renameSync(temporary, file);
72
+ try {
73
+ const dirFd = fs.openSync(path.dirname(file), 'r');
74
+ try { fs.fsyncSync(dirFd); } finally { fs.closeSync(dirFd); }
75
+ } catch { /* directory fsync is unavailable on some Windows filesystems */ }
76
+ } catch (error) {
77
+ try { fs.unlinkSync(temporary); } catch { /* best effort */ }
78
+ throw error;
79
+ }
80
+ }
81
+
82
+ function canonicalUrl(raw) {
83
+ if (typeof raw !== 'string' || raw.trim() === '') return null;
84
+ try {
85
+ const parsed = new URL(raw.trim());
86
+ parsed.protocol = parsed.protocol.toLowerCase();
87
+ parsed.hostname = parsed.hostname.toLowerCase();
88
+ parsed.hash = '';
89
+ if (parsed.pathname.length > 1) parsed.pathname = parsed.pathname.replace(/\/+$/, '');
90
+ return parsed.toString();
91
+ } catch { return null; }
92
+ }
93
+
94
+ function canonicalCommand(raw) {
95
+ const tokens = Array.isArray(raw) ? raw : [raw];
96
+ if (tokens.some((token) => typeof token !== 'string' || token.trim() === '')) return null;
97
+ return tokens.map((token, index) => {
98
+ const trimmed = token.trim();
99
+ if (index > 0) return trimmed;
100
+ const basename = trimmed.replace(/\\/g, '/').split('/').pop().toLowerCase();
101
+ return basename.replace(/\.(?:cmd|exe|bat)$/i, '');
102
+ });
103
+ }
104
+
105
+ function canonicalArgs(raw) {
106
+ if (raw === undefined) return [];
107
+ if (!Array.isArray(raw)
108
+ || raw.some((token) => typeof token !== 'string' || token.trim() === '')) return null;
109
+ return raw.map((token) => token.trim());
110
+ }
111
+
112
+ function semanticIdentity(entry) {
113
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) return null;
114
+ const remote = canonicalUrl(entry.url || entry.httpUrl || entry.serverUrl);
115
+ if (remote) return { transport: 'remote', url: remote };
116
+ const command = canonicalCommand(entry.command);
117
+ if (!command) return null;
118
+ const args = canonicalArgs(entry.args);
119
+ if (!args) return null;
120
+ return { transport: 'stdio', argv: [...command, ...args] };
121
+ }
122
+
123
+ function fingerprint(entry) {
124
+ const identity = semanticIdentity(entry);
125
+ return identity
126
+ ? crypto.createHash('sha256').update(`neocortex-mcp-identity-v1\0${JSON.stringify(identity)}`).digest('hex')
127
+ : null;
128
+ }
129
+
130
+ function readOwnership(destFile, target, rootKey) {
131
+ const file = ownershipPath(destFile);
132
+ if (!fs.existsSync(file)) return { ok: true, file, value: { schemaVersion: 1, target, rootKey, entries: {} }, text: null };
133
+ try {
134
+ const text = fs.readFileSync(file, 'utf8');
135
+ const value = JSON.parse(text);
136
+ if (!value || value.schemaVersion !== 1 || typeof value.target !== 'string' || value.rootKey !== rootKey
137
+ || !value.entries || typeof value.entries !== 'object' || Array.isArray(value.entries)) {
138
+ throw new Error('invalid ownership schema');
139
+ }
140
+ return { ok: true, file, value, text };
141
+ } catch (error) {
142
+ console.error(`[${target}] ERROR: MCP_OWNERSHIP_MANIFEST_INVALID (${error.message}); leaving config untouched.`);
143
+ return { ok: false, file };
144
+ }
145
+ }
146
+
147
+ function planSemanticMerge(existing, baseline, managed, ownership, target) {
148
+ const next = { ...existing };
149
+ const nextOwned = { ...ownership.entries };
150
+ const blockers = [];
151
+ let added = 0;
152
+ let updated = 0;
153
+ let deduplicated = 0;
154
+
155
+ for (const name of managed) {
156
+ const desired = baseline[name];
157
+ const desiredFingerprint = fingerprint(desired);
158
+ if (!desiredFingerprint) {
159
+ blockers.push(`MCP_BASELINE_IDENTITY_INVALID:${name}`);
160
+ continue;
161
+ }
162
+ const aliases = Object.entries(next)
163
+ .filter(([candidate]) => candidate !== name)
164
+ .filter(([, entry]) => fingerprint(entry) === desiredFingerprint)
165
+ .map(([candidate]) => candidate);
166
+ if (aliases.length > 1) {
167
+ blockers.push(`MCP_SEMANTIC_DUPLICATE_AMBIGUOUS:${name}:${aliases.sort().join(',')}`);
168
+ continue;
169
+ }
170
+
171
+ const current = next[name];
172
+ const currentFingerprint = fingerprint(current);
173
+ const ownedFingerprint = nextOwned[name] && nextOwned[name].fingerprint;
174
+ const currentlyOwned = typeof ownedFingerprint === 'string' && currentFingerprint === ownedFingerprint;
175
+
176
+ if (aliases.length === 1) {
177
+ if (currentlyOwned) {
178
+ delete next[name];
179
+ delete nextOwned[name];
180
+ deduplicated += 1;
181
+ }
182
+ // A user-owned semantic alias is authoritative. Never duplicate or claim it.
183
+ continue;
184
+ }
185
+
186
+ if (current !== undefined && !currentlyOwned && currentFingerprint !== desiredFingerprint) {
187
+ blockers.push(`MCP_UNOWNED_NAME_CONFLICT:${name}`);
188
+ continue;
189
+ }
190
+
191
+ if (current === undefined) added += 1;
192
+ else if (JSON.stringify(current) !== JSON.stringify(desired)) updated += 1;
193
+ next[name] = desired;
194
+ nextOwned[name] = { fingerprint: desiredFingerprint };
195
+ }
196
+
197
+ if (blockers.length) {
198
+ for (const reason of blockers) console.error(`[${target}] ERROR: ${reason}; leaving config untouched.`);
199
+ }
200
+ return { next, entries: nextOwned, blockers, added, updated, deduplicated };
201
+ }
202
+
203
+ function mergeJson(stubFile, destFile, rootKey, target, managedTopLevelCsv = '') {
55
204
  const stubResult = readJson(stubFile, target);
56
205
  if (!stubResult.ok) return 1;
57
206
  const stub = stubResult.value;
58
207
  const baseline = stub[rootKey] && typeof stub[rootKey] === 'object' ? stub[rootKey] : {};
59
208
  const managed = MANAGED_SERVERS.filter((name) => Object.prototype.hasOwnProperty.call(baseline, name));
60
209
  ensureDir(destFile);
210
+ const ownership = readOwnership(destFile, target, rootKey);
211
+ if (!ownership.ok) return 3;
61
212
 
62
213
  if (!fs.existsSync(destFile)) {
63
- fs.writeFileSync(destFile, stableJson(stub));
214
+ const entries = Object.fromEntries(managed.map((name) => [name, { fingerprint: fingerprint(baseline[name]) }]));
215
+ atomicWrite(destFile, stableJson(stub));
216
+ atomicWrite(ownership.file, stableJson({ ...ownership.value, entries }));
64
217
  console.log(`[${target}] Created MCP config: ${destFile} (added ${managed.length})`);
65
218
  return 0;
66
219
  }
@@ -74,22 +227,38 @@ function mergeJson(stubFile, destFile, rootKey, target) {
74
227
  if (!existing[rootKey] || typeof existing[rootKey] !== 'object' || Array.isArray(existing[rootKey])) {
75
228
  existing[rootKey] = {};
76
229
  }
77
-
78
- let added = 0;
79
- let updated = 0;
80
- for (const name of managed) {
81
- if (Object.prototype.hasOwnProperty.call(existing[rootKey], name)) updated += 1;
82
- else added += 1;
83
- existing[rootKey][name] = baseline[name];
230
+ const migratedTopLevel = [];
231
+ for (const name of managedTopLevelCsv.split(',').map((entry) => entry.trim()).filter(Boolean)) {
232
+ if (!Object.prototype.hasOwnProperty.call(existing, name)) continue;
233
+ if (Object.prototype.hasOwnProperty.call(existing[rootKey], name)
234
+ && fingerprint(existing[rootKey][name]) !== fingerprint(existing[name])) {
235
+ console.error(`[${target}] ERROR: MCP_LEGACY_SCOPE_CONFLICT:${name}; leaving config untouched.`);
236
+ return 3;
237
+ }
238
+ if (!Object.prototype.hasOwnProperty.call(existing[rootKey], name)) existing[rootKey][name] = existing[name];
239
+ migratedTopLevel.push(name);
84
240
  }
85
241
 
242
+ const plan = planSemanticMerge(existing[rootKey], baseline, managed, ownership.value, target);
243
+ if (plan.blockers.length) return 3;
244
+ existing[rootKey] = plan.next;
245
+ for (const name of migratedTopLevel) delete existing[name];
246
+
86
247
  const next = stableJson(existing);
248
+ const nextOwnership = stableJson({ ...ownership.value, entries: plan.entries });
87
249
  if (next !== existingResult.text) {
88
- fs.writeFileSync(destFile, next);
89
- console.log(`[${target}] Merged MCP config into: ${destFile} (added ${added}, updated ${updated}, preserved user entries)`);
250
+ if (migratedTopLevel.length > 0) {
251
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
252
+ const structuralBackup = `${destFile}.neocortex-backup-structural-${stamp}`;
253
+ fs.copyFileSync(destFile, structuralBackup);
254
+ console.log(`[${target}] Legacy MCP scope migration backup created: ${structuralBackup}`);
255
+ }
256
+ atomicWrite(destFile, next);
257
+ console.log(`[${target}] Merged MCP config into: ${destFile} (added ${plan.added}, updated ${plan.updated}, deduplicated ${plan.deduplicated}, preserved user entries)`);
90
258
  } else {
91
259
  console.log(`[${target}] MCP config already up to date: ${destFile} (preserved user entries)`);
92
260
  }
261
+ if (nextOwnership !== ownership.text) atomicWrite(ownership.file, nextOwnership);
93
262
  return 0;
94
263
  }
95
264
 
@@ -109,7 +278,7 @@ function splitManagedTomlSections(text) {
109
278
  let currentName = null;
110
279
  let current = [];
111
280
  const flush = () => {
112
- if (currentName && MANAGED_SERVERS.includes(currentName)) {
281
+ if (currentName) {
113
282
  sections.set(currentName, current.join('\n').replace(/\s+$/, '') + '\n');
114
283
  }
115
284
  };
@@ -119,6 +288,10 @@ function splitManagedTomlSections(text) {
119
288
  flush();
120
289
  currentName = match[1];
121
290
  current = [line];
291
+ } else if (/^\[[^\]]+\]$/.test(line)) {
292
+ flush();
293
+ currentName = null;
294
+ current = [];
122
295
  } else if (currentName) {
123
296
  current.push(line);
124
297
  }
@@ -127,6 +300,14 @@ function splitManagedTomlSections(text) {
127
300
  return sections;
128
301
  }
129
302
 
303
+ function tomlEntry(section) {
304
+ const command = section.match(/^command\s*=\s*"([^"]+)"/m)?.[1];
305
+ const argsRaw = section.match(/^args\s*=\s*\[([^\]]*)\]/m)?.[1];
306
+ const args = argsRaw === undefined ? undefined : [...argsRaw.matchAll(/"((?:\\.|[^"])*)"/g)].map((match) => match[1].replace(/\\"/g, '"'));
307
+ const url = section.match(/^(?:url|http_url|server_url)\s*=\s*"([^"]+)"/m)?.[1];
308
+ return url ? { url } : { command, ...(args ? { args } : {}) };
309
+ }
310
+
130
311
  function removeManagedTomlSections(text, managed) {
131
312
  const lines = text.split(/\r?\n/);
132
313
  const out = [];
@@ -146,11 +327,18 @@ function mergeToml(stubFile, destFile, target) {
146
327
  if (stubInvalid) throw new Error(`invalid stub TOML: ${stubInvalid}`);
147
328
  const stubSections = splitManagedTomlSections(stubText);
148
329
  const managed = [...stubSections.keys()];
149
- const managedBlock = managed.map((name) => stubSections.get(name).trimEnd()).join('\n\n');
330
+ const baseline = Object.fromEntries(managed.map((name) => [name, tomlEntry(stubSections.get(name))]));
150
331
  ensureDir(destFile);
332
+ const ownership = readOwnership(destFile, target, 'mcp_servers');
333
+ if (!ownership.ok) return 3;
151
334
 
152
335
  if (!fs.existsSync(destFile)) {
153
- fs.writeFileSync(destFile, `# Neocortex MCP Servers\n${managedBlock}\n`);
336
+ const managedBlock = managed.map((name) => stubSections.get(name).trimEnd()).join('\n\n');
337
+ atomicWrite(destFile, `# Neocortex MCP Servers\n${managedBlock}\n`);
338
+ atomicWrite(ownership.file, stableJson({
339
+ ...ownership.value,
340
+ entries: Object.fromEntries(managed.map((name) => [name, { fingerprint: fingerprint(baseline[name]) }])),
341
+ }));
154
342
  console.log(`[${target}] Created MCP config: ${destFile} (added ${managed.length})`);
155
343
  return 0;
156
344
  }
@@ -162,23 +350,35 @@ function mergeToml(stubFile, destFile, target) {
162
350
  return 0;
163
351
  }
164
352
 
165
- const preserved = removeManagedTomlSections(existing, managed).replace(/\n*# Neocortex MCP Servers\s*\n*$/, '\n');
166
- const next = `${preserved.trimEnd()}\n\n# Neocortex MCP Servers\n${managedBlock}\n`;
353
+ const existingSections = splitManagedTomlSections(existing);
354
+ const existingEntries = Object.fromEntries([...existingSections].map(([name, section]) => [name, tomlEntry(section)]));
355
+ const plan = planSemanticMerge(existingEntries, baseline, managed, ownership.value, target);
356
+ if (plan.blockers.length) return 3;
357
+ const namesToRemove = managed.filter((name) => !Object.prototype.hasOwnProperty.call(plan.next, name)
358
+ || Object.prototype.hasOwnProperty.call(plan.entries, name));
359
+ const namesToAppend = managed.filter((name) => Object.prototype.hasOwnProperty.call(plan.entries, name));
360
+ const preserved = removeManagedTomlSections(existing, namesToRemove).replace(/\n*# Neocortex MCP Servers\s*\n*$/, '\n');
361
+ const managedBlock = namesToAppend.map((name) => stubSections.get(name).trimEnd()).join('\n\n');
362
+ const next = managedBlock
363
+ ? `${preserved.trimEnd()}\n\n# Neocortex MCP Servers\n${managedBlock}\n`
364
+ : `${preserved.trimEnd()}\n`;
365
+ const nextOwnership = stableJson({ ...ownership.value, entries: plan.entries });
167
366
  if (next !== existing) {
168
- fs.writeFileSync(destFile, next);
169
- console.log(`[${target}] Merged MCP config into: ${destFile} (managed ${managed.length}, preserved unknown sections)`);
367
+ atomicWrite(destFile, next);
368
+ console.log(`[${target}] Merged MCP config into: ${destFile} (managed ${namesToAppend.length}, deduplicated ${plan.deduplicated}, preserved unknown sections)`);
170
369
  } else {
171
370
  console.log(`[${target}] MCP config already up to date: ${destFile} (preserved unknown sections)`);
172
371
  }
372
+ if (nextOwnership !== ownership.text) atomicWrite(ownership.file, nextOwnership);
173
373
  return 0;
174
374
  }
175
375
 
176
- const [mode, stubFile, destFile, rootOrTarget, maybeTarget] = process.argv.slice(2);
376
+ const [mode, stubFile, destFile, rootOrTarget, maybeTarget, managedTopLevelCsv] = process.argv.slice(2);
177
377
  if (!mode || !stubFile || !destFile) usage();
178
378
 
179
379
  try {
180
380
  const code = mode === 'json'
181
- ? mergeJson(stubFile, destFile, rootOrTarget, maybeTarget || 'MCP')
381
+ ? mergeJson(stubFile, destFile, rootOrTarget, maybeTarget || 'MCP', managedTopLevelCsv)
182
382
  : mode === 'toml'
183
383
  ? mergeToml(stubFile, destFile, rootOrTarget || 'MCP')
184
384
  : (usage(), 64);
@@ -58,7 +58,7 @@ explicit server opt-in rather than a local assumption.
58
58
  ┌────────────────────────────────────────────────────────────┐
59
59
  │ │
60
60
  │ ####### N E O C O R T E X │
61
- │ ### ######## v4.60.43 │
61
+ │ ### ######## v4.60.45 │
62
62
  │ ######### ##### │
63
63
  │ ## ############## Development Orchestrator │
64
64
  │ ## ### ###### ## OrNexus Team │
@@ -64,7 +64,10 @@ install_vscode() {
64
64
 
65
65
  # 3. User-level additive merge MCP config
66
66
  if [ -f "$vscode_target/mcp.json" ]; then
67
- _merge_vscode_mcp "$vscode_target/mcp.json" "$(_vscode_user_mcp_path)" || true
67
+ _merge_vscode_mcp "$vscode_target/mcp.json" "$(_vscode_user_mcp_path)" || {
68
+ echo "[VSCODE] ERROR: MCP merge blocked; user config was preserved." >&2
69
+ return 1
70
+ }
68
71
  fi
69
72
 
70
73
  echo "[VSCODE] Thin-client installation complete!"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: "neocortex"
3
- description: "Neocortex v4.60.43 | OrNexus Team"
3
+ description: "Neocortex v4.60.45 | OrNexus Team"
4
4
  tools:
5
5
  # Read / Edit (built-in tool sets)
6
6
  - read
@@ -68,7 +68,7 @@ SEMPRE que este agente for invocado, imprima o banner abaixo como PRIMEIRO outpu
68
68
  ┌────────────────────────────────────────────────────────────┐
69
69
  │ │
70
70
  │ ####### N E O C O R T E X │
71
- │ ### ######## v4.60.43 │
71
+ │ ### ######## v4.60.45 │
72
72
  │ ######### ##### │
73
73
  │ ## ############## Development Orchestrator │
74
74
  │ ## ### ###### ## OrNexus Team │