@theronap/cortex-mcp 0.9.73 → 0.9.74
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/lib/skills.mjs +30 -2
- package/package.json +1 -1
package/lib/skills.mjs
CHANGED
|
@@ -160,16 +160,37 @@ export function installSkills(opts = {}) {
|
|
|
160
160
|
return summary
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
// Decide what to install from a served org-skill list: drop invalid names
|
|
164
|
-
// (bundled wins — core skills are inalterable). Pure, so it's
|
|
163
|
+
// Decide what to install from a served org-skill list: drop invalid names, bundled collisions
|
|
164
|
+
// (bundled wins — core skills are inalterable) and cross-brain collisions. Pure, so it's
|
|
165
|
+
// unit-testable without a network.
|
|
165
166
|
export function planOrgInstall(served, bundledNames) {
|
|
166
167
|
const bundled = new Set(bundledNames)
|
|
167
168
|
const install = []
|
|
168
169
|
const skipped = []
|
|
170
|
+
|
|
171
|
+
// /api/skills now fans out across EVERY brain the caller belongs to (ADR-0022 acrossMyBrains), so
|
|
172
|
+
// for the first time two brains can serve the SAME skill name. Installing both would write one
|
|
173
|
+
// file twice and silently leave whichever landed last — an executable body from a brain the user
|
|
174
|
+
// never chose. There is no principled winner, so a cross-brain collision installs NEITHER and says
|
|
175
|
+
// so, matching how a bundled collision already resolves: when in doubt, do not install.
|
|
176
|
+
const byName = new Map()
|
|
177
|
+
for (const s of served ?? []) {
|
|
178
|
+
const n = (s?.name ?? '').trim()
|
|
179
|
+
if (!byName.has(n)) byName.set(n, [])
|
|
180
|
+
byName.get(n).push(s)
|
|
181
|
+
}
|
|
182
|
+
|
|
169
183
|
for (const s of served ?? []) {
|
|
170
184
|
const name = (s?.name ?? '').trim()
|
|
171
185
|
if (!NAME_RE.test(name) || typeof s?.body_md !== 'string' || !s.body_md.trim()) { skipped.push({ name: name || '(unnamed)', why: 'invalid' }); continue }
|
|
172
186
|
if (bundled.has(name)) { skipped.push({ name, why: 'collides with a bundled core skill' }); continue }
|
|
187
|
+
const dupes = byName.get(name) ?? []
|
|
188
|
+
if (dupes.length > 1) {
|
|
189
|
+
// Name the brains so the owner knows which to rename. `brain` is the tag acrossMyBrains adds.
|
|
190
|
+
const brains = [...new Set(dupes.map((d) => d?.brain).filter(Boolean))]
|
|
191
|
+
skipped.push({ name, why: `published by ${dupes.length} brains (${brains.join(', ') || 'unknown'}) — rename one; installing neither` })
|
|
192
|
+
continue
|
|
193
|
+
}
|
|
173
194
|
install.push({ name, source: s.body_md })
|
|
174
195
|
}
|
|
175
196
|
return { install, skipped }
|
|
@@ -201,6 +222,13 @@ export async function syncOrgSkills(opts = {}) {
|
|
|
201
222
|
if (res.ok) {
|
|
202
223
|
served = (await res.json())?.skills ?? []
|
|
203
224
|
summary.source = 'server'
|
|
225
|
+
} else {
|
|
226
|
+
// ⚠ DO NOT MAKE THIS SILENT AGAIN. `if (res.ok)` alone is how a 100% failure hid for weeks:
|
|
227
|
+
// /api/skills answered 409 to every multi-brain caller, this fell through to the cache below,
|
|
228
|
+
// and the only log line ('unreachable and no cache') does NOT print when a cache exists. So
|
|
229
|
+
// org skills silently stopped updating and nothing anywhere said so. Fail-soft is right —
|
|
230
|
+
// SessionStart must not break — but fail-soft is not fail-quiet.
|
|
231
|
+
log(` · org skills: server said ${res.status} — using last-good cache. Run \`doctor\` if this persists.`)
|
|
204
232
|
}
|
|
205
233
|
} catch { /* fall through to cache */ }
|
|
206
234
|
if (!served && Array.isArray(cache?.skills)) { served = cache.skills; summary.source = 'cache' }
|