crewly 1.20.133 → 1.20.134

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 (219) hide show
  1. package/dist/backend/backend/src/constants.d.ts +82 -0
  2. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  3. package/dist/backend/backend/src/constants.js +87 -0
  4. package/dist/backend/backend/src/constants.js.map +1 -1
  5. package/dist/backend/backend/src/controllers/bundle/bundle.controller.d.ts +44 -0
  6. package/dist/backend/backend/src/controllers/bundle/bundle.controller.d.ts.map +1 -0
  7. package/dist/backend/backend/src/controllers/bundle/bundle.controller.js +122 -0
  8. package/dist/backend/backend/src/controllers/bundle/bundle.controller.js.map +1 -0
  9. package/dist/backend/backend/src/controllers/bundle/bundle.routes.d.ts +25 -0
  10. package/dist/backend/backend/src/controllers/bundle/bundle.routes.d.ts.map +1 -0
  11. package/dist/backend/backend/src/controllers/bundle/bundle.routes.js +33 -0
  12. package/dist/backend/backend/src/controllers/bundle/bundle.routes.js.map +1 -0
  13. package/dist/backend/backend/src/index.d.ts.map +1 -1
  14. package/dist/backend/backend/src/index.js +14 -0
  15. package/dist/backend/backend/src/index.js.map +1 -1
  16. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  17. package/dist/backend/backend/src/routes/api.routes.js +4 -0
  18. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  19. package/dist/backend/backend/src/services/bundle/bundle-apply.factory.d.ts +62 -0
  20. package/dist/backend/backend/src/services/bundle/bundle-apply.factory.d.ts.map +1 -0
  21. package/dist/backend/backend/src/services/bundle/bundle-apply.factory.js +173 -0
  22. package/dist/backend/backend/src/services/bundle/bundle-apply.factory.js.map +1 -0
  23. package/dist/backend/backend/src/services/bundle/bundle-apply.service.d.ts +359 -0
  24. package/dist/backend/backend/src/services/bundle/bundle-apply.service.d.ts.map +1 -0
  25. package/dist/backend/backend/src/services/bundle/bundle-apply.service.js +888 -0
  26. package/dist/backend/backend/src/services/bundle/bundle-apply.service.js.map +1 -0
  27. package/dist/backend/backend/src/services/bundle/bundle-catalog.d.ts +167 -0
  28. package/dist/backend/backend/src/services/bundle/bundle-catalog.d.ts.map +1 -0
  29. package/dist/backend/backend/src/services/bundle/bundle-catalog.js +248 -0
  30. package/dist/backend/backend/src/services/bundle/bundle-catalog.js.map +1 -0
  31. package/dist/backend/backend/src/services/bundle/bundle-connectors.d.ts +43 -0
  32. package/dist/backend/backend/src/services/bundle/bundle-connectors.d.ts.map +1 -0
  33. package/dist/backend/backend/src/services/bundle/bundle-connectors.js +57 -0
  34. package/dist/backend/backend/src/services/bundle/bundle-connectors.js.map +1 -0
  35. package/dist/backend/backend/src/services/bundle/bundle-deps.d.ts +37 -0
  36. package/dist/backend/backend/src/services/bundle/bundle-deps.d.ts.map +1 -0
  37. package/dist/backend/backend/src/services/bundle/bundle-deps.js +60 -0
  38. package/dist/backend/backend/src/services/bundle/bundle-deps.js.map +1 -0
  39. package/dist/backend/backend/src/services/bundle/bundle-manifest.d.ts +93 -0
  40. package/dist/backend/backend/src/services/bundle/bundle-manifest.d.ts.map +1 -0
  41. package/dist/backend/backend/src/services/bundle/bundle-manifest.js +532 -0
  42. package/dist/backend/backend/src/services/bundle/bundle-manifest.js.map +1 -0
  43. package/dist/backend/backend/src/services/bundle/bundle-placeholders.d.ts +100 -0
  44. package/dist/backend/backend/src/services/bundle/bundle-placeholders.d.ts.map +1 -0
  45. package/dist/backend/backend/src/services/bundle/bundle-placeholders.js +183 -0
  46. package/dist/backend/backend/src/services/bundle/bundle-placeholders.js.map +1 -0
  47. package/dist/backend/backend/src/services/bundle/bundle-runtime.d.ts +36 -0
  48. package/dist/backend/backend/src/services/bundle/bundle-runtime.d.ts.map +1 -0
  49. package/dist/backend/backend/src/services/bundle/bundle-runtime.js +38 -0
  50. package/dist/backend/backend/src/services/bundle/bundle-runtime.js.map +1 -0
  51. package/dist/backend/backend/src/services/bundle/bundle-skill-installer.d.ts +105 -0
  52. package/dist/backend/backend/src/services/bundle/bundle-skill-installer.d.ts.map +1 -0
  53. package/dist/backend/backend/src/services/bundle/bundle-skill-installer.js +144 -0
  54. package/dist/backend/backend/src/services/bundle/bundle-skill-installer.js.map +1 -0
  55. package/dist/backend/backend/src/services/bundle/bundle-state.store.d.ts +48 -0
  56. package/dist/backend/backend/src/services/bundle/bundle-state.store.d.ts.map +1 -0
  57. package/dist/backend/backend/src/services/bundle/bundle-state.store.js +90 -0
  58. package/dist/backend/backend/src/services/bundle/bundle-state.store.js.map +1 -0
  59. package/dist/backend/backend/src/services/bundle/bundle-team.builder.d.ts +81 -0
  60. package/dist/backend/backend/src/services/bundle/bundle-team.builder.d.ts.map +1 -0
  61. package/dist/backend/backend/src/services/bundle/bundle-team.builder.js +172 -0
  62. package/dist/backend/backend/src/services/bundle/bundle-team.builder.js.map +1 -0
  63. package/dist/backend/backend/src/services/bundle/bundle-time.d.ts +59 -0
  64. package/dist/backend/backend/src/services/bundle/bundle-time.d.ts.map +1 -0
  65. package/dist/backend/backend/src/services/bundle/bundle-time.js +98 -0
  66. package/dist/backend/backend/src/services/bundle/bundle-time.js.map +1 -0
  67. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
  68. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +5 -0
  69. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -1
  70. package/dist/backend/backend/src/services/core/storage.service.js +1 -1
  71. package/dist/backend/backend/src/services/core/storage.service.js.map +1 -1
  72. package/dist/backend/backend/src/services/core/teams-backup.service.d.ts.map +1 -1
  73. package/dist/backend/backend/src/services/core/teams-backup.service.js +4 -2
  74. package/dist/backend/backend/src/services/core/teams-backup.service.js.map +1 -1
  75. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.factory.d.ts.map +1 -1
  76. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.factory.js +2 -0
  77. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.factory.js.map +1 -1
  78. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.d.ts +14 -2
  79. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.d.ts.map +1 -1
  80. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.js +37 -1
  81. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.js.map +1 -1
  82. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +21 -0
  83. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  84. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +85 -0
  85. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  86. package/dist/backend/backend/src/services/template/template.service.d.ts.map +1 -1
  87. package/dist/backend/backend/src/services/template/template.service.js +5 -2
  88. package/dist/backend/backend/src/services/template/template.service.js.map +1 -1
  89. package/dist/backend/backend/src/services/workflow/cron-task.service.d.ts.map +1 -1
  90. package/dist/backend/backend/src/services/workflow/cron-task.service.js +4 -2
  91. package/dist/backend/backend/src/services/workflow/cron-task.service.js.map +1 -1
  92. package/dist/backend/backend/src/types/solution-bundle.types.d.ts +367 -0
  93. package/dist/backend/backend/src/types/solution-bundle.types.d.ts.map +1 -0
  94. package/dist/backend/backend/src/types/solution-bundle.types.js +17 -0
  95. package/dist/backend/backend/src/types/solution-bundle.types.js.map +1 -0
  96. package/dist/backend/backend/src/types/team-template.types.d.ts +8 -0
  97. package/dist/backend/backend/src/types/team-template.types.d.ts.map +1 -1
  98. package/dist/backend/backend/src/types/team-template.types.js.map +1 -1
  99. package/dist/backend/build-info.json +2 -2
  100. package/dist/cli/backend/src/constants.d.ts +82 -0
  101. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  102. package/dist/cli/backend/src/constants.js +87 -0
  103. package/dist/cli/backend/src/constants.js.map +1 -1
  104. package/dist/cli/backend/src/services/bundle/bundle-apply.service.d.ts +359 -0
  105. package/dist/cli/backend/src/services/bundle/bundle-apply.service.d.ts.map +1 -0
  106. package/dist/cli/backend/src/services/bundle/bundle-apply.service.js +888 -0
  107. package/dist/cli/backend/src/services/bundle/bundle-apply.service.js.map +1 -0
  108. package/dist/cli/backend/src/services/bundle/bundle-catalog.d.ts +167 -0
  109. package/dist/cli/backend/src/services/bundle/bundle-catalog.d.ts.map +1 -0
  110. package/dist/cli/backend/src/services/bundle/bundle-catalog.js +248 -0
  111. package/dist/cli/backend/src/services/bundle/bundle-catalog.js.map +1 -0
  112. package/dist/cli/backend/src/services/bundle/bundle-deps.d.ts +37 -0
  113. package/dist/cli/backend/src/services/bundle/bundle-deps.d.ts.map +1 -0
  114. package/dist/cli/backend/src/services/bundle/bundle-deps.js +60 -0
  115. package/dist/cli/backend/src/services/bundle/bundle-deps.js.map +1 -0
  116. package/dist/cli/backend/src/services/bundle/bundle-manifest.d.ts +93 -0
  117. package/dist/cli/backend/src/services/bundle/bundle-manifest.d.ts.map +1 -0
  118. package/dist/cli/backend/src/services/bundle/bundle-manifest.js +532 -0
  119. package/dist/cli/backend/src/services/bundle/bundle-manifest.js.map +1 -0
  120. package/dist/cli/backend/src/services/bundle/bundle-placeholders.d.ts +100 -0
  121. package/dist/cli/backend/src/services/bundle/bundle-placeholders.d.ts.map +1 -0
  122. package/dist/cli/backend/src/services/bundle/bundle-placeholders.js +183 -0
  123. package/dist/cli/backend/src/services/bundle/bundle-placeholders.js.map +1 -0
  124. package/dist/cli/backend/src/services/bundle/bundle-runtime.d.ts +36 -0
  125. package/dist/cli/backend/src/services/bundle/bundle-runtime.d.ts.map +1 -0
  126. package/dist/cli/backend/src/services/bundle/bundle-runtime.js +38 -0
  127. package/dist/cli/backend/src/services/bundle/bundle-runtime.js.map +1 -0
  128. package/dist/cli/backend/src/services/bundle/bundle-skill-installer.d.ts +105 -0
  129. package/dist/cli/backend/src/services/bundle/bundle-skill-installer.d.ts.map +1 -0
  130. package/dist/cli/backend/src/services/bundle/bundle-skill-installer.js +144 -0
  131. package/dist/cli/backend/src/services/bundle/bundle-skill-installer.js.map +1 -0
  132. package/dist/cli/backend/src/services/bundle/bundle-state.store.d.ts +48 -0
  133. package/dist/cli/backend/src/services/bundle/bundle-state.store.d.ts.map +1 -0
  134. package/dist/cli/backend/src/services/bundle/bundle-state.store.js +90 -0
  135. package/dist/cli/backend/src/services/bundle/bundle-state.store.js.map +1 -0
  136. package/dist/cli/backend/src/services/bundle/bundle-team.builder.d.ts +81 -0
  137. package/dist/cli/backend/src/services/bundle/bundle-team.builder.d.ts.map +1 -0
  138. package/dist/cli/backend/src/services/bundle/bundle-team.builder.js +172 -0
  139. package/dist/cli/backend/src/services/bundle/bundle-team.builder.js.map +1 -0
  140. package/dist/cli/backend/src/services/bundle/bundle-time.d.ts +59 -0
  141. package/dist/cli/backend/src/services/bundle/bundle-time.d.ts.map +1 -0
  142. package/dist/cli/backend/src/services/bundle/bundle-time.js +98 -0
  143. package/dist/cli/backend/src/services/bundle/bundle-time.js.map +1 -0
  144. package/dist/cli/backend/src/services/core/storage.service.js +1 -1
  145. package/dist/cli/backend/src/services/core/storage.service.js.map +1 -1
  146. package/dist/cli/backend/src/services/core/teams-backup.service.d.ts.map +1 -1
  147. package/dist/cli/backend/src/services/core/teams-backup.service.js +4 -2
  148. package/dist/cli/backend/src/services/core/teams-backup.service.js.map +1 -1
  149. package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.d.ts +72 -0
  150. package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.d.ts.map +1 -0
  151. package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.js +389 -0
  152. package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.js.map +1 -0
  153. package/dist/cli/backend/src/services/messaging/index.d.ts +15 -0
  154. package/dist/cli/backend/src/services/messaging/index.d.ts.map +1 -0
  155. package/dist/cli/backend/src/services/messaging/index.js +20 -0
  156. package/dist/cli/backend/src/services/messaging/index.js.map +1 -0
  157. package/dist/cli/backend/src/services/messaging/queue-processor.service.d.ts +224 -0
  158. package/dist/cli/backend/src/services/messaging/queue-processor.service.d.ts.map +1 -0
  159. package/dist/cli/backend/src/services/messaging/queue-processor.service.js +969 -0
  160. package/dist/cli/backend/src/services/messaging/queue-processor.service.js.map +1 -0
  161. package/dist/cli/backend/src/services/messaging/response-router.service.d.ts +110 -0
  162. package/dist/cli/backend/src/services/messaging/response-router.service.d.ts.map +1 -0
  163. package/dist/cli/backend/src/services/messaging/response-router.service.js +268 -0
  164. package/dist/cli/backend/src/services/messaging/response-router.service.js.map +1 -0
  165. package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.d.ts +14 -2
  166. package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.d.ts.map +1 -1
  167. package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.js +37 -1
  168. package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.js.map +1 -1
  169. package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.d.ts +133 -0
  170. package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.d.ts.map +1 -0
  171. package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.js +232 -0
  172. package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.js.map +1 -0
  173. package/dist/cli/backend/src/services/skill/skill-catalog.service.d.ts +277 -0
  174. package/dist/cli/backend/src/services/skill/skill-catalog.service.d.ts.map +1 -0
  175. package/dist/cli/backend/src/services/skill/skill-catalog.service.js +708 -0
  176. package/dist/cli/backend/src/services/skill/skill-catalog.service.js.map +1 -0
  177. package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.d.ts +243 -0
  178. package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.d.ts.map +1 -0
  179. package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.js +439 -0
  180. package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.js.map +1 -0
  181. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +21 -0
  182. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  183. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +85 -0
  184. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  185. package/dist/cli/backend/src/services/template/template.service.d.ts.map +1 -1
  186. package/dist/cli/backend/src/services/template/template.service.js +5 -2
  187. package/dist/cli/backend/src/services/template/template.service.js.map +1 -1
  188. package/dist/cli/backend/src/services/workflow/cron-task.service.d.ts +316 -0
  189. package/dist/cli/backend/src/services/workflow/cron-task.service.d.ts.map +1 -0
  190. package/dist/cli/backend/src/services/workflow/cron-task.service.js +1043 -0
  191. package/dist/cli/backend/src/services/workflow/cron-task.service.js.map +1 -0
  192. package/dist/cli/backend/src/types/cron-task.types.d.ts +95 -0
  193. package/dist/cli/backend/src/types/cron-task.types.d.ts.map +1 -0
  194. package/dist/cli/backend/src/types/cron-task.types.js +10 -0
  195. package/dist/cli/backend/src/types/cron-task.types.js.map +1 -0
  196. package/dist/cli/backend/src/types/solution-bundle.types.d.ts +367 -0
  197. package/dist/cli/backend/src/types/solution-bundle.types.d.ts.map +1 -0
  198. package/dist/cli/backend/src/types/solution-bundle.types.js +17 -0
  199. package/dist/cli/backend/src/types/solution-bundle.types.js.map +1 -0
  200. package/dist/cli/backend/src/types/team-template.types.d.ts +8 -0
  201. package/dist/cli/backend/src/types/team-template.types.d.ts.map +1 -1
  202. package/dist/cli/backend/src/types/team-template.types.js.map +1 -1
  203. package/dist/cli/cli/src/commands/deploy-bundle.d.ts +160 -0
  204. package/dist/cli/cli/src/commands/deploy-bundle.d.ts.map +1 -0
  205. package/dist/cli/cli/src/commands/deploy-bundle.js +386 -0
  206. package/dist/cli/cli/src/commands/deploy-bundle.js.map +1 -0
  207. package/dist/cli/cli/src/commands/onboard.d.ts +32 -1
  208. package/dist/cli/cli/src/commands/onboard.d.ts.map +1 -1
  209. package/dist/cli/cli/src/commands/onboard.js +80 -3
  210. package/dist/cli/cli/src/commands/onboard.js.map +1 -1
  211. package/dist/cli/cli/src/constants.d.ts +5 -0
  212. package/dist/cli/cli/src/constants.d.ts.map +1 -1
  213. package/dist/cli/cli/src/constants.js +5 -0
  214. package/dist/cli/cli/src/constants.js.map +1 -1
  215. package/dist/cli/cli/src/index.js +15 -0
  216. package/dist/cli/cli/src/index.js.map +1 -1
  217. package/frontend/dist/assets/{index-e6c403b6.js → index-97bb17d4.js} +274 -274
  218. package/frontend/dist/index.html +1 -1
  219. package/package.json +1 -1
