@cuylabs/agent-core 0.4.0 → 0.5.0

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 (66) hide show
  1. package/README.md +57 -8
  2. package/dist/builder-RcTZuYnO.d.ts +34 -0
  3. package/dist/capabilities/index.d.ts +97 -0
  4. package/dist/capabilities/index.js +46 -0
  5. package/dist/chunk-6TDTQJ4P.js +116 -0
  6. package/dist/chunk-7MUFEN4K.js +559 -0
  7. package/dist/chunk-BDBZ3SLK.js +745 -0
  8. package/dist/chunk-DWYX7ASF.js +26 -0
  9. package/dist/chunk-FG4MD5MU.js +54 -0
  10. package/dist/chunk-IMGQOTU2.js +2019 -0
  11. package/dist/chunk-IVUJDISU.js +556 -0
  12. package/dist/chunk-LRHOS4ZN.js +584 -0
  13. package/dist/chunk-OTUGSCED.js +691 -0
  14. package/dist/chunk-P6YF7USR.js +182 -0
  15. package/dist/chunk-QAQADS4X.js +258 -0
  16. package/dist/chunk-QWFMX226.js +879 -0
  17. package/dist/{chunk-6VKLWNRE.js → chunk-SDSBEQXG.js} +1 -132
  18. package/dist/chunk-VBWWUHWI.js +724 -0
  19. package/dist/chunk-VEKUXUVF.js +41 -0
  20. package/dist/chunk-X635CM2F.js +305 -0
  21. package/dist/chunk-YUUJK53A.js +91 -0
  22. package/dist/chunk-ZXAKHMWH.js +283 -0
  23. package/dist/config-D2xeGEHK.d.ts +52 -0
  24. package/dist/context/index.d.ts +259 -0
  25. package/dist/context/index.js +26 -0
  26. package/dist/identifiers-BLUxFqV_.d.ts +12 -0
  27. package/dist/index-p0kOsVsE.d.ts +1067 -0
  28. package/dist/index-tmhaADz5.d.ts +198 -0
  29. package/dist/index.d.ts +210 -5736
  30. package/dist/index.js +2126 -7766
  31. package/dist/mcp/index.d.ts +26 -0
  32. package/dist/mcp/index.js +14 -0
  33. package/dist/messages-BYWGn8TY.d.ts +110 -0
  34. package/dist/middleware/index.d.ts +7 -0
  35. package/dist/middleware/index.js +12 -0
  36. package/dist/models/index.d.ts +33 -0
  37. package/dist/models/index.js +12 -0
  38. package/dist/network-D76DS5ot.d.ts +5 -0
  39. package/dist/prompt/index.d.ts +224 -0
  40. package/dist/prompt/index.js +45 -0
  41. package/dist/reasoning/index.d.ts +71 -0
  42. package/dist/reasoning/index.js +47 -0
  43. package/dist/registry-CuRWWtcT.d.ts +164 -0
  44. package/dist/resolver-DOfZ-xuk.d.ts +254 -0
  45. package/dist/runner-C7aMP_x3.d.ts +596 -0
  46. package/dist/runtime/index.d.ts +357 -0
  47. package/dist/runtime/index.js +64 -0
  48. package/dist/session-manager-Uawm2Le7.d.ts +274 -0
  49. package/dist/skill/index.d.ts +103 -0
  50. package/dist/skill/index.js +39 -0
  51. package/dist/storage/index.d.ts +167 -0
  52. package/dist/storage/index.js +50 -0
  53. package/dist/sub-agent/index.d.ts +14 -0
  54. package/dist/sub-agent/index.js +15 -0
  55. package/dist/tool/index.d.ts +173 -1
  56. package/dist/tool/index.js +12 -3
  57. package/dist/tool-DYp6-cC3.d.ts +239 -0
  58. package/dist/tool-pFAnJc5Y.d.ts +419 -0
  59. package/dist/tracker-DClqYqTj.d.ts +96 -0
  60. package/dist/tracking/index.d.ts +109 -0
  61. package/dist/tracking/index.js +20 -0
  62. package/dist/types-CQaXbRsS.d.ts +47 -0
  63. package/dist/types-MM1JoX5T.d.ts +810 -0
  64. package/dist/types-VQgymC1N.d.ts +156 -0
  65. package/package.json +89 -5
  66. package/dist/index-BlSTfS-W.d.ts +0 -470
