Setting the file. One moment. Citations · Vercel Optimize · vercel-labs/agent-skills · Skills Docslib/citations.mjs
JavaScript·139 lines·4 KB
join
(
HERE
,
'..'
,
'references'
,
'docs-library.json'
);
9
10let cached;
11
12export async function loadLibrary() {
13 if (cached) return cached;
14 const raw = await readFile(LIBRARY_PATH, 'utf-8');
15 cached = JSON.parse(raw);
16 return cached;
17}
18
19export async function isKnownUrl(url) {
20 const lib = await loadLibrary();
21 return lib.urls.some(e => e.url === url);
22}
23
24export async function lookupUrl(url) {
25 const lib = await loadLibrary();
26 return lib.urls.find(e => e.url === url);
27}
28
29export async function lookupSkillRule(ref) {
30 const lib = await loadLibrary();
31 const m = ref.match(/^([\w-]+):([\w-]+)$/);
32 if (!m) return undefined;
33 return lib.ruleSkillRefs.find(r => r.skill === m[1] && r.rule === m[2]);
34}
35
36// Narrow semver subset: "*", "fw@*", "fw@14", "fw@>=15.0.0", "fw@<X", "fw@X.Y", "fw@X.Y.Z", "a || b".
37export function matchesFrameworkVersion(pattern, framework, version) {
38 if (pattern === '*') return true;
39
40 if (pattern.includes('||')) {
41 return pattern.split('||').map(p => p.trim()).some(p =>
42 matchesFrameworkVersion(p, framework, version)
43 );
44 }
45
46 const m = pattern.match(/^([\w-]+)@(.+)$/);
47 if (!m) return false;
48 const [, fw, range] = m;
49
50 if (fw !== framework) return false;
51 if (range === '*') return true;
52
53 const verParts = parseVersion(version);
54 if (!verParts) return false;
55
56 let m2 = range.match(/^>=\s*(.+)$/);
57 if (m2) {
58 const min = parseVersion(m2[1]);
59 return min ? compareVersion(verParts, min) >= 0 : false;
60 }
61
62 m2 = range.match(/^<\s*(.+)$/);
63 if (m2) {
64 const max = parseVersion(m2[1]);
65 return max ? compareVersion(verParts, max) < 0 : false;
66 }
67
68 if (/^\d+$/.test(range)) {
69 return verParts[0] === Number(range);
70 }
71
72 m2 = range.match(/^(\d+)\.(\d+)$/);
73 if (m2) {
74 return verParts[0] === Number(m2[1]) && verParts[1] === Number(m2[2]);
75 }
76
77 const exact = parseVersion(range);
78 if (exact) return compareVersion(verParts, exact) === 0;
79
80 return false;
81}
82
83function parseVersion(v) {
84 const m = String(v).replace(/^[v^~]+/, '').match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
85 if (!m) return null;
86 return [Number(m[1]) || 0, Number(m[2]) || 0, Number(m[3]) || 0];
87}
88
89function compareVersion(a, b) {
90 for (let i = 0; i < 3; i++) {
91 if (a[i] !== b[i]) return a[i] - b[i];
92 }
93 return 0;
94}
95
96// Filtered subset embedded in recommender prompt — LLM never sees URLs for features not in user's stack.
97export async function libraryForStack(framework, version) {
98 const lib = await loadLibrary();
99 const matches = (frameworks) =>
100 frameworks.some(p => matchesFrameworkVersion(p, framework, version) || p === '*');
101 return {
102 urls: lib.urls.filter(e => matches(e.applicableFrameworks)),
103 ruleSkillRefs: lib.ruleSkillRefs.filter(r => matches(r.applicableFrameworks)),
104 };
105}
106
107export async function sanitizeCitations(rec, framework, version) {
108 const lib = await loadLibrary();
109 const strippedUnknown = [];
110 const strippedVersion = [];
111 const kept = [];
112
113 for (const cite of rec.citations ?? []) {
114 const ruleRef = await lookupSkillRule(cite);
115 if (ruleRef) {
116 if (matchesFrameworkVersion(ruleRef.applicableFrameworks.join(' || '), framework, version) || ruleRef.applicableFrameworks.includes('*')) {
117 kept.push(cite);
118 } else {
119 strippedVersion.push(cite);
120 }
121 continue;
122 }
123
124 const entry = lib.urls.find(e => e.url === cite);
125 if (!entry) {
126 strippedUnknown.push(cite);
127 continue;
128 }
129 if (entry.applicableFrameworks.includes('*') ||
130 entry.applicableFrameworks.some(p => matchesFrameworkVersion(p, framework, version))) {
131 kept.push(cite);
132 } else {
133 strippedVersion.push(cite);
134 }
135 }
136
137 rec.citations = kept;
138 return { rec, strippedUnknown, strippedVersion };
139}