@@ -0,0 +1,708 @@
1
+ /**
2
+ * Skill Catalog Service
3
+ *
4
+ * Scans skill directories (orchestrator and agent), reads SKILL.md
5
+ * (YAML frontmatter + markdown body) from each, and generates formatted
6
+ * catalog Markdown files at ~/.crewly/skills/.
7
+ *
8
+ * Generates two catalogs:
9
+ * - SKILLS_CATALOG.md for orchestrator skills
10
+ * - AGENT_SKILLS_CATALOG.md for agent skills
11
+ *
12
+ * Each catalog provides a human-readable and LLM-readable reference of all
13
+ * available skills grouped by category, including usage examples and parameter
14
+ * documentation extracted from each skill's SKILL.md body.
15
+ *
16
+ * @module services/skill/skill-catalog.service
17
+ */
18
+ import * as fs from 'fs/promises';
19
+ import * as path from 'path';
20
+ import { existsSync, mkdirSync } from 'fs';
21
+ import * as os from 'os';
22
+ import { LoggerService } from '../core/logger.service.js';
23
+ import { CREWLY_CONSTANTS } from '../../constants.js';
24
+ import { parseSkillMd } from '../../utils/skill-md-parser.js';
25
+ // =============================================================================
26
+ // Constants
27
+ // =============================================================================
28
+ /** Name of the generated orchestrator catalog file */
29
+ const CATALOG_FILENAME = 'SKILLS_CATALOG.md';
30
+ /** Name of the generated agent catalog file */
31
+ const AGENT_CATALOG_FILENAME = 'AGENT_SKILLS_CATALOG.md';
32
+ /** Subdirectory under ~/.crewly where the catalog is written */
33
+ const CATALOG_SUBDIR = 'skills';
34
+ /** Relative path from project root to orchestrator skills */
35
+ const ORCHESTRATOR_SKILLS_RELATIVE_PATH = 'config/skills/orchestrator';
36
+ /** Relative path from project root to agent skills */
37
+ const AGENT_SKILLS_RELATIVE_PATH = 'config/skills/agent';
38
+ /** Directory names to skip when scanning for skills */
39
+ const SKIP_DIRECTORIES = ['_common'];
40
+ /** Skill definition file name expected in each skill directory */
41
+ const SKILL_DEFINITION_FILE = 'SKILL.md';
42
+ /** Legacy skill definition file name (for backward compatibility) */
43
+ const LEGACY_SKILL_DEFINITION_FILE = 'skill.json';
44
+ /** Markdown heading pattern for detecting section boundaries */
45
+ const HEADING_PATTERN = /^#{1,2}\s/;
46
+ // =============================================================================
47
+ // Service
48
+ // =============================================================================
49
+ /**
50
+ * Service for generating a Markdown skills catalog from orchestrator skill directories.
51
+ *
52
+ * Scans config/skills/orchestrator/ for subdirectories (skipping _common),
53
+ * reads skill.json and instructions.md from each, groups skills by category,
54
+ * and writes a formatted SKILLS_CATALOG.md to ~/.crewly/skills/.
55
+ *
56
+ * Uses a singleton pattern consistent with other Crewly services.
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * const catalogService = SkillCatalogService.getInstance('/path/to/project');
61
+ * const result = await catalogService.generateCatalog();
62
+ * console.log(`Generated catalog at ${result.catalogPath} with ${result.skillCount} skills`);
63
+ * ```
64
+ */
65
+ export class SkillCatalogService {
66
+ static instance = null;
67
+ logger;
68
+ projectRoot;
69
+ catalogDir;
70
+ /**
71
+ * Create a new SkillCatalogService instance.
72
+ *
73
+ * @param projectRoot - Absolute path to the project root directory.
74
+ * Defaults to the current working directory if not provided.
75
+ */
76
+ constructor(projectRoot) {
77
+ this.logger = LoggerService.getInstance().createComponentLogger('SkillCatalogService');
78
+ // #222: Resolve install dir reliably instead of defaulting to CWD
79
+ this.projectRoot = projectRoot || this.resolveInstallDir();
80
+ this.catalogDir = path.join(os.homedir(), CREWLY_CONSTANTS.PATHS.CREWLY_HOME, CATALOG_SUBDIR);
81
+ }
82
+ /**
83
+ * #222: Resolve the Crewly install directory from __dirname or CWD.
84
+ *
85
+ * @returns Absolute path to the Crewly install directory
86
+ */
87
+ resolveInstallDir() {
88
+ const candidates = [
89
+ path.resolve(__dirname, '..', '..', '..'), // from src/services/skill/
90
+ path.resolve(__dirname, '..', '..', '..', '..'), // from dist/src/services/skill/
91
+ ];
92
+ for (const candidate of candidates) {
93
+ if (existsSync(path.join(candidate, 'config', 'skills'))) {
94
+ return candidate;
95
+ }
96
+ }
97
+ return process.cwd();
98
+ }
99
+ /**
100
+ * Get the singleton SkillCatalogService instance.
101
+ *
102
+ * Creates a new instance on first call with the provided project root,
103
+ * or returns the existing instance on subsequent calls.
104
+ *
105
+ * @param projectRoot - Absolute path to the project root directory
106
+ * @returns The shared SkillCatalogService instance
107
+ */
108
+ static getInstance(projectRoot) {
109
+ if (!SkillCatalogService.instance) {
110
+ SkillCatalogService.instance = new SkillCatalogService(projectRoot);
111
+ }
112
+ return SkillCatalogService.instance;
113
+ }
114
+ /**
115
+ * Reset the singleton instance.
116
+ *
117
+ * Used primarily for testing to ensure a clean state between test runs.
118
+ */
119
+ static clearInstance() {
120
+ SkillCatalogService.instance = null;
121
+ }
122
+ /**
123
+ * Generate the skills catalog by scanning skill directories and writing
124
+ * the formatted Markdown file.
125
+ *
126
+ * This method performs the following steps:
127
+ * 1. Ensures the output directory (~/.crewly/skills/) exists
128
+ * 2. Scans config/skills/orchestrator/ for skill subdirectories
129
+ * 3. Reads skill.json and instructions.md from each valid directory
130
+ * 4. Groups loaded skills by their category field
131
+ * 5. Renders a Markdown catalog document
132
+ * 6. Writes the catalog to ~/.crewly/skills/SKILLS_CATALOG.md
133
+ *
134
+ * @returns A result object with success status, path, and counts
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * const result = await catalogService.generateCatalog();
139
+ * if (result.success) {
140
+ * console.log(`Catalog written to ${result.catalogPath}`);
141
+ * } else {
142
+ * console.error(`Catalog generation failed: ${result.error}`);
143
+ * }
144
+ * ```
145
+ */
146
+ async generateCatalog() {
147
+ const catalogPath = this.getCatalogPath();
148
+ try {
149
+ this.logger.info('Starting skills catalog generation', {
150
+ projectRoot: this.projectRoot,
151
+ catalogPath,
152
+ });
153
+ // Ensure the output directory exists
154
+ this.ensureCatalogDirectory();
155
+ // Scan and load all skill directories
156
+ const skills = await this.scanSkillDirectories();
157
+ if (skills.length === 0) {
158
+ this.logger.warn('No skills found during catalog generation');
159
+ const emptyMarkdown = this.renderCatalog(new Map());
160
+ await fs.writeFile(catalogPath, emptyMarkdown, 'utf-8');
161
+ return {
162
+ success: true,
163
+ catalogPath,
164
+ skillCount: 0,
165
+ categoryCount: 0,
166
+ };
167
+ }
168
+ // Group skills by category
169
+ const grouped = this.groupByCategory(skills);
170
+ // Render the Markdown catalog
171
+ const markdown = this.renderCatalog(grouped);
172
+ // Write the catalog file
173
+ await fs.writeFile(catalogPath, markdown, 'utf-8');
174
+ const categoryCount = grouped.size;
175
+ this.logger.info('Skills catalog generated successfully', {
176
+ skillCount: skills.length,
177
+ categoryCount,
178
+ catalogPath,
179
+ });
180
+ return {
181
+ success: true,
182
+ catalogPath,
183
+ skillCount: skills.length,
184
+ categoryCount,
185
+ };
186
+ }
187
+ catch (error) {
188
+ const errorMessage = error instanceof Error ? error.message : String(error);
189
+ this.logger.error('Failed to generate skills catalog', {
190
+ error: errorMessage,
191
+ });
192
+ return {
193
+ success: false,
194
+ catalogPath,
195
+ skillCount: 0,
196
+ categoryCount: 0,
197
+ error: errorMessage,
198
+ };
199
+ }
200
+ }
201
+ /**
202
+ * Generate the agent skills catalog by scanning agent skill directories
203
+ * and writing a formatted Markdown file.
204
+ *
205
+ * This method mirrors generateCatalog() but targets config/skills/agent/
206
+ * and writes to ~/.crewly/skills/AGENT_SKILLS_CATALOG.md.
207
+ *
208
+ * @returns A result object with success status, path, and counts
209
+ *
210
+ * @example
211
+ * ```typescript
212
+ * const result = await catalogService.generateAgentCatalog();
213
+ * if (result.success) {
214
+ * console.log(`Agent catalog written to ${result.catalogPath}`);
215
+ * }
216
+ * ```
217
+ */
218
+ async generateAgentCatalog() {
219
+ const catalogPath = this.getAgentCatalogPath();
220
+ try {
221
+ this.logger.info('Starting agent skills catalog generation', {
222
+ projectRoot: this.projectRoot,
223
+ catalogPath,
224
+ });
225
+ this.ensureCatalogDirectory();
226
+ // Scan bundled agent skills
227
+ const bundledSkills = await this.scanSkillDirectoriesAt(AGENT_SKILLS_RELATIVE_PATH);
228
+ // Use absolute path for agent skills because agents run in their target
229
+ // project directory (e.g. ~/projects/business_os), NOT the Crewly root.
230
+ // Relative paths like config/skills/agent/ won't resolve from there.
231
+ const absoluteSkillsPath = path.join(this.projectRoot, AGENT_SKILLS_RELATIVE_PATH);
232
+ // Also scan marketplace-installed skills
233
+ const marketplacePath = path.join(os.homedir(), '.crewly', 'marketplace', 'skills');
234
+ const marketplaceSkills = await this.scanSkillDirectoriesAt(marketplacePath, { absolute: true, setBasePath: true });
235
+ // Scan addon-installed skills at ~/.crewly/skills/agent/ (e.g. from crewly-pro)
236
+ const addonSkillsPath = path.join(os.homedir(), '.crewly', 'skills', 'agent');
237
+ const addonSkills = await this.scanSkillDirectoriesAt(addonSkillsPath, { absolute: true, setBasePath: true });
238
+ // Merge: addon skills take precedence over bundled on name conflict
239
+ const skillsByName = new Map();
240
+ for (const s of bundledSkills) {
241
+ skillsByName.set(s.definition.name, s);
242
+ }
243
+ for (const s of marketplaceSkills) {
244
+ skillsByName.set(s.definition.name, s);
245
+ }
246
+ for (const s of addonSkills) {
247
+ skillsByName.set(s.definition.name, s);
248
+ }
249
+ const skills = Array.from(skillsByName.values());
250
+ if (skills.length === 0) {
251
+ this.logger.warn('No agent skills found during catalog generation');
252
+ const emptyMarkdown = this.renderCatalogWithConfig(new Map(), {
253
+ title: 'Agent Skills Catalog',
254
+ skillsRelativePath: absoluteSkillsPath,
255
+ });
256
+ await fs.writeFile(catalogPath, emptyMarkdown, 'utf-8');
257
+ return {
258
+ success: true,
259
+ catalogPath,
260
+ skillCount: 0,
261
+ categoryCount: 0,
262
+ };
263
+ }
264
+ const grouped = this.groupByCategory(skills);
265
+ const markdown = this.renderCatalogWithConfig(grouped, {
266
+ title: 'Agent Skills Catalog',
267
+ skillsRelativePath: absoluteSkillsPath,
268
+ });
269
+ await fs.writeFile(catalogPath, markdown, 'utf-8');
270
+ const categoryCount = grouped.size;
271
+ this.logger.info('Agent skills catalog generated successfully', {
272
+ skillCount: skills.length,
273
+ categoryCount,
274
+ catalogPath,
275
+ marketplaceSkillCount: marketplaceSkills.length,
276
+ addonSkillCount: addonSkills.length,
277
+ });
278
+ return {
279
+ success: true,
280
+ catalogPath,
281
+ skillCount: skills.length,
282
+ categoryCount,
283
+ };
284
+ }
285
+ catch (error) {
286
+ const errorMessage = error instanceof Error ? error.message : String(error);
287
+ this.logger.error('Failed to generate agent skills catalog', {
288
+ error: errorMessage,
289
+ });
290
+ return {
291
+ success: false,
292
+ catalogPath,
293
+ skillCount: 0,
294
+ categoryCount: 0,
295
+ error: errorMessage,
296
+ };
297
+ }
298
+ }
299
+ /**
300
+ * Get the absolute path where the orchestrator catalog file will be written.
301
+ *
302
+ * @returns Absolute path to ~/.crewly/skills/SKILLS_CATALOG.md
303
+ */
304
+ getCatalogPath() {
305
+ return path.join(this.catalogDir, CATALOG_FILENAME);
306
+ }
307
+ /**
308
+ * Get the absolute path where the agent catalog file will be written.
309
+ *
310
+ * @returns Absolute path to ~/.crewly/skills/AGENT_SKILLS_CATALOG.md
311
+ */
312
+ getAgentCatalogPath() {
313
+ return path.join(this.catalogDir, AGENT_CATALOG_FILENAME);
314
+ }
315
+ // ==========================================================================
316
+ // Private methods
317
+ // ==========================================================================
318
+ /**
319
+ * Ensure the catalog output directory exists, creating it recursively
320
+ * if necessary.
321
+ *
322
+ * Uses synchronous mkdirSync since this is a one-time setup operation
323
+ * that must complete before writing the catalog.
324
+ */
325
+ ensureCatalogDirectory() {
326
+ if (!existsSync(this.catalogDir)) {
327
+ mkdirSync(this.catalogDir, { recursive: true });
328
+ }
329
+ }
330
+ /**
331
+ * Scan the orchestrator skills directory for valid skill subdirectories,
332
+ * plus agent skills whose `assignableRoles` includes `orchestrator`.
333
+ *
334
+ * Pipeline-#4 fix (spec 2026-05-05-request-decompose-pipeline-gap.md, Patch C
335
+ * zero-LOC alternative): some materialisation skills (e.g.
336
+ * `agent/core/break-down-request`) are role-shared between agents and the
337
+ * orchestrator and live under `config/skills/agent/core/`. Without this
338
+ * cross-scan, the orchestrator's catalog would not surface them and the orc
339
+ * would have no discoverable path to materialise WorkItems for a Request,
340
+ * even though the prompt at prompt-builder.service.ts:374 instructs it to.
341
+ *
342
+ * Skills already present under `config/skills/orchestrator/` win on
343
+ * collisions (deduped by name), so adding an explicit orchestrator-side
344
+ * skill later transparently shadows the agent-side one.
345
+ *
346
+ * @returns Array of loaded skills with their definitions and instructions
347
+ */
348
+ async scanSkillDirectories() {
349
+ const orchestratorSkills = await this.scanSkillDirectoriesAt(ORCHESTRATOR_SKILLS_RELATIVE_PATH);
350
+ // setBasePath: cross-scanned agent skills live under config/skills/agent/core/,
351
+ // so their catalog usage line must point there — without it the entry was
352
+ // rendered under the orchestrator path and the orc ran
353
+ // `orchestrator/<skill>/execute.sh` which does not exist (finding 12).
354
+ const agentCoreSkills = await this.scanSkillDirectoriesAt(`${AGENT_SKILLS_RELATIVE_PATH}/core`, {
355
+ setBasePath: true,
356
+ });
357
+ const orchestratorTagged = agentCoreSkills.filter((skill) => (skill.definition.assignableRoles ?? []).includes('orchestrator'));
358
+ // Dedup by skill name, with explicit orchestrator-side winning on collision.
359
+ const seenNames = new Set(orchestratorSkills.map((s) => s.definition.name));
360
+ const merged = [...orchestratorSkills];
361
+ for (const skill of orchestratorTagged) {
362
+ if (!seenNames.has(skill.definition.name)) {
363
+ merged.push(skill);
364
+ seenNames.add(skill.definition.name);
365
+ }
366
+ }
367
+ merged.sort((a, b) => a.definition.name.localeCompare(b.definition.name));
368
+ return merged;
369
+ }
370
+ /**
371
+ * Scan a skills directory for valid skill subdirectories.
372
+ *
373
+ * Reads each subdirectory (skipping entries in SKIP_DIRECTORIES),
374
+ * loads skill.json and instructions.md from each, and returns an array
375
+ * of successfully loaded skills. Directories that fail to load are
376
+ * logged as warnings but do not cause the entire scan to fail.
377
+ *
378
+ * When a subdirectory has no skill.json, it is treated as a category
379
+ * directory (e.g. core/, marketplace/) and its children are scanned.
380
+ *
381
+ * @param pathOrRelative - Path to the skills directory. Joined with
382
+ * projectRoot by default; used directly when `options.absolute` is true.
383
+ * @param options - Optional flags to control path resolution and basePath tagging
384
+ * @returns Array of loaded skills with their definitions and instructions
385
+ */
386
+ async scanSkillDirectoriesAt(pathOrRelative, options) {
387
+ const skillsRootDir = options?.absolute
388
+ ? pathOrRelative
389
+ : path.join(this.projectRoot, pathOrRelative);
390
+ if (!existsSync(skillsRootDir)) {
391
+ if (!options?.absolute) {
392
+ this.logger.warn('Skills directory not found', { path: skillsRootDir });
393
+ }
394
+ return [];
395
+ }
396
+ const entries = await fs.readdir(skillsRootDir, { withFileTypes: true });
397
+ const skills = [];
398
+ for (const entry of entries) {
399
+ if (!entry.isDirectory()) {
400
+ continue;
401
+ }
402
+ if (SKIP_DIRECTORIES.includes(entry.name)) {
403
+ this.logger.debug('Skipping excluded directory', { dirName: entry.name });
404
+ continue;
405
+ }
406
+ const skillDir = path.join(skillsRootDir, entry.name);
407
+ const skillMdPath = path.join(skillDir, SKILL_DEFINITION_FILE);
408
+ const legacyJsonPath = path.join(skillDir, LEGACY_SKILL_DEFINITION_FILE);
409
+ if (existsSync(skillMdPath) || existsSync(legacyJsonPath)) {
410
+ // Direct skill directory
411
+ const skill = await this.loadSkillFromDirectory(skillsRootDir, entry.name);
412
+ if (skill) {
413
+ if (options?.setBasePath) {
414
+ skill.basePath = skillsRootDir;
415
+ }
416
+ skills.push(skill);
417
+ }
418
+ }
419
+ else {
420
+ // Category subdirectory (e.g., core/, marketplace/) — recurse
421
+ const nestedEntries = await fs.readdir(skillDir, { withFileTypes: true });
422
+ for (const nested of nestedEntries) {
423
+ if (!nested.isDirectory())
424
+ continue;
425
+ const skill = await this.loadSkillFromDirectory(skillDir, nested.name);
426
+ if (skill) {
427
+ skill.basePath = skillDir;
428
+ skills.push(skill);
429
+ }
430
+ }
431
+ }
432
+ }
433
+ skills.sort((a, b) => a.definition.name.localeCompare(b.definition.name));
434
+ return skills;
435
+ }
436
+ /**
437
+ * Load a single skill from a directory by reading its SKILL.md file
438
+ * (YAML frontmatter + markdown body). Falls back to legacy skill.json
439
+ * + instructions.md format for backward compatibility.
440
+ *
441
+ * @param parentDir - Absolute path to the parent skills directory
442
+ * @param dirName - Name of the skill subdirectory
443
+ * @returns A LoadedSkill object, or null if loading fails
444
+ */
445
+ async loadSkillFromDirectory(parentDir, dirName) {
446
+ const skillDir = path.join(parentDir, dirName);
447
+ const skillMdPath = path.join(skillDir, SKILL_DEFINITION_FILE);
448
+ const legacyJsonPath = path.join(skillDir, LEGACY_SKILL_DEFINITION_FILE);
449
+ // Try SKILL.md first, then fall back to legacy skill.json
450
+ if (existsSync(skillMdPath)) {
451
+ return this.loadSkillFromSkillMd(skillDir, skillMdPath, dirName);
452
+ }
453
+ else if (existsSync(legacyJsonPath)) {
454
+ return this.loadSkillFromLegacyJson(skillDir, legacyJsonPath, dirName);
455
+ }
456
+ this.logger.debug('No SKILL.md or skill.json found, skipping directory', {
457
+ dirName,
458
+ });
459
+ return null;
460
+ }
461
+ /**
462
+ * Load a skill from SKILL.md format (YAML frontmatter + markdown body).
463
+ *
464
+ * @param skillDir - Absolute path to the skill directory
465
+ * @param skillMdPath - Path to the SKILL.md file
466
+ * @param dirName - Name of the skill subdirectory
467
+ * @returns A LoadedSkill object, or null if loading fails
468
+ */
469
+ async loadSkillFromSkillMd(skillDir, skillMdPath, dirName) {
470
+ try {
471
+ const content = await fs.readFile(skillMdPath, 'utf-8');
472
+ const { frontmatter, body } = parseSkillMd(content);
473
+ const definition = {
474
+ id: frontmatter.id || `skill-${dirName}`,
475
+ name: frontmatter.name,
476
+ description: frontmatter.description,
477
+ category: frontmatter.category,
478
+ execution: frontmatter.execution,
479
+ tags: frontmatter.tags,
480
+ version: frontmatter.version,
481
+ assignableRoles: frontmatter.assignableRoles,
482
+ };
483
+ if (!definition.name || !definition.description || !definition.category) {
484
+ this.logger.warn('SKILL.md missing required fields', {
485
+ dirName,
486
+ path: skillMdPath,
487
+ });
488
+ return null;
489
+ }
490
+ return {
491
+ definition,
492
+ instructions: body,
493
+ dirName,
494
+ };
495
+ }
496
+ catch (error) {
497
+ const errorMessage = error instanceof Error ? error.message : String(error);
498
+ this.logger.warn('Failed to load skill from SKILL.md', {
499
+ dirName,
500
+ error: errorMessage,
501
+ });
502
+ return null;
503
+ }
504
+ }
505
+ /**
506
+ * Load a skill from legacy skill.json + instructions.md format.
507
+ *
508
+ * @param skillDir - Absolute path to the skill directory
509
+ * @param skillJsonPath - Path to the skill.json file
510
+ * @param dirName - Name of the skill subdirectory
511
+ * @returns A LoadedSkill object, or null if loading fails
512
+ */
513
+ async loadSkillFromLegacyJson(skillDir, skillJsonPath, dirName) {
514
+ try {
515
+ const skillJsonContent = await fs.readFile(skillJsonPath, 'utf-8');
516
+ const definition = JSON.parse(skillJsonContent);
517
+ if (!definition.id || !definition.name || !definition.description || !definition.category) {
518
+ this.logger.warn('Skill definition missing required fields', {
519
+ dirName,
520
+ id: definition.id,
521
+ });
522
+ return null;
523
+ }
524
+ let instructions = '';
525
+ const instructionsPath = path.join(skillDir, 'instructions.md');
526
+ if (existsSync(instructionsPath)) {
527
+ instructions = await fs.readFile(instructionsPath, 'utf-8');
528
+ }
529
+ return {
530
+ definition,
531
+ instructions,
532
+ dirName,
533
+ };
534
+ }
535
+ catch (error) {
536
+ const errorMessage = error instanceof Error ? error.message : String(error);
537
+ this.logger.warn('Failed to load skill from skill.json', {
538
+ dirName,
539
+ error: errorMessage,
540
+ });
541
+ return null;
542
+ }
543
+ }
544
+ /**
545
+ * Group loaded skills by their category field.
546
+ *
547
+ * Categories are title-cased for display (e.g., "management" becomes "Management").
548
+ * Skills within each category are maintained in their current sorted order.
549
+ *
550
+ * @param skills - Array of loaded skills to group
551
+ * @returns Map of category display name to array of skills in that category
552
+ */
553
+ groupByCategory(skills) {
554
+ const grouped = new Map();
555
+ for (const skill of skills) {
556
+ const categoryKey = this.formatCategoryName(skill.definition.category);
557
+ const existing = grouped.get(categoryKey) ?? [];
558
+ existing.push(skill);
559
+ grouped.set(categoryKey, existing);
560
+ }
561
+ // Sort categories alphabetically
562
+ const sorted = new Map(Array.from(grouped.entries()).sort(([a], [b]) => a.localeCompare(b)));
563
+ return sorted;
564
+ }
565
+ /**
566
+ * Format a raw category string into a display-friendly title.
567
+ *
568
+ * Capitalizes the first letter of each word and replaces hyphens/underscores
569
+ * with spaces (e.g., "task-management" becomes "Task Management").
570
+ *
571
+ * @param category - Raw category string from skill.json
572
+ * @returns Formatted category display name
573
+ */
574
+ formatCategoryName(category) {
575
+ return category
576
+ .split(/[-_\s]+/)
577
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
578
+ .join(' ');
579
+ }
580
+ /**
581
+ * Render the complete Markdown catalog document from grouped skills.
582
+ *
583
+ * Delegates to renderCatalogWithConfig() with orchestrator defaults.
584
+ *
585
+ * @param groupedSkills - Map of category name to skills in that category
586
+ * @returns Complete Markdown document as a string
587
+ */
588
+ renderCatalog(groupedSkills) {
589
+ // Use absolute path so skills work regardless of the orchestrator's cwd
590
+ const absoluteSkillsPath = path.join(this.projectRoot, ORCHESTRATOR_SKILLS_RELATIVE_PATH);
591
+ return this.renderCatalogWithConfig(groupedSkills, {
592
+ title: 'Orchestrator Skills Catalog',
593
+ skillsRelativePath: absoluteSkillsPath,
594
+ });
595
+ }
596
+ /**
597
+ * Render the complete Markdown catalog document from grouped skills
598
+ * with configurable title and path prefix.
599
+ *
600
+ * Produces the full catalog including header, usage instructions,
601
+ * category sections, and individual skill entries with descriptions
602
+ * and parameter documentation extracted from instructions.md.
603
+ *
604
+ * @param groupedSkills - Map of category name to skills in that category
605
+ * @param config - Catalog rendering configuration
606
+ * @param config.title - Catalog title for the Markdown heading
607
+ * @param config.skillsRelativePath - Relative path prefix for skill usage commands
608
+ * @returns Complete Markdown document as a string
609
+ */
610
+ renderCatalogWithConfig(groupedSkills, config) {
611
+ const timestamp = new Date().toISOString();
612
+ const lines = [];
613
+ // Header
614
+ lines.push(`# ${config.title}`);
615
+ lines.push(`> Auto-generated on ${timestamp}. Execute skills via bash scripts.`);
616
+ lines.push('');
617
+ // How to Use section
618
+ lines.push('## How to Use');
619
+ lines.push('');
620
+ lines.push('All skills follow the pattern:');
621
+ lines.push('```bash');
622
+ lines.push(`bash ${config.skillsRelativePath}/{skill-name}/execute.sh '{"param":"value"}'`);
623
+ lines.push('```');
624
+ lines.push('All scripts output JSON to stdout. Errors go to stderr.');
625
+ lines.push('');
626
+ // Category sections
627
+ for (const [categoryName, skills] of groupedSkills) {
628
+ lines.push(`## ${categoryName}`);
629
+ lines.push('');
630
+ for (const skill of skills) {
631
+ lines.push(...this.renderSkillEntry(skill, config.skillsRelativePath));
632
+ }
633
+ }
634
+ return lines.join('\n');
635
+ }
636
+ /**
637
+ * Render a single skill entry as Markdown lines.
638
+ *
639
+ * Includes the skill name, description, usage command, and any
640
+ * Parameters section extracted from the skill's instructions.md.
641
+ *
642
+ * @param skill - The loaded skill to render
643
+ * @param skillsRelativePath - Relative path prefix for usage command
644
+ * @returns Array of Markdown lines for this skill entry
645
+ */
646
+ renderSkillEntry(skill, skillsRelativePath = ORCHESTRATOR_SKILLS_RELATIVE_PATH) {
647
+ const lines = [];
648
+ // Skill heading and description
649
+ lines.push(`### ${skill.definition.name}`);
650
+ lines.push(skill.definition.description);
651
+ lines.push('');
652
+ // Usage line — use the skill's basePath if it has one (marketplace skills),
653
+ // otherwise fall back to the catalog-level skillsRelativePath (bundled skills)
654
+ const effectivePath = skill.basePath || skillsRelativePath;
655
+ lines.push(`**Usage:** \`bash ${effectivePath}/${skill.dirName}/execute.sh '{}'\``);
656
+ lines.push('');
657
+ // Extract and include Parameters section from instructions.md if available
658
+ const parametersSection = this.extractParametersSection(skill.instructions);
659
+ if (parametersSection) {
660
+ lines.push(parametersSection);
661
+ lines.push('');
662
+ }
663
+ // Separator
664
+ lines.push('---');
665
+ lines.push('');
666
+ return lines;
667
+ }
668
+ /**
669
+ * Extract the Parameters section from an instructions.md file's content.
670
+ *
671
+ * Uses a line-based approach to find the "## Parameters" heading and capture
672
+ * all content until the next heading of the same or higher level (# or ##)
673
+ * or the end of the file. Returns null if no Parameters section is found.
674
+ *
675
+ * @param instructionsContent - Full content of the instructions.md file
676
+ * @returns The Parameters section content (without the heading), or null
677
+ */
678
+ extractParametersSection(instructionsContent) {
679
+ if (!instructionsContent) {
680
+ return null;
681
+ }
682
+ const lines = instructionsContent.split('\n');
683
+ let capturing = false;
684
+ const captured = [];
685
+ for (const line of lines) {
686
+ if (capturing) {
687
+ // Stop capturing when we hit another heading (# or ##)
688
+ if (HEADING_PATTERN.test(line)) {
689
+ break;
690
+ }
691
+ captured.push(line);
692
+ }
693
+ else if (line.match(/^## Parameters\s*$/)) {
694
+ // Start capturing after the Parameters heading
695
+ capturing = true;
696
+ }
697
+ }
698
+ if (!capturing || captured.length === 0) {
699
+ return null;
700
+ }
701
+ const content = captured.join('\n').trim();
702
+ if (content.length === 0) {
703
+ return null;
704
+ }
705
+ return content;
706
+ }
707
+ }
708
+ //# sourceMappingURL=skill-catalog.service.js.map