@agentforge/skills 0.16.42 → 0.16.43

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/dist/index.js CHANGED
@@ -438,390 +438,241 @@ function extractBody(content) {
438
438
  return matter(content).content.trim();
439
439
  }
440
440
  var logger3 = createLogger("agentforge:skills:registry", { level: LogLevel.INFO });
441
+ function discoverSkills(config, skills, rootTrustMap, emit) {
442
+ skills.clear();
443
+ rootTrustMap.clear();
444
+ const scanErrors = [];
445
+ const normalizedRoots = config.skillRoots.map(normalizeRootConfig);
446
+ const plainPaths = normalizedRoots.map((root) => root.path);
447
+ for (const root of normalizedRoots) {
448
+ const resolvedPath = resolve(expandHome(root.path));
449
+ rootTrustMap.set(resolvedPath, root.trust);
450
+ }
451
+ const candidates = scanAllSkillRoots(plainPaths);
452
+ let successCount = 0;
453
+ let warningCount = 0;
454
+ for (const candidate of candidates) {
455
+ const result = parseSkillContent(candidate.content, candidate.dirName);
456
+ if (!result.success) {
457
+ warningCount++;
458
+ recordScanError(scanErrors, candidate.skillPath, result.error || "Unknown parse error");
459
+ emit("skill:warning" /* SKILL_WARNING */, {
460
+ skillPath: candidate.skillPath,
461
+ rootPath: candidate.rootPath,
462
+ error: result.error
463
+ });
464
+ logger3.warn("Skipping invalid skill", {
465
+ skillPath: candidate.skillPath,
466
+ ...result.error ? { error: result.error } : {}
467
+ });
468
+ continue;
469
+ }
470
+ const skill = {
471
+ metadata: result.metadata,
472
+ skillPath: candidate.skillPath,
473
+ rootPath: candidate.rootPath,
474
+ trustLevel: rootTrustMap.get(candidate.rootPath) ?? "untrusted"
475
+ };
476
+ if (skills.has(skill.metadata.name)) {
477
+ warningCount++;
478
+ const existing = skills.get(skill.metadata.name);
479
+ const warningMessage = `Duplicate skill name "${skill.metadata.name}" from "${candidate.rootPath}" \u2014 keeping version from "${existing.rootPath}" (first root takes precedence)`;
480
+ recordScanError(scanErrors, candidate.skillPath, warningMessage);
481
+ emit("skill:warning" /* SKILL_WARNING */, {
482
+ skillPath: candidate.skillPath,
483
+ rootPath: candidate.rootPath,
484
+ duplicateOf: existing.skillPath,
485
+ error: warningMessage
486
+ });
487
+ logger3.warn("Duplicate skill name, keeping first", {
488
+ name: skill.metadata.name,
489
+ kept: existing.skillPath,
490
+ skipped: candidate.skillPath
491
+ });
492
+ continue;
493
+ }
494
+ skills.set(skill.metadata.name, skill);
495
+ successCount++;
496
+ emit("skill:discovered" /* SKILL_DISCOVERED */, skill);
497
+ logger3.debug("Skill discovered", {
498
+ name: skill.metadata.name,
499
+ description: skill.metadata.description.slice(0, 80),
500
+ skillPath: skill.skillPath
501
+ });
502
+ }
503
+ logger3.info("Skill registry populated", {
504
+ rootsScanned: config.skillRoots.length,
505
+ skillsDiscovered: successCount,
506
+ warnings: warningCount
507
+ });
508
+ return scanErrors;
509
+ }
510
+ function recordScanError(scanErrors, path, error) {
511
+ scanErrors.push({ path, error });
512
+ }
513
+ var logger4 = createLogger("agentforge:skills:registry", { level: LogLevel.INFO });
514
+ function addRegistryEventHandler(eventHandlers, event, handler) {
515
+ if (!eventHandlers.has(event)) {
516
+ eventHandlers.set(event, /* @__PURE__ */ new Set());
517
+ }
518
+ eventHandlers.get(event).add(handler);
519
+ }
520
+ function removeRegistryEventHandler(eventHandlers, event, handler) {
521
+ const handlers = eventHandlers.get(event);
522
+ if (handlers) {
523
+ handlers.delete(handler);
524
+ }
525
+ }
526
+ function emitRegistryEvent(eventHandlers, event, data) {
527
+ const handlers = eventHandlers.get(event);
528
+ if (!handlers) {
529
+ return;
530
+ }
531
+ handlers.forEach((handler) => {
532
+ try {
533
+ handler(data);
534
+ } catch (error) {
535
+ logger4.error("Skill event handler error", {
536
+ event,
537
+ error: error instanceof Error ? error.message : String(error),
538
+ ...error instanceof Error && error.stack ? { stack: error.stack } : {}
539
+ });
540
+ }
541
+ });
542
+ }
543
+ var logger5 = createLogger("agentforge:skills:registry", { level: LogLevel.INFO });
544
+ function generateSkillPrompt(config, allSkills, totalDiscovered, options) {
545
+ if (!config.enabled) {
546
+ logger5.debug("Skill prompt generation skipped (disabled)", {
547
+ enabled: config.enabled ?? false
548
+ });
549
+ return "";
550
+ }
551
+ let skills = allSkills;
552
+ if (options?.skills && options.skills.length > 0) {
553
+ const requested = new Set(options.skills);
554
+ skills = skills.filter((skill) => requested.has(skill.metadata.name));
555
+ }
556
+ if (config.maxDiscoveredSkills !== void 0 && config.maxDiscoveredSkills >= 0) {
557
+ skills = skills.slice(0, config.maxDiscoveredSkills);
558
+ }
559
+ if (skills.length === 0) {
560
+ logger5.debug("Skill prompt generation produced empty result", {
561
+ totalDiscovered,
562
+ filterApplied: !!(options?.skills && options.skills.length > 0),
563
+ ...config.maxDiscoveredSkills !== void 0 ? { maxCap: config.maxDiscoveredSkills } : {}
564
+ });
565
+ return "";
566
+ }
567
+ const xml = `<available_skills>
568
+ ${skills.map(renderSkillEntry).join("\n")}
569
+ </available_skills>`;
570
+ const estimatedTokens = Math.ceil(xml.length / 4);
571
+ logger5.info("Skill prompt generated", {
572
+ skillCount: skills.length,
573
+ totalDiscovered,
574
+ filterApplied: !!(options?.skills && options.skills.length > 0),
575
+ ...config.maxDiscoveredSkills !== void 0 ? { maxCap: config.maxDiscoveredSkills } : {},
576
+ estimatedTokens,
577
+ xmlLength: xml.length
578
+ });
579
+ return xml;
580
+ }
581
+ function renderSkillEntry(skill) {
582
+ return [
583
+ " <skill>",
584
+ ` <name>${escapeXml(skill.metadata.name)}</name>`,
585
+ ` <description>${escapeXml(skill.metadata.description)}</description>`,
586
+ ` <location>${escapeXml(skill.skillPath)}</location>`,
587
+ " </skill>"
588
+ ].join("\n");
589
+ }
590
+ function escapeXml(str) {
591
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
592
+ }
593
+
594
+ // src/registry-query-api.ts
595
+ function getSkill(skills, name) {
596
+ return skills.get(name);
597
+ }
598
+ function getAllSkills(skills) {
599
+ return Array.from(skills.values());
600
+ }
601
+ function hasSkill(skills, name) {
602
+ return skills.has(name);
603
+ }
604
+ function getSkillCount(skills) {
605
+ return skills.size;
606
+ }
607
+ function getSkillNames(skills) {
608
+ return Array.from(skills.keys());
609
+ }
610
+ function getScanErrors(scanErrors) {
611
+ return scanErrors;
612
+ }
613
+ function getAllowedTools(skills, name) {
614
+ return skills.get(name)?.metadata.allowedTools;
615
+ }
616
+
617
+ // src/registry.ts
441
618
  var SkillRegistry = class {
442
619
  skills = /* @__PURE__ */ new Map();
443
620
  eventHandlers = /* @__PURE__ */ new Map();
444
621
  config;
445
622
  scanErrors = [];
446
- /** Maps resolved root paths → trust levels for skill trust assignment */
447
623
  rootTrustMap = /* @__PURE__ */ new Map();
448
- /**
449
- * Create a SkillRegistry and immediately scan configured roots for skills.
450
- *
451
- * @param config - Registry configuration with skill root paths
452
- *
453
- * @example
454
- * ```ts
455
- * const registry = new SkillRegistry({
456
- * skillRoots: ['.agentskills', '~/.agentskills', './project-skills'],
457
- * });
458
- * console.log(`Discovered ${registry.size()} skills`);
459
- * ```
460
- */
461
624
  constructor(config) {
462
625
  this.config = config;
463
626
  this.discover();
464
627
  }
465
- /**
466
- * Scan all configured roots and populate the registry.
467
- *
468
- * Called automatically during construction. Can be called again
469
- * to re-scan (clears existing skills first).
470
- */
471
628
  discover() {
472
- this.skills.clear();
473
- this.scanErrors = [];
474
- this.rootTrustMap.clear();
475
- const normalizedRoots = this.config.skillRoots.map(normalizeRootConfig);
476
- const plainPaths = normalizedRoots.map((r) => r.path);
477
- for (const root of normalizedRoots) {
478
- const resolvedPath = resolve(expandHome(root.path));
479
- this.rootTrustMap.set(resolvedPath, root.trust);
480
- }
481
- const candidates = scanAllSkillRoots(plainPaths);
482
- let successCount = 0;
483
- let warningCount = 0;
484
- for (const candidate of candidates) {
485
- const result = parseSkillContent(candidate.content, candidate.dirName);
486
- if (!result.success) {
487
- warningCount++;
488
- this.scanErrors.push({
489
- path: candidate.skillPath,
490
- error: result.error || "Unknown parse error"
491
- });
492
- this.emit("skill:warning" /* SKILL_WARNING */, {
493
- skillPath: candidate.skillPath,
494
- rootPath: candidate.rootPath,
495
- error: result.error
496
- });
497
- logger3.warn("Skipping invalid skill", {
498
- skillPath: candidate.skillPath,
499
- ...result.error ? { error: result.error } : {}
500
- });
501
- continue;
502
- }
503
- const skill = {
504
- metadata: result.metadata,
505
- skillPath: candidate.skillPath,
506
- rootPath: candidate.rootPath,
507
- trustLevel: this.rootTrustMap.get(candidate.rootPath) ?? "untrusted"
508
- };
509
- if (this.skills.has(skill.metadata.name)) {
510
- const existing = this.skills.get(skill.metadata.name);
511
- warningCount++;
512
- const warningMsg = `Duplicate skill name "${skill.metadata.name}" from "${candidate.rootPath}" \u2014 keeping version from "${existing.rootPath}" (first root takes precedence)`;
513
- this.scanErrors.push({
514
- path: candidate.skillPath,
515
- error: warningMsg
516
- });
517
- this.emit("skill:warning" /* SKILL_WARNING */, {
518
- skillPath: candidate.skillPath,
519
- rootPath: candidate.rootPath,
520
- duplicateOf: existing.skillPath,
521
- error: warningMsg
522
- });
523
- logger3.warn("Duplicate skill name, keeping first", {
524
- name: skill.metadata.name,
525
- kept: existing.skillPath,
526
- skipped: candidate.skillPath
527
- });
528
- continue;
529
- }
530
- this.skills.set(skill.metadata.name, skill);
531
- successCount++;
532
- this.emit("skill:discovered" /* SKILL_DISCOVERED */, skill);
533
- logger3.debug("Skill discovered", {
534
- name: skill.metadata.name,
535
- description: skill.metadata.description.slice(0, 80),
536
- skillPath: skill.skillPath
537
- });
538
- }
539
- logger3.info("Skill registry populated", {
540
- rootsScanned: this.config.skillRoots.length,
541
- skillsDiscovered: successCount,
542
- warnings: warningCount
629
+ this.scanErrors = discoverSkills(this.config, this.skills, this.rootTrustMap, (event, data) => {
630
+ this.emit(event, data);
543
631
  });
544
632
  }
545
- // ─── Query API (parallel to ToolRegistry) ──────────────────────────────
546
- /**
547
- * Get a skill by name.
548
- *
549
- * @param name - The skill name
550
- * @returns The skill, or undefined if not found
551
- *
552
- * @example
553
- * ```ts
554
- * const skill = registry.get('code-review');
555
- * if (skill) {
556
- * console.log(skill.metadata.description);
557
- * }
558
- * ```
559
- */
560
633
  get(name) {
561
- return this.skills.get(name);
562
- }
563
- /**
564
- * Get all discovered skills.
565
- *
566
- * @returns Array of all skills
567
- *
568
- * @example
569
- * ```ts
570
- * const allSkills = registry.getAll();
571
- * console.log(`Total skills: ${allSkills.length}`);
572
- * ```
573
- */
634
+ return getSkill(this.skills, name);
635
+ }
574
636
  getAll() {
575
- return Array.from(this.skills.values());
576
- }
577
- /**
578
- * Check if a skill exists in the registry.
579
- *
580
- * @param name - The skill name
581
- * @returns True if the skill exists
582
- *
583
- * @example
584
- * ```ts
585
- * if (registry.has('code-review')) {
586
- * console.log('Skill available!');
587
- * }
588
- * ```
589
- */
637
+ return getAllSkills(this.skills);
638
+ }
590
639
  has(name) {
591
- return this.skills.has(name);
592
- }
593
- /**
594
- * Get the number of discovered skills.
595
- *
596
- * @returns Number of skills in the registry
597
- *
598
- * @example
599
- * ```ts
600
- * console.log(`Registry has ${registry.size()} skills`);
601
- * ```
602
- */
640
+ return hasSkill(this.skills, name);
641
+ }
603
642
  size() {
604
- return this.skills.size;
643
+ return getSkillCount(this.skills);
605
644
  }
