facult 2.8.4 → 2.8.5

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,6 +1,6 @@
1
1
  {
2
2
  "name": "facult",
3
- "version": "2.8.4",
3
+ "version": "2.8.5",
4
4
  "description": "Manage canonical AI capabilities, sync surfaces, and evolution state.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,5 +1,5 @@
1
1
  import { mkdir, readdir } from "node:fs/promises";
2
- import { basename, dirname, join, relative } from "node:path";
2
+ import { basename, dirname, isAbsolute, join, relative } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { getAdapter } from "./adapters";
5
5
  import { parseCliContextArgs, resolveCliContextRoot } from "./cli-context";
@@ -215,7 +215,8 @@ function extractIndexMeta(entry: unknown): {
215
215
  function findPreviousEntryByCanonicalRef(
216
216
  previous: Record<string, unknown> | undefined,
217
217
  canonicalRef: string | undefined,
218
- fallbackName: string
218
+ fallbackName: string,
219
+ source: IndexedSource
219
220
  ): unknown {
220
221
  if (!previous) {
221
222
  return undefined;
@@ -233,7 +234,8 @@ function findPreviousEntryByCanonicalRef(
233
234
  const legacyFallback = previous[fallbackName];
234
235
  if (
235
236
  isPlainObject(legacyFallback) &&
236
- typeof legacyFallback.canonicalRef !== "string"
237
+ typeof legacyFallback.canonicalRef !== "string" &&
238
+ legacyFallbackMatchesSource(legacyFallback, source)
237
239
  ) {
238
240
  return legacyFallback;
239
241
  }
@@ -243,7 +245,8 @@ function findPreviousEntryByCanonicalRef(
243
245
  function findPreviousMcpEntry(
244
246
  previous: Record<string, unknown> | undefined,
245
247
  canonicalRef: string | undefined,
246
- name: string
248
+ name: string,
249
+ source: IndexedSource
247
250
  ): unknown {
248
251
  if (!previous) {
249
252
  return undefined;
@@ -252,11 +255,73 @@ function findPreviousMcpEntry(
252
255
  if (!isPlainObject(candidate)) {
253
256
  return undefined;
254
257
  }
255
- return typeof candidate.canonicalRef !== "string" ||
256
- (typeof canonicalRef === "string" &&
257
- candidate.canonicalRef === canonicalRef)
258
- ? candidate
259
- : undefined;
258
+ if (typeof candidate.canonicalRef !== "string") {
259
+ return legacyFallbackMatchesSource(candidate, source)
260
+ ? candidate
261
+ : undefined;
262
+ }
263
+ if (
264
+ typeof canonicalRef === "string" &&
265
+ candidate.canonicalRef === canonicalRef
266
+ ) {
267
+ return candidate;
268
+ }
269
+ return undefined;
270
+ }
271
+
272
+ function pathIsWithinRoot(pathValue: string, rootDir: string): boolean {
273
+ if (!isAbsolute(pathValue)) {
274
+ return false;
275
+ }
276
+ const rel = relative(rootDir, pathValue);
277
+ return rel === "" || !(rel.startsWith("..") || isAbsolute(rel));
278
+ }
279
+
280
+ function legacyFallbackMatchesSource(
281
+ entry: Record<string, unknown>,
282
+ source: IndexedSource
283
+ ): boolean {
284
+ let hasSourceSignal = false;
285
+
286
+ const entrySourceKind = entry.sourceKind;
287
+ if (typeof entrySourceKind === "string") {
288
+ hasSourceSignal = true;
289
+ if (entrySourceKind !== source.sourceKind) {
290
+ return false;
291
+ }
292
+ }
293
+
294
+ const entryScope = entry.scope;
295
+ if (typeof entryScope === "string") {
296
+ hasSourceSignal = true;
297
+ if (entryScope !== source.scope) {
298
+ return false;
299
+ }
300
+ }
301
+
302
+ const entryProjectRoot = entry.projectRoot;
303
+ if (typeof entryProjectRoot === "string") {
304
+ hasSourceSignal = true;
305
+ if (entryProjectRoot !== source.projectRoot) {
306
+ return false;
307
+ }
308
+ }
309
+
310
+ const entrySourceRoot = entry.sourceRoot;
311
+ if (typeof entrySourceRoot === "string") {
312
+ hasSourceSignal = true;
313
+ if (entrySourceRoot !== source.rootDir) {
314
+ return false;
315
+ }
316
+ }
317
+
318
+ const entryPath = entry.path;
319
+ if (typeof entryPath === "string") {
320
+ hasSourceSignal = true;
321
+ return pathIsWithinRoot(entryPath, source.rootDir);
322
+ }
323
+
324
+ return hasSourceSignal;
260
325
  }
261
326
 
262
327
  function stripQuotes(s: string): string {
@@ -556,7 +621,8 @@ async function indexSkills(
556
621
  const prev = findPreviousEntryByCanonicalRef(
557
622
  previous,
558
623
  canonicalRef,
559
- name
624
+ name,
625
+ source
560
626
  );
561
627
  const meta = extractIndexMeta(prev);
562
628
 
@@ -618,7 +684,7 @@ async function indexMcpServers(
618
684
  const lm = await statIsoTime(mcpConfigPath);
619
685
  for (const name of Object.keys(serversObj).sort()) {
620
686
  const canonicalRef = canonicalRefForPath(source, "mcp", mcpConfigPath);
621
- const prev = findPreviousMcpEntry(previous, canonicalRef, name);
687
+ const prev = findPreviousMcpEntry(previous, canonicalRef, name, source);
622
688
  const meta = extractIndexMeta(prev);
623
689
  out[name] = {
624
690
  name,
@@ -666,7 +732,12 @@ async function indexAgents(
666
732
  const name =
667
733
  basename(p) === "agent.toml" ? basename(dirname(p)) : basename(p);
668
734
  const canonicalRef = canonicalRefForPath(source, "agents", p);
669
- const prev = findPreviousEntryByCanonicalRef(previous, canonicalRef, name);
735
+ const prev = findPreviousEntryByCanonicalRef(
736
+ previous,
737
+ canonicalRef,
738
+ name,
739
+ source
740
+ );
670
741
  const meta = extractIndexMeta(prev);
671
742
  let description: string | undefined;
672
743
  try {