@@ -0,0 +1,584 @@
1
+ // src/skill/discovery/constants.ts
2
+ var DEFAULT_EXTERNAL_DIRS = [".agents", ".claude"];
3
+ var DEFAULT_MAX_SCAN_DEPTH = 4;
4
+ var MAX_SKILLS_PER_ROOT = 500;
5
+ var SCOPE_PRIORITY = {
6
+ project: 0,
7
+ user: 1,
8
+ global: 2,
9
+ builtin: 3
10
+ };
11
+
12
+ // src/skill/loader/constants.ts
13
+ var SKILL_FILENAME = "SKILL.md";
14
+ var DEFAULT_SKILL_MAX_SIZE = 102400;
15
+ var RESOURCE_DIRS = {
16
+ scripts: "script",
17
+ references: "reference",
18
+ assets: "asset",
19
+ examples: "example"
20
+ };
21
+
22
+ // src/skill/loader/frontmatter.ts
23
+ function parseFrontmatter(raw) {
24
+ const trimmed = raw.trimStart();
25
+ if (!trimmed.startsWith("---")) {
26
+ return { data: {}, body: raw };
27
+ }
28
+ const endIndex = trimmed.indexOf("\n---", 3);
29
+ if (endIndex === -1) {
30
+ return { data: {}, body: raw };
31
+ }
32
+ const yamlBlock = trimmed.slice(4, endIndex).trim();
33
+ const body = trimmed.slice(endIndex + 4).trim();
34
+ const data = {};
35
+ const lines = yamlBlock.split("\n");
36
+ let currentKey = null;
37
+ let continuationValue = "";
38
+ for (const line of lines) {
39
+ if (line.trimStart().startsWith("#") || line.trim() === "") continue;
40
+ const kvMatch = line.match(/^(\w[\w-]*)\s*:\s*(.*)/);
41
+ if (kvMatch) {
42
+ if (currentKey && continuationValue) {
43
+ data[currentKey] = parseFrontmatterValue(continuationValue.trim());
44
+ continuationValue = "";
45
+ }
46
+ const key = kvMatch[1];
47
+ const rawValue = kvMatch[2].trim();
48
+ if (rawValue.startsWith("[") && rawValue.endsWith("]")) {
49
+ data[key] = rawValue.slice(1, -1).split(",").map((segment) => segment.trim()).filter((segment) => segment.length > 0);
50
+ currentKey = null;
51
+ } else if (rawValue === "" || rawValue === "|" || rawValue === ">") {
52
+ currentKey = key;
53
+ continuationValue = "";
54
+ } else {
55
+ data[key] = parseFrontmatterValue(rawValue);
56
+ currentKey = null;
57
+ }
58
+ } else if (currentKey) {
59
+ continuationValue += (continuationValue ? "\n" : "") + line.trim();
60
+ } else if (line.trimStart().startsWith("- ")) {
61
+ const keys = Object.keys(data);
62
+ const lastKey = keys.length > 0 ? keys[keys.length - 1] : null;
63
+ if (!lastKey) continue;
64
+ const existing = data[lastKey];
65
+ const item = line.trimStart().slice(2).trim();
66
+ if (Array.isArray(existing)) {
67
+ existing.push(item);
68
+ } else {
69
+ data[lastKey] = [item];
70
+ }
71
+ }
72
+ }
73
+ if (currentKey && continuationValue) {
74
+ data[currentKey] = parseFrontmatterValue(continuationValue.trim());
75
+ }
76
+ return { data, body };
77
+ }
78
+ function parseFrontmatterValue(raw) {
79
+ if (raw.startsWith('"') && raw.endsWith('"') || raw.startsWith("'") && raw.endsWith("'")) {
80
+ return raw.slice(1, -1);
81
+ }
82
+ if (raw === "true") return true;
83
+ if (raw === "false") return false;
84
+ if (/^\d+(\.\d+)?$/.test(raw)) return Number(raw);
85
+ if (raw === "null" || raw === "~") return null;
86
+ return raw;
87
+ }
88
+
89
+ // src/skill/loader/metadata.ts
90
+ import { readFile, stat } from "fs/promises";
91
+ import { join } from "path";
92
+ async function loadSkillMetadata(filePath, scope, source, maxSize) {
93
+ try {
94
+ const info = await stat(filePath);
95
+ if (!info.isFile() || info.size > maxSize) return null;
96
+ const raw = await readFile(filePath, "utf-8");
97
+ const { data } = parseFrontmatter(raw);
98
+ const name = typeof data.name === "string" ? data.name.trim() : null;
99
+ const description = typeof data.description === "string" ? data.description.trim() : null;
100
+ if (!name || !description) return null;
101
+ return {
102
+ name,
103
+ description,
104
+ version: typeof data.version === "string" ? data.version : void 0,
105
+ scope,
106
+ source,
107
+ filePath,
108
+ baseDir: join(filePath, ".."),
109
+ tags: Array.isArray(data.tags) ? data.tags.map(String) : void 0,
110
+ dependencies: Array.isArray(data.dependencies) ? data.dependencies.map(String) : void 0
111
+ };
112
+ } catch {
113
+ return null;
114
+ }
115
+ }
116
+
117
+ // src/skill/loader/resources.ts
118
+ import { readFile as readFile2, readdir, stat as stat2 } from "fs/promises";
119
+ import { extname, join as join2, relative } from "path";
120
+ async function loadSkillContent(metadata) {
121
+ const raw = await readFile2(metadata.filePath, "utf-8");
122
+ const { body } = parseFrontmatter(raw);
123
+ const resources = await scanSkillResources(metadata.baseDir);
124
+ return {
125
+ ...metadata,
126
+ body,
127
+ resources
128
+ };
129
+ }
130
+ async function loadResourceContent(resource) {
131
+ return readFile2(resource.absolutePath, "utf-8");
132
+ }
133
+ async function scanSkillResources(baseDir) {
134
+ const resources = [];
135
+ for (const [dirName, resourceType] of Object.entries(RESOURCE_DIRS)) {
136
+ const dirPath = join2(baseDir, dirName);
137
+ try {
138
+ const info = await stat2(dirPath);
139
+ if (!info.isDirectory()) continue;
140
+ } catch {
141
+ continue;
142
+ }
143
+ await collectFiles(dirPath, baseDir, resourceType, resources);
144
+ }
145
+ return resources;
146
+ }
147
+ async function collectFiles(dirPath, baseDir, type, resources, maxDepth = 3, currentDepth = 0) {
148
+ if (currentDepth >= maxDepth) return;
149
+ try {
150
+ const entries = await readdir(dirPath, { withFileTypes: true });
151
+ for (const entry of entries) {
152
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
153
+ const fullPath = join2(dirPath, entry.name);
154
+ if (entry.isFile()) {
155
+ resources.push({
156
+ relativePath: relative(baseDir, fullPath),
157
+ type,
158
+ absolutePath: fullPath
159
+ });
160
+ } else if (entry.isDirectory()) {
161
+ await collectFiles(fullPath, baseDir, type, resources, maxDepth, currentDepth + 1);
162
+ }
163
+ }
164
+ } catch {
165
+ }
166
+ }
167
+ function inferResourceType(filePath) {
168
+ const ext = extname(filePath).toLowerCase();
169
+ if ([".sh", ".bash", ".py", ".rb", ".js", ".ts"].includes(ext)) {
170
+ return "script";
171
+ }
172
+ if ([".md", ".txt", ".rst", ".adoc"].includes(ext)) {
173
+ return "reference";
174
+ }
175
+ if ([".json", ".yaml", ".yml", ".toml", ".xml", ".html", ".css"].includes(ext)) {
176
+ return "asset";
177
+ }
178
+ return "reference";
179
+ }
180
+
181
+ // src/skill/discovery/discover.ts
182
+ import { homedir } from "os";
183
+ import { join as join5, resolve as resolve2 } from "path";
184
+
185
+ // src/skill/discovery/dedupe.ts
186
+ function deduplicateSkills(skills) {
187
+ const byName = /* @__PURE__ */ new Map();
188
+ for (const skill of skills) {
189
+ const existing = byName.get(skill.name);
190
+ if (!existing) {
191
+ byName.set(skill.name, skill);
192
+ continue;
193
+ }
194
+ const existingPriority = SCOPE_PRIORITY[existing.scope];
195
+ const newPriority = SCOPE_PRIORITY[skill.scope];
196
+ if (newPriority <= existingPriority) {
197
+ byName.set(skill.name, skill);
198
+ }
199
+ }
200
+ return Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
201
+ }
202
+
203
+ // src/skill/discovery/fs.ts
204
+ import { access, stat as stat3 } from "fs/promises";
205
+ import { dirname, join as join3, resolve, sep } from "path";
206
+ async function dirExists(path) {
207
+ try {
208
+ const info = await stat3(path);
209
+ return info.isDirectory();
210
+ } catch {
211
+ return false;
212
+ }
213
+ }
214
+ async function findGitRoot(startDir) {
215
+ let dir = resolve(startDir);
216
+ const root = sep === "/" ? "/" : dir.slice(0, 3);
217
+ while (dir !== root) {
218
+ try {
219
+ await access(join3(dir, ".git"));
220
+ return dir;
221
+ } catch {
222
+ const parent = dirname(dir);
223
+ if (parent === dir) break;
224
+ dir = parent;
225
+ }
226
+ }
227
+ return void 0;
228
+ }
229
+ function dirsBetween(from, to) {
230
+ const fromResolved = resolve(from);
231
+ const toResolved = resolve(to);
232
+ if (!toResolved.startsWith(fromResolved)) return [fromResolved];
233
+ const dirs = [];
234
+ let current = toResolved;
235
+ while (current.startsWith(fromResolved)) {
236
+ dirs.push(current);
237
+ const parent = dirname(current);
238
+ if (parent === current) break;
239
+ current = parent;
240
+ }
241
+ dirs.reverse();
242
+ return dirs;
243
+ }
244
+
245
+ // src/skill/discovery/scan.ts
246
+ import { readdir as readdir2 } from "fs/promises";
247
+ import { join as join4 } from "path";
248
+ async function scanRoot(root, maxDepth, maxFileSize = DEFAULT_SKILL_MAX_SIZE) {
249
+ const skills = [];
250
+ const errors = [];
251
+ let dirsScanned = 0;
252
+ const queue = [[root.path, 0]];
253
+ while (queue.length > 0 && skills.length < MAX_SKILLS_PER_ROOT) {
254
+ const [dirPath, depth] = queue.shift();
255
+ if (depth > maxDepth) continue;
256
+ dirsScanned++;
257
+ try {
258
+ const entries = await readdir2(dirPath, { withFileTypes: true });
259
+ const hasSkillFile = entries.some((entry) => entry.isFile() && entry.name === SKILL_FILENAME);
260
+ if (hasSkillFile) {
261
+ const filePath = join4(dirPath, SKILL_FILENAME);
262
+ const metadata = await loadSkillMetadata(
263
+ filePath,
264
+ root.scope,
265
+ root.source,
266
+ maxFileSize
267
+ );
268
+ if (metadata) {
269
+ skills.push(metadata);
270
+ } else {
271
+ errors.push({
272
+ path: filePath,
273
+ reason: "Invalid or incomplete frontmatter (name and description required)"
274
+ });
275
+ }
276
+ continue;
277
+ }
278
+ for (const entry of entries) {
279
+ if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build") {
280
+ continue;
281
+ }
282
+ queue.push([join4(dirPath, entry.name), depth + 1]);
283
+ }
284
+ } catch (error) {
285
+ errors.push({
286
+ path: dirPath,
287
+ reason: `Cannot read directory: ${error instanceof Error ? error.message : String(error)}`
288
+ });
289
+ }
290
+ }
291
+ return { skills, errors, dirsScanned };
292
+ }
293
+
294
+ // src/skill/discovery/discover.ts
295
+ async function discoverSkills(cwd, config) {
296
+ const startTime = Date.now();
297
+ const resolvedCwd = resolve2(cwd);
298
+ const errors = [];
299
+ let dirsScanned = 0;
300
+ const externalDirs = config?.externalDirs ?? DEFAULT_EXTERNAL_DIRS;
301
+ const maxDepth = config?.maxScanDepth ?? DEFAULT_MAX_SCAN_DEPTH;
302
+ const maxFileSize = config?.maxFileSize ?? DEFAULT_SKILL_MAX_SIZE;
303
+ const roots = [];
304
+ if (config?.roots) {
305
+ for (const root of config.roots) {
306
+ const absRoot = resolve2(resolvedCwd, root);
307
+ roots.push({ path: absRoot, scope: "global", source: { type: "local", root: absRoot } });
308
+ }
309
+ }
310
+ const home = homedir();
311
+ for (const dir of externalDirs) {
312
+ const skillsDir = join5(home, dir, "skills");
313
+ if (await dirExists(skillsDir)) {
314
+ roots.push({
315
+ path: skillsDir,
316
+ scope: "user",
317
+ source: { type: "local", root: join5(home, dir) }
318
+ });
319
+ }
320
+ }
321
+ const gitRoot = await findGitRoot(resolvedCwd);
322
+ const projectDirs = gitRoot ? dirsBetween(gitRoot, resolvedCwd) : [resolvedCwd];
323
+ for (const dir of projectDirs) {
324
+ for (const extDir of externalDirs) {
325
+ const skillsDir = join5(dir, extDir, "skills");
326
+ if (await dirExists(skillsDir)) {
327
+ roots.push({
328
+ path: skillsDir,
329
+ scope: "project",
330
+ source: { type: "local", root: join5(dir, extDir) }
331
+ });
332
+ }
333
+ }
334
+ }
335
+ const allSkills = [];
336
+ for (const root of roots) {
337
+ const result = await scanRoot(root, maxDepth, maxFileSize);
338
+ allSkills.push(...result.skills);
339
+ errors.push(...result.errors);
340
+ dirsScanned += result.dirsScanned;
341
+ }
342
+ return {
343
+ skills: deduplicateSkills(allSkills),
344
+ errors,
345
+ dirsScanned,
346
+ durationMs: Date.now() - startTime
347
+ };
348
+ }
349
+
350
+ // src/skill/registry.ts
351
+ var SkillRegistry = class {
352
+ /** All discovered skill metadata indexed by name */
353
+ skills;
354
+ /** Cached full content for skills that have been loaded */
355
+ contentCache;
356
+ /** Discovery metadata */
357
+ discoveryResult;
358
+ constructor(discoveryResult) {
359
+ this.discoveryResult = discoveryResult;
360
+ this.skills = /* @__PURE__ */ new Map();
361
+ this.contentCache = /* @__PURE__ */ new Map();
362
+ for (const skill of discoveryResult.skills) {
363
+ this.skills.set(skill.name, skill);
364
+ }
365
+ }
366
+ // ==========================================================================
367
+ // Lookup
368
+ // ==========================================================================
369
+ /** Get a skill's metadata by name. Returns undefined if not found. */
370
+ get(name) {
371
+ return this.skills.get(name);
372
+ }
373
+ /** Check if a skill exists by name. */
374
+ has(name) {
375
+ return this.skills.has(name);
376
+ }
377
+ /** Get all skill metadata entries. */
378
+ list() {
379
+ return Array.from(this.skills.values());
380
+ }
381
+ /** Number of registered skills. */
382
+ get size() {
383
+ return this.skills.size;
384
+ }
385
+ /** Skill names as an array. */
386
+ get names() {
387
+ return Array.from(this.skills.keys());
388
+ }
389
+ // ==========================================================================
390
+ // Content Loading (L2 + L3)
391
+ // ==========================================================================
392
+ /**
393
+ * Load a skill's full content (L2: body + resource listing).
394
+ *
395
+ * Results are cached — subsequent calls return the cached content
396
+ * without re-reading the filesystem.
397
+ *
398
+ * @param name Skill name
399
+ * @returns Full skill content, or null if the skill doesn't exist
400
+ */
401
+ async loadContent(name) {
402
+ const cached = this.contentCache.get(name);
403
+ if (cached) return cached;
404
+ const metadata = this.skills.get(name);
405
+ if (!metadata) return null;
406
+ const content = await loadSkillContent(metadata);
407
+ this.contentCache.set(name, content);
408
+ return content;
409
+ }
410
+ /**
411
+ * Load a specific bundled resource from a skill (L3).
412
+ *
413
+ * The skill's content must be loaded first (via `loadContent`)
414
+ * so the resource listing is available.
415
+ *
416
+ * @param skillName Skill name
417
+ * @param relativePath Relative path to the resource within the skill dir
418
+ * @returns Resource file content as UTF-8 string
419
+ * @throws If the skill or resource doesn't exist
420
+ */
421
+ async loadResource(skillName, relativePath) {
422
+ const content = await this.loadContent(skillName);
423
+ if (!content) {
424
+ throw new Error(`Skill not found: "${skillName}"`);
425
+ }
426
+ const resource = content.resources.find(
427
+ (r) => r.relativePath === relativePath
428
+ );
429
+ if (!resource) {
430
+ const available = content.resources.map((r) => r.relativePath).join(", ");
431
+ throw new Error(
432
+ `Resource "${relativePath}" not found in skill "${skillName}". Available: ${available || "(none)"}`
433
+ );
434
+ }
435
+ return loadResourceContent(resource);
436
+ }
437
+ /**
438
+ * Get the list of resources for a loaded skill.
439
+ *
440
+ * @param name Skill name
441
+ * @returns Resource list, or empty array if skill isn't loaded yet
442
+ */
443
+ getResources(name) {
444
+ return this.contentCache.get(name)?.resources ?? [];
445
+ }
446
+ // ==========================================================================
447
+ // Prompt Summary (L1 — always in system prompt)
448
+ // ==========================================================================
449
+ /**
450
+ * Format a summary of all available skills for injection into the system prompt.
451
+ *
452
+ * This is the L1 layer — the agent sees names and descriptions of all
453
+ * available skills, enabling it to decide which to activate via tool calls.
454
+ *
455
+ * The output format uses XML tags (consistent with the instruction format
456
+ * in `formatInstructions`) for clear delineation in the prompt.
457
+ *
458
+ * Returns empty string if no skills are available.
459
+ *
460
+ * @example Output:
461
+ * ```xml
462
+ * <available-skills>
463
+ *
464
+ * You have access to the following skills. To activate a skill and load its
465
+ * full instructions, call the `skill` tool with the skill's name.
466
+ *
467
+ * <skill name="testing" scope="project">
468
+ * Write comprehensive test suites with vitest, covering unit, integration,
469
+ * and snapshot testing patterns.
470
+ * </skill>
471
+ *
472
+ * <skill name="frontend-design" scope="user">
473
+ * Create distinctive, production-grade frontend interfaces with high design quality.
474
+ * </skill>
475
+ *
476
+ * </available-skills>
477
+ * ```
478
+ */
479
+ formatSummary() {
480
+ if (this.skills.size === 0) return "";
481
+ const lines = [];
482
+ lines.push("<available-skills>");
483
+ lines.push("");
484
+ lines.push(
485
+ "You have access to the following skills. To activate a skill and load its full instructions, call the `skill` tool with the skill's name. Only load a skill when the current task matches its description."
486
+ );
487
+ for (const skill of this.skills.values()) {
488
+ lines.push("");
489
+ lines.push(`<skill name="${skill.name}" scope="${skill.scope}">`);
490
+ lines.push(skill.description);
491
+ if (skill.dependencies && skill.dependencies.length > 0) {
492
+ lines.push(`Depends on: ${skill.dependencies.join(", ")}`);
493
+ }
494
+ lines.push("</skill>");
495
+ }
496
+ lines.push("");
497
+ lines.push("</available-skills>");
498
+ return lines.join("\n");
499
+ }
500
+ /**
501
+ * Format the full content of a loaded skill for a tool response.
502
+ *
503
+ * Wraps the skill body in XML with metadata, and lists bundled resources
504
+ * so the agent knows what's available at L3.
505
+ *
506
+ * @param content Previously loaded skill content
507
+ * @returns Formatted string for tool response
508
+ */
509
+ formatContent(content) {
510
+ const lines = [];
511
+ lines.push(`<skill-content name="${content.name}">`);
512
+ lines.push("");
513
+ lines.push(content.body);
514
+ if (content.resources.length > 0) {
515
+ lines.push("");
516
+ lines.push("<bundled-resources>");
517
+ lines.push(
518
+ "This skill includes the following bundled files. To read one, call the `skill_resource` tool with the skill name and relative path."
519
+ );
520
+ lines.push("");
521
+ const byType = /* @__PURE__ */ new Map();
522
+ for (const r of content.resources) {
523
+ const list = byType.get(r.type) ?? [];
524
+ list.push(r);
525
+ byType.set(r.type, list);
526
+ }
527
+ for (const [type, resources] of byType) {
528
+ lines.push(` ${type}s:`);
529
+ for (const r of resources) {
530
+ lines.push(` - ${r.relativePath}`);
531
+ }
532
+ }
533
+ lines.push("</bundled-resources>");
534
+ }
535
+ if (content.dependencies && content.dependencies.length > 0) {
536
+ lines.push("");
537
+ lines.push(
538
+ `<dependencies>This skill works best when combined with: ${content.dependencies.join(", ")}</dependencies>`
539
+ );
540
+ }
541
+ lines.push("");
542
+ lines.push("</skill-content>");
543
+ return lines.join("\n");
544
+ }
545
+ // ==========================================================================
546
+ // Cache Management
547
+ // ==========================================================================
548
+ /** Clear the content cache, forcing reloads on next access. */
549
+ clearContentCache() {
550
+ this.contentCache.clear();
551
+ }
552
+ /** Check if a skill's content has been loaded and cached. */
553
+ isContentLoaded(name) {
554
+ return this.contentCache.has(name);
555
+ }
556
+ };
557
+ async function createSkillRegistry(cwd, config) {
558
+ const result = await discoverSkills(cwd, config);
559
+ return new SkillRegistry(result);
560
+ }
561
+ function emptySkillRegistry() {
562
+ return new SkillRegistry({
563
+ skills: [],
564
+ errors: [],
565
+ dirsScanned: 0,
566
+ durationMs: 0
567
+ });
568
+ }
569
+
570
+ export {
571
+ DEFAULT_EXTERNAL_DIRS,
572
+ DEFAULT_MAX_SCAN_DEPTH,
573
+ SKILL_FILENAME,
574
+ DEFAULT_SKILL_MAX_SIZE,
575
+ parseFrontmatter,
576
+ loadSkillMetadata,
577
+ loadSkillContent,
578
+ loadResourceContent,
579
+ inferResourceType,
580
+ discoverSkills,
581
+ SkillRegistry,
582
+ createSkillRegistry,
583
+ emptySkillRegistry
584
+ };