606
- /**
607
- * Get all skill names.
608
- *
609
- * @returns Array of skill names
610
- */
611
645
  getNames() {
612
- return Array.from(this.skills.keys());
613
- }
614
- /**
615
- * Get errors/warnings from the last scan.
616
- *
617
- * Useful for diagnostics and observability.
618
- *
619
- * @returns Array of scan errors with paths
620
- */
646
+ return getSkillNames(this.skills);
647
+ }
621
648
  getScanErrors() {
622
- return this.scanErrors;
623
- }
624
- /**
625
- * Check whether untrusted script access is allowed via config override.
626
- *
627
- * Used by activation tools to pass the override flag to trust policy checks.
628
- *
629
- * @returns True if `allowUntrustedScripts` is set in config
630
- */
649
+ return getScanErrors(this.scanErrors);
650
+ }
631
651
  getAllowUntrustedScripts() {
632
652
  return this.config.allowUntrustedScripts ?? false;
633
653
  }
634
- /**
635
- * Get the `allowed-tools` list for a skill.
636
- *
637
- * Returns the `allowedTools` array from the skill's frontmatter metadata,
638
- * enabling agents to filter their tool set based on what the skill expects.
639
- *
640
- * @param name - The skill name
641
- * @returns Array of allowed tool names, or undefined if skill not found or field not set
642
- *
643
- * @example
644
- * ```ts
645
- * const allowed = registry.getAllowedTools('code-review');
646
- * if (allowed) {
647
- * const filteredTools = allTools.filter(t => allowed.includes(t.name));
648
- * }
649
- * ```
650
- */
651
654
  getAllowedTools(name) {
652
- const skill = this.skills.get(name);
653
- return skill?.metadata.allowedTools;
654
- }
655
- // ─── Prompt Generation ─────────────────────────────────────────────────
656
- /**
657
- * Generate an `<available_skills>` XML block for system prompt injection.
658
- *
659
- * Returns an empty string when:
660
- * - `config.enabled` is `false` (default) — agents operate with unmodified prompts
661
- * - No skills match the filter criteria
662
- *
663
- * The output composes naturally with `toolRegistry.generatePrompt()` —
664
- * simply concatenate both into the system prompt.
665
- *
666
- * @param options - Optional filtering (subset of skill names)
667
- * @returns XML string or empty string
668
- *
669
- * @example
670
- * ```ts
671
- * // All skills
672
- * const xml = registry.generatePrompt();
673
- *
674
- * // Subset for a focused agent
675
- * const xml = registry.generatePrompt({ skills: ['code-review', 'testing'] });
676
- *
677
- * // Compose with tool prompt
678
- * const systemPrompt = [
679
- * toolRegistry.generatePrompt(),
680
- * skillRegistry.generatePrompt(),
681
- * ].filter(Boolean).join('\n\n');
682
- * ```
683
- */
655
+ return getAllowedTools(this.skills, name);
656
+ }
684
657
  generatePrompt(options) {
685
- if (!this.config.enabled) {
686
- logger3.debug("Skill prompt generation skipped (disabled)", {
687
- enabled: this.config.enabled ?? false
688
- });
689
- return "";
690
- }
691
- let skills = this.getAll();
692
- if (options?.skills && options.skills.length > 0) {
693
- const requested = new Set(options.skills);
694
- skills = skills.filter((s) => requested.has(s.metadata.name));
695
- }
696
- if (this.config.maxDiscoveredSkills !== void 0 && this.config.maxDiscoveredSkills >= 0) {
697
- skills = skills.slice(0, this.config.maxDiscoveredSkills);
698
- }
699
- if (skills.length === 0) {
700
- logger3.debug("Skill prompt generation produced empty result", {
701
- totalDiscovered: this.size(),
702
- filterApplied: !!(options?.skills && options.skills.length > 0),
703
- ...this.config.maxDiscoveredSkills !== void 0 ? { maxCap: this.config.maxDiscoveredSkills } : {}
704
- });
705
- return "";
706
- }
707
- const skillEntries = skills.map((skill) => {
708
- const lines = [
709
- " <skill>",
710
- ` <name>${escapeXml(skill.metadata.name)}</name>`,
711
- ` <description>${escapeXml(skill.metadata.description)}</description>`,
712
- ` <location>${escapeXml(skill.skillPath)}</location>`,
713
- " </skill>"
714
- ];
715
- return lines.join("\n");
716
- });
717
- const xml = `<available_skills>
718
- ${skillEntries.join("\n")}
719
- </available_skills>`;
720
- const estimatedTokens = Math.ceil(xml.length / 4);
721
- logger3.info("Skill prompt generated", {
722
- skillCount: skills.length,
723
- totalDiscovered: this.size(),
724
- filterApplied: !!(options?.skills && options.skills.length > 0),
725
- ...this.config.maxDiscoveredSkills !== void 0 ? { maxCap: this.config.maxDiscoveredSkills } : {},
726
- estimatedTokens,
727
- xmlLength: xml.length
728
- });
729
- return xml;
730
- }
731
- // ─── Event System ──────────────────────────────────────────────────────
732
- /**
733
- * Register an event handler.
734
- *
735
- * @param event - The event to listen for
736
- * @param handler - The handler function
737
- *
738
- * @example
739
- * ```ts
740
- * registry.on(SkillRegistryEvent.SKILL_DISCOVERED, (skill) => {
741
- * console.log('Found skill:', skill.metadata.name);
742
- * });
743
- * ```
744
- */
658
+ return generateSkillPrompt(this.config, this.getAll(), this.size(), options);
659
+ }
745
660
  on(event, handler) {
746
- if (!this.eventHandlers.has(event)) {
747
- this.eventHandlers.set(event, /* @__PURE__ */ new Set());
748
- }
749
- this.eventHandlers.get(event).add(handler);
750
- }
751
- /**
752
- * Unregister an event handler.
753
- *
754
- * @param event - The event to stop listening for
755
- * @param handler - The handler function to remove
756
- */
757
- off(event, handler) {
758
- const handlers = this.eventHandlers.get(event);
759
- if (handlers) {
760
- handlers.delete(handler);
761
- }
661
+ addRegistryEventHandler(this.eventHandlers, event, handler);
762
662
  }
