@danielblomma/cortex-mcp 2.0.12 → 2.0.13

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@danielblomma/cortex-mcp",
3
3
  "mcpName": "io.github.DanielBlomma/cortex",
4
- "version": "2.0.12",
4
+ "version": "2.0.13",
5
5
  "description": "Local, repo-scoped context platform for coding assistants. Semantic search, graph relationships, and architectural rule context.",
6
6
  "type": "module",
7
7
  "author": "Daniel Blomma",
@@ -46,6 +46,7 @@ type ManifestEntry = {
46
46
  };
47
47
 
48
48
  type LocalSkillRecord = {
49
+ cli: SkillCli;
49
50
  scope: string;
50
51
  updated_at: string;
51
52
  path: string;
@@ -88,7 +89,29 @@ function readState(): LocalSkillsState {
88
89
  if (!existsSync(path)) return { skills: {} };
89
90
  try {
90
91
  const parsed = JSON.parse(readFileSync(path, "utf8")) as LocalSkillsState;
91
- return { skills: parsed.skills ?? {}, last_synced_at: parsed.last_synced_at };
92
+ const normalizedSkills: Record<string, LocalSkillRecord> = {};
93
+ for (const [key, record] of Object.entries(parsed.skills ?? {})) {
94
+ if (!record || typeof record !== "object") continue;
95
+ const inferredCli =
96
+ record.path?.includes("/.codex/skills/")
97
+ ? "codex"
98
+ : "claude";
99
+ const cli =
100
+ record.cli === "codex" || record.cli === "claude"
101
+ ? record.cli
102
+ : inferredCli;
103
+ const normalizedKey = key.includes(":") ? key : `${cli}:${key}`;
104
+ normalizedSkills[normalizedKey] = {
105
+ cli,
106
+ scope: String(record.scope ?? "global"),
107
+ updated_at: String(record.updated_at ?? ""),
108
+ path: String(record.path ?? ""),
109
+ };
110
+ }
111
+ return {
112
+ skills: normalizedSkills,
113
+ last_synced_at: parsed.last_synced_at,
114
+ };
92
115
  } catch {
93
116
  return { skills: {} };
94
117
  }
@@ -103,19 +126,22 @@ function writeState(state: LocalSkillsState): void {
103
126
  }
104
127
 
105
128
  /**
106
- * Resolve the on-disk SKILL.md path for a skill. Global skills live under
107
- * ~/.claude/skills (Claude Code's user-scope skills directory); cli:codex
108
- * skills live under ~/.codex/skills. cli:claude scope is treated as
109
- * Claude-only and lands in ~/.claude/skills.
129
+ * Resolve the on-disk SKILL.md path for a skill install target. Global
130
+ * skills are installed once per CLI, so the destination root depends on the
131
+ * active sync target rather than just the stored scope.
110
132
  */
111
- function skillFilePath(scope: string, name: string): string {
133
+ function skillFilePath(cli: SkillCli, name: string): string {
112
134
  const root =
113
- scope === "cli:codex"
135
+ cli === "codex"
114
136
  ? join(homedir(), ".codex", "skills")
115
137
  : join(homedir(), ".claude", "skills");
116
138
  return join(root, name, "SKILL.md");
117
139
  }
118
140
 
141
+ function stateSkillKey(cli: SkillCli, name: string): string {
142
+ return `${cli}:${name}`;
143
+ }
144
+
119
145
  function shouldSyncForCli(scope: string, cli: SkillCli): boolean {
120
146
  if (scope === "global") return true;
121
147
  return scope === `cli:${cli}`;
@@ -222,7 +248,8 @@ export async function runSkillSyncForCli(
222
248
 
223
249
  // Detect adds + changes
224
250
  for (const entry of relevantManifest) {
225
- const local = state.skills[entry.name];
251
+ const skillKey = stateSkillKey(cli, entry.name);
252
+ const local = state.skills[skillKey];
226
253
  const isNew = !local;
227
254
  const isChanged =
228
255
  Boolean(local) &&
@@ -243,7 +270,7 @@ export async function runSkillSyncForCli(
243
270
  };
244
271
  }
245
272
 
246
- const path = skillFilePath(entry.scope, entry.name);
273
+ const path = skillFilePath(cli, entry.name);
247
274
  try {
248
275
  writeSkillFile(path, body);
249
276
  } catch (err) {
@@ -257,7 +284,8 @@ export async function runSkillSyncForCli(
257
284
  };
258
285
  }
259
286
 
260
- state.skills[entry.name] = {
287
+ state.skills[skillKey] = {
288
+ cli,
261
289
  scope: entry.scope,
262
290
  updated_at: entry.updated_at,
263
291
  path,
@@ -269,7 +297,10 @@ export async function runSkillSyncForCli(
269
297
  // dropped (or disabled). We only consider state entries whose scope
270
298
  // matches this cli, so we don't accidentally remove the other CLI's
271
299
  // skills when running a per-cli tick.
272
- for (const [name, record] of Object.entries(state.skills)) {
300
+ for (const [skillKey, record] of Object.entries(state.skills)) {
301
+ if (record.cli !== cli) continue;
302
+ const [, name] = skillKey.split(":", 2);
303
+ if (!name) continue;
273
304
  if (!shouldSyncForCli(record.scope, cli)) continue;
274
305
  if (remoteByName.has(name)) continue;
275
306
  try {
@@ -277,7 +308,7 @@ export async function runSkillSyncForCli(
277
308
  } catch {
278
309
  // best-effort; if unlink fails the next tick will retry
279
310
  }
280
- delete state.skills[name];
311
+ delete state.skills[skillKey];
281
312
  removed.push(name);
282
313
  }
283
314