@skillkit/core 1.3.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.
package/dist/index.js ADDED
@@ -0,0 +1,3676 @@
1
+ // src/types.ts
2
+ import { z } from "zod";
3
+ var AgentType = z.enum([
4
+ "claude-code",
5
+ "codex",
6
+ "cursor",
7
+ "antigravity",
8
+ "opencode",
9
+ "gemini-cli",
10
+ "amp",
11
+ "clawdbot",
12
+ "droid",
13
+ "github-copilot",
14
+ "goose",
15
+ "kilo",
16
+ "kiro-cli",
17
+ "roo",
18
+ "trae",
19
+ "windsurf",
20
+ "universal"
21
+ ]);
22
+ var GitProvider = z.enum(["github", "gitlab", "bitbucket", "local"]);
23
+ var SkillFrontmatter = z.object({
24
+ name: z.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "Skill name must be lowercase alphanumeric with hyphens, no leading/trailing/consecutive hyphens"),
25
+ description: z.string().min(1).max(1024),
26
+ license: z.string().optional(),
27
+ compatibility: z.string().max(500).optional(),
28
+ metadata: z.record(z.string()).optional(),
29
+ "allowed-tools": z.string().optional(),
30
+ version: z.string().optional(),
31
+ author: z.string().optional(),
32
+ tags: z.array(z.string()).optional(),
33
+ agents: z.array(AgentType).optional()
34
+ });
35
+ var SkillMetadata = z.object({
36
+ name: z.string(),
37
+ description: z.string(),
38
+ source: z.string(),
39
+ sourceType: GitProvider,
40
+ subpath: z.string().optional(),
41
+ installedAt: z.string().datetime(),
42
+ updatedAt: z.string().datetime().optional(),
43
+ enabled: z.boolean().default(true),
44
+ version: z.string().optional(),
45
+ checksum: z.string().optional()
46
+ });
47
+ var SkillLocation = z.enum(["project", "global"]);
48
+ var Skill = z.object({
49
+ name: z.string(),
50
+ description: z.string(),
51
+ path: z.string(),
52
+ location: SkillLocation,
53
+ metadata: SkillMetadata.optional(),
54
+ enabled: z.boolean().default(true)
55
+ });
56
+ var SkillkitConfig = z.object({
57
+ version: z.literal(1),
58
+ agent: AgentType.default("universal"),
59
+ skillsDir: z.string().optional(),
60
+ enabledSkills: z.array(z.string()).optional(),
61
+ disabledSkills: z.array(z.string()).optional(),
62
+ autoSync: z.boolean().default(true)
63
+ });
64
+
65
+ // src/skills.ts
66
+ import { existsSync, readdirSync, readFileSync } from "fs";
67
+ import { join, basename } from "path";
68
+ import { parse as parseYaml } from "yaml";
69
+ var SKILL_DISCOVERY_PATHS = [
70
+ "skills",
71
+ "skills/.curated",
72
+ "skills/.experimental",
73
+ "skills/.system",
74
+ ".agents/skills",
75
+ ".agent/skills",
76
+ ".claude/skills",
77
+ ".codex/skills",
78
+ ".cursor/skills",
79
+ ".factory/skills",
80
+ ".gemini/skills",
81
+ ".github/skills",
82
+ ".goose/skills",
83
+ ".kilocode/skills",
84
+ ".kiro/skills",
85
+ ".opencode/skills",
86
+ ".roo/skills",
87
+ ".trae/skills",
88
+ ".windsurf/skills",
89
+ ".clawdbot/skills",
90
+ ".antigravity/skills",
91
+ ".copilot/skills"
92
+ ];
93
+ function discoverSkillsInDir(dir) {
94
+ const skills = [];
95
+ if (!existsSync(dir)) {
96
+ return skills;
97
+ }
98
+ const entries = readdirSync(dir, { withFileTypes: true });
99
+ for (const entry of entries) {
100
+ if (!entry.isDirectory()) continue;
101
+ const skillPath = join(dir, entry.name);
102
+ const skillMdPath = join(skillPath, "SKILL.md");
103
+ if (existsSync(skillMdPath)) {
104
+ const skill = parseSkill(skillPath);
105
+ if (skill) {
106
+ skills.push(skill);
107
+ }
108
+ }
109
+ }
110
+ return skills;
111
+ }
112
+ function discoverSkillsRecursive(dir, seen, maxDepth = 5, currentDepth = 0) {
113
+ const skills = [];
114
+ if (currentDepth >= maxDepth || !existsSync(dir)) {
115
+ return skills;
116
+ }
117
+ try {
118
+ const entries = readdirSync(dir, { withFileTypes: true });
119
+ for (const entry of entries) {
120
+ if (entry.name === "node_modules" || entry.name === ".git") {
121
+ continue;
122
+ }
123
+ if (!entry.isDirectory()) continue;
124
+ const entryPath = join(dir, entry.name);
125
+ const skillMdPath = join(entryPath, "SKILL.md");
126
+ if (existsSync(skillMdPath)) {
127
+ const skill = parseSkill(entryPath);
128
+ if (skill && !seen.has(skill.name)) {
129
+ seen.add(skill.name);
130
+ skills.push(skill);
131
+ }
132
+ } else {
133
+ const subSkills = discoverSkillsRecursive(entryPath, seen, maxDepth, currentDepth + 1);
134
+ skills.push(...subSkills);
135
+ }
136
+ }
137
+ } catch {
138
+ }
139
+ return skills;
140
+ }
141
+ function discoverSkills(rootDir) {
142
+ const skills = [];
143
+ const seen = /* @__PURE__ */ new Set();
144
+ const rootSkillMd = join(rootDir, "SKILL.md");
145
+ if (existsSync(rootSkillMd)) {
146
+ const skill = parseSkill(rootDir);
147
+ if (skill && !seen.has(skill.name)) {
148
+ seen.add(skill.name);
149
+ skills.push(skill);
150
+ }
151
+ }
152
+ for (const searchPath of SKILL_DISCOVERY_PATHS) {
153
+ const fullPath = join(rootDir, searchPath);
154
+ if (existsSync(fullPath)) {
155
+ for (const skill of discoverSkillsInDir(fullPath)) {
156
+ if (!seen.has(skill.name)) {
157
+ seen.add(skill.name);
158
+ skills.push(skill);
159
+ }
160
+ }
161
+ }
162
+ }
163
+ for (const skill of discoverSkillsInDir(rootDir)) {
164
+ if (!seen.has(skill.name)) {
165
+ seen.add(skill.name);
166
+ skills.push(skill);
167
+ }
168
+ }
169
+ if (skills.length === 0) {
170
+ skills.push(...discoverSkillsRecursive(rootDir, seen));
171
+ }
172
+ return skills;
173
+ }
174
+ function parseSkill(skillPath, location = "project") {
175
+ const skillMdPath = join(skillPath, "SKILL.md");
176
+ if (!existsSync(skillMdPath)) {
177
+ return null;
178
+ }
179
+ try {
180
+ const content = readFileSync(skillMdPath, "utf-8");
181
+ const frontmatter = extractFrontmatter(content);
182
+ if (!frontmatter) {
183
+ const name = basename(skillPath);
184
+ return {
185
+ name,
186
+ description: "No description available",
187
+ path: skillPath,
188
+ location,
189
+ enabled: true
190
+ };
191
+ }
192
+ const parsed = SkillFrontmatter.safeParse(frontmatter);
193
+ if (!parsed.success) {
194
+ return {
195
+ name: frontmatter.name || basename(skillPath),
196
+ description: frontmatter.description || "No description available",
197
+ path: skillPath,
198
+ location,
199
+ enabled: true
200
+ };
201
+ }
202
+ const metadata = loadMetadata(skillPath);
203
+ return {
204
+ name: parsed.data.name,
205
+ description: parsed.data.description,
206
+ path: skillPath,
207
+ location,
208
+ metadata: metadata ?? void 0,
209
+ enabled: metadata?.enabled ?? true
210
+ };
211
+ } catch {
212
+ return null;
213
+ }
214
+ }
215
+ function extractFrontmatter(content) {
216
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
217
+ if (!match) {
218
+ return null;
219
+ }
220
+ try {
221
+ return parseYaml(match[1]);
222
+ } catch {
223
+ return null;
224
+ }
225
+ }
226
+ function extractField(content, field) {
227
+ const frontmatter = extractFrontmatter(content);
228
+ if (!frontmatter || !(field in frontmatter)) {
229
+ return null;
230
+ }
231
+ const value = frontmatter[field];
232
+ return typeof value === "string" ? value : null;
233
+ }
234
+ function loadMetadata(skillPath) {
235
+ const metadataPath = join(skillPath, ".skillkit.json");
236
+ if (!existsSync(metadataPath)) {
237
+ return null;
238
+ }
239
+ try {
240
+ const content = readFileSync(metadataPath, "utf-8");
241
+ const data = JSON.parse(content);
242
+ const parsed = SkillMetadata.safeParse(data);
243
+ return parsed.success ? parsed.data : null;
244
+ } catch {
245
+ return null;
246
+ }
247
+ }
248
+ function readSkillContent(skillPath) {
249
+ const skillMdPath = join(skillPath, "SKILL.md");
250
+ if (!existsSync(skillMdPath)) {
251
+ return null;
252
+ }
253
+ try {
254
+ return readFileSync(skillMdPath, "utf-8");
255
+ } catch {
256
+ return null;
257
+ }
258
+ }
259
+ function findSkill(name, searchDirs) {
260
+ for (const dir of searchDirs) {
261
+ if (!existsSync(dir)) continue;
262
+ const skillPath = join(dir, name);
263
+ if (existsSync(skillPath)) {
264
+ const location = dir.includes(process.cwd()) ? "project" : "global";
265
+ return parseSkill(skillPath, location);
266
+ }
267
+ }
268
+ return null;
269
+ }
270
+ function findAllSkills(searchDirs) {
271
+ const skills = [];
272
+ const seen = /* @__PURE__ */ new Set();
273
+ for (const dir of searchDirs) {
274
+ if (!existsSync(dir)) continue;
275
+ const location = dir.includes(process.cwd()) ? "project" : "global";
276
+ const discovered = discoverSkills(dir);
277
+ for (const skill of discovered) {
278
+ if (!seen.has(skill.name)) {
279
+ seen.add(skill.name);
280
+ skills.push({ ...skill, location });
281
+ }
282
+ }
283
+ }
284
+ return skills;
285
+ }
286
+ function validateSkill(skillPath) {
287
+ const errors = [];
288
+ const warnings = [];
289
+ const dirName = basename(skillPath);
290
+ const skillMdPath = join(skillPath, "SKILL.md");
291
+ if (!existsSync(skillMdPath)) {
292
+ errors.push("Missing SKILL.md file");
293
+ return { valid: false, errors };
294
+ }
295
+ const content = readFileSync(skillMdPath, "utf-8");
296
+ const frontmatter = extractFrontmatter(content);
297
+ if (!frontmatter) {
298
+ errors.push("Missing YAML frontmatter in SKILL.md");
299
+ return { valid: false, errors };
300
+ }
301
+ const parsed = SkillFrontmatter.safeParse(frontmatter);
302
+ if (!parsed.success) {
303
+ for (const issue of parsed.error.issues) {
304
+ errors.push(`${issue.path.join(".") || "frontmatter"}: ${issue.message}`);
305
+ }
306
+ }
307
+ if (parsed.success) {
308
+ const data = parsed.data;
309
+ if (data.name !== dirName) {
310
+ warnings.push(`name "${data.name}" does not match directory name "${dirName}"`);
311
+ }
312
+ if (data.description && data.description.length < 50) {
313
+ warnings.push("description is short; consider describing what the skill does AND when to use it");
314
+ }
315
+ }
316
+ const bodyContent = content.replace(/^---[\s\S]*?---\s*/, "");
317
+ const lineCount = bodyContent.split("\n").length;
318
+ if (lineCount > 500) {
319
+ warnings.push(`SKILL.md has ${lineCount} lines; consider moving detailed content to references/`);
320
+ }
321
+ return { valid: errors.length === 0, errors, warnings };
322
+ }
323
+ function isPathInside(child, parent) {
324
+ const relative = child.replace(parent, "");
325
+ return !relative.startsWith("..") && !relative.includes("/..");
326
+ }
327
+
328
+ // src/config.ts
329
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
330
+ import { join as join2, dirname } from "path";
331
+ import { homedir } from "os";
332
+ import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
333
+ var CONFIG_FILE = "skillkit.yaml";
334
+ var METADATA_FILE = ".skillkit.json";
335
+ function getProjectConfigPath() {
336
+ return join2(process.cwd(), CONFIG_FILE);
337
+ }
338
+ function getGlobalConfigPath() {
339
+ return join2(homedir(), ".config", "skillkit", CONFIG_FILE);
340
+ }
341
+ function loadConfig() {
342
+ const projectPath = getProjectConfigPath();
343
+ const globalPath = getGlobalConfigPath();
344
+ if (existsSync2(projectPath)) {
345
+ try {
346
+ const content = readFileSync2(projectPath, "utf-8");
347
+ const data = parseYaml2(content);
348
+ const parsed = SkillkitConfig.safeParse(data);
349
+ if (parsed.success) {
350
+ return parsed.data;
351
+ }
352
+ } catch {
353
+ }
354
+ }
355
+ if (existsSync2(globalPath)) {
356
+ try {
357
+ const content = readFileSync2(globalPath, "utf-8");
358
+ const data = parseYaml2(content);
359
+ const parsed = SkillkitConfig.safeParse(data);
360
+ if (parsed.success) {
361
+ return parsed.data;
362
+ }
363
+ } catch {
364
+ }
365
+ }
366
+ return {
367
+ version: 1,
368
+ agent: "universal",
369
+ autoSync: true
370
+ };
371
+ }
372
+ function saveConfig(config, global = false) {
373
+ const configPath = global ? getGlobalConfigPath() : getProjectConfigPath();
374
+ const dir = dirname(configPath);
375
+ if (!existsSync2(dir)) {
376
+ mkdirSync(dir, { recursive: true });
377
+ }
378
+ const content = stringifyYaml(config);
379
+ writeFileSync(configPath, content, "utf-8");
380
+ }
381
+ function getSearchDirs(adapter) {
382
+ const dirs = [];
383
+ dirs.push(join2(process.cwd(), adapter.skillsDir));
384
+ dirs.push(join2(process.cwd(), ".agent", "skills"));
385
+ dirs.push(join2(homedir(), adapter.skillsDir));
386
+ dirs.push(join2(homedir(), ".agent", "skills"));
387
+ return dirs;
388
+ }
389
+ function getInstallDir(adapter, global = false) {
390
+ if (global) {
391
+ return join2(homedir(), adapter.skillsDir);
392
+ }
393
+ return join2(process.cwd(), adapter.skillsDir);
394
+ }
395
+ function getAgentConfigPath(adapter) {
396
+ return join2(process.cwd(), adapter.configFile);
397
+ }
398
+ function saveSkillMetadata(skillPath, metadata) {
399
+ const metadataPath = join2(skillPath, METADATA_FILE);
400
+ writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
401
+ }
402
+ function loadSkillMetadata(skillPath) {
403
+ const metadataPath = join2(skillPath, METADATA_FILE);
404
+ if (!existsSync2(metadataPath)) {
405
+ return null;
406
+ }
407
+ try {
408
+ const content = readFileSync2(metadataPath, "utf-8");
409
+ return JSON.parse(content);
410
+ } catch {
411
+ return null;
412
+ }
413
+ }
414
+ function setSkillEnabled(skillPath, enabled) {
415
+ const metadata = loadSkillMetadata(skillPath);
416
+ if (!metadata) {
417
+ return false;
418
+ }
419
+ metadata.enabled = enabled;
420
+ metadata.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
421
+ saveSkillMetadata(skillPath, metadata);
422
+ return true;
423
+ }
424
+ async function initProject(type, adapter) {
425
+ const skillsDir = join2(process.cwd(), adapter.skillsDir);
426
+ if (!existsSync2(skillsDir)) {
427
+ mkdirSync(skillsDir, { recursive: true });
428
+ }
429
+ const config = {
430
+ version: 1,
431
+ agent: type,
432
+ autoSync: true
433
+ };
434
+ saveConfig(config);
435
+ const agentConfigPath = join2(process.cwd(), adapter.configFile);
436
+ if (!existsSync2(agentConfigPath)) {
437
+ writeFileSync(agentConfigPath, `# ${adapter.name} Configuration
438
+
439
+ `, "utf-8");
440
+ }
441
+ }
442
+
443
+ // src/providers/github.ts
444
+ import { execSync } from "child_process";
445
+ import { existsSync as existsSync3, rmSync } from "fs";
446
+ import { join as join3, basename as basename2 } from "path";
447
+ import { tmpdir } from "os";
448
+ import { randomUUID } from "crypto";
449
+
450
+ // src/providers/base.ts
451
+ function parseShorthand(source) {
452
+ const cleaned = source.replace(/^\/+|\/+$/g, "");
453
+ const parts = cleaned.split("/");
454
+ if (parts.length < 2) {
455
+ return null;
456
+ }
457
+ const owner = parts[0];
458
+ const repo = parts[1];
459
+ const subpath = parts.length > 2 ? parts.slice(2).join("/") : void 0;
460
+ return { owner, repo, subpath };
461
+ }
462
+ function isLocalPath(source) {
463
+ return source.startsWith("/") || source.startsWith("./") || source.startsWith("../") || source.startsWith("~/") || source.startsWith(".");
464
+ }
465
+ function isGitUrl(source) {
466
+ return source.startsWith("git@") || source.startsWith("https://") || source.startsWith("http://") || source.startsWith("ssh://");
467
+ }
468
+
469
+ // src/providers/github.ts
470
+ var GitHubProvider = class {
471
+ type = "github";
472
+ name = "GitHub";
473
+ baseUrl = "https://github.com";
474
+ parseSource(source) {
475
+ if (source.startsWith("https://github.com/")) {
476
+ const path = source.replace("https://github.com/", "").replace(/\.git$/, "");
477
+ return parseShorthand(path);
478
+ }
479
+ if (source.startsWith("git@github.com:")) {
480
+ const path = source.replace("git@github.com:", "").replace(/\.git$/, "");
481
+ return parseShorthand(path);
482
+ }
483
+ if (!isGitUrl(source) && !source.includes(":")) {
484
+ return parseShorthand(source);
485
+ }
486
+ return null;
487
+ }
488
+ matches(source) {
489
+ return source.startsWith("https://github.com/") || source.startsWith("git@github.com:") || !isGitUrl(source) && !source.includes(":") && source.includes("/");
490
+ }
491
+ getCloneUrl(owner, repo) {
492
+ return `https://github.com/${owner}/${repo}.git`;
493
+ }
494
+ getSshUrl(owner, repo) {
495
+ return `git@github.com:${owner}/${repo}.git`;
496
+ }
497
+ async clone(source, _targetDir, options = {}) {
498
+ const parsed = this.parseSource(source);
499
+ if (!parsed) {
500
+ return { success: false, error: `Invalid GitHub source: ${source}` };
501
+ }
502
+ const { owner, repo, subpath } = parsed;
503
+ const cloneUrl = options.ssh ? this.getSshUrl(owner, repo) : this.getCloneUrl(owner, repo);
504
+ const tempDir = join3(tmpdir(), `skillkit-${randomUUID()}`);
505
+ try {
506
+ const args = ["clone"];
507
+ if (options.depth) {
508
+ args.push("--depth", String(options.depth));
509
+ }
510
+ if (options.branch) {
511
+ args.push("--branch", options.branch);
512
+ }
513
+ args.push(cloneUrl, tempDir);
514
+ execSync(`git ${args.join(" ")}`, {
515
+ stdio: ["pipe", "pipe", "pipe"],
516
+ encoding: "utf-8"
517
+ });
518
+ const searchDir = subpath ? join3(tempDir, subpath) : tempDir;
519
+ const skills = discoverSkills(searchDir);
520
+ return {
521
+ success: true,
522
+ path: searchDir,
523
+ tempRoot: tempDir,
524
+ skills: skills.map((s) => s.name),
525
+ discoveredSkills: skills.map((s) => ({
526
+ name: s.name,
527
+ dirName: basename2(s.path),
528
+ path: s.path
529
+ }))
530
+ };
531
+ } catch (error) {
532
+ if (existsSync3(tempDir)) {
533
+ rmSync(tempDir, { recursive: true, force: true });
534
+ }
535
+ const message = error instanceof Error ? error.message : String(error);
536
+ return { success: false, error: `Failed to clone: ${message}` };
537
+ }
538
+ }
539
+ };
540
+
541
+ // src/providers/gitlab.ts
542
+ import { execSync as execSync2 } from "child_process";
543
+ import { existsSync as existsSync4, rmSync as rmSync2 } from "fs";
544
+ import { join as join4, basename as basename3 } from "path";
545
+ import { tmpdir as tmpdir2 } from "os";
546
+ import { randomUUID as randomUUID2 } from "crypto";
547
+ var GitLabProvider = class {
548
+ type = "gitlab";
549
+ name = "GitLab";
550
+ baseUrl = "https://gitlab.com";
551
+ parseSource(source) {
552
+ if (source.startsWith("https://gitlab.com/")) {
553
+ const path = source.replace("https://gitlab.com/", "").replace(/\.git$/, "");
554
+ return parseShorthand(path);
555
+ }
556
+ if (source.startsWith("git@gitlab.com:")) {
557
+ const path = source.replace("git@gitlab.com:", "").replace(/\.git$/, "");
558
+ return parseShorthand(path);
559
+ }
560
+ if (source.startsWith("gitlab:")) {
561
+ return parseShorthand(source.replace("gitlab:", ""));
562
+ }
563
+ if (source.startsWith("gitlab.com/")) {
564
+ return parseShorthand(source.replace("gitlab.com/", ""));
565
+ }
566
+ return null;
567
+ }
568
+ matches(source) {
569
+ return source.startsWith("https://gitlab.com/") || source.startsWith("git@gitlab.com:") || source.startsWith("gitlab:") || source.startsWith("gitlab.com/");
570
+ }
571
+ getCloneUrl(owner, repo) {
572
+ return `https://gitlab.com/${owner}/${repo}.git`;
573
+ }
574
+ getSshUrl(owner, repo) {
575
+ return `git@gitlab.com:${owner}/${repo}.git`;
576
+ }
577
+ async clone(source, _targetDir, options = {}) {
578
+ const parsed = this.parseSource(source);
579
+ if (!parsed) {
580
+ return { success: false, error: `Invalid GitLab source: ${source}` };
581
+ }
582
+ const { owner, repo, subpath } = parsed;
583
+ const cloneUrl = options.ssh ? this.getSshUrl(owner, repo) : this.getCloneUrl(owner, repo);
584
+ const tempDir = join4(tmpdir2(), `skillkit-${randomUUID2()}`);
585
+ try {
586
+ const args = ["clone"];
587
+ if (options.depth) {
588
+ args.push("--depth", String(options.depth));
589
+ }
590
+ if (options.branch) {
591
+ args.push("--branch", options.branch);
592
+ }
593
+ args.push(cloneUrl, tempDir);
594
+ execSync2(`git ${args.join(" ")}`, {
595
+ stdio: ["pipe", "pipe", "pipe"],
596
+ encoding: "utf-8"
597
+ });
598
+ const searchDir = subpath ? join4(tempDir, subpath) : tempDir;
599
+ const skills = discoverSkills(searchDir);
600
+ return {
601
+ success: true,
602
+ path: searchDir,
603
+ tempRoot: tempDir,
604
+ skills: skills.map((s) => s.name),
605
+ discoveredSkills: skills.map((s) => ({
606
+ name: s.name,
607
+ dirName: basename3(s.path),
608
+ path: s.path
609
+ }))
610
+ };
611
+ } catch (error) {
612
+ if (existsSync4(tempDir)) {
613
+ rmSync2(tempDir, { recursive: true, force: true });
614
+ }
615
+ const message = error instanceof Error ? error.message : String(error);
616
+ return { success: false, error: `Failed to clone: ${message}` };
617
+ }
618
+ }
619
+ };
620
+
621
+ // src/providers/bitbucket.ts
622
+ import { execSync as execSync3 } from "child_process";
623
+ import { existsSync as existsSync5, rmSync as rmSync3 } from "fs";
624
+ import { join as join5, basename as basename4 } from "path";
625
+ import { tmpdir as tmpdir3 } from "os";
626
+ import { randomUUID as randomUUID3 } from "crypto";
627
+ var BitbucketProvider = class {
628
+ type = "bitbucket";
629
+ name = "Bitbucket";
630
+ baseUrl = "https://bitbucket.org";
631
+ parseSource(source) {
632
+ if (source.startsWith("https://bitbucket.org/")) {
633
+ const path = source.replace("https://bitbucket.org/", "").replace(/\.git$/, "");
634
+ return parseShorthand(path);
635
+ }
636
+ if (source.startsWith("git@bitbucket.org:")) {
637
+ const path = source.replace("git@bitbucket.org:", "").replace(/\.git$/, "");
638
+ return parseShorthand(path);
639
+ }
640
+ if (source.startsWith("bitbucket:")) {
641
+ return parseShorthand(source.replace("bitbucket:", ""));
642
+ }
643
+ if (source.startsWith("bitbucket.org/")) {
644
+ return parseShorthand(source.replace("bitbucket.org/", ""));
645
+ }
646
+ return null;
647
+ }
648
+ matches(source) {
649
+ return source.startsWith("https://bitbucket.org/") || source.startsWith("git@bitbucket.org:") || source.startsWith("bitbucket:") || source.startsWith("bitbucket.org/");
650
+ }
651
+ getCloneUrl(owner, repo) {
652
+ return `https://bitbucket.org/${owner}/${repo}.git`;
653
+ }
654
+ getSshUrl(owner, repo) {
655
+ return `git@bitbucket.org:${owner}/${repo}.git`;
656
+ }
657
+ async clone(source, _targetDir, options = {}) {
658
+ const parsed = this.parseSource(source);
659
+ if (!parsed) {
660
+ return { success: false, error: `Invalid Bitbucket source: ${source}` };
661
+ }
662
+ const { owner, repo, subpath } = parsed;
663
+ const cloneUrl = options.ssh ? this.getSshUrl(owner, repo) : this.getCloneUrl(owner, repo);
664
+ const tempDir = join5(tmpdir3(), `skillkit-${randomUUID3()}`);
665
+ try {
666
+ const args = ["clone"];
667
+ if (options.depth) {
668
+ args.push("--depth", String(options.depth));
669
+ }
670
+ if (options.branch) {
671
+ args.push("--branch", options.branch);
672
+ }
673
+ args.push(cloneUrl, tempDir);
674
+ execSync3(`git ${args.join(" ")}`, {
675
+ stdio: ["pipe", "pipe", "pipe"],
676
+ encoding: "utf-8"
677
+ });
678
+ const searchDir = subpath ? join5(tempDir, subpath) : tempDir;
679
+ const skills = discoverSkills(searchDir);
680
+ return {
681
+ success: true,
682
+ path: searchDir,
683
+ tempRoot: tempDir,
684
+ skills: skills.map((s) => s.name),
685
+ discoveredSkills: skills.map((s) => ({
686
+ name: s.name,
687
+ dirName: basename4(s.path),
688
+ path: s.path
689
+ }))
690
+ };
691
+ } catch (error) {
692
+ if (existsSync5(tempDir)) {
693
+ rmSync3(tempDir, { recursive: true, force: true });
694
+ }
695
+ const message = error instanceof Error ? error.message : String(error);
696
+ return { success: false, error: `Failed to clone: ${message}` };
697
+ }
698
+ }
699
+ };
700
+
701
+ // src/providers/local.ts
702
+ import { existsSync as existsSync6, statSync, realpathSync } from "fs";
703
+ import { join as join6, resolve, basename as basename5 } from "path";
704
+ import { homedir as homedir2 } from "os";
705
+ var LocalProvider = class {
706
+ type = "local";
707
+ name = "Local Filesystem";
708
+ baseUrl = "";
709
+ parseSource(source) {
710
+ if (!isLocalPath(source)) {
711
+ return null;
712
+ }
713
+ let expandedPath = source;
714
+ if (source.startsWith("~/")) {
715
+ expandedPath = join6(homedir2(), source.slice(2));
716
+ }
717
+ const absolutePath = resolve(expandedPath);
718
+ const dirName = basename5(absolutePath);
719
+ return {
720
+ owner: "local",
721
+ repo: dirName,
722
+ subpath: absolutePath
723
+ };
724
+ }
725
+ matches(source) {
726
+ return isLocalPath(source);
727
+ }
728
+ getCloneUrl(_owner, _repo) {
729
+ return "";
730
+ }
731
+ getSshUrl(_owner, _repo) {
732
+ return "";
733
+ }
734
+ async clone(source, _targetDir, _options = {}) {
735
+ const parsed = this.parseSource(source);
736
+ if (!parsed || !parsed.subpath) {
737
+ return { success: false, error: `Invalid local path: ${source}` };
738
+ }
739
+ const sourcePath = parsed.subpath;
740
+ if (!existsSync6(sourcePath)) {
741
+ return { success: false, error: `Path does not exist: ${sourcePath}` };
742
+ }
743
+ const stats = statSync(sourcePath);
744
+ if (!stats.isDirectory()) {
745
+ return { success: false, error: `Path is not a directory: ${sourcePath}` };
746
+ }
747
+ try {
748
+ let actualPath = sourcePath;
749
+ try {
750
+ actualPath = realpathSync(sourcePath);
751
+ } catch {
752
+ }
753
+ const skills = discoverSkills(actualPath);
754
+ return {
755
+ success: true,
756
+ path: actualPath,
757
+ skills: skills.map((s) => s.name),
758
+ discoveredSkills: skills.map((s) => ({
759
+ name: s.name,
760
+ dirName: basename5(s.path),
761
+ path: s.path
762
+ }))
763
+ };
764
+ } catch (error) {
765
+ const message = error instanceof Error ? error.message : String(error);
766
+ return { success: false, error: `Failed to process local path: ${message}` };
767
+ }
768
+ }
769
+ };
770
+
771
+ // src/providers/index.ts
772
+ var providers = [
773
+ new LocalProvider(),
774
+ new GitLabProvider(),
775
+ new BitbucketProvider(),
776
+ new GitHubProvider()
777
+ ];
778
+ function getProvider(type) {
779
+ return providers.find((p) => p.type === type);
780
+ }
781
+ function getAllProviders() {
782
+ return providers;
783
+ }
784
+ function detectProvider(source) {
785
+ return providers.find((p) => p.matches(source));
786
+ }
787
+ function parseSource(source) {
788
+ for (const provider of providers) {
789
+ if (provider.matches(source)) {
790
+ const parsed = provider.parseSource(source);
791
+ if (parsed) {
792
+ return { provider, ...parsed };
793
+ }
794
+ }
795
+ }
796
+ return null;
797
+ }
798
+
799
+ // src/translator/types.ts
800
+ import { z as z2 } from "zod";
801
+ var AGENT_FORMAT_MAP = {
802
+ "claude-code": "skill-md",
803
+ "codex": "skill-md",
804
+ "gemini-cli": "skill-md",
805
+ "opencode": "skill-md",
806
+ "antigravity": "skill-md",
807
+ "amp": "skill-md",
808
+ "clawdbot": "skill-md",
809
+ "droid": "skill-md",
810
+ "goose": "skill-md",
811
+ "kilo": "skill-md",
812
+ "kiro-cli": "skill-md",
813
+ "roo": "skill-md",
814
+ "trae": "skill-md",
815
+ "universal": "skill-md",
816
+ "cursor": "cursor-mdc",
817
+ "windsurf": "markdown-rules",
818
+ "github-copilot": "markdown-rules"
819
+ };
820
+ var TranslatableSkillFrontmatter = z2.object({
821
+ name: z2.string(),
822
+ description: z2.string(),
823
+ version: z2.string().optional(),
824
+ author: z2.string().optional(),
825
+ license: z2.string().optional(),
826
+ tags: z2.array(z2.string()).optional(),
827
+ compatibility: z2.string().optional(),
828
+ "allowed-tools": z2.string().optional(),
829
+ metadata: z2.record(z2.string()).optional(),
830
+ // Cursor-specific fields
831
+ globs: z2.array(z2.string()).optional(),
832
+ alwaysApply: z2.boolean().optional(),
833
+ // Agent optimization hints
834
+ agents: z2.object({
835
+ optimized: z2.array(z2.string()).optional(),
836
+ compatible: z2.array(z2.string()).optional()
837
+ }).optional()
838
+ });
839
+
840
+ // src/translator/formats/skill-md.ts
841
+ import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
842
+ var SKILL_MD_AGENTS = [
843
+ "claude-code",
844
+ "codex",
845
+ "gemini-cli",
846
+ "opencode",
847
+ "antigravity",
848
+ "amp",
849
+ "clawdbot",
850
+ "droid",
851
+ "goose",
852
+ "kilo",
853
+ "kiro-cli",
854
+ "roo",
855
+ "trae",
856
+ "universal"
857
+ ];
858
+ function extractFrontmatter2(content) {
859
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)/);
860
+ if (!match) {
861
+ return { frontmatter: null, body: content };
862
+ }
863
+ try {
864
+ const frontmatter = parseYaml3(match[1]);
865
+ return { frontmatter, body: match[2] || "" };
866
+ } catch {
867
+ return { frontmatter: null, body: content };
868
+ }
869
+ }
870
+ var SkillMdTranslator = class {
871
+ format = "skill-md";
872
+ agents = SKILL_MD_AGENTS;
873
+ /**
874
+ * Parse SKILL.md content into canonical format
875
+ */
876
+ parse(content, filename) {
877
+ const { frontmatter, body } = extractFrontmatter2(content);
878
+ if (!frontmatter) {
879
+ const nameMatch = content.match(/^#\s+(.+)$/m);
880
+ const name2 = nameMatch ? nameMatch[1].toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "") : filename?.replace(/\.md$/i, "").replace(/^SKILL$/i, "unnamed-skill") || "unnamed-skill";
881
+ return {
882
+ name: name2,
883
+ description: "Skill without frontmatter",
884
+ content: body.trim(),
885
+ sourceFormat: "skill-md"
886
+ };
887
+ }
888
+ const name = frontmatter.name || "unnamed-skill";
889
+ const description = frontmatter.description || "No description";
890
+ return {
891
+ name,
892
+ description,
893
+ version: frontmatter.version,
894
+ author: frontmatter.author,
895
+ license: frontmatter.license,
896
+ tags: frontmatter.tags,
897
+ compatibility: frontmatter.compatibility,
898
+ allowedTools: frontmatter["allowed-tools"],
899
+ metadata: frontmatter.metadata,
900
+ content: body.trim(),
901
+ sourceFormat: "skill-md",
902
+ sourceAgent: frontmatter.sourceAgent,
903
+ globs: frontmatter.globs,
904
+ alwaysApply: frontmatter.alwaysApply,
905
+ agentHints: frontmatter.agents
906
+ };
907
+ }
908
+ /**
909
+ * Check if content is SKILL.md format
910
+ */
911
+ detect(content, filename) {
912
+ if (filename && /SKILL\.md$/i.test(filename)) {
913
+ return true;
914
+ }
915
+ const { frontmatter } = extractFrontmatter2(content);
916
+ if (frontmatter && ("name" in frontmatter || "description" in frontmatter)) {
917
+ return true;
918
+ }
919
+ return false;
920
+ }
921
+ /**
922
+ * Generate SKILL.md content for target agent
923
+ */
924
+ generate(skill, targetAgent, options = {}) {
925
+ const warnings = [];
926
+ const incompatible = [];
927
+ const frontmatter = {
928
+ name: skill.name,
929
+ description: skill.description
930
+ };
931
+ if (skill.version) frontmatter.version = skill.version;
932
+ if (skill.author) frontmatter.author = skill.author;
933
+ if (skill.license) frontmatter.license = skill.license;
934
+ if (skill.tags?.length) frontmatter.tags = skill.tags;
935
+ if (skill.compatibility) frontmatter.compatibility = skill.compatibility;
936
+ if (skill.allowedTools) frontmatter["allowed-tools"] = skill.allowedTools;
937
+ if (skill.metadata && Object.keys(skill.metadata).length > 0) {
938
+ frontmatter.metadata = skill.metadata;
939
+ }
940
+ if (skill.globs?.length) {
941
+ frontmatter.globs = skill.globs;
942
+ warnings.push("Cursor globs preserved but may not be used by target agent");
943
+ }
944
+ if (skill.alwaysApply !== void 0) {
945
+ frontmatter.alwaysApply = skill.alwaysApply;
946
+ warnings.push("Cursor alwaysApply preserved but may not be used by target agent");
947
+ }
948
+ if (options.addMetadata) {
949
+ frontmatter.translatedFrom = skill.sourceFormat;
950
+ frontmatter.translatedAt = (/* @__PURE__ */ new Date()).toISOString();
951
+ frontmatter.targetAgent = targetAgent;
952
+ }
953
+ const yamlContent = stringifyYaml2(frontmatter, {
954
+ lineWidth: 0,
955
+ defaultKeyType: "PLAIN",
956
+ defaultStringType: "QUOTE_DOUBLE"
957
+ }).trim();
958
+ const content = `---
959
+ ${yamlContent}
960
+ ---
961
+
962
+ ${skill.content}`;
963
+ return {
964
+ success: true,
965
+ content,
966
+ filename: this.getFilename(skill.name, targetAgent),
967
+ warnings,
968
+ incompatible,
969
+ targetFormat: "skill-md",
970
+ targetAgent
971
+ };
972
+ }
973
+ /**
974
+ * Get the expected filename for SKILL.md format
975
+ */
976
+ getFilename(_skillName, _targetAgent) {
977
+ return "SKILL.md";
978
+ }
979
+ };
980
+ var skillMdTranslator = new SkillMdTranslator();
981
+
982
+ // src/translator/formats/cursor.ts
983
+ import { parse as parseYaml4, stringify as stringifyYaml3 } from "yaml";
984
+ function extractFrontmatter3(content) {
985
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)/);
986
+ if (!match) {
987
+ return { frontmatter: null, body: content };
988
+ }
989
+ try {
990
+ const frontmatter = parseYaml4(match[1]);
991
+ return { frontmatter, body: match[2] || "" };
992
+ } catch {
993
+ return { frontmatter: null, body: content };
994
+ }
995
+ }
996
+ function inferGlobs(skill) {
997
+ const globs = [];
998
+ const content = skill.content.toLowerCase();
999
+ const tags = skill.tags || [];
1000
+ const tagExtensions = {
1001
+ "react": ["**/*.tsx", "**/*.jsx"],
1002
+ "typescript": ["**/*.ts", "**/*.tsx"],
1003
+ "javascript": ["**/*.js", "**/*.jsx"],
1004
+ "python": ["**/*.py"],
1005
+ "rust": ["**/*.rs"],
1006
+ "go": ["**/*.go"],
1007
+ "css": ["**/*.css", "**/*.scss", "**/*.sass"],
1008
+ "html": ["**/*.html", "**/*.htm"],
1009
+ "vue": ["**/*.vue"],
1010
+ "svelte": ["**/*.svelte"],
1011
+ "angular": ["**/*.ts", "**/*.html"],
1012
+ "nextjs": ["**/*.tsx", "**/*.ts", "**/*.jsx", "**/*.js"],
1013
+ "testing": ["**/*.test.*", "**/*.spec.*"],
1014
+ "api": ["**/api/**/*", "**/routes/**/*"]
1015
+ };
1016
+ for (const tag of tags) {
1017
+ const normalized = tag.toLowerCase();
1018
+ if (tagExtensions[normalized]) {
1019
+ globs.push(...tagExtensions[normalized]);
1020
+ }
1021
+ }
1022
+ if (content.includes("component") || content.includes("jsx") || content.includes("tsx")) {
1023
+ if (!globs.includes("**/*.tsx")) globs.push("**/*.tsx");
1024
+ if (!globs.includes("**/*.jsx")) globs.push("**/*.jsx");
1025
+ }
1026
+ if (content.includes(".py") || content.includes("python")) {
1027
+ if (!globs.includes("**/*.py")) globs.push("**/*.py");
1028
+ }
1029
+ return [...new Set(globs)];
1030
+ }
1031
+ var CursorTranslator = class {
1032
+ format = "cursor-mdc";
1033
+ agents = ["cursor"];
1034
+ /**
1035
+ * Parse Cursor MDC content into canonical format
1036
+ */
1037
+ parse(content, filename) {
1038
+ const { frontmatter, body } = extractFrontmatter3(content);
1039
+ let name = "unnamed-skill";
1040
+ if (filename) {
1041
+ name = filename.replace(/\.(mdc|md|cursorrules)$/i, "").replace(/^\./, "").toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
1042
+ }
1043
+ if (!frontmatter) {
1044
+ return {
1045
+ name,
1046
+ description: "Cursor rules",
1047
+ content: body.trim(),
1048
+ sourceFormat: "cursor-mdc",
1049
+ sourceAgent: "cursor"
1050
+ };
1051
+ }
1052
+ const description = frontmatter.description || "Cursor rule";
1053
+ return {
1054
+ name: frontmatter.name || name,
1055
+ description,
1056
+ content: body.trim(),
1057
+ sourceFormat: "cursor-mdc",
1058
+ sourceAgent: "cursor",
1059
+ globs: frontmatter.globs,
1060
+ alwaysApply: frontmatter.alwaysApply,
1061
+ tags: frontmatter.tags,
1062
+ version: frontmatter.version,
1063
+ author: frontmatter.author
1064
+ };
1065
+ }
1066
+ /**
1067
+ * Check if content is Cursor MDC format
1068
+ */
1069
+ detect(content, filename) {
1070
+ if (filename) {
1071
+ if (/\.mdc$/i.test(filename)) return true;
1072
+ if (/\.cursorrules$/i.test(filename)) return true;
1073
+ if (/cursorrules/i.test(filename)) return true;
1074
+ }
1075
+ const { frontmatter } = extractFrontmatter3(content);
1076
+ if (frontmatter) {
1077
+ if ("globs" in frontmatter || "alwaysApply" in frontmatter) {
1078
+ return true;
1079
+ }
1080
+ }
1081
+ return false;
1082
+ }
1083
+ /**
1084
+ * Generate Cursor MDC content
1085
+ */
1086
+ generate(skill, targetAgent, options = {}) {
1087
+ const warnings = [];
1088
+ const incompatible = [];
1089
+ const frontmatter = {
1090
+ description: skill.description
1091
+ };
1092
+ let globs = options.globs || skill.globs;
1093
+ if (!globs?.length) {
1094
+ globs = inferGlobs(skill);
1095
+ if (globs.length > 0) {
1096
+ warnings.push(`Inferred glob patterns: ${globs.join(", ")}`);
1097
+ }
1098
+ }
1099
+ if (globs?.length) {
1100
+ frontmatter.globs = globs;
1101
+ }
1102
+ const alwaysApply = options.alwaysApply ?? skill.alwaysApply;
1103
+ if (alwaysApply !== void 0) {
1104
+ frontmatter.alwaysApply = alwaysApply;
1105
+ }
1106
+ if (skill.allowedTools) {
1107
+ incompatible.push("allowed-tools: Cursor does not support tool restrictions");
1108
+ }
1109
+ if (skill.compatibility) {
1110
+ warnings.push("compatibility info added as comment");
1111
+ }
1112
+ if (options.addMetadata) {
1113
+ frontmatter.translatedFrom = skill.sourceFormat;
1114
+ frontmatter.originalName = skill.name;
1115
+ }
1116
+ const yamlContent = stringifyYaml3(frontmatter, {
1117
+ lineWidth: 0,
1118
+ defaultKeyType: "PLAIN"
1119
+ }).trim();
1120
+ let body = skill.content;
1121
+ if (skill.compatibility && options.preserveComments !== false) {
1122
+ body = `<!-- Compatibility: ${skill.compatibility} -->
1123
+
1124
+ ${body}`;
1125
+ }
1126
+ const content = `---
1127
+ ${yamlContent}
1128
+ ---
1129
+
1130
+ ${body}`;
1131
+ return {
1132
+ success: true,
1133
+ content,
1134
+ filename: this.getFilename(skill.name, targetAgent),
1135
+ warnings,
1136
+ incompatible,
1137
+ targetFormat: "cursor-mdc",
1138
+ targetAgent
1139
+ };
1140
+ }
1141
+ /**
1142
+ * Generate .cursorrules content (alternative format)
1143
+ */
1144
+ generateCursorrules(skill) {
1145
+ let content = "";
1146
+ content += `# ${skill.name}
1147
+ `;
1148
+ content += `# ${skill.description}
1149
+
1150
+ `;
1151
+ content += skill.content;
1152
+ return content;
1153
+ }
1154
+ /**
1155
+ * Get the expected filename for Cursor format
1156
+ */
1157
+ getFilename(skillName, _targetAgent) {
1158
+ const safeName = skillName.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
1159
+ return `${safeName}.mdc`;
1160
+ }
1161
+ };
1162
+ var cursorTranslator = new CursorTranslator();
1163
+
1164
+ // src/translator/formats/windsurf.ts
1165
+ function parseMetadataComments(content) {
1166
+ const metadata = {};
1167
+ const lines = content.split("\n");
1168
+ const bodyLines = [];
1169
+ let inMetadata = true;
1170
+ for (const line of lines) {
1171
+ if (inMetadata) {
1172
+ const match = line.match(/^<!--\s*(\w+):\s*(.+?)\s*-->$/);
1173
+ if (match) {
1174
+ metadata[match[1].toLowerCase()] = match[2];
1175
+ continue;
1176
+ }
1177
+ if (line.trim() && !line.startsWith("<!--")) {
1178
+ inMetadata = false;
1179
+ }
1180
+ }
1181
+ bodyLines.push(line);
1182
+ }
1183
+ return {
1184
+ metadata,
1185
+ body: bodyLines.join("\n").trim()
1186
+ };
1187
+ }
1188
+ var WindsurfTranslator = class {
1189
+ format = "markdown-rules";
1190
+ agents = ["windsurf"];
1191
+ /**
1192
+ * Parse Windsurf rules content into canonical format
1193
+ */
1194
+ parse(content, filename) {
1195
+ const { metadata, body } = parseMetadataComments(content);
1196
+ let name = metadata.name;
1197
+ if (!name) {
1198
+ const headingMatch = body.match(/^#\s+(.+)$/m);
1199
+ if (headingMatch) {
1200
+ name = headingMatch[1].toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
1201
+ } else if (filename) {
1202
+ name = filename.replace(/\.(windsurfrules|md)$/i, "").replace(/^\./, "").toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
1203
+ } else {
1204
+ name = "unnamed-skill";
1205
+ }
1206
+ }
1207
+ let description = metadata.description;
1208
+ if (!description) {
1209
+ const paragraphMatch = body.match(/^(?!#)[^\n]+/m);
1210
+ if (paragraphMatch) {
1211
+ description = paragraphMatch[0].substring(0, 200);
1212
+ if (description.length === 200) description += "...";
1213
+ } else {
1214
+ description = "Windsurf rules";
1215
+ }
1216
+ }
1217
+ return {
1218
+ name,
1219
+ description,
1220
+ content: body,
1221
+ sourceFormat: "markdown-rules",
1222
+ sourceAgent: "windsurf",
1223
+ version: metadata.version,
1224
+ author: metadata.author,
1225
+ tags: metadata.tags?.split(",").map((t) => t.trim())
1226
+ };
1227
+ }
1228
+ /**
1229
+ * Check if content is Windsurf format
1230
+ */
1231
+ detect(content, filename) {
1232
+ if (filename) {
1233
+ if (/\.windsurfrules$/i.test(filename)) return true;
1234
+ if (/windsurfrules/i.test(filename)) return true;
1235
+ }
1236
+ if (content.startsWith("<!--") && content.includes("-->")) {
1237
+ const { metadata } = parseMetadataComments(content);
1238
+ if (Object.keys(metadata).length > 0) {
1239
+ return true;
1240
+ }
1241
+ }
1242
+ return false;
1243
+ }
1244
+ /**
1245
+ * Generate Windsurf rules content
1246
+ */
1247
+ generate(skill, targetAgent, options = {}) {
1248
+ const warnings = [];
1249
+ const incompatible = [];
1250
+ const lines = [];
1251
+ if (options.addMetadata !== false) {
1252
+ lines.push(`<!-- name: ${skill.name} -->`);
1253
+ lines.push(`<!-- description: ${skill.description} -->`);
1254
+ if (skill.version) lines.push(`<!-- version: ${skill.version} -->`);
1255
+ if (skill.author) lines.push(`<!-- author: ${skill.author} -->`);
1256
+ if (skill.tags?.length) lines.push(`<!-- tags: ${skill.tags.join(", ")} -->`);
1257
+ if (options.addMetadata) {
1258
+ lines.push(`<!-- translatedFrom: ${skill.sourceFormat} -->`);
1259
+ lines.push(`<!-- translatedAt: ${(/* @__PURE__ */ new Date()).toISOString()} -->`);
1260
+ }
1261
+ lines.push("");
1262
+ }
1263
+ if (skill.globs?.length) {
1264
+ incompatible.push("globs: Windsurf does not support file pattern matching");
1265
+ }
1266
+ if (skill.alwaysApply !== void 0) {
1267
+ incompatible.push("alwaysApply: Windsurf does not support conditional application");
1268
+ }
1269
+ if (skill.allowedTools) {
1270
+ incompatible.push("allowed-tools: Windsurf does not support tool restrictions");
1271
+ }
1272
+ lines.push(skill.content);
1273
+ return {
1274
+ success: true,
1275
+ content: lines.join("\n"),
1276
+ filename: this.getFilename(skill.name, targetAgent),
1277
+ warnings,
1278
+ incompatible,
1279
+ targetFormat: "markdown-rules",
1280
+ targetAgent
1281
+ };
1282
+ }
1283
+ /**
1284
+ * Get the expected filename for Windsurf format
1285
+ */
1286
+ getFilename(_skillName, _targetAgent) {
1287
+ return ".windsurfrules";
1288
+ }
1289
+ };
1290
+ var windsurfTranslator = new WindsurfTranslator();
1291
+
1292
+ // src/translator/formats/copilot.ts
1293
+ function parseMetadataComments2(content) {
1294
+ const metadata = {};
1295
+ const lines = content.split("\n");
1296
+ const bodyLines = [];
1297
+ let inMetadata = true;
1298
+ for (const line of lines) {
1299
+ if (inMetadata) {
1300
+ const match = line.match(/^<!--\s*(\w+):\s*(.+?)\s*-->$/);
1301
+ if (match) {
1302
+ metadata[match[1].toLowerCase()] = match[2];
1303
+ continue;
1304
+ }
1305
+ if (line.trim() && !line.startsWith("<!--")) {
1306
+ inMetadata = false;
1307
+ }
1308
+ }
1309
+ bodyLines.push(line);
1310
+ }
1311
+ return {
1312
+ metadata,
1313
+ body: bodyLines.join("\n").trim()
1314
+ };
1315
+ }
1316
+ var CopilotTranslator = class {
1317
+ format = "markdown-rules";
1318
+ agents = ["github-copilot"];
1319
+ /**
1320
+ * Parse Copilot instructions into canonical format
1321
+ */
1322
+ parse(content, filename) {
1323
+ const { metadata, body } = parseMetadataComments2(content);
1324
+ let name = metadata.name;
1325
+ if (!name) {
1326
+ const headingMatch = body.match(/^#\s+(.+)$/m);
1327
+ if (headingMatch) {
1328
+ name = headingMatch[1].toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
1329
+ } else if (filename) {
1330
+ name = filename.replace(/\.(md)$/i, "").replace(/copilot-instructions/i, "copilot-rules").toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
1331
+ } else {
1332
+ name = "copilot-instructions";
1333
+ }
1334
+ }
1335
+ let description = metadata.description;
1336
+ if (!description) {
1337
+ const paragraphMatch = body.match(/^(?!#)[^\n]+/m);
1338
+ if (paragraphMatch) {
1339
+ description = paragraphMatch[0].substring(0, 200);
1340
+ if (description.length === 200) description += "...";
1341
+ } else {
1342
+ description = "GitHub Copilot instructions";
1343
+ }
1344
+ }
1345
+ return {
1346
+ name,
1347
+ description,
1348
+ content: body,
1349
+ sourceFormat: "markdown-rules",
1350
+ sourceAgent: "github-copilot",
1351
+ version: metadata.version,
1352
+ author: metadata.author,
1353
+ tags: metadata.tags?.split(",").map((t) => t.trim())
1354
+ };
1355
+ }
1356
+ /**
1357
+ * Check if content is GitHub Copilot format
1358
+ */
1359
+ detect(content, filename) {
1360
+ if (filename) {
1361
+ if (/copilot-instructions\.md$/i.test(filename)) return true;
1362
+ if (/copilot.*instructions/i.test(filename)) return true;
1363
+ }
1364
+ if (content.toLowerCase().includes("github copilot")) {
1365
+ return true;
1366
+ }
1367
+ return false;
1368
+ }
1369
+ /**
1370
+ * Generate GitHub Copilot instructions content
1371
+ */
1372
+ generate(skill, targetAgent, options = {}) {
1373
+ const warnings = [];
1374
+ const incompatible = [];
1375
+ const lines = [];
1376
+ lines.push("<!-- This file provides custom instructions for GitHub Copilot -->");
1377
+ lines.push("<!-- Generated by SkillKit - https://github.com/rohitg00/skillkit -->");
1378
+ lines.push("");
1379
+ if (options.addMetadata) {
1380
+ lines.push(`<!-- name: ${skill.name} -->`);
1381
+ lines.push(`<!-- description: ${skill.description} -->`);
1382
+ if (skill.version) lines.push(`<!-- version: ${skill.version} -->`);
1383
+ if (skill.author) lines.push(`<!-- author: ${skill.author} -->`);
1384
+ lines.push(`<!-- translatedFrom: ${skill.sourceFormat} -->`);
1385
+ lines.push(`<!-- translatedAt: ${(/* @__PURE__ */ new Date()).toISOString()} -->`);
1386
+ lines.push("");
1387
+ }
1388
+ if (skill.globs?.length) {
1389
+ incompatible.push("globs: GitHub Copilot does not support file pattern matching");
1390
+ }
1391
+ if (skill.alwaysApply !== void 0) {
1392
+ incompatible.push("alwaysApply: GitHub Copilot does not support conditional application");
1393
+ }
1394
+ if (skill.allowedTools) {
1395
+ incompatible.push("allowed-tools: GitHub Copilot does not support tool restrictions");
1396
+ }
1397
+ if (skill.metadata && Object.keys(skill.metadata).length > 0) {
1398
+ warnings.push("Custom metadata fields not supported by GitHub Copilot");
1399
+ }
1400
+ if (!skill.content.trim().startsWith("#")) {
1401
+ lines.push(`# ${skill.name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ")}`);
1402
+ lines.push("");
1403
+ if (skill.description) {
1404
+ lines.push(skill.description);
1405
+ lines.push("");
1406
+ }
1407
+ }
1408
+ lines.push(skill.content);
1409
+ return {
1410
+ success: true,
1411
+ content: lines.join("\n"),
1412
+ filename: this.getFilename(skill.name, targetAgent),
1413
+ warnings,
1414
+ incompatible,
1415
+ targetFormat: "markdown-rules",
1416
+ targetAgent
1417
+ };
1418
+ }
1419
+ /**
1420
+ * Get the expected filename for GitHub Copilot format
1421
+ */
1422
+ getFilename(_skillName, _targetAgent) {
1423
+ return "copilot-instructions.md";
1424
+ }
1425
+ /**
1426
+ * Get the expected path for the instructions file
1427
+ */
1428
+ getPath(_skillName) {
1429
+ return ".github/copilot-instructions.md";
1430
+ }
1431
+ };
1432
+ var copilotTranslator = new CopilotTranslator();
1433
+
1434
+ // src/translator/registry.ts
1435
+ import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
1436
+ import { dirname as dirname2, basename as basename6 } from "path";
1437
+ var TranslatorRegistry = class {
1438
+ translators = /* @__PURE__ */ new Map();
1439
+ agentTranslators = /* @__PURE__ */ new Map();
1440
+ constructor() {
1441
+ this.register(skillMdTranslator);
1442
+ this.register(cursorTranslator);
1443
+ this.register(windsurfTranslator);
1444
+ this.register(copilotTranslator);
1445
+ }
1446
+ /**
1447
+ * Register a format translator
1448
+ */
1449
+ register(translator) {
1450
+ this.translators.set(translator.format, translator);
1451
+ for (const agent of translator.agents) {
1452
+ this.agentTranslators.set(agent, translator);
1453
+ }
1454
+ }
1455
+ /**
1456
+ * Get translator for a specific format
1457
+ */
1458
+ getTranslator(format) {
1459
+ return this.translators.get(format);
1460
+ }
1461
+ /**
1462
+ * Get translator for a specific agent
1463
+ */
1464
+ getTranslatorForAgent(agent) {
1465
+ const specific = this.agentTranslators.get(agent);
1466
+ if (specific) {
1467
+ return specific;
1468
+ }
1469
+ return skillMdTranslator;
1470
+ }
1471
+ /**
1472
+ * Get the format category for an agent
1473
+ */
1474
+ getFormatForAgent(agent) {
1475
+ return AGENT_FORMAT_MAP[agent] || "skill-md";
1476
+ }
1477
+ /**
1478
+ * Detect the format of content
1479
+ */
1480
+ detectFormat(content, filename) {
1481
+ for (const [format, translator] of this.translators) {
1482
+ if (translator.detect(content, filename)) {
1483
+ return format;
1484
+ }
1485
+ }
1486
+ if (content.startsWith("---")) {
1487
+ return "skill-md";
1488
+ }
1489
+ return null;
1490
+ }
1491
+ /**
1492
+ * Detect the source agent from content
1493
+ */
1494
+ detectSourceAgent(content, filename) {
1495
+ const format = this.detectFormat(content, filename);
1496
+ if (!format) return null;
1497
+ if (filename) {
1498
+ const lower = filename.toLowerCase();
1499
+ if (lower.includes("cursor") || lower.endsWith(".mdc")) return "cursor";
1500
+ if (lower.includes("windsurf")) return "windsurf";
1501
+ if (lower.includes("copilot")) return "github-copilot";
1502
+ }
1503
+ for (const [agent, agentFormat] of Object.entries(AGENT_FORMAT_MAP)) {
1504
+ if (agentFormat === format) {
1505
+ return agent;
1506
+ }
1507
+ }
1508
+ return null;
1509
+ }
1510
+ /**
1511
+ * Parse content into canonical format
1512
+ */
1513
+ parse(content, filename) {
1514
+ const format = this.detectFormat(content, filename);
1515
+ if (!format) {
1516
+ return skillMdTranslator.parse(content, filename);
1517
+ }
1518
+ const translator = this.translators.get(format);
1519
+ if (!translator) {
1520
+ return null;
1521
+ }
1522
+ return translator.parse(content, filename);
1523
+ }
1524
+ /**
1525
+ * Translate skill to target agent format
1526
+ */
1527
+ translate(skill, targetAgent, options = {}) {
1528
+ const translator = this.getTranslatorForAgent(targetAgent);
1529
+ return translator.generate(skill, targetAgent, options);
1530
+ }
1531
+ /**
1532
+ * Translate content directly from one agent to another
1533
+ */
1534
+ translateContent(content, targetAgent, options = {}) {
1535
+ const skill = this.parse(content, options.sourceFilename);
1536
+ if (!skill) {
1537
+ return {
1538
+ success: false,
1539
+ content: "",
1540
+ filename: "",
1541
+ warnings: [],
1542
+ incompatible: ["Failed to parse source content"],
1543
+ targetFormat: this.getFormatForAgent(targetAgent),
1544
+ targetAgent
1545
+ };
1546
+ }
1547
+ return this.translate(skill, targetAgent, options);
1548
+ }
1549
+ /**
1550
+ * Translate a skill file to target agent format
1551
+ */
1552
+ translateFile(sourcePath, targetAgent, options = {}) {
1553
+ if (!existsSync7(sourcePath)) {
1554
+ return {
1555
+ success: false,
1556
+ content: "",
1557
+ filename: "",
1558
+ warnings: [],
1559
+ incompatible: [`Source file not found: ${sourcePath}`],
1560
+ targetFormat: this.getFormatForAgent(targetAgent),
1561
+ targetAgent
1562
+ };
1563
+ }
1564
+ const content = readFileSync3(sourcePath, "utf-8");
1565
+ const filename = basename6(sourcePath);
1566
+ return this.translateContent(content, targetAgent, {
1567
+ ...options,
1568
+ sourceFilename: filename
1569
+ });
1570
+ }
1571
+ /**
1572
+ * Translate and write to target path
1573
+ */
1574
+ translateAndWrite(sourcePath, targetPath, targetAgent, options = {}) {
1575
+ const result = this.translateFile(sourcePath, targetAgent, options);
1576
+ if (result.success) {
1577
+ const targetDir = dirname2(targetPath);
1578
+ if (!existsSync7(targetDir)) {
1579
+ mkdirSync2(targetDir, { recursive: true });
1580
+ }
1581
+ writeFileSync2(targetPath, result.content, "utf-8");
1582
+ }
1583
+ return result;
1584
+ }
1585
+ /**
1586
+ * Get all supported agents
1587
+ */
1588
+ getSupportedAgents() {
1589
+ return Object.keys(AGENT_FORMAT_MAP);
1590
+ }
1591
+ /**
1592
+ * Get all registered formats
1593
+ */
1594
+ getRegisteredFormats() {
1595
+ return Array.from(this.translators.keys());
1596
+ }
1597
+ /**
1598
+ * Check if translation is supported between two agents
1599
+ */
1600
+ canTranslate(from, to) {
1601
+ return this.getSupportedAgents().includes(from) && this.getSupportedAgents().includes(to);
1602
+ }
1603
+ /**
1604
+ * Get translation compatibility info
1605
+ */
1606
+ getCompatibilityInfo(from, to) {
1607
+ const fromFormat = this.getFormatForAgent(from);
1608
+ const toFormat = this.getFormatForAgent(to);
1609
+ const warnings = [];
1610
+ const lossyFields = [];
1611
+ if (fromFormat === "cursor-mdc" && toFormat !== "cursor-mdc") {
1612
+ lossyFields.push("globs", "alwaysApply");
1613
+ warnings.push("Cursor-specific features (globs, alwaysApply) will be preserved but not used");
1614
+ }
1615
+ if (fromFormat === "skill-md" && toFormat === "markdown-rules") {
1616
+ lossyFields.push("allowed-tools", "metadata");
1617
+ warnings.push("Advanced SKILL.md features will be stored as comments");
1618
+ }
1619
+ return {
1620
+ supported: this.canTranslate(from, to),
1621
+ warnings,
1622
+ lossyFields
1623
+ };
1624
+ }
1625
+ };
1626
+ var translatorRegistry = new TranslatorRegistry();
1627
+
1628
+ // src/translator/index.ts
1629
+ function translateSkill(content, targetAgent, options) {
1630
+ return translatorRegistry.translateContent(content, targetAgent, options);
1631
+ }
1632
+ function translateSkillFile(sourcePath, targetAgent, options) {
1633
+ return translatorRegistry.translateFile(sourcePath, targetAgent, options);
1634
+ }
1635
+ function parseSkillContent(content, filename) {
1636
+ return translatorRegistry.parse(content, filename);
1637
+ }
1638
+ function detectSkillFormat(content, filename) {
1639
+ return translatorRegistry.detectFormat(content, filename);
1640
+ }
1641
+ function getSupportedTranslationAgents() {
1642
+ return translatorRegistry.getSupportedAgents();
1643
+ }
1644
+ function canTranslate(from, to) {
1645
+ return translatorRegistry.canTranslate(from, to);
1646
+ }
1647
+
1648
+ // src/context/types.ts
1649
+ import { z as z3 } from "zod";
1650
+ var DependencyInfo = z3.object({
1651
+ name: z3.string(),
1652
+ version: z3.string().optional(),
1653
+ dev: z3.boolean().default(false)
1654
+ });
1655
+ var Detection = z3.object({
1656
+ name: z3.string(),
1657
+ version: z3.string().optional(),
1658
+ confidence: z3.number().min(0).max(100).default(100),
1659
+ source: z3.string().optional()
1660
+ // Where it was detected from
1661
+ });
1662
+ var ProjectStack = z3.object({
1663
+ languages: z3.array(Detection).default([]),
1664
+ frameworks: z3.array(Detection).default([]),
1665
+ libraries: z3.array(Detection).default([]),
1666
+ styling: z3.array(Detection).default([]),
1667
+ testing: z3.array(Detection).default([]),
1668
+ databases: z3.array(Detection).default([]),
1669
+ tools: z3.array(Detection).default([]),
1670
+ runtime: z3.array(Detection).default([])
1671
+ });
1672
+ var ProjectPatterns = z3.object({
1673
+ components: z3.string().optional(),
1674
+ // 'functional', 'class', 'mixed'
1675
+ stateManagement: z3.string().optional(),
1676
+ // 'redux', 'zustand', 'context', etc.
1677
+ apiStyle: z3.string().optional(),
1678
+ // 'rest', 'graphql', 'trpc', 'server-actions'
1679
+ styling: z3.string().optional(),
1680
+ // 'css-modules', 'tailwind', 'styled-components'
1681
+ testing: z3.string().optional(),
1682
+ // 'jest', 'vitest', 'playwright'
1683
+ linting: z3.string().optional(),
1684
+ // 'eslint', 'biome'
1685
+ formatting: z3.string().optional()
1686
+ // 'prettier', 'biome'
1687
+ });
1688
+ var SkillPreferences = z3.object({
1689
+ installed: z3.array(z3.string()).default([]),
1690
+ recommended: z3.array(z3.string()).default([]),
1691
+ excluded: z3.array(z3.string()).default([]),
1692
+ autoSync: z3.boolean().default(true),
1693
+ securityLevel: z3.enum(["low", "medium", "high"]).default("medium"),
1694
+ testingRequired: z3.boolean().default(false)
1695
+ });
1696
+ var AgentConfig = z3.object({
1697
+ primary: z3.string().optional(),
1698
+ // Primary agent
1699
+ detected: z3.array(z3.string()).default([]),
1700
+ // Auto-detected agents
1701
+ synced: z3.array(z3.string()).default([]),
1702
+ // Agents to sync skills to
1703
+ disabled: z3.array(z3.string()).default([])
1704
+ // Agents to skip
1705
+ });
1706
+ var ProjectContext = z3.object({
1707
+ version: z3.literal(1),
1708
+ project: z3.object({
1709
+ name: z3.string(),
1710
+ description: z3.string().optional(),
1711
+ type: z3.string().optional(),
1712
+ // 'web-app', 'cli', 'library', 'api', etc.
1713
+ path: z3.string().optional()
1714
+ }),
1715
+ stack: ProjectStack,
1716
+ patterns: ProjectPatterns.optional(),
1717
+ skills: SkillPreferences.optional(),
1718
+ agents: AgentConfig.optional(),
1719
+ createdAt: z3.string().datetime().optional(),
1720
+ updatedAt: z3.string().datetime().optional()
1721
+ });
1722
+ var CONTEXT_FILE = ".skillkit/context.yaml";
1723
+ var CONTEXT_DIR = ".skillkit";
1724
+ var PROJECT_TYPE_HINTS = {
1725
+ "web-app": ["react", "vue", "angular", "svelte", "nextjs", "nuxt", "remix"],
1726
+ "api": ["express", "fastify", "koa", "hono", "fastapi", "flask", "django"],
1727
+ "cli": ["commander", "yargs", "clipanion", "clap", "cobra"],
1728
+ "library": ["rollup", "esbuild", "tsup", "unbuild"],
1729
+ "mobile": ["react-native", "expo", "flutter", "ionic"],
1730
+ "desktop": ["electron", "tauri"]
1731
+ };
1732
+
1733
+ // src/context/detector.ts
1734
+ import { existsSync as existsSync8, readFileSync as readFileSync4, readdirSync as readdirSync2 } from "fs";
1735
+ import { join as join7, basename as basename7 } from "path";
1736
+ var FRAMEWORK_PATTERNS = {
1737
+ // Frontend frameworks
1738
+ react: {
1739
+ dependencies: ["react", "react-dom"],
1740
+ files: [],
1741
+ category: "frameworks",
1742
+ tags: ["react", "frontend", "ui"]
1743
+ },
1744
+ nextjs: {
1745
+ dependencies: ["next"],
1746
+ files: ["next.config.js", "next.config.mjs", "next.config.ts"],
1747
+ category: "frameworks",
1748
+ tags: ["nextjs", "react", "ssr", "frontend"]
1749
+ },
1750
+ vue: {
1751
+ dependencies: ["vue"],
1752
+ files: [],
1753
+ category: "frameworks",
1754
+ tags: ["vue", "frontend", "ui"]
1755
+ },
1756
+ nuxt: {
1757
+ dependencies: ["nuxt"],
1758
+ files: ["nuxt.config.js", "nuxt.config.ts"],
1759
+ category: "frameworks",
1760
+ tags: ["nuxt", "vue", "ssr", "frontend"]
1761
+ },
1762
+ angular: {
1763
+ dependencies: ["@angular/core"],
1764
+ files: ["angular.json"],
1765
+ category: "frameworks",
1766
+ tags: ["angular", "frontend", "ui"]
1767
+ },
1768
+ svelte: {
1769
+ dependencies: ["svelte"],
1770
+ files: ["svelte.config.js"],
1771
+ category: "frameworks",
1772
+ tags: ["svelte", "frontend", "ui"]
1773
+ },
1774
+ remix: {
1775
+ dependencies: ["@remix-run/react"],
1776
+ files: ["remix.config.js"],
1777
+ category: "frameworks",
1778
+ tags: ["remix", "react", "ssr", "frontend"]
1779
+ },
1780
+ astro: {
1781
+ dependencies: ["astro"],
1782
+ files: ["astro.config.mjs", "astro.config.ts"],
1783
+ category: "frameworks",
1784
+ tags: ["astro", "static", "frontend"]
1785
+ },
1786
+ solid: {
1787
+ dependencies: ["solid-js"],
1788
+ files: [],
1789
+ category: "frameworks",
1790
+ tags: ["solid", "frontend", "ui"]
1791
+ },
1792
+ // Backend frameworks
1793
+ express: {
1794
+ dependencies: ["express"],
1795
+ files: [],
1796
+ category: "frameworks",
1797
+ tags: ["express", "nodejs", "api", "backend"]
1798
+ },
1799
+ fastify: {
1800
+ dependencies: ["fastify"],
1801
+ files: [],
1802
+ category: "frameworks",
1803
+ tags: ["fastify", "nodejs", "api", "backend"]
1804
+ },
1805
+ hono: {
1806
+ dependencies: ["hono"],
1807
+ files: [],
1808
+ category: "frameworks",
1809
+ tags: ["hono", "edge", "api", "backend"]
1810
+ },
1811
+ koa: {
1812
+ dependencies: ["koa"],
1813
+ files: [],
1814
+ category: "frameworks",
1815
+ tags: ["koa", "nodejs", "api", "backend"]
1816
+ },
1817
+ nestjs: {
1818
+ dependencies: ["@nestjs/core"],
1819
+ files: ["nest-cli.json"],
1820
+ category: "frameworks",
1821
+ tags: ["nestjs", "nodejs", "api", "backend"]
1822
+ },
1823
+ // Mobile
1824
+ "react-native": {
1825
+ dependencies: ["react-native"],
1826
+ files: ["metro.config.js", "app.json"],
1827
+ category: "frameworks",
1828
+ tags: ["react-native", "mobile", "ios", "android"]
1829
+ },
1830
+ expo: {
1831
+ dependencies: ["expo"],
1832
+ files: ["app.json", "expo.json"],
1833
+ category: "frameworks",
1834
+ tags: ["expo", "react-native", "mobile"]
1835
+ },
1836
+ // Desktop
1837
+ electron: {
1838
+ dependencies: ["electron"],
1839
+ files: ["electron.js", "electron-builder.json"],
1840
+ category: "frameworks",
1841
+ tags: ["electron", "desktop"]
1842
+ },
1843
+ tauri: {
1844
+ dependencies: ["@tauri-apps/api"],
1845
+ files: ["tauri.conf.json", "src-tauri"],
1846
+ category: "frameworks",
1847
+ tags: ["tauri", "desktop", "rust"]
1848
+ }
1849
+ };
1850
+ var LIBRARY_PATTERNS = {
1851
+ // State management
1852
+ redux: {
1853
+ dependencies: ["redux", "@reduxjs/toolkit"],
1854
+ category: "libraries",
1855
+ tags: ["redux", "state-management"]
1856
+ },
1857
+ zustand: {
1858
+ dependencies: ["zustand"],
1859
+ category: "libraries",
1860
+ tags: ["zustand", "state-management"]
1861
+ },
1862
+ jotai: {
1863
+ dependencies: ["jotai"],
1864
+ category: "libraries",
1865
+ tags: ["jotai", "state-management"]
1866
+ },
1867
+ recoil: {
1868
+ dependencies: ["recoil"],
1869
+ category: "libraries",
1870
+ tags: ["recoil", "state-management"]
1871
+ },
1872
+ mobx: {
1873
+ dependencies: ["mobx"],
1874
+ category: "libraries",
1875
+ tags: ["mobx", "state-management"]
1876
+ },
1877
+ // Data fetching
1878
+ tanstack: {
1879
+ dependencies: ["@tanstack/react-query", "@tanstack/query-core"],
1880
+ category: "libraries",
1881
+ tags: ["tanstack", "data-fetching", "caching"]
1882
+ },
1883
+ swr: {
1884
+ dependencies: ["swr"],
1885
+ category: "libraries",
1886
+ tags: ["swr", "data-fetching"]
1887
+ },
1888
+ trpc: {
1889
+ dependencies: ["@trpc/client", "@trpc/server"],
1890
+ category: "libraries",
1891
+ tags: ["trpc", "api", "typescript"]
1892
+ },
1893
+ graphql: {
1894
+ dependencies: ["graphql", "@apollo/client", "urql"],
1895
+ category: "libraries",
1896
+ tags: ["graphql", "api"]
1897
+ },
1898
+ // UI Components
1899
+ "shadcn-ui": {
1900
+ dependencies: ["@radix-ui/react-slot"],
1901
+ files: ["components.json"],
1902
+ category: "libraries",
1903
+ tags: ["shadcn", "ui", "components"]
1904
+ },
1905
+ "material-ui": {
1906
+ dependencies: ["@mui/material"],
1907
+ category: "libraries",
1908
+ tags: ["mui", "material", "ui", "components"]
1909
+ },
1910
+ "chakra-ui": {
1911
+ dependencies: ["@chakra-ui/react"],
1912
+ category: "libraries",
1913
+ tags: ["chakra", "ui", "components"]
1914
+ },
1915
+ "ant-design": {
1916
+ dependencies: ["antd"],
1917
+ category: "libraries",
1918
+ tags: ["antd", "ui", "components"]
1919
+ },
1920
+ // Form handling
1921
+ "react-hook-form": {
1922
+ dependencies: ["react-hook-form"],
1923
+ category: "libraries",
1924
+ tags: ["forms", "validation"]
1925
+ },
1926
+ formik: {
1927
+ dependencies: ["formik"],
1928
+ category: "libraries",
1929
+ tags: ["forms", "validation"]
1930
+ },
1931
+ zod: {
1932
+ dependencies: ["zod"],
1933
+ category: "libraries",
1934
+ tags: ["validation", "schema", "typescript"]
1935
+ },
1936
+ // Animation
1937
+ "framer-motion": {
1938
+ dependencies: ["framer-motion"],
1939
+ category: "libraries",
1940
+ tags: ["animation", "motion"]
1941
+ }
1942
+ };
1943
+ var STYLING_PATTERNS = {
1944
+ tailwindcss: {
1945
+ dependencies: ["tailwindcss"],
1946
+ files: ["tailwind.config.js", "tailwind.config.ts", "tailwind.config.cjs"],
1947
+ category: "styling",
1948
+ tags: ["tailwind", "css", "utility-first"]
1949
+ },
1950
+ "styled-components": {
1951
+ dependencies: ["styled-components"],
1952
+ category: "styling",
1953
+ tags: ["styled-components", "css-in-js"]
1954
+ },
1955
+ emotion: {
1956
+ dependencies: ["@emotion/react", "@emotion/styled"],
1957
+ category: "styling",
1958
+ tags: ["emotion", "css-in-js"]
1959
+ },
1960
+ sass: {
1961
+ dependencies: ["sass", "node-sass"],
1962
+ category: "styling",
1963
+ tags: ["sass", "scss", "css"]
1964
+ },
1965
+ less: {
1966
+ dependencies: ["less"],
1967
+ category: "styling",
1968
+ tags: ["less", "css"]
1969
+ },
1970
+ "css-modules": {
1971
+ files: ["*.module.css", "*.module.scss"],
1972
+ category: "styling",
1973
+ tags: ["css-modules", "css"]
1974
+ }
1975
+ };
1976
+ var TESTING_PATTERNS = {
1977
+ jest: {
1978
+ dependencies: ["jest"],
1979
+ files: ["jest.config.js", "jest.config.ts"],
1980
+ category: "testing",
1981
+ tags: ["jest", "testing", "unit-testing"]
1982
+ },
1983
+ vitest: {
1984
+ dependencies: ["vitest"],
1985
+ files: ["vitest.config.ts", "vitest.config.js"],
1986
+ category: "testing",
1987
+ tags: ["vitest", "testing", "unit-testing"]
1988
+ },
1989
+ playwright: {
1990
+ dependencies: ["@playwright/test"],
1991
+ files: ["playwright.config.ts", "playwright.config.js"],
1992
+ category: "testing",
1993
+ tags: ["playwright", "e2e", "testing"]
1994
+ },
1995
+ cypress: {
1996
+ dependencies: ["cypress"],
1997
+ files: ["cypress.config.ts", "cypress.config.js", "cypress.json"],
1998
+ category: "testing",
1999
+ tags: ["cypress", "e2e", "testing"]
2000
+ },
2001
+ mocha: {
2002
+ dependencies: ["mocha"],
2003
+ category: "testing",
2004
+ tags: ["mocha", "testing"]
2005
+ },
2006
+ "testing-library": {
2007
+ dependencies: ["@testing-library/react", "@testing-library/vue"],
2008
+ category: "testing",
2009
+ tags: ["testing-library", "testing"]
2010
+ }
2011
+ };
2012
+ var DATABASE_PATTERNS = {
2013
+ prisma: {
2014
+ dependencies: ["@prisma/client", "prisma"],
2015
+ files: ["prisma/schema.prisma"],
2016
+ category: "databases",
2017
+ tags: ["prisma", "orm", "database"]
2018
+ },
2019
+ drizzle: {
2020
+ dependencies: ["drizzle-orm"],
2021
+ files: ["drizzle.config.ts"],
2022
+ category: "databases",
2023
+ tags: ["drizzle", "orm", "database"]
2024
+ },
2025
+ typeorm: {
2026
+ dependencies: ["typeorm"],
2027
+ category: "databases",
2028
+ tags: ["typeorm", "orm", "database"]
2029
+ },
2030
+ mongoose: {
2031
+ dependencies: ["mongoose"],
2032
+ category: "databases",
2033
+ tags: ["mongoose", "mongodb", "database"]
2034
+ },
2035
+ supabase: {
2036
+ dependencies: ["@supabase/supabase-js"],
2037
+ category: "databases",
2038
+ tags: ["supabase", "postgres", "database", "auth"]
2039
+ },
2040
+ firebase: {
2041
+ dependencies: ["firebase", "firebase-admin"],
2042
+ category: "databases",
2043
+ tags: ["firebase", "database", "auth"]
2044
+ }
2045
+ };
2046
+ var TOOL_PATTERNS = {
2047
+ eslint: {
2048
+ dependencies: ["eslint"],
2049
+ files: [".eslintrc", ".eslintrc.js", ".eslintrc.json", "eslint.config.js"],
2050
+ category: "tools",
2051
+ tags: ["eslint", "linting"]
2052
+ },
2053
+ prettier: {
2054
+ dependencies: ["prettier"],
2055
+ files: [".prettierrc", ".prettierrc.js", "prettier.config.js"],
2056
+ category: "tools",
2057
+ tags: ["prettier", "formatting"]
2058
+ },
2059
+ biome: {
2060
+ dependencies: ["@biomejs/biome"],
2061
+ files: ["biome.json"],
2062
+ category: "tools",
2063
+ tags: ["biome", "linting", "formatting"]
2064
+ },
2065
+ husky: {
2066
+ dependencies: ["husky"],
2067
+ files: [".husky"],
2068
+ category: "tools",
2069
+ tags: ["husky", "git-hooks"]
2070
+ },
2071
+ "lint-staged": {
2072
+ dependencies: ["lint-staged"],
2073
+ category: "tools",
2074
+ tags: ["lint-staged", "git-hooks"]
2075
+ },
2076
+ turbo: {
2077
+ dependencies: ["turbo"],
2078
+ files: ["turbo.json"],
2079
+ category: "tools",
2080
+ tags: ["turbo", "monorepo", "build"]
2081
+ },
2082
+ nx: {
2083
+ dependencies: ["nx"],
2084
+ files: ["nx.json"],
2085
+ category: "tools",
2086
+ tags: ["nx", "monorepo", "build"]
2087
+ },
2088
+ docker: {
2089
+ files: ["Dockerfile", "docker-compose.yml", "docker-compose.yaml"],
2090
+ category: "tools",
2091
+ tags: ["docker", "containers"]
2092
+ }
2093
+ };
2094
+ var ProjectDetector = class {
2095
+ projectPath;
2096
+ packageJson = null;
2097
+ files = /* @__PURE__ */ new Set();
2098
+ constructor(projectPath) {
2099
+ this.projectPath = projectPath;
2100
+ }
2101
+ /**
2102
+ * Analyze the project and return detected stack
2103
+ */
2104
+ analyze() {
2105
+ this.loadPackageJson();
2106
+ this.scanFiles();
2107
+ const stack = {
2108
+ languages: this.detectLanguages(),
2109
+ frameworks: this.detectFromPatterns(FRAMEWORK_PATTERNS),
2110
+ libraries: this.detectFromPatterns(LIBRARY_PATTERNS),
2111
+ styling: this.detectFromPatterns(STYLING_PATTERNS),
2112
+ testing: this.detectFromPatterns(TESTING_PATTERNS),
2113
+ databases: this.detectFromPatterns(DATABASE_PATTERNS),
2114
+ tools: this.detectFromPatterns(TOOL_PATTERNS),
2115
+ runtime: this.detectRuntime()
2116
+ };
2117
+ return stack;
2118
+ }
2119
+ /**
2120
+ * Detect project patterns and conventions
2121
+ */
2122
+ detectPatterns() {
2123
+ const patterns = {};
2124
+ if (this.hasFile("*.tsx") || this.hasFile("*.jsx")) {
2125
+ patterns.components = "functional";
2126
+ }
2127
+ const deps = this.getDependencies();
2128
+ if (deps.has("zustand")) patterns.stateManagement = "zustand";
2129
+ else if (deps.has("@reduxjs/toolkit") || deps.has("redux")) patterns.stateManagement = "redux";
2130
+ else if (deps.has("jotai")) patterns.stateManagement = "jotai";
2131
+ else if (deps.has("recoil")) patterns.stateManagement = "recoil";
2132
+ else if (deps.has("mobx")) patterns.stateManagement = "mobx";
2133
+ if (deps.has("@trpc/client")) patterns.apiStyle = "trpc";
2134
+ else if (deps.has("graphql") || deps.has("@apollo/client")) patterns.apiStyle = "graphql";
2135
+ else if (deps.has("next") && this.hasFile("app/**/actions.ts")) patterns.apiStyle = "server-actions";
2136
+ else patterns.apiStyle = "rest";
2137
+ if (deps.has("tailwindcss")) patterns.styling = "tailwind";
2138
+ else if (deps.has("styled-components")) patterns.styling = "styled-components";
2139
+ else if (deps.has("@emotion/react")) patterns.styling = "emotion";
2140
+ else if (this.hasFile("*.module.css")) patterns.styling = "css-modules";
2141
+ if (deps.has("vitest")) patterns.testing = "vitest";
2142
+ else if (deps.has("jest")) patterns.testing = "jest";
2143
+ else if (deps.has("@playwright/test")) patterns.testing = "playwright";
2144
+ else if (deps.has("cypress")) patterns.testing = "cypress";
2145
+ if (deps.has("@biomejs/biome")) patterns.linting = "biome";
2146
+ else if (deps.has("eslint")) patterns.linting = "eslint";
2147
+ if (deps.has("@biomejs/biome")) patterns.formatting = "biome";
2148
+ else if (deps.has("prettier")) patterns.formatting = "prettier";
2149
+ return patterns;
2150
+ }
2151
+ /**
2152
+ * Detect project type
2153
+ */
2154
+ detectProjectType() {
2155
+ const deps = this.getDependencies();
2156
+ if (deps.has("react-native") || deps.has("expo")) return "mobile";
2157
+ if (deps.has("electron") || deps.has("@tauri-apps/api")) return "desktop";
2158
+ if (deps.has("next") || deps.has("nuxt") || deps.has("remix")) return "web-app";
2159
+ if (deps.has("express") || deps.has("fastify") || deps.has("hono")) return "api";
2160
+ if (deps.has("commander") || deps.has("clipanion") || deps.has("yargs")) return "cli";
2161
+ if (this.hasFile("rollup.config.js") || this.hasFile("tsup.config.ts")) return "library";
2162
+ if (deps.has("react") || deps.has("vue") || deps.has("svelte")) return "web-app";
2163
+ return "unknown";
2164
+ }
2165
+ /**
2166
+ * Get project name
2167
+ */
2168
+ getProjectName() {
2169
+ if (this.packageJson && typeof this.packageJson.name === "string") {
2170
+ return this.packageJson.name;
2171
+ }
2172
+ return basename7(this.projectPath);
2173
+ }
2174
+ /**
2175
+ * Get project description
2176
+ */
2177
+ getProjectDescription() {
2178
+ if (this.packageJson && typeof this.packageJson.description === "string") {
2179
+ return this.packageJson.description;
2180
+ }
2181
+ return void 0;
2182
+ }
2183
+ // Private helpers
2184
+ loadPackageJson() {
2185
+ const packageJsonPath = join7(this.projectPath, "package.json");
2186
+ if (existsSync8(packageJsonPath)) {
2187
+ try {
2188
+ const content = readFileSync4(packageJsonPath, "utf-8");
2189
+ this.packageJson = JSON.parse(content);
2190
+ } catch {
2191
+ this.packageJson = null;
2192
+ }
2193
+ }
2194
+ }
2195
+ scanFiles() {
2196
+ try {
2197
+ const entries = readdirSync2(this.projectPath, { withFileTypes: true });
2198
+ for (const entry of entries) {
2199
+ this.files.add(entry.name);
2200
+ if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
2201
+ try {
2202
+ const subEntries = readdirSync2(join7(this.projectPath, entry.name));
2203
+ for (const subEntry of subEntries) {
2204
+ this.files.add(`${entry.name}/${subEntry}`);
2205
+ }
2206
+ } catch {
2207
+ }
2208
+ }
2209
+ }
2210
+ } catch {
2211
+ }
2212
+ }
2213
+ getDependencies() {
2214
+ const deps = /* @__PURE__ */ new Set();
2215
+ if (!this.packageJson) return deps;
2216
+ const dependencies = this.packageJson.dependencies;
2217
+ const devDependencies = this.packageJson.devDependencies;
2218
+ if (dependencies) {
2219
+ for (const dep of Object.keys(dependencies)) {
2220
+ deps.add(dep);
2221
+ }
2222
+ }
2223
+ if (devDependencies) {
2224
+ for (const dep of Object.keys(devDependencies)) {
2225
+ deps.add(dep);
2226
+ }
2227
+ }
2228
+ return deps;
2229
+ }
2230
+ getVersion(depName) {
2231
+ if (!this.packageJson) return void 0;
2232
+ const dependencies = this.packageJson.dependencies;
2233
+ const devDependencies = this.packageJson.devDependencies;
2234
+ const version = dependencies?.[depName] || devDependencies?.[depName];
2235
+ if (version) {
2236
+ return version.replace(/^[\^~>=<]+/, "");
2237
+ }
2238
+ return void 0;
2239
+ }
2240
+ hasFile(pattern) {
2241
+ if (pattern.includes("*")) {
2242
+ const regex = new RegExp(pattern.replace(/\*/g, ".*"));
2243
+ for (const file of this.files) {
2244
+ if (regex.test(file)) return true;
2245
+ }
2246
+ return false;
2247
+ }
2248
+ return this.files.has(pattern);
2249
+ }
2250
+ detectLanguages() {
2251
+ const languages = [];
2252
+ if (this.hasFile("tsconfig.json") || this.files.has("*.ts")) {
2253
+ const tsVersion = this.getVersion("typescript");
2254
+ languages.push({
2255
+ name: "typescript",
2256
+ version: tsVersion,
2257
+ confidence: 100,
2258
+ source: "tsconfig.json"
2259
+ });
2260
+ }
2261
+ if (this.hasFile("jsconfig.json") || this.packageJson && !this.hasFile("tsconfig.json")) {
2262
+ languages.push({
2263
+ name: "javascript",
2264
+ confidence: 90,
2265
+ source: "package.json"
2266
+ });
2267
+ }
2268
+ if (this.hasFile("pyproject.toml") || this.hasFile("requirements.txt")) {
2269
+ languages.push({
2270
+ name: "python",
2271
+ confidence: 100,
2272
+ source: "pyproject.toml"
2273
+ });
2274
+ }
2275
+ if (this.hasFile("Cargo.toml")) {
2276
+ languages.push({
2277
+ name: "rust",
2278
+ confidence: 100,
2279
+ source: "Cargo.toml"
2280
+ });
2281
+ }
2282
+ if (this.hasFile("go.mod")) {
2283
+ languages.push({
2284
+ name: "go",
2285
+ confidence: 100,
2286
+ source: "go.mod"
2287
+ });
2288
+ }
2289
+ return languages;
2290
+ }
2291
+ detectRuntime() {
2292
+ const runtime = [];
2293
+ if (this.packageJson) {
2294
+ const engines = this.packageJson.engines;
2295
+ const nodeVersion = engines?.node;
2296
+ runtime.push({
2297
+ name: "nodejs",
2298
+ version: nodeVersion?.replace(/[>=<^~]+/g, ""),
2299
+ confidence: 100,
2300
+ source: "package.json"
2301
+ });
2302
+ }
2303
+ if (this.hasFile("bun.lockb") || this.hasFile("bunfig.toml")) {
2304
+ runtime.push({
2305
+ name: "bun",
2306
+ confidence: 100,
2307
+ source: "bun.lockb"
2308
+ });
2309
+ }
2310
+ if (this.hasFile("deno.json") || this.hasFile("deno.jsonc")) {
2311
+ runtime.push({
2312
+ name: "deno",
2313
+ confidence: 100,
2314
+ source: "deno.json"
2315
+ });
2316
+ }
2317
+ return runtime;
2318
+ }
2319
+ detectFromPatterns(patterns) {
2320
+ const detected = [];
2321
+ const deps = this.getDependencies();
2322
+ for (const [name, pattern] of Object.entries(patterns)) {
2323
+ let found = false;
2324
+ let source;
2325
+ if (pattern.dependencies) {
2326
+ for (const dep of pattern.dependencies) {
2327
+ if (deps.has(dep)) {
2328
+ found = true;
2329
+ source = "package.json";
2330
+ break;
2331
+ }
2332
+ }
2333
+ }
2334
+ if (!found && pattern.files) {
2335
+ for (const file of pattern.files) {
2336
+ if (this.hasFile(file)) {
2337
+ found = true;
2338
+ source = file;
2339
+ break;
2340
+ }
2341
+ }
2342
+ }
2343
+ if (found) {
2344
+ const version = pattern.dependencies ? this.getVersion(pattern.dependencies[0]) : void 0;
2345
+ detected.push({
2346
+ name,
2347
+ version,
2348
+ confidence: 100,
2349
+ source
2350
+ });
2351
+ }
2352
+ }
2353
+ return detected;
2354
+ }
2355
+ };
2356
+ function analyzeProject(projectPath) {
2357
+ const detector = new ProjectDetector(projectPath);
2358
+ return detector.analyze();
2359
+ }
2360
+ function getStackTags(stack) {
2361
+ const tags = /* @__PURE__ */ new Set();
2362
+ const addTags = (detections, patterns) => {
2363
+ for (const detection of detections) {
2364
+ const pattern = patterns[detection.name];
2365
+ if (pattern?.tags) {
2366
+ for (const tag of pattern.tags) {
2367
+ tags.add(tag);
2368
+ }
2369
+ } else {
2370
+ tags.add(detection.name);
2371
+ }
2372
+ }
2373
+ };
2374
+ for (const lang of stack.languages) {
2375
+ tags.add(lang.name);
2376
+ }
2377
+ addTags(stack.frameworks, FRAMEWORK_PATTERNS);
2378
+ addTags(stack.libraries, LIBRARY_PATTERNS);
2379
+ addTags(stack.styling, STYLING_PATTERNS);
2380
+ addTags(stack.testing, TESTING_PATTERNS);
2381
+ addTags(stack.databases, DATABASE_PATTERNS);
2382
+ return Array.from(tags);
2383
+ }
2384
+
2385
+ // src/context/manager.ts
2386
+ import { existsSync as existsSync9, readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
2387
+ import { join as join8 } from "path";
2388
+ import { parse as parseYaml5, stringify as stringifyYaml4 } from "yaml";
2389
+ var ContextManager = class {
2390
+ projectPath;
2391
+ context = null;
2392
+ constructor(projectPath = process.cwd()) {
2393
+ this.projectPath = projectPath;
2394
+ }
2395
+ /**
2396
+ * Get the context file path
2397
+ */
2398
+ getContextPath() {
2399
+ return join8(this.projectPath, CONTEXT_FILE);
2400
+ }
2401
+ /**
2402
+ * Check if context exists
2403
+ */
2404
+ exists() {
2405
+ return existsSync9(this.getContextPath());
2406
+ }
2407
+ /**
2408
+ * Load context from file
2409
+ */
2410
+ load() {
2411
+ const contextPath = this.getContextPath();
2412
+ if (!existsSync9(contextPath)) {
2413
+ return null;
2414
+ }
2415
+ try {
2416
+ const content = readFileSync5(contextPath, "utf-8");
2417
+ const data = parseYaml5(content);
2418
+ const parsed = ProjectContext.safeParse(data);
2419
+ if (parsed.success) {
2420
+ this.context = parsed.data;
2421
+ return this.context;
2422
+ }
2423
+ console.warn("Context file exists but failed schema validation");
2424
+ return null;
2425
+ } catch (error) {
2426
+ console.warn("Failed to load context:", error);
2427
+ return null;
2428
+ }
2429
+ }
2430
+ /**
2431
+ * Save context to file
2432
+ */
2433
+ save(context) {
2434
+ const contextPath = this.getContextPath();
2435
+ const contextDir = join8(this.projectPath, CONTEXT_DIR);
2436
+ if (!existsSync9(contextDir)) {
2437
+ mkdirSync3(contextDir, { recursive: true });
2438
+ }
2439
+ context.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2440
+ const content = stringifyYaml4(context, {
2441
+ lineWidth: 0,
2442
+ defaultKeyType: "PLAIN"
2443
+ });
2444
+ writeFileSync3(contextPath, content, "utf-8");
2445
+ this.context = context;
2446
+ }
2447
+ /**
2448
+ * Initialize a new context from project detection
2449
+ */
2450
+ init(options = {}) {
2451
+ if (this.exists() && !options.force) {
2452
+ const existing = this.load();
2453
+ if (existing) {
2454
+ return existing;
2455
+ }
2456
+ }
2457
+ const detector = new ProjectDetector(this.projectPath);
2458
+ const stack = options.skipDetection ? this.getEmptyStack() : detector.analyze();
2459
+ const patterns = options.skipDetection ? {} : detector.detectPatterns();
2460
+ const projectType = options.skipDetection ? "unknown" : detector.detectProjectType();
2461
+ const projectName = detector.getProjectName();
2462
+ const projectDescription = detector.getProjectDescription();
2463
+ const context = {
2464
+ version: 1,
2465
+ project: {
2466
+ name: projectName,
2467
+ description: projectDescription,
2468
+ type: projectType,
2469
+ path: this.projectPath
2470
+ },
2471
+ stack,
2472
+ patterns,
2473
+ skills: {
2474
+ installed: [],
2475
+ recommended: [],
2476
+ excluded: [],
2477
+ autoSync: true,
2478
+ securityLevel: "medium",
2479
+ testingRequired: false
2480
+ },
2481
+ agents: {
2482
+ primary: void 0,
2483
+ detected: [],
2484
+ synced: [],
2485
+ disabled: []
2486
+ },
2487
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2488
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2489
+ };
2490
+ this.save(context);
2491
+ return context;
2492
+ }
2493
+ /**
2494
+ * Update context with new detection
2495
+ */
2496
+ refresh() {
2497
+ const existing = this.load();
2498
+ const detector = new ProjectDetector(this.projectPath);
2499
+ const stack = detector.analyze();
2500
+ const patterns = detector.detectPatterns();
2501
+ const projectType = detector.detectProjectType();
2502
+ if (existing) {
2503
+ const merged = {
2504
+ ...existing,
2505
+ stack,
2506
+ patterns,
2507
+ project: {
2508
+ ...existing.project,
2509
+ type: projectType
2510
+ },
2511
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2512
+ };
2513
+ this.save(merged);
2514
+ return merged;
2515
+ }
2516
+ return this.init();
2517
+ }
2518
+ /**
2519
+ * Get current context (load if not loaded)
2520
+ */
2521
+ get() {
2522
+ if (this.context) {
2523
+ return this.context;
2524
+ }
2525
+ return this.load();
2526
+ }
2527
+ /**
2528
+ * Update skills in context
2529
+ */
2530
+ updateSkills(skills) {
2531
+ const context = this.get();
2532
+ if (!context) {
2533
+ throw new Error("No context found. Run `skillkit context init` first.");
2534
+ }
2535
+ context.skills = {
2536
+ ...context.skills,
2537
+ ...skills
2538
+ };
2539
+ this.save(context);
2540
+ }
2541
+ /**
2542
+ * Update agents in context
2543
+ */
2544
+ updateAgents(agents) {
2545
+ const context = this.get();
2546
+ if (!context) {
2547
+ throw new Error("No context found. Run `skillkit context init` first.");
2548
+ }
2549
+ context.agents = {
2550
+ ...context.agents,
2551
+ ...agents
2552
+ };
2553
+ this.save(context);
2554
+ }
2555
+ /**
2556
+ * Set primary agent
2557
+ */
2558
+ setPrimaryAgent(agent) {
2559
+ this.updateAgents({ primary: agent });
2560
+ }
2561
+ /**
2562
+ * Add agent to synced list
2563
+ */
2564
+ addSyncedAgent(agent) {
2565
+ const context = this.get();
2566
+ if (!context) {
2567
+ throw new Error("No context found");
2568
+ }
2569
+ const synced = new Set(context.agents?.synced || []);
2570
+ synced.add(agent);
2571
+ this.updateAgents({ synced: Array.from(synced) });
2572
+ }
2573
+ /**
2574
+ * Remove agent from synced list
2575
+ */
2576
+ removeSyncedAgent(agent) {
2577
+ const context = this.get();
2578
+ if (!context) {
2579
+ throw new Error("No context found");
2580
+ }
2581
+ const synced = new Set(context.agents?.synced || []);
2582
+ synced.delete(agent);
2583
+ this.updateAgents({ synced: Array.from(synced) });
2584
+ }
2585
+ /**
2586
+ * Add installed skill
2587
+ */
2588
+ addInstalledSkill(skillName) {
2589
+ const context = this.get();
2590
+ if (!context) return;
2591
+ const installed = new Set(context.skills?.installed || []);
2592
+ installed.add(skillName);
2593
+ this.updateSkills({ installed: Array.from(installed) });
2594
+ }
2595
+ /**
2596
+ * Remove installed skill
2597
+ */
2598
+ removeInstalledSkill(skillName) {
2599
+ const context = this.get();
2600
+ if (!context) return;
2601
+ const installed = new Set(context.skills?.installed || []);
2602
+ installed.delete(skillName);
2603
+ this.updateSkills({ installed: Array.from(installed) });
2604
+ }
2605
+ /**
2606
+ * Export context to file
2607
+ */
2608
+ export(options = {}) {
2609
+ const context = this.get();
2610
+ if (!context) {
2611
+ throw new Error("No context found");
2612
+ }
2613
+ const exported = { ...context };
2614
+ if (!options.includeSkills) {
2615
+ delete exported.skills;
2616
+ }
2617
+ if (!options.includeAgents) {
2618
+ delete exported.agents;
2619
+ }
2620
+ if (options.format === "json") {
2621
+ return JSON.stringify(exported, null, 2);
2622
+ }
2623
+ return stringifyYaml4(exported, {
2624
+ lineWidth: 0,
2625
+ defaultKeyType: "PLAIN"
2626
+ });
2627
+ }
2628
+ /**
2629
+ * Import context from content
2630
+ */
2631
+ import(content, options = {}) {
2632
+ let data;
2633
+ try {
2634
+ data = JSON.parse(content);
2635
+ } catch {
2636
+ data = parseYaml5(content);
2637
+ }
2638
+ const parsed = ProjectContext.safeParse(data);
2639
+ if (!parsed.success) {
2640
+ throw new Error(`Invalid context format: ${parsed.error.message}`);
2641
+ }
2642
+ const imported = parsed.data;
2643
+ if (options.merge && this.exists()) {
2644
+ const existing = this.load();
2645
+ const merged = this.mergeContexts(existing, imported);
2646
+ this.save(merged);
2647
+ return merged;
2648
+ }
2649
+ if (!options.overwrite && this.exists()) {
2650
+ throw new Error("Context already exists. Use --merge or --overwrite.");
2651
+ }
2652
+ this.save(imported);
2653
+ return imported;
2654
+ }
2655
+ /**
2656
+ * Merge two contexts
2657
+ */
2658
+ mergeContexts(existing, imported) {
2659
+ return {
2660
+ version: 1,
2661
+ project: {
2662
+ ...existing.project,
2663
+ ...imported.project
2664
+ },
2665
+ stack: this.mergeStacks(existing.stack, imported.stack),
2666
+ patterns: {
2667
+ ...existing.patterns,
2668
+ ...imported.patterns
2669
+ },
2670
+ skills: {
2671
+ installed: [.../* @__PURE__ */ new Set([...existing.skills?.installed || [], ...imported.skills?.installed || []])],
2672
+ recommended: [.../* @__PURE__ */ new Set([...existing.skills?.recommended || [], ...imported.skills?.recommended || []])],
2673
+ excluded: [.../* @__PURE__ */ new Set([...existing.skills?.excluded || [], ...imported.skills?.excluded || []])],
2674
+ autoSync: imported.skills?.autoSync ?? existing.skills?.autoSync ?? true,
2675
+ securityLevel: imported.skills?.securityLevel ?? existing.skills?.securityLevel ?? "medium",
2676
+ testingRequired: imported.skills?.testingRequired ?? existing.skills?.testingRequired ?? false
2677
+ },
2678
+ agents: {
2679
+ primary: imported.agents?.primary ?? existing.agents?.primary,
2680
+ detected: [.../* @__PURE__ */ new Set([...existing.agents?.detected || [], ...imported.agents?.detected || []])],
2681
+ synced: [.../* @__PURE__ */ new Set([...existing.agents?.synced || [], ...imported.agents?.synced || []])],
2682
+ disabled: [.../* @__PURE__ */ new Set([...existing.agents?.disabled || [], ...imported.agents?.disabled || []])]
2683
+ },
2684
+ createdAt: existing.createdAt,
2685
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2686
+ };
2687
+ }
2688
+ /**
2689
+ * Merge two stacks
2690
+ */
2691
+ mergeStacks(existing, imported) {
2692
+ const mergeArrays = (a, b) => {
2693
+ const names = new Set(a.map((item) => item.name));
2694
+ const result = [...a];
2695
+ for (const item of b) {
2696
+ if (!names.has(item.name)) {
2697
+ result.push(item);
2698
+ }
2699
+ }
2700
+ return result;
2701
+ };
2702
+ return {
2703
+ languages: mergeArrays(existing.languages, imported.languages),
2704
+ frameworks: mergeArrays(existing.frameworks, imported.frameworks),
2705
+ libraries: mergeArrays(existing.libraries, imported.libraries),
2706
+ styling: mergeArrays(existing.styling, imported.styling),
2707
+ testing: mergeArrays(existing.testing, imported.testing),
2708
+ databases: mergeArrays(existing.databases, imported.databases),
2709
+ tools: mergeArrays(existing.tools, imported.tools),
2710
+ runtime: mergeArrays(existing.runtime, imported.runtime)
2711
+ };
2712
+ }
2713
+ /**
2714
+ * Get empty stack
2715
+ */
2716
+ getEmptyStack() {
2717
+ return {
2718
+ languages: [],
2719
+ frameworks: [],
2720
+ libraries: [],
2721
+ styling: [],
2722
+ testing: [],
2723
+ databases: [],
2724
+ tools: [],
2725
+ runtime: []
2726
+ };
2727
+ }
2728
+ /**
2729
+ * Get recommended skill tags based on stack
2730
+ */
2731
+ getRecommendedTags() {
2732
+ const context = this.get();
2733
+ if (!context) return [];
2734
+ return getStackTags(context.stack);
2735
+ }
2736
+ };
2737
+ function createContextManager(projectPath) {
2738
+ return new ContextManager(projectPath);
2739
+ }
2740
+ function loadContext(projectPath) {
2741
+ const manager = new ContextManager(projectPath);
2742
+ return manager.load();
2743
+ }
2744
+ function initContext(projectPath, options) {
2745
+ const manager = new ContextManager(projectPath);
2746
+ return manager.init(options);
2747
+ }
2748
+
2749
+ // src/context/sync.ts
2750
+ import { existsSync as existsSync10, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, copyFileSync, readdirSync as readdirSync3 } from "fs";
2751
+ import { join as join9 } from "path";
2752
+ var AGENT_DIRS = {
2753
+ "claude-code": { skillsDir: ".claude/skills", configFile: "AGENTS.md" },
2754
+ "cursor": { skillsDir: ".cursor/skills", configFile: ".cursorrules" },
2755
+ "codex": { skillsDir: ".codex/skills", configFile: "AGENTS.md" },
2756
+ "gemini-cli": { skillsDir: ".gemini/skills", configFile: "GEMINI.md" },
2757
+ "opencode": { skillsDir: ".opencode/skills", configFile: "AGENTS.md" },
2758
+ "antigravity": { skillsDir: ".antigravity/skills", configFile: "AGENTS.md" },
2759
+ "amp": { skillsDir: ".agents/skills", configFile: "AGENTS.md" },
2760
+ "clawdbot": { skillsDir: "skills", configFile: "AGENTS.md" },
2761
+ "droid": { skillsDir: ".factory/skills", configFile: "AGENTS.md" },
2762
+ "github-copilot": { skillsDir: ".github/skills", configFile: "AGENTS.md" },
2763
+ "goose": { skillsDir: ".goose/skills", configFile: "AGENTS.md" },
2764
+ "kilo": { skillsDir: ".kilocode/skills", configFile: "AGENTS.md" },
2765
+ "kiro-cli": { skillsDir: ".kiro/skills", configFile: "AGENTS.md" },
2766
+ "roo": { skillsDir: ".roo/skills", configFile: "AGENTS.md" },
2767
+ "trae": { skillsDir: ".trae/skills", configFile: "AGENTS.md" },
2768
+ "windsurf": { skillsDir: ".windsurf/skills", configFile: "AGENTS.md" },
2769
+ "universal": { skillsDir: ".agent/skills", configFile: "AGENTS.md" }
2770
+ };
2771
+ var ContextSync = class {
2772
+ projectPath;
2773
+ contextManager;
2774
+ constructor(projectPath = process.cwd()) {
2775
+ this.projectPath = projectPath;
2776
+ this.contextManager = new ContextManager(projectPath);
2777
+ }
2778
+ /**
2779
+ * Detect which agents are installed/configured on the system
2780
+ */
2781
+ detectAgents() {
2782
+ const detected = [];
2783
+ for (const [agent, config] of Object.entries(AGENT_DIRS)) {
2784
+ const skillsPath = join9(this.projectPath, config.skillsDir);
2785
+ const configPath = join9(this.projectPath, config.configFile);
2786
+ if (existsSync10(skillsPath) || existsSync10(configPath)) {
2787
+ detected.push(agent);
2788
+ }
2789
+ }
2790
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
2791
+ if (homeDir) {
2792
+ if (existsSync10(join9(homeDir, ".claude"))) {
2793
+ if (!detected.includes("claude-code")) detected.push("claude-code");
2794
+ }
2795
+ if (existsSync10(join9(homeDir, ".cursor"))) {
2796
+ if (!detected.includes("cursor")) detected.push("cursor");
2797
+ }
2798
+ }
2799
+ return detected;
2800
+ }
2801
+ /**
2802
+ * Get agents to sync to based on context
2803
+ */
2804
+ getTargetAgents(options = {}) {
2805
+ if (options.agents?.length) {
2806
+ return options.agents;
2807
+ }
2808
+ const context = this.contextManager.get();
2809
+ if (context?.agents?.synced?.length) {
2810
+ return context.agents.synced;
2811
+ }
2812
+ return this.detectAgents();
2813
+ }
2814
+ /**
2815
+ * Sync all skills to all target agents
2816
+ */
2817
+ async syncAll(options = {}) {
2818
+ const targetAgents = this.getTargetAgents(options);
2819
+ const results = [];
2820
+ const skills = this.getSourceSkills();
2821
+ for (const agent of targetAgents) {
2822
+ const result = await this.syncToAgent(agent, skills, options);
2823
+ results.push(result);
2824
+ }
2825
+ if (!options.dryRun) {
2826
+ this.contextManager.updateAgents({
2827
+ detected: this.detectAgents(),
2828
+ synced: targetAgents
2829
+ });
2830
+ }
2831
+ return {
2832
+ totalAgents: targetAgents.length,
2833
+ successfulAgents: results.filter((r) => r.success).length,
2834
+ totalSkills: skills.length,
2835
+ results
2836
+ };
2837
+ }
2838
+ /**
2839
+ * Sync skills to a specific agent
2840
+ */
2841
+ async syncToAgent(agent, skills, options = {}) {
2842
+ const result = {
2843
+ success: true,
2844
+ agent,
2845
+ skillsSynced: 0,
2846
+ skillsSkipped: 0,
2847
+ errors: [],
2848
+ warnings: [],
2849
+ files: []
2850
+ };
2851
+ const sourceSkills = skills || this.getSourceSkills();
2852
+ const agentConfig = AGENT_DIRS[agent];
2853
+ if (!agentConfig) {
2854
+ result.success = false;
2855
+ result.errors.push(`Unknown agent: ${agent}`);
2856
+ return result;
2857
+ }
2858
+ const targetDir = join9(this.projectPath, agentConfig.skillsDir);
2859
+ if (!options.dryRun && !existsSync10(targetDir)) {
2860
+ mkdirSync4(targetDir, { recursive: true });
2861
+ }
2862
+ for (const skill of sourceSkills) {
2863
+ try {
2864
+ const skillResult = await this.syncSkill(skill, agent, targetDir, options);
2865
+ if (skillResult.synced) {
2866
+ result.skillsSynced++;
2867
+ result.files.push(skillResult.file);
2868
+ } else {
2869
+ result.skillsSkipped++;
2870
+ if (skillResult.warning) {
2871
+ result.warnings.push(skillResult.warning);
2872
+ }
2873
+ }
2874
+ } catch (error) {
2875
+ result.errors.push(`Failed to sync ${skill.name}: ${error}`);
2876
+ result.skillsSkipped++;
2877
+ }
2878
+ }
2879
+ if (result.errors.length > 0) {
2880
+ result.success = false;
2881
+ }
2882
+ return result;
2883
+ }
2884
+ /**
2885
+ * Sync a single skill to an agent
2886
+ */
2887
+ async syncSkill(skill, agent, targetDir, options) {
2888
+ const skillMdPath = join9(skill.path, "SKILL.md");
2889
+ if (!existsSync10(skillMdPath)) {
2890
+ return { synced: false, warning: `No SKILL.md found for ${skill.name}` };
2891
+ }
2892
+ const translation = translateSkillFile(skillMdPath, agent, {
2893
+ addMetadata: true
2894
+ });
2895
+ if (!translation.success) {
2896
+ return { synced: false, warning: `Translation failed for ${skill.name}` };
2897
+ }
2898
+ const targetSkillDir = join9(targetDir, skill.name);
2899
+ const outputPath = join9(targetSkillDir, translation.filename);
2900
+ if (existsSync10(outputPath) && !options.force) {
2901
+ return { synced: false, warning: `${skill.name} already exists (use --force to overwrite)` };
2902
+ }
2903
+ if (!options.dryRun) {
2904
+ if (!existsSync10(targetSkillDir)) {
2905
+ mkdirSync4(targetSkillDir, { recursive: true });
2906
+ }
2907
+ writeFileSync4(outputPath, translation.content, "utf-8");
2908
+ await this.copySkillAssets(skill.path, targetSkillDir);
2909
+ }
2910
+ return { synced: true, file: outputPath };
2911
+ }
2912
+ /**
2913
+ * Copy additional skill assets (references, scripts, etc.)
2914
+ */
2915
+ async copySkillAssets(sourcePath, targetPath) {
2916
+ const assetDirs = ["references", "scripts", "assets", "templates"];
2917
+ for (const assetDir of assetDirs) {
2918
+ const sourceAssetDir = join9(sourcePath, assetDir);
2919
+ if (existsSync10(sourceAssetDir)) {
2920
+ const targetAssetDir = join9(targetPath, assetDir);
2921
+ if (!existsSync10(targetAssetDir)) {
2922
+ mkdirSync4(targetAssetDir, { recursive: true });
2923
+ }
2924
+ try {
2925
+ const files = readdirSync3(sourceAssetDir);
2926
+ for (const file of files) {
2927
+ const sourcefile = join9(sourceAssetDir, file);
2928
+ const targetFile = join9(targetAssetDir, file);
2929
+ copyFileSync(sourcefile, targetFile);
2930
+ }
2931
+ } catch {
2932
+ }
2933
+ }
2934
+ }
2935
+ }
2936
+ /**
2937
+ * Get source skills to sync
2938
+ */
2939
+ getSourceSkills() {
2940
+ const context = this.contextManager.get();
2941
+ const primaryAgent = context?.agents?.primary;
2942
+ const searchDirs = [];
2943
+ if (primaryAgent && AGENT_DIRS[primaryAgent]) {
2944
+ searchDirs.push(join9(this.projectPath, AGENT_DIRS[primaryAgent].skillsDir));
2945
+ }
2946
+ const commonDirs = [
2947
+ ".claude/skills",
2948
+ ".cursor/skills",
2949
+ ".agent/skills",
2950
+ "skills"
2951
+ ];
2952
+ for (const dir of commonDirs) {
2953
+ const fullPath = join9(this.projectPath, dir);
2954
+ if (!searchDirs.includes(fullPath) && existsSync10(fullPath)) {
2955
+ searchDirs.push(fullPath);
2956
+ }
2957
+ }
2958
+ const skills = findAllSkills(searchDirs);
2959
+ return skills.map((s) => ({ name: s.name, path: s.path }));
2960
+ }
2961
+ /**
2962
+ * Check sync status - which agents have which skills
2963
+ */
2964
+ checkStatus() {
2965
+ const status = {};
2966
+ for (const [agent, config] of Object.entries(AGENT_DIRS)) {
2967
+ const skillsPath = join9(this.projectPath, config.skillsDir);
2968
+ const skills = [];
2969
+ if (existsSync10(skillsPath)) {
2970
+ try {
2971
+ const entries = readdirSync3(skillsPath, { withFileTypes: true });
2972
+ for (const entry of entries) {
2973
+ if (entry.isDirectory()) {
2974
+ skills.push(entry.name);
2975
+ }
2976
+ }
2977
+ } catch {
2978
+ }
2979
+ }
2980
+ status[agent] = {
2981
+ hasSkills: skills.length > 0,
2982
+ skillCount: skills.length,
2983
+ skills
2984
+ };
2985
+ }
2986
+ return status;
2987
+ }
2988
+ };
2989
+ function createContextSync(projectPath) {
2990
+ return new ContextSync(projectPath);
2991
+ }
2992
+ async function syncToAllAgents(projectPath, options) {
2993
+ const sync = new ContextSync(projectPath);
2994
+ return sync.syncAll(options);
2995
+ }
2996
+ async function syncToAgent(agent, projectPath, options) {
2997
+ const sync = new ContextSync(projectPath);
2998
+ return sync.syncToAgent(agent, void 0, options);
2999
+ }
3000
+
3001
+ // src/recommend/types.ts
3002
+ import { z as z4 } from "zod";
3003
+ var SkillSummary = z4.object({
3004
+ name: z4.string(),
3005
+ description: z4.string().optional(),
3006
+ source: z4.string().optional(),
3007
+ // Repository source
3008
+ tags: z4.array(z4.string()).default([]),
3009
+ compatibility: z4.object({
3010
+ frameworks: z4.array(z4.string()).default([]),
3011
+ languages: z4.array(z4.string()).default([]),
3012
+ libraries: z4.array(z4.string()).default([]),
3013
+ minVersion: z4.record(z4.string()).optional()
3014
+ // { "react": "18.0.0" }
3015
+ }).optional(),
3016
+ popularity: z4.number().default(0),
3017
+ // Downloads/stars
3018
+ quality: z4.number().min(0).max(100).default(50),
3019
+ // Quality score
3020
+ lastUpdated: z4.string().datetime().optional(),
3021
+ verified: z4.boolean().default(false)
3022
+ });
3023
+ var DEFAULT_SCORING_WEIGHTS = {
3024
+ framework: 40,
3025
+ language: 20,
3026
+ library: 15,
3027
+ tag: 10,
3028
+ popularity: 5,
3029
+ quality: 5,
3030
+ freshness: 5
3031
+ };
3032
+ var TAG_TO_TECH = {
3033
+ // Frontend frameworks
3034
+ react: ["react", "react-dom", "@types/react"],
3035
+ vue: ["vue", "@vue/core"],
3036
+ angular: ["@angular/core"],
3037
+ svelte: ["svelte"],
3038
+ solid: ["solid-js"],
3039
+ qwik: ["@builder.io/qwik"],
3040
+ // Meta-frameworks
3041
+ nextjs: ["next"],
3042
+ nuxt: ["nuxt"],
3043
+ remix: ["@remix-run/react"],
3044
+ astro: ["astro"],
3045
+ gatsby: ["gatsby"],
3046
+ sveltekit: ["@sveltejs/kit"],
3047
+ // Backend
3048
+ express: ["express"],
3049
+ fastify: ["fastify"],
3050
+ koa: ["koa"],
3051
+ hono: ["hono"],
3052
+ nestjs: ["@nestjs/core"],
3053
+ fastapi: ["fastapi"],
3054
+ django: ["django"],
3055
+ flask: ["flask"],
3056
+ // Languages
3057
+ typescript: ["typescript", "@types/node"],
3058
+ javascript: [],
3059
+ // Inferred from lack of typescript
3060
+ python: [],
3061
+ rust: [],
3062
+ go: [],
3063
+ // Styling
3064
+ tailwind: ["tailwindcss"],
3065
+ "styled-components": ["styled-components"],
3066
+ emotion: ["@emotion/react"],
3067
+ "css-modules": [],
3068
+ sass: ["sass", "node-sass"],
3069
+ // State management
3070
+ redux: ["@reduxjs/toolkit", "redux"],
3071
+ zustand: ["zustand"],
3072
+ mobx: ["mobx"],
3073
+ jotai: ["jotai"],
3074
+ recoil: ["recoil"],
3075
+ // Testing
3076
+ jest: ["jest"],
3077
+ vitest: ["vitest"],
3078
+ playwright: ["@playwright/test"],
3079
+ cypress: ["cypress"],
3080
+ testing: ["@testing-library/react"],
3081
+ // Databases
3082
+ postgres: ["pg", "@prisma/client"],
3083
+ mongodb: ["mongoose", "mongodb"],
3084
+ supabase: ["@supabase/supabase-js"],
3085
+ firebase: ["firebase"],
3086
+ prisma: ["@prisma/client"],
3087
+ drizzle: ["drizzle-orm"],
3088
+ // Tools
3089
+ eslint: ["eslint"],
3090
+ prettier: ["prettier"],
3091
+ biome: ["@biomejs/biome"],
3092
+ turborepo: ["turbo"],
3093
+ monorepo: ["lerna", "nx"],
3094
+ // API
3095
+ graphql: ["graphql", "@apollo/client"],
3096
+ trpc: ["@trpc/server"],
3097
+ rest: [],
3098
+ // Auth
3099
+ auth: ["next-auth", "@auth/core", "passport"],
3100
+ "better-auth": ["better-auth"],
3101
+ // Deployment
3102
+ vercel: ["vercel"],
3103
+ docker: [],
3104
+ kubernetes: [],
3105
+ // Other
3106
+ ai: ["openai", "@anthropic-ai/sdk", "ai"],
3107
+ shadcn: [],
3108
+ security: [],
3109
+ performance: [],
3110
+ accessibility: []
3111
+ };
3112
+ function getTechTags(techName) {
3113
+ const tags = [];
3114
+ for (const [tag, techs] of Object.entries(TAG_TO_TECH)) {
3115
+ if (techs.includes(techName)) {
3116
+ tags.push(tag);
3117
+ }
3118
+ }
3119
+ return tags;
3120
+ }
3121
+
3122
+ // src/recommend/engine.ts
3123
+ var RecommendationEngine = class {
3124
+ weights;
3125
+ index = null;
3126
+ constructor(weights) {
3127
+ this.weights = { ...DEFAULT_SCORING_WEIGHTS, ...weights };
3128
+ }
3129
+ /**
3130
+ * Load skill index from cache or generate from local skills
3131
+ */
3132
+ loadIndex(index) {
3133
+ this.index = index;
3134
+ }
3135
+ /**
3136
+ * Get loaded index
3137
+ */
3138
+ getIndex() {
3139
+ return this.index;
3140
+ }
3141
+ /**
3142
+ * Score a single skill against a project profile
3143
+ */
3144
+ scoreSkill(profile, skill) {
3145
+ const reasons = [];
3146
+ const warnings = [];
3147
+ let totalScore = 0;
3148
+ const frameworkMatch = this.matchFrameworks(profile.stack, skill);
3149
+ reasons.push(frameworkMatch);
3150
+ totalScore += frameworkMatch.weight;
3151
+ const languageMatch = this.matchLanguages(profile.stack, skill);
3152
+ reasons.push(languageMatch);
3153
+ totalScore += languageMatch.weight;
3154
+ const libraryMatch = this.matchLibraries(profile.stack, skill);
3155
+ reasons.push(libraryMatch);
3156
+ totalScore += libraryMatch.weight;
3157
+ const tagMatch = this.matchTags(profile, skill);
3158
+ reasons.push(tagMatch);
3159
+ totalScore += tagMatch.weight;
3160
+ const popularityScore = this.scorePopularity(skill);
3161
+ reasons.push(popularityScore);
3162
+ totalScore += popularityScore.weight;
3163
+ const qualityScore = this.scoreQuality(skill);
3164
+ reasons.push(qualityScore);
3165
+ totalScore += qualityScore.weight;
3166
+ const freshnessScore = this.scoreFreshness(skill);
3167
+ reasons.push(freshnessScore);
3168
+ totalScore += freshnessScore.weight;
3169
+ if (skill.compatibility?.minVersion) {
3170
+ for (const [tech, minVer] of Object.entries(skill.compatibility.minVersion)) {
3171
+ const detected = this.findDetectedVersion(profile.stack, tech);
3172
+ if (detected && this.isVersionLower(detected, minVer)) {
3173
+ warnings.push(`Requires ${tech} ${minVer}+, you have ${detected}`);
3174
+ }
3175
+ }
3176
+ }
3177
+ if (profile.installedSkills.includes(skill.name)) {
3178
+ warnings.push("Already installed");
3179
+ }
3180
+ return {
3181
+ skill,
3182
+ score: Math.min(100, Math.round(totalScore)),
3183
+ reasons,
3184
+ warnings
3185
+ };
3186
+ }
3187
+ /**
3188
+ * Match frameworks in project stack against skill
3189
+ */
3190
+ matchFrameworks(stack, skill) {
3191
+ const matched = [];
3192
+ const skillFrameworks = skill.compatibility?.frameworks || [];
3193
+ const skillTags = skill.tags || [];
3194
+ for (const detection of stack.frameworks) {
3195
+ const techName = detection.name.toLowerCase();
3196
+ if (skillFrameworks.some((f) => f.toLowerCase() === techName)) {
3197
+ matched.push(detection.name);
3198
+ continue;
3199
+ }
3200
+ const relatedTags = getTechTags(techName);
3201
+ for (const tag of skillTags) {
3202
+ if (relatedTags.includes(tag.toLowerCase()) || tag.toLowerCase() === techName) {
3203
+ matched.push(detection.name);
3204
+ break;
3205
+ }
3206
+ }
3207
+ for (const [tagName, techs] of Object.entries(TAG_TO_TECH)) {
3208
+ if (techs.includes(techName) && skillTags.includes(tagName)) {
3209
+ if (!matched.includes(detection.name)) {
3210
+ matched.push(detection.name);
3211
+ }
3212
+ }
3213
+ }
3214
+ }
3215
+ const ratio = matched.length / Math.max(stack.frameworks.length, 1);
3216
+ const weight = Math.round(this.weights.framework * ratio);
3217
+ return {
3218
+ category: "framework",
3219
+ description: matched.length > 0 ? `Matches frameworks: ${matched.join(", ")}` : "No framework match",
3220
+ weight,
3221
+ matched
3222
+ };
3223
+ }
3224
+ /**
3225
+ * Match languages in project stack against skill
3226
+ */
3227
+ matchLanguages(stack, skill) {
3228
+ const matched = [];
3229
+ const skillLanguages = skill.compatibility?.languages || [];
3230
+ const skillTags = skill.tags || [];
3231
+ for (const detection of stack.languages) {
3232
+ const langName = detection.name.toLowerCase();
3233
+ if (skillLanguages.some((l) => l.toLowerCase() === langName)) {
3234
+ matched.push(detection.name);
3235
+ continue;
3236
+ }
3237
+ if (skillTags.some((t) => t.toLowerCase() === langName)) {
3238
+ matched.push(detection.name);
3239
+ }
3240
+ }
3241
+ const ratio = matched.length / Math.max(stack.languages.length, 1);
3242
+ const weight = Math.round(this.weights.language * ratio);
3243
+ return {
3244
+ category: "language",
3245
+ description: matched.length > 0 ? `Matches languages: ${matched.join(", ")}` : "No language match",
3246
+ weight,
3247
+ matched
3248
+ };
3249
+ }
3250
+ /**
3251
+ * Match libraries in project stack against skill
3252
+ */
3253
+ matchLibraries(stack, skill) {
3254
+ const matched = [];
3255
+ const skillLibraries = skill.compatibility?.libraries || [];
3256
+ const skillTags = skill.tags || [];
3257
+ const allLibraries = [...stack.libraries, ...stack.styling, ...stack.testing, ...stack.databases];
3258
+ for (const detection of allLibraries) {
3259
+ const libName = detection.name.toLowerCase();
3260
+ if (skillLibraries.some((l) => l.toLowerCase() === libName)) {
3261
+ matched.push(detection.name);
3262
+ continue;
3263
+ }
3264
+ const relatedTags = getTechTags(libName);
3265
+ for (const tag of skillTags) {
3266
+ if (relatedTags.includes(tag.toLowerCase()) || tag.toLowerCase() === libName) {
3267
+ matched.push(detection.name);
3268
+ break;
3269
+ }
3270
+ }
3271
+ }
3272
+ const ratio = matched.length / Math.max(allLibraries.length, 1);
3273
+ const weight = Math.round(this.weights.library * ratio);
3274
+ return {
3275
+ category: "library",
3276
+ description: matched.length > 0 ? `Matches libraries: ${matched.join(", ")}` : "No library match",
3277
+ weight,
3278
+ matched
3279
+ };
3280
+ }
3281
+ /**
3282
+ * Match tags based on project patterns and type
3283
+ */
3284
+ matchTags(profile, skill) {
3285
+ const matched = [];
3286
+ const skillTags = skill.tags || [];
3287
+ const projectTags = this.extractProjectTags(profile);
3288
+ for (const tag of skillTags) {
3289
+ if (projectTags.includes(tag.toLowerCase())) {
3290
+ matched.push(tag);
3291
+ }
3292
+ }
3293
+ const ratio = matched.length / Math.max(skillTags.length, 1);
3294
+ const weight = Math.round(this.weights.tag * ratio);
3295
+ return {
3296
+ category: "tag",
3297
+ description: matched.length > 0 ? `Matches tags: ${matched.join(", ")}` : "No tag match",
3298
+ weight,
3299
+ matched
3300
+ };
3301
+ }
3302
+ /**
3303
+ * Extract relevant tags from project profile
3304
+ */
3305
+ extractProjectTags(profile) {
3306
+ const tags = [];
3307
+ if (profile.type) {
3308
+ tags.push(profile.type.toLowerCase());
3309
+ }
3310
+ if (profile.patterns) {
3311
+ if (profile.patterns.components) tags.push(profile.patterns.components);
3312
+ if (profile.patterns.stateManagement) tags.push(profile.patterns.stateManagement);
3313
+ if (profile.patterns.apiStyle) tags.push(profile.patterns.apiStyle);
3314
+ if (profile.patterns.styling) tags.push(profile.patterns.styling);
3315
+ if (profile.patterns.testing) tags.push(profile.patterns.testing);
3316
+ }
3317
+ const allDetections = [
3318
+ ...profile.stack.frameworks,
3319
+ ...profile.stack.languages,
3320
+ ...profile.stack.libraries,
3321
+ ...profile.stack.styling,
3322
+ ...profile.stack.testing,
3323
+ ...profile.stack.databases,
3324
+ ...profile.stack.tools
3325
+ ];
3326
+ for (const detection of allDetections) {
3327
+ tags.push(detection.name.toLowerCase());
3328
+ const related = getTechTags(detection.name.toLowerCase());
3329
+ tags.push(...related);
3330
+ }
3331
+ return [...new Set(tags)];
3332
+ }
3333
+ /**
3334
+ * Score based on popularity (downloads/stars)
3335
+ */
3336
+ scorePopularity(skill) {
3337
+ const popularity = skill.popularity ?? 0;
3338
+ const normalized = Math.min(popularity / 1e3, 1);
3339
+ const weight = Math.round(this.weights.popularity * normalized);
3340
+ return {
3341
+ category: "popularity",
3342
+ description: popularity > 0 ? `${popularity} downloads/stars` : "No popularity data",
3343
+ weight,
3344
+ matched: popularity > 0 ? [popularity.toString()] : []
3345
+ };
3346
+ }
3347
+ /**
3348
+ * Score based on quality metrics
3349
+ */
3350
+ scoreQuality(skill) {
3351
+ const quality = skill.quality ?? 50;
3352
+ const normalized = quality / 100;
3353
+ const weight = Math.round(this.weights.quality * normalized);
3354
+ return {
3355
+ category: "quality",
3356
+ description: `Quality score: ${skill.quality}/100`,
3357
+ weight,
3358
+ matched: skill.verified ? ["verified"] : []
3359
+ };
3360
+ }
3361
+ /**
3362
+ * Score based on freshness (last update)
3363
+ */
3364
+ scoreFreshness(skill) {
3365
+ if (!skill.lastUpdated) {
3366
+ return {
3367
+ category: "freshness",
3368
+ description: "No update date available",
3369
+ weight: 0,
3370
+ matched: []
3371
+ };
3372
+ }
3373
+ const lastUpdate = new Date(skill.lastUpdated);
3374
+ const now = /* @__PURE__ */ new Date();
3375
+ const daysSinceUpdate = (now.getTime() - lastUpdate.getTime()) / (1e3 * 60 * 60 * 24);
3376
+ let normalized = Math.max(0, 1 - daysSinceUpdate / 365);
3377
+ if (daysSinceUpdate <= 30) normalized = 1;
3378
+ const weight = Math.round(this.weights.freshness * normalized);
3379
+ return {
3380
+ category: "freshness",
3381
+ description: daysSinceUpdate <= 30 ? "Recently updated" : `Updated ${Math.round(daysSinceUpdate)} days ago`,
3382
+ weight,
3383
+ matched: [skill.lastUpdated]
3384
+ };
3385
+ }
3386
+ /**
3387
+ * Find detected version for a technology
3388
+ */
3389
+ findDetectedVersion(stack, tech) {
3390
+ const techLower = tech.toLowerCase();
3391
+ const allDetections = [
3392
+ ...stack.frameworks,
3393
+ ...stack.languages,
3394
+ ...stack.libraries,
3395
+ ...stack.styling,
3396
+ ...stack.testing,
3397
+ ...stack.databases,
3398
+ ...stack.tools,
3399
+ ...stack.runtime
3400
+ ];
3401
+ const found = allDetections.find((d) => d.name.toLowerCase() === techLower);
3402
+ return found?.version || null;
3403
+ }
3404
+ /**
3405
+ * Compare semantic versions (basic implementation)
3406
+ */
3407
+ isVersionLower(current, required) {
3408
+ const parseVersion = (v) => v.replace(/[^0-9.]/g, "").split(".").map(Number);
3409
+ const currentParts = parseVersion(current);
3410
+ const requiredParts = parseVersion(required);
3411
+ for (let i = 0; i < Math.max(currentParts.length, requiredParts.length); i++) {
3412
+ const c = currentParts[i] || 0;
3413
+ const r = requiredParts[i] || 0;
3414
+ if (c < r) return true;
3415
+ if (c > r) return false;
3416
+ }
3417
+ return false;
3418
+ }
3419
+ /**
3420
+ * Get top recommendations for a project profile
3421
+ */
3422
+ recommend(profile, options = {}) {
3423
+ const {
3424
+ limit = 10,
3425
+ minScore = 30,
3426
+ categories,
3427
+ excludeInstalled = true,
3428
+ includeReasons = true
3429
+ } = options;
3430
+ if (!this.index) {
3431
+ return {
3432
+ recommendations: [],
3433
+ profile,
3434
+ totalSkillsScanned: 0,
3435
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3436
+ };
3437
+ }
3438
+ let skills = this.index.skills;
3439
+ if (categories && categories.length > 0) {
3440
+ skills = skills.filter((s) => s.tags?.some((t) => categories.includes(t)));
3441
+ }
3442
+ const scored = skills.map((skill) => this.scoreSkill(profile, skill));
3443
+ let filtered = scored.filter((s) => s.score >= minScore);
3444
+ if (excludeInstalled) {
3445
+ filtered = filtered.filter((s) => !profile.installedSkills.includes(s.skill.name));
3446
+ }
3447
+ filtered = filtered.filter((s) => !profile.excludedSkills.includes(s.skill.name));
3448
+ filtered.sort((a, b) => b.score - a.score);
3449
+ const recommendations = filtered.slice(0, limit);
3450
+ if (!includeReasons) {
3451
+ recommendations.forEach((r) => {
3452
+ r.reasons = [];
3453
+ });
3454
+ }
3455
+ return {
3456
+ recommendations,
3457
+ profile,
3458
+ totalSkillsScanned: skills.length,
3459
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3460
+ };
3461
+ }
3462
+ /**
3463
+ * Search skills by query (task-based search)
3464
+ */
3465
+ search(options) {
3466
+ const { query, limit = 10, semantic = true, filters } = options;
3467
+ if (!this.index) {
3468
+ return [];
3469
+ }
3470
+ const trimmedQuery = query.trim();
3471
+ if (!trimmedQuery) {
3472
+ return [];
3473
+ }
3474
+ const queryTerms = trimmedQuery.toLowerCase().split(/\s+/).filter((t) => t.length > 0);
3475
+ if (queryTerms.length === 0) {
3476
+ return [];
3477
+ }
3478
+ const results = [];
3479
+ for (const skill of this.index.skills) {
3480
+ if (filters?.tags && filters.tags.length > 0) {
3481
+ if (!skill.tags?.some((t) => filters.tags.includes(t))) continue;
3482
+ }
3483
+ if (filters?.verified && !skill.verified) continue;
3484
+ const { relevance, matchedTerms, snippet } = this.calculateRelevance(
3485
+ skill,
3486
+ queryTerms,
3487
+ semantic
3488
+ );
3489
+ if (relevance > 0 && (!filters?.minScore || relevance >= filters.minScore)) {
3490
+ results.push({ skill, relevance, matchedTerms, snippet });
3491
+ }
3492
+ }
3493
+ results.sort((a, b) => b.relevance - a.relevance);
3494
+ return results.slice(0, limit);
3495
+ }
3496
+ /**
3497
+ * Calculate search relevance for a skill
3498
+ */
3499
+ calculateRelevance(skill, queryTerms, semantic) {
3500
+ const matchedTerms = [];
3501
+ let relevance = 0;
3502
+ const nameLower = skill.name.toLowerCase();
3503
+ for (const term of queryTerms) {
3504
+ if (nameLower.includes(term)) {
3505
+ relevance += 40;
3506
+ matchedTerms.push(term);
3507
+ }
3508
+ }
3509
+ const descLower = (skill.description || "").toLowerCase();
3510
+ for (const term of queryTerms) {
3511
+ if (descLower.includes(term) && !matchedTerms.includes(term)) {
3512
+ relevance += 20;
3513
+ matchedTerms.push(term);
3514
+ }
3515
+ }
3516
+ const tagsLower = (skill.tags || []).map((t) => t.toLowerCase());
3517
+ for (const term of queryTerms) {
3518
+ if (tagsLower.some((t) => t.includes(term)) && !matchedTerms.includes(term)) {
3519
+ relevance += 25;
3520
+ matchedTerms.push(term);
3521
+ }
3522
+ }
3523
+ if (semantic) {
3524
+ for (const term of queryTerms) {
3525
+ const relatedTags = getTechTags(term);
3526
+ for (const relatedTag of relatedTags) {
3527
+ if (tagsLower.includes(relatedTag) || nameLower.includes(relatedTag) || descLower.includes(relatedTag)) {
3528
+ if (!matchedTerms.includes(term)) {
3529
+ relevance += 15;
3530
+ matchedTerms.push(term);
3531
+ }
3532
+ }
3533
+ }
3534
+ const tagTechs = TAG_TO_TECH[term] || [];
3535
+ for (const tech of tagTechs) {
3536
+ if (skill.compatibility?.libraries?.includes(tech)) {
3537
+ if (!matchedTerms.includes(term)) {
3538
+ relevance += 15;
3539
+ matchedTerms.push(term);
3540
+ }
3541
+ }
3542
+ }
3543
+ }
3544
+ }
3545
+ let snippet;
3546
+ if (skill.description && matchedTerms.length > 0) {
3547
+ snippet = skill.description.slice(0, 150);
3548
+ if (skill.description.length > 150) snippet += "...";
3549
+ }
3550
+ relevance = Math.min(100, relevance);
3551
+ return { relevance, matchedTerms, snippet };
3552
+ }
3553
+ /**
3554
+ * Check freshness of installed skills against project dependencies
3555
+ *
3556
+ * A skill is considered:
3557
+ * - 'current': skill's minVersion is <= project version (compatible and up to date)
3558
+ * - 'outdated': skill targets a significantly older major version
3559
+ * - 'unknown': no version requirements specified
3560
+ */
3561
+ checkFreshness(profile, installedSkills) {
3562
+ const results = [];
3563
+ for (const skill of installedSkills) {
3564
+ if (!skill.compatibility?.minVersion) {
3565
+ results.push({
3566
+ skill: skill.name,
3567
+ status: "unknown",
3568
+ details: { message: "No version requirements specified" }
3569
+ });
3570
+ continue;
3571
+ }
3572
+ let status = "current";
3573
+ const messages = [];
3574
+ for (const [tech, minVer] of Object.entries(skill.compatibility.minVersion)) {
3575
+ const detected = this.findDetectedVersion(profile.stack, tech);
3576
+ if (detected) {
3577
+ const majorSkill = parseInt(minVer.split(".")[0]) || 0;
3578
+ const majorProject = parseInt(detected.split(".")[0]) || 0;
3579
+ if (majorProject - majorSkill >= 2) {
3580
+ status = "outdated";
3581
+ messages.push(`Skill targets ${tech} ${minVer}, you have ${detected}`);
3582
+ }
3583
+ }
3584
+ }
3585
+ results.push({
3586
+ skill: skill.name,
3587
+ status,
3588
+ details: messages.length > 0 ? { message: messages.join("; ") } : { message: "Up to date" }
3589
+ });
3590
+ }
3591
+ return results;
3592
+ }
3593
+ };
3594
+ function createRecommendationEngine(weights) {
3595
+ return new RecommendationEngine(weights);
3596
+ }
3597
+ export {
3598
+ AGENT_FORMAT_MAP,
3599
+ AgentType,
3600
+ BitbucketProvider,
3601
+ CONTEXT_DIR,
3602
+ CONTEXT_FILE,
3603
+ ContextManager,
3604
+ ContextSync,
3605
+ CopilotTranslator,
3606
+ CursorTranslator,
3607
+ DEFAULT_SCORING_WEIGHTS,
3608
+ GitHubProvider,
3609
+ GitLabProvider,
3610
+ GitProvider,
3611
+ LocalProvider,
3612
+ PROJECT_TYPE_HINTS,
3613
+ ProjectDetector,
3614
+ RecommendationEngine,
3615
+ SKILL_DISCOVERY_PATHS,
3616
+ Skill,
3617
+ SkillFrontmatter,
3618
+ SkillLocation,
3619
+ SkillMdTranslator,
3620
+ SkillMetadata,
3621
+ SkillSummary,
3622
+ SkillkitConfig,
3623
+ TAG_TO_TECH,
3624
+ TranslatorRegistry,
3625
+ WindsurfTranslator,
3626
+ analyzeProject,
3627
+ canTranslate,
3628
+ copilotTranslator,
3629
+ createContextManager,
3630
+ createContextSync,
3631
+ createRecommendationEngine,
3632
+ cursorTranslator,
3633
+ detectProvider,
3634
+ detectSkillFormat,
3635
+ discoverSkills,
3636
+ extractField,
3637
+ extractFrontmatter,
3638
+ findAllSkills,
3639
+ findSkill,
3640
+ getAgentConfigPath,
3641
+ getAllProviders,
3642
+ getGlobalConfigPath,
3643
+ getInstallDir,
3644
+ getProjectConfigPath,
3645
+ getProvider,
3646
+ getSearchDirs,
3647
+ getStackTags,
3648
+ getSupportedTranslationAgents,
3649
+ getTechTags,
3650
+ initContext,
3651
+ initProject,
3652
+ isGitUrl,
3653
+ isLocalPath,
3654
+ isPathInside,
3655
+ loadConfig,
3656
+ loadContext,
3657
+ loadMetadata,
3658
+ loadSkillMetadata,
3659
+ parseShorthand,
3660
+ parseSkill,
3661
+ parseSkillContent,
3662
+ parseSource,
3663
+ readSkillContent,
3664
+ saveConfig,
3665
+ saveSkillMetadata,
3666
+ setSkillEnabled,
3667
+ skillMdTranslator,
3668
+ syncToAgent,
3669
+ syncToAllAgents,
3670
+ translateSkill,
3671
+ translateSkillFile,
3672
+ translatorRegistry,
3673
+ validateSkill,
3674
+ windsurfTranslator
3675
+ };
3676
+ //# sourceMappingURL=index.js.map