763
- /**
764
- * Emit an event to all registered handlers.
765
- *
766
- * @param event - The event to emit
767
- * @param data - The event data
768
- * @private
769
- */
770
- emit(event, data) {
771
- const handlers = this.eventHandlers.get(event);
772
- if (handlers) {
773
- handlers.forEach((handler) => {
774
- try {
775
- handler(data);
776
- } catch (error) {
777
- logger3.error("Skill event handler error", {
778
- event,
779
- error: error instanceof Error ? error.message : String(error),
780
- ...error instanceof Error && error.stack ? { stack: error.stack } : {}
781
- });
782
- }
783
- });
784
- }
663
+ off(event, handler) {
664
+ removeRegistryEventHandler(this.eventHandlers, event, handler);
785
665
  }
786
- /**
787
- * Emit an event (public API for activation tools).
788
- *
789
- * Used by skill activation tools to emit `skill:activated` and
790
- * `skill:resource-loaded` events through the registry's event system.
791
- *
792
- * @param event - The event to emit
793
- * @param data - The event data
794
- */
795
666
  emitEvent(event, data) {
796
667
  this.emit(event, data);
797
668
  }
798
- // ─── Tool Integration ────────────────────────────────────────────────
799
- /**
800
- * Create activation tools pre-wired to this registry instance.
801
- *
802
- * Returns `activate-skill` and `read-skill-resource` tools that
803
- * agents can use to load skill instructions and resources on demand.
804
- *
805
- * @returns Array of [activate-skill, read-skill-resource] tools
806
- *
807
- * @example
808
- * ```ts
809
- * const agent = createReActAgent({
810
- * model: llm,
811
- * tools: [
812
- * ...toolRegistry.toLangChainTools(),
813
- * ...skillRegistry.toActivationTools(),
814
- * ],
815
- * });
816
- * ```
817
- */
818
669
  toActivationTools() {
819
670
  return createSkillActivationTools(this);
820
671
  }
672
+ emit(event, data) {
673
+ emitRegistryEvent(this.eventHandlers, event, data);
674
+ }
821
675
  };
822
- function escapeXml(str) {
823
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
824
- }
825
676
 
826
677
  export { SkillRegistry, SkillRegistryEvent, TrustPolicyReason, createActivateSkillTool, createReadSkillResourceTool, createSkillActivationTools, evaluateTrustPolicy, expandHome, isScriptResource, normalizeRootConfig, parseSkillContent, resolveResourcePath, scanAllSkillRoots, scanSkillRoot, validateSkillDescription, validateSkillName, validateSkillNameMatchesDir };
827
678
  //# sourceMappingURL=index.js.map