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