oh-my-agent 1.1.0 → 1.1.2
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.
Potentially problematic release.
This version of oh-my-agent might be problematic. Click here for more details.
- package/README.md +48 -57
- package/dist/application/OrganizeSkillsUseCase.d.ts +24 -0
- package/dist/application/OrganizeSkillsUseCase.d.ts.map +1 -0
- package/dist/application/OrganizeSkillsUseCase.js +57 -0
- package/dist/application/OrganizeSkillsUseCase.js.map +1 -0
- package/dist/domain/skill/Skill.d.ts +12 -0
- package/dist/domain/skill/Skill.d.ts.map +1 -0
- package/dist/domain/skill/Skill.js +2 -0
- package/dist/domain/skill/Skill.js.map +1 -0
- package/dist/domain/skill/SkillClassifier.d.ts +12 -0
- package/dist/domain/skill/SkillClassifier.d.ts.map +1 -0
- package/dist/domain/skill/SkillClassifier.js +60 -0
- package/dist/domain/skill/SkillClassifier.js.map +1 -0
- package/dist/domain/skill/SkillDomain.d.ts +12 -0
- package/dist/domain/skill/SkillDomain.d.ts.map +1 -0
- package/dist/domain/skill/SkillDomain.js +142 -0
- package/dist/domain/skill/SkillDomain.js.map +1 -0
- package/dist/index.js +58 -18
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/config/PathResolver.d.ts +8 -0
- package/dist/infrastructure/config/PathResolver.d.ts.map +1 -0
- package/dist/infrastructure/config/PathResolver.js +24 -0
- package/dist/infrastructure/config/PathResolver.js.map +1 -0
- package/dist/infrastructure/fs/LibraryScanner.d.ts +19 -0
- package/dist/infrastructure/fs/LibraryScanner.d.ts.map +1 -0
- package/dist/infrastructure/fs/LibraryScanner.js +94 -0
- package/dist/infrastructure/fs/LibraryScanner.js.map +1 -0
- package/dist/infrastructure/fs/SymlinkManager.d.ts +12 -0
- package/dist/infrastructure/fs/SymlinkManager.d.ts.map +1 -0
- package/dist/infrastructure/fs/SymlinkManager.js +111 -0
- package/dist/infrastructure/fs/SymlinkManager.js.map +1 -0
- package/dist/infrastructure/skills/SkillsRegistry.d.ts +15 -0
- package/dist/infrastructure/skills/SkillsRegistry.d.ts.map +1 -0
- package/dist/infrastructure/skills/SkillsRegistry.js +102 -0
- package/dist/infrastructure/skills/SkillsRegistry.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
# oh-my-agent
|
|
2
2
|
|
|
3
|
-
> AI 스킬을 자동으로
|
|
3
|
+
> AI 스킬을 자동으로 진단·설치·관리하는 MCP 서버
|
|
4
4
|
|
|
5
|
-
프로젝트의 기술 스택을 분석해
|
|
5
|
+
프로젝트의 기술 스택을 분석해 [skills.sh](https://skills.sh) 에서 최적의 스킬을 찾고 설치합니다.
|
|
6
|
+
설치 수·배포자 신뢰도·보안을 종합한 점수제로 가장 적합한 스킬을 선택합니다.
|
|
6
7
|
|
|
7
|
-
## 설치
|
|
8
|
+
## 설치
|
|
8
9
|
|
|
9
|
-
### Claude Code
|
|
10
|
+
### Claude Code 전역 등록
|
|
10
11
|
|
|
11
12
|
```bash
|
|
12
13
|
claude mcp add oh-my-agent npx -y oh-my-agent
|
|
13
14
|
```
|
|
14
15
|
|
|
15
|
-
### 또는 프로젝트별 `.mcp.json`
|
|
16
|
+
### 또는 프로젝트별 `.mcp.json`
|
|
16
17
|
|
|
17
18
|
```json
|
|
18
19
|
{
|
|
@@ -25,89 +26,79 @@ claude mcp add oh-my-agent npx -y oh-my-agent
|
|
|
25
26
|
}
|
|
26
27
|
```
|
|
27
28
|
|
|
28
|
-
##
|
|
29
|
+
## 사용법
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
MCP 등록 후 Claude Code에서 자연어로 요청합니다.
|
|
32
|
+
|
|
33
|
+
### 스킬 자동 설치 (가장 자주 씀)
|
|
31
34
|
|
|
32
35
|
```
|
|
33
|
-
|
|
34
|
-
├── frontend/ ← next-best-practices.md, vercel-react-best-practices.md ...
|
|
35
|
-
├── testing/ ← vitest-practices.md, jest-practices.md ...
|
|
36
|
-
├── security/ ← jwt-auth-practices.md ...
|
|
37
|
-
├── backend/ ← prisma-best-practices.md, express-api-patterns.md ...
|
|
38
|
-
├── devops/ ← docker-deployment.md ...
|
|
39
|
-
└── manager/ ← agile-practices.md ...
|
|
36
|
+
이 프로젝트에 맞는 AI 스킬 자동으로 설치해줘.
|
|
40
37
|
```
|
|
41
38
|
|
|
42
|
-
|
|
39
|
+
1. `package.json` 분석 → 기술 스택 감지
|
|
40
|
+
2. skills.sh에서 각 스택별 스킬 검색
|
|
41
|
+
3. 인기도·신뢰도·보안 점수화 → 최고 스킬 선택
|
|
42
|
+
4. `npx skills add` 로 `.claude/skills/` 에 설치
|
|
43
|
+
5. `CLAUDE.md` 자동 동기화
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
---
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
### 진단만 (설치 없이 미리보기)
|
|
47
48
|
|
|
48
49
|
```
|
|
49
|
-
|
|
50
|
-
출력: { detectedStack, recommendedSkills[], summary }
|
|
50
|
+
이 프로젝트 기술 스택 분석하고 어떤 스킬 추천하는지 보여줘.
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
---
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
### 특정 스킬 지정 설치
|
|
56
56
|
|
|
57
57
|
```
|
|
58
|
-
|
|
59
|
-
출력: { results[], claudeMdUpdated, summary }
|
|
58
|
+
vercel-labs/next-skills@next-best-practices 스킬 설치해줘.
|
|
60
59
|
```
|
|
61
60
|
|
|
62
|
-
|
|
61
|
+
---
|
|
63
62
|
|
|
64
|
-
|
|
63
|
+
### 키워드로 스킬 탐색
|
|
65
64
|
|
|
66
65
|
```
|
|
67
|
-
|
|
68
|
-
출력: { misplaced[], correctlyPlaced, autoFixed, summary }
|
|
66
|
+
prisma 관련 스킬 찾아줘.
|
|
69
67
|
```
|
|
70
68
|
|
|
71
|
-
|
|
69
|
+
---
|
|
72
70
|
|
|
73
|
-
|
|
71
|
+
### CLAUDE.md 재동기화
|
|
74
72
|
|
|
75
73
|
```
|
|
76
|
-
|
|
77
|
-
출력: { action, installedSkills[], addedToMd[], removedFromMd[], brokenLinks[] }
|
|
74
|
+
.claude/skills/ 폴더 기준으로 CLAUDE.md 다시 sync해줘.
|
|
78
75
|
```
|
|
79
76
|
|
|
80
|
-
##
|
|
77
|
+
## 제공 도구 (MCP Tools)
|
|
81
78
|
|
|
82
|
-
|
|
79
|
+
| 도구 | 설명 |
|
|
80
|
+
|------|------|
|
|
81
|
+
| `diagnose_project` | 기술 스택 분석 + skills.sh 검색 + 점수화된 추천 목록 반환 |
|
|
82
|
+
| `setup_project` | 진단 후 최고 점수 스킬 자동 설치 + CLAUDE.md 동기화 |
|
|
83
|
+
| `find_skills` | 키워드로 skills.sh 검색 |
|
|
84
|
+
| `sync_claude_md` | `.claude/skills/` 실제 파일 기준 CLAUDE.md 동기화 |
|
|
83
85
|
|
|
84
|
-
|
|
85
|
-
<!-- oh-my-agent:skills:start -->
|
|
86
|
-
## Active Skills
|
|
86
|
+
## 점수 체계
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
- **testing/vitest-practices** — Vitest 단위 테스트
|
|
88
|
+
skills.sh에서 검색된 후보 스킬을 아래 기준으로 점수화합니다.
|
|
90
89
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
| 항목 | 가중치 | 기준 |
|
|
91
|
+
|------|--------|------|
|
|
92
|
+
| 인기도 | 50% | skills.sh 설치 수 |
|
|
93
|
+
| 배포자 신뢰도 | 35% | 공식 조직·검증 개발자 우선 (`anthropics`, `vercel-labs`, `microsoft` 등) |
|
|
94
|
+
| 보안 | 15% | 신뢰도 대리 지표 |
|
|
95
|
+
|
|
96
|
+
## 감지 가능한 기술 스택
|
|
95
97
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
| next, nextjs | frontend | next-best-practices |
|
|
101
|
-
| react | frontend | vercel-react-best-practices |
|
|
102
|
-
| react-native | frontend | vercel-react-native-skills |
|
|
103
|
-
| vitest | testing | vitest-practices |
|
|
104
|
-
| jest | testing | jest-practices |
|
|
105
|
-
| cypress | testing | e2e-testing |
|
|
106
|
-
| playwright | testing | playwright-testing |
|
|
107
|
-
| prisma | backend | prisma-best-practices |
|
|
108
|
-
| express | backend | express-api-patterns |
|
|
109
|
-
| jsonwebtoken, jose | security | jwt-auth-practices |
|
|
110
|
-
| docker | devops | docker-deployment |
|
|
98
|
+
Next.js · React · React Native · Vue · Nuxt · Svelte · Angular
|
|
99
|
+
Vitest · Jest · Playwright · Cypress
|
|
100
|
+
Prisma · Express · Fastify · tRPC · GraphQL
|
|
101
|
+
Docker · Kubernetes
|
|
111
102
|
|
|
112
103
|
## 요구사항
|
|
113
104
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Domain } from "../domain/skill/SkillDomain.js";
|
|
2
|
+
export interface OrganizeSkillsInput {
|
|
3
|
+
libraryPath?: string;
|
|
4
|
+
autoFix?: boolean;
|
|
5
|
+
targetDomain?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface MisplacedSkill {
|
|
8
|
+
skillName: string;
|
|
9
|
+
currentDomain: string;
|
|
10
|
+
predictedDomain: Domain | "unknown";
|
|
11
|
+
confidence: number;
|
|
12
|
+
filePath: string;
|
|
13
|
+
}
|
|
14
|
+
export interface OrganizeSkillsOutput {
|
|
15
|
+
misplaced: MisplacedSkill[];
|
|
16
|
+
correctlyPlaced: number;
|
|
17
|
+
autoFixed: number;
|
|
18
|
+
summary: string;
|
|
19
|
+
}
|
|
20
|
+
export declare class OrganizeSkillsUseCase {
|
|
21
|
+
private classifier;
|
|
22
|
+
execute(input: OrganizeSkillsInput): Promise<OrganizeSkillsOutput>;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=OrganizeSkillsUseCase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OrganizeSkillsUseCase.d.ts","sourceRoot":"","sources":["../../src/application/OrganizeSkillsUseCase.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gCAAgC,CAAC;AAE7D,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,qBAAqB;IAChC,OAAO,CAAC,UAAU,CAAyB;IAErC,OAAO,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC;CA0DzE"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fse from "fs-extra";
|
|
4
|
+
import { SkillClassifier } from "../domain/skill/SkillClassifier.js";
|
|
5
|
+
import { LibraryScanner } from "../infrastructure/fs/LibraryScanner.js";
|
|
6
|
+
import { PathResolver } from "../infrastructure/config/PathResolver.js";
|
|
7
|
+
export class OrganizeSkillsUseCase {
|
|
8
|
+
classifier = new SkillClassifier();
|
|
9
|
+
async execute(input) {
|
|
10
|
+
const libraryPath = input.libraryPath
|
|
11
|
+
? PathResolver.resolveHome(input.libraryPath)
|
|
12
|
+
: PathResolver.getLibraryPath();
|
|
13
|
+
const autoFix = input.autoFix ?? false;
|
|
14
|
+
const targetDomain = input.targetDomain;
|
|
15
|
+
const scanner = new LibraryScanner(libraryPath);
|
|
16
|
+
const allSkills = await scanner.scanAll(true);
|
|
17
|
+
const filtered = targetDomain
|
|
18
|
+
? allSkills.filter((s) => s.domain === targetDomain)
|
|
19
|
+
: allSkills;
|
|
20
|
+
const misplaced = [];
|
|
21
|
+
let correctlyPlaced = 0;
|
|
22
|
+
let autoFixed = 0;
|
|
23
|
+
for (const skill of filtered) {
|
|
24
|
+
const result = this.classifier.classify(skill.skillName, skill.content);
|
|
25
|
+
if (result.predictedDomain === "unknown" ||
|
|
26
|
+
result.predictedDomain === skill.domain) {
|
|
27
|
+
correctlyPlaced++;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
misplaced.push({
|
|
31
|
+
skillName: skill.skillName,
|
|
32
|
+
currentDomain: skill.domain,
|
|
33
|
+
predictedDomain: result.predictedDomain,
|
|
34
|
+
confidence: result.confidence,
|
|
35
|
+
filePath: skill.filePath,
|
|
36
|
+
});
|
|
37
|
+
if (autoFix) {
|
|
38
|
+
const targetDir = path.join(libraryPath, result.predictedDomain);
|
|
39
|
+
const targetPath = path.join(targetDir, `${skill.skillName}.md`);
|
|
40
|
+
try {
|
|
41
|
+
await fse.ensureDir(targetDir);
|
|
42
|
+
await fs.rename(skill.filePath, targetPath);
|
|
43
|
+
autoFixed++;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// 이동 실패 → 무시
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const summary = `총 ${filtered.length}개 스킬 분석 | ` +
|
|
51
|
+
`올바른 위치: ${correctlyPlaced}개 | ` +
|
|
52
|
+
`잘못된 위치: ${misplaced.length}개` +
|
|
53
|
+
(autoFix ? ` | 자동 수정: ${autoFixed}개` : "");
|
|
54
|
+
return { misplaced, correctlyPlaced, autoFixed, summary };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=OrganizeSkillsUseCase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OrganizeSkillsUseCase.js","sourceRoot":"","sources":["../../src/application/OrganizeSkillsUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,aAAa,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,0CAA0C,CAAC;AAwBxE,MAAM,OAAO,qBAAqB;IACxB,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IAE3C,KAAK,CAAC,OAAO,CAAC,KAA0B;QACtC,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW;YACnC,CAAC,CAAC,YAAY,CAAC,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC;YAC7C,CAAC,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC;QACvC,MAAM,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC;QAExC,MAAM,OAAO,GAAG,IAAI,cAAc,CAAC,WAAW,CAAC,CAAC;QAChD,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAE9C,MAAM,QAAQ,GAAG,YAAY;YAC3B,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,YAAY,CAAC;YACpD,CAAC,CAAC,SAAS,CAAC;QAEd,MAAM,SAAS,GAAqB,EAAE,CAAC;QACvC,IAAI,eAAe,GAAG,CAAC,CAAC;QACxB,IAAI,SAAS,GAAG,CAAC,CAAC;QAElB,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YAExE,IACE,MAAM,CAAC,eAAe,KAAK,SAAS;gBACpC,MAAM,CAAC,eAAe,KAAK,KAAK,CAAC,MAAM,EACvC,CAAC;gBACD,eAAe,EAAE,CAAC;gBAClB,SAAS;YACX,CAAC;YAED,SAAS,CAAC,IAAI,CAAC;gBACb,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,aAAa,EAAE,KAAK,CAAC,MAAM;gBAC3B,eAAe,EAAE,MAAM,CAAC,eAAe;gBACvC,UAAU,EAAE,MAAM,CAAC,UAAU;gBAC7B,QAAQ,EAAE,KAAK,CAAC,QAAQ;aACzB,CAAC,CAAC;YAEH,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,eAAe,CAAC,CAAC;gBACjE,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,KAAK,CAAC,SAAS,KAAK,CAAC,CAAC;gBACjE,IAAI,CAAC;oBACH,MAAM,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;oBAC/B,MAAM,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;oBAC5C,SAAS,EAAE,CAAC;gBACd,CAAC;gBAAC,MAAM,CAAC;oBACP,aAAa;gBACf,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,OAAO,GACX,KAAK,QAAQ,CAAC,MAAM,YAAY;YAChC,WAAW,eAAe,MAAM;YAChC,WAAW,SAAS,CAAC,MAAM,GAAG;YAC9B,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,SAAS,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAE7C,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;IAC5D,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Skill.d.ts","sourceRoot":"","sources":["../../../src/domain/skill/Skill.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Skill.js","sourceRoot":"","sources":["../../../src/domain/skill/Skill.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Domain } from "./SkillDomain.js";
|
|
2
|
+
export interface ClassificationResult {
|
|
3
|
+
predictedDomain: Domain | "unknown";
|
|
4
|
+
confidence: number;
|
|
5
|
+
scores: Record<string, number>;
|
|
6
|
+
}
|
|
7
|
+
export declare class SkillClassifier {
|
|
8
|
+
classify(skillName: string, content?: string): ClassificationResult;
|
|
9
|
+
private tokenize;
|
|
10
|
+
private countMatches;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=SkillClassifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SkillClassifier.d.ts","sourceRoot":"","sources":["../../../src/domain/skill/SkillClassifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEzE,MAAM,WAAW,oBAAoB;IACnC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,qBAAa,eAAe;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,oBAAoB;IAsDnE,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,YAAY;CAOrB"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { DOMAINS, DOMAIN_KEYWORDS } from "./SkillDomain.js";
|
|
2
|
+
export class SkillClassifier {
|
|
3
|
+
classify(skillName, content) {
|
|
4
|
+
const tokens = this.tokenize(skillName);
|
|
5
|
+
// frontmatter description 추출 (1.5× 가중치 적용)
|
|
6
|
+
let descriptionTokens = [];
|
|
7
|
+
if (content) {
|
|
8
|
+
const descMatch = content.match(/^description:\s*(.+)$/m);
|
|
9
|
+
if (descMatch) {
|
|
10
|
+
descriptionTokens = this.tokenize(descMatch[1]);
|
|
11
|
+
}
|
|
12
|
+
// 나머지 content 토큰 추가
|
|
13
|
+
tokens.push(...this.tokenize(content));
|
|
14
|
+
}
|
|
15
|
+
const scores = {};
|
|
16
|
+
let totalScore = 0;
|
|
17
|
+
for (const domain of DOMAINS) {
|
|
18
|
+
const keywords = DOMAIN_KEYWORDS[domain];
|
|
19
|
+
let score = 0;
|
|
20
|
+
for (const { keyword, weight } of keywords) {
|
|
21
|
+
const keyTokens = this.tokenize(keyword);
|
|
22
|
+
// 일반 토큰에서 매칭
|
|
23
|
+
const nameMatches = this.countMatches(tokens, keyTokens);
|
|
24
|
+
score += nameMatches * weight;
|
|
25
|
+
// description 토큰에서 매칭 (1.5× 가중치)
|
|
26
|
+
const descMatches = this.countMatches(descriptionTokens, keyTokens);
|
|
27
|
+
score += descMatches * weight * 1.5;
|
|
28
|
+
}
|
|
29
|
+
scores[domain] = score;
|
|
30
|
+
totalScore += score;
|
|
31
|
+
}
|
|
32
|
+
if (totalScore === 0) {
|
|
33
|
+
return { predictedDomain: "unknown", confidence: 0, scores };
|
|
34
|
+
}
|
|
35
|
+
const bestDomain = (Object.entries(scores).sort(([, a], [, b]) => b - a)[0][0]);
|
|
36
|
+
const bestScore = scores[bestDomain];
|
|
37
|
+
const confidence = bestScore / totalScore;
|
|
38
|
+
return {
|
|
39
|
+
predictedDomain: confidence >= 0.4 ? bestDomain : "unknown",
|
|
40
|
+
confidence,
|
|
41
|
+
scores,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
tokenize(text) {
|
|
45
|
+
return text
|
|
46
|
+
.toLowerCase()
|
|
47
|
+
.replace(/[_\-./\\]/g, " ")
|
|
48
|
+
.split(/\s+/)
|
|
49
|
+
.filter((t) => t.length > 0);
|
|
50
|
+
}
|
|
51
|
+
countMatches(tokens, keywords) {
|
|
52
|
+
let count = 0;
|
|
53
|
+
for (const keyword of keywords) {
|
|
54
|
+
if (tokens.includes(keyword))
|
|
55
|
+
count++;
|
|
56
|
+
}
|
|
57
|
+
return count;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=SkillClassifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SkillClassifier.js","sourceRoot":"","sources":["../../../src/domain/skill/SkillClassifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const DOMAINS: readonly ["frontend", "testing", "security", "backend", "devops", "manager"];
|
|
2
|
+
export type Domain = (typeof DOMAINS)[number];
|
|
3
|
+
export interface KeywordEntry {
|
|
4
|
+
keyword: string;
|
|
5
|
+
weight: 1 | 2 | 3;
|
|
6
|
+
}
|
|
7
|
+
export declare const DOMAIN_KEYWORDS: Record<Domain, KeywordEntry[]>;
|
|
8
|
+
export declare const STACK_TO_SKILL: Record<string, {
|
|
9
|
+
domain: string;
|
|
10
|
+
skillName: string;
|
|
11
|
+
}>;
|
|
12
|
+
//# sourceMappingURL=SkillDomain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SkillDomain.d.ts","sourceRoot":"","sources":["../../../src/domain/skill/SkillDomain.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,8EAOV,CAAC;AAEX,MAAM,MAAM,MAAM,GAAG,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9C,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;CACnB;AAED,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,CAgH1D,CAAC;AAGF,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAkBhF,CAAC"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
export const DOMAINS = [
|
|
2
|
+
"frontend",
|
|
3
|
+
"testing",
|
|
4
|
+
"security",
|
|
5
|
+
"backend",
|
|
6
|
+
"devops",
|
|
7
|
+
"manager",
|
|
8
|
+
];
|
|
9
|
+
export const DOMAIN_KEYWORDS = {
|
|
10
|
+
frontend: [
|
|
11
|
+
// weight 3 — 프레임워크 정확 매칭
|
|
12
|
+
{ keyword: "next", weight: 3 },
|
|
13
|
+
{ keyword: "nextjs", weight: 3 },
|
|
14
|
+
{ keyword: "react", weight: 3 },
|
|
15
|
+
{ keyword: "vue", weight: 3 },
|
|
16
|
+
{ keyword: "nuxt", weight: 3 },
|
|
17
|
+
{ keyword: "svelte", weight: 3 },
|
|
18
|
+
{ keyword: "angular", weight: 3 },
|
|
19
|
+
{ keyword: "vite", weight: 3 },
|
|
20
|
+
// weight 2 — 도구/개념
|
|
21
|
+
{ keyword: "tailwind", weight: 2 },
|
|
22
|
+
{ keyword: "css", weight: 2 },
|
|
23
|
+
{ keyword: "ui", weight: 2 },
|
|
24
|
+
{ keyword: "component", weight: 2 },
|
|
25
|
+
{ keyword: "storybook", weight: 2 },
|
|
26
|
+
{ keyword: "webpack", weight: 2 },
|
|
27
|
+
{ keyword: "typescript", weight: 2 },
|
|
28
|
+
// weight 1 — 일반 용어
|
|
29
|
+
{ keyword: "web", weight: 1 },
|
|
30
|
+
{ keyword: "html", weight: 1 },
|
|
31
|
+
{ keyword: "spa", weight: 1 },
|
|
32
|
+
{ keyword: "ssr", weight: 1 },
|
|
33
|
+
{ keyword: "client", weight: 1 },
|
|
34
|
+
],
|
|
35
|
+
testing: [
|
|
36
|
+
{ keyword: "vitest", weight: 3 },
|
|
37
|
+
{ keyword: "jest", weight: 3 },
|
|
38
|
+
{ keyword: "cypress", weight: 3 },
|
|
39
|
+
{ keyword: "playwright", weight: 3 },
|
|
40
|
+
{ keyword: "testing-library", weight: 3 },
|
|
41
|
+
{ keyword: "mocha", weight: 3 },
|
|
42
|
+
{ keyword: "e2e", weight: 2 },
|
|
43
|
+
{ keyword: "unit", weight: 2 },
|
|
44
|
+
{ keyword: "mock", weight: 2 },
|
|
45
|
+
{ keyword: "coverage", weight: 2 },
|
|
46
|
+
{ keyword: "assertion", weight: 2 },
|
|
47
|
+
{ keyword: "test", weight: 1 },
|
|
48
|
+
{ keyword: "spec", weight: 1 },
|
|
49
|
+
{ keyword: "qa", weight: 1 },
|
|
50
|
+
],
|
|
51
|
+
security: [
|
|
52
|
+
{ keyword: "owasp", weight: 3 },
|
|
53
|
+
{ keyword: "jwt", weight: 3 },
|
|
54
|
+
{ keyword: "jose", weight: 3 },
|
|
55
|
+
{ keyword: "oauth", weight: 3 },
|
|
56
|
+
{ keyword: "xss", weight: 3 },
|
|
57
|
+
{ keyword: "csrf", weight: 3 },
|
|
58
|
+
{ keyword: "cors", weight: 2 },
|
|
59
|
+
{ keyword: "auth", weight: 2 },
|
|
60
|
+
{ keyword: "authentication", weight: 2 },
|
|
61
|
+
{ keyword: "authorization", weight: 2 },
|
|
62
|
+
{ keyword: "ssl", weight: 2 },
|
|
63
|
+
{ keyword: "tls", weight: 2 },
|
|
64
|
+
{ keyword: "encryption", weight: 2 },
|
|
65
|
+
{ keyword: "security", weight: 1 },
|
|
66
|
+
{ keyword: "permission", weight: 1 },
|
|
67
|
+
{ keyword: "role", weight: 1 },
|
|
68
|
+
],
|
|
69
|
+
backend: [
|
|
70
|
+
{ keyword: "prisma", weight: 3 },
|
|
71
|
+
{ keyword: "express", weight: 3 },
|
|
72
|
+
{ keyword: "trpc", weight: 3 },
|
|
73
|
+
{ keyword: "fastify", weight: 3 },
|
|
74
|
+
{ keyword: "nestjs", weight: 3 },
|
|
75
|
+
{ keyword: "graphql", weight: 3 },
|
|
76
|
+
{ keyword: "rest", weight: 2 },
|
|
77
|
+
{ keyword: "api", weight: 2 },
|
|
78
|
+
{ keyword: "database", weight: 2 },
|
|
79
|
+
{ keyword: "postgresql", weight: 2 },
|
|
80
|
+
{ keyword: "mysql", weight: 2 },
|
|
81
|
+
{ keyword: "mongodb", weight: 2 },
|
|
82
|
+
{ keyword: "orm", weight: 2 },
|
|
83
|
+
{ keyword: "server", weight: 1 },
|
|
84
|
+
{ keyword: "endpoint", weight: 1 },
|
|
85
|
+
{ keyword: "http", weight: 1 },
|
|
86
|
+
{ keyword: "middleware", weight: 1 },
|
|
87
|
+
],
|
|
88
|
+
devops: [
|
|
89
|
+
{ keyword: "docker", weight: 3 },
|
|
90
|
+
{ keyword: "kubernetes", weight: 3 },
|
|
91
|
+
{ keyword: "k8s", weight: 3 },
|
|
92
|
+
{ keyword: "terraform", weight: 3 },
|
|
93
|
+
{ keyword: "github-actions", weight: 3 },
|
|
94
|
+
{ keyword: "ci", weight: 2 },
|
|
95
|
+
{ keyword: "cd", weight: 2 },
|
|
96
|
+
{ keyword: "pipeline", weight: 2 },
|
|
97
|
+
{ keyword: "deploy", weight: 2 },
|
|
98
|
+
{ keyword: "container", weight: 2 },
|
|
99
|
+
{ keyword: "helm", weight: 2 },
|
|
100
|
+
{ keyword: "aws", weight: 2 },
|
|
101
|
+
{ keyword: "gcp", weight: 2 },
|
|
102
|
+
{ keyword: "azure", weight: 2 },
|
|
103
|
+
{ keyword: "infra", weight: 1 },
|
|
104
|
+
{ keyword: "devops", weight: 1 },
|
|
105
|
+
{ keyword: "monitoring", weight: 1 },
|
|
106
|
+
],
|
|
107
|
+
manager: [
|
|
108
|
+
{ keyword: "agile", weight: 3 },
|
|
109
|
+
{ keyword: "scrum", weight: 3 },
|
|
110
|
+
{ keyword: "kanban", weight: 3 },
|
|
111
|
+
{ keyword: "sprint", weight: 2 },
|
|
112
|
+
{ keyword: "roadmap", weight: 2 },
|
|
113
|
+
{ keyword: "backlog", weight: 2 },
|
|
114
|
+
{ keyword: "milestone", weight: 2 },
|
|
115
|
+
{ keyword: "jira", weight: 2 },
|
|
116
|
+
{ keyword: "project", weight: 1 },
|
|
117
|
+
{ keyword: "planning", weight: 1 },
|
|
118
|
+
{ keyword: "meeting", weight: 1 },
|
|
119
|
+
{ keyword: "standup", weight: 1 },
|
|
120
|
+
],
|
|
121
|
+
};
|
|
122
|
+
// 기술 스택 → 스킬 매핑 테이블
|
|
123
|
+
export const STACK_TO_SKILL = {
|
|
124
|
+
next: { domain: "frontend", skillName: "next-best-practices" },
|
|
125
|
+
nextjs: { domain: "frontend", skillName: "next-best-practices" },
|
|
126
|
+
react: { domain: "frontend", skillName: "vercel-react-best-practices" },
|
|
127
|
+
"react-native": { domain: "frontend", skillName: "vercel-react-native-skills" },
|
|
128
|
+
vue: { domain: "frontend", skillName: "vue-best-practices" },
|
|
129
|
+
vitest: { domain: "testing", skillName: "vitest-practices" },
|
|
130
|
+
jest: { domain: "testing", skillName: "jest-practices" },
|
|
131
|
+
cypress: { domain: "testing", skillName: "e2e-testing" },
|
|
132
|
+
playwright: { domain: "testing", skillName: "playwright-testing" },
|
|
133
|
+
prisma: { domain: "backend", skillName: "prisma-best-practices" },
|
|
134
|
+
express: { domain: "backend", skillName: "express-api-patterns" },
|
|
135
|
+
trpc: { domain: "backend", skillName: "trpc-patterns" },
|
|
136
|
+
graphql: { domain: "backend", skillName: "graphql-api-patterns" },
|
|
137
|
+
jsonwebtoken: { domain: "security", skillName: "jwt-auth-practices" },
|
|
138
|
+
jose: { domain: "security", skillName: "jwt-auth-practices" },
|
|
139
|
+
docker: { domain: "devops", skillName: "docker-deployment" },
|
|
140
|
+
kubernetes: { domain: "devops", skillName: "kubernetes-deployment" },
|
|
141
|
+
};
|
|
142
|
+
//# sourceMappingURL=SkillDomain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SkillDomain.js","sourceRoot":"","sources":["../../../src/domain/skill/SkillDomain.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -2,17 +2,55 @@
|
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
5
7
|
import { DiagnoseProjectUseCase } from "./application/DiagnoseProjectUseCase.js";
|
|
6
8
|
import { SetupSkillsUseCase } from "./application/SetupSkillsUseCase.js";
|
|
7
9
|
import { SyncClaudeMdUseCase } from "./application/SyncClaudeMdUseCase.js";
|
|
8
|
-
import {
|
|
10
|
+
import { SkillsShClient } from "./infrastructure/skills/SkillsShClient.js";
|
|
11
|
+
import { SkillsScorer } from "./infrastructure/skills/SkillsScorer.js";
|
|
12
|
+
// ─── CLI 서브커맨드 처리 ──────────────────────────────────────────────────────
|
|
13
|
+
const subcommand = process.argv[2];
|
|
14
|
+
if (subcommand === "init") {
|
|
15
|
+
const cwd = process.cwd();
|
|
16
|
+
const mcpJsonPath = path.join(cwd, ".mcp.json");
|
|
17
|
+
const entry = {
|
|
18
|
+
command: "npx",
|
|
19
|
+
args: ["-y", "oh-my-agent"],
|
|
20
|
+
};
|
|
21
|
+
let config = { mcpServers: {} };
|
|
22
|
+
if (fs.existsSync(mcpJsonPath)) {
|
|
23
|
+
try {
|
|
24
|
+
const existing = JSON.parse(fs.readFileSync(mcpJsonPath, "utf-8"));
|
|
25
|
+
if (existing && typeof existing === "object") {
|
|
26
|
+
config = existing;
|
|
27
|
+
if (!config.mcpServers)
|
|
28
|
+
config.mcpServers = {};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// 파싱 실패 시 새로 덮어씀
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const alreadyExists = "oh-my-agent" in config.mcpServers;
|
|
36
|
+
config.mcpServers["oh-my-agent"] = entry;
|
|
37
|
+
fs.writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
38
|
+
if (alreadyExists) {
|
|
39
|
+
console.log(`✔ .mcp.json already had oh-my-agent — updated: ${mcpJsonPath}`);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.log(`✔ .mcp.json created: ${mcpJsonPath}`);
|
|
43
|
+
}
|
|
44
|
+
console.log("Reload Claude Code to activate the MCP server.");
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
9
47
|
const server = new McpServer({
|
|
10
48
|
name: "oh-my-agent",
|
|
11
49
|
version: "1.0.0",
|
|
12
50
|
});
|
|
13
51
|
// ─── diagnose_project ────────────────────────────────────────────────────────
|
|
14
|
-
server.tool("diagnose_project", `프로젝트의
|
|
15
|
-
|
|
52
|
+
server.tool("diagnose_project", `프로젝트의 기술 스택을 감지하고 설치 가능한 추천 스킬 목록을 반환합니다.
|
|
53
|
+
스킬을 실제로 설치하려면 setup_project 도구를 사용하세요.`, {
|
|
16
54
|
projectPath: z
|
|
17
55
|
.string()
|
|
18
56
|
.optional()
|
|
@@ -29,26 +67,26 @@ setup_project를 호출하기 전에 먼저 실행해 어떤 스킬이 설치될
|
|
|
29
67
|
};
|
|
30
68
|
});
|
|
31
69
|
// ─── setup_project ───────────────────────────────────────────────────────────
|
|
32
|
-
server.tool("setup_project",
|
|
33
|
-
|
|
34
|
-
|
|
70
|
+
server.tool("setup_project", `프로젝트에 AI 스킬을 자동으로 설치합니다.
|
|
71
|
+
기술 스택을 감지하고 skills.sh에서 최적의 스킬을 찾아 .claude/skills/ 에 설치한 뒤 CLAUDE.md를 업데이트합니다.
|
|
72
|
+
스킬 설치가 필요할 때 이 도구를 직접 호출하세요. 외부 CLI를 별도로 실행하지 마세요.`, {
|
|
35
73
|
projectPath: z.string().describe("세팅할 프로젝트 경로"),
|
|
36
74
|
skillRefs: z
|
|
37
75
|
.array(z.string())
|
|
38
76
|
.optional()
|
|
39
|
-
.describe('
|
|
77
|
+
.describe('설치할 스킬을 직접 지정 (예: ["vercel-labs/next-skills@next-best-practices"]). 생략하면 자동 감지.'),
|
|
40
78
|
includeMetaSkills: z
|
|
41
79
|
.boolean()
|
|
42
80
|
.optional()
|
|
43
|
-
.describe("find-skills, skill-creator
|
|
81
|
+
.describe("메타 스킬(find-skills, skill-creator) 포함 여부 (기본값: true)"),
|
|
44
82
|
syncClaudeMd: z
|
|
45
83
|
.boolean()
|
|
46
84
|
.optional()
|
|
47
|
-
.describe("설치 후
|
|
85
|
+
.describe("설치 후 CLAUDE.md 동기화 여부 (기본값: true)"),
|
|
48
86
|
dryRun: z
|
|
49
87
|
.boolean()
|
|
50
88
|
.optional()
|
|
51
|
-
.describe("실제 설치 없이
|
|
89
|
+
.describe("실제 설치 없이 설치 예정 목록만 반환 (기본값: false)"),
|
|
52
90
|
}, async ({ projectPath, skillRefs, includeMetaSkills, syncClaudeMd, dryRun }) => {
|
|
53
91
|
const useCase = new SetupSkillsUseCase();
|
|
54
92
|
const result = await useCase.execute({
|
|
@@ -63,30 +101,32 @@ find-skills, skill-creator 메타 스킬도 함께 설치됩니다.`, {
|
|
|
63
101
|
};
|
|
64
102
|
});
|
|
65
103
|
// ─── find_skills ─────────────────────────────────────────────────────────────
|
|
66
|
-
server.tool("find_skills",
|
|
67
|
-
|
|
68
|
-
query: z.string().describe("
|
|
104
|
+
server.tool("find_skills", `키워드로 skills.sh에서 스킬을 검색하고 점수화된 후보 목록을 반환합니다.
|
|
105
|
+
검색 결과를 보고 설치하려면 setup_project 도구에 skillRefs를 전달하세요. CLI를 직접 실행하지 마세요.`, {
|
|
106
|
+
query: z.string().describe("검색 키워드 (예: 'nextjs', 'testing', 'docker')"),
|
|
69
107
|
projectPath: z
|
|
70
108
|
.string()
|
|
71
109
|
.optional()
|
|
72
110
|
.describe("실행 기준 프로젝트 경로 (기본값: 현재 디렉토리)"),
|
|
73
111
|
}, async ({ query, projectPath }) => {
|
|
74
|
-
const
|
|
112
|
+
const client = new SkillsShClient();
|
|
113
|
+
const scorer = new SkillsScorer();
|
|
75
114
|
const cwd = projectPath ?? process.cwd();
|
|
76
115
|
try {
|
|
77
|
-
const
|
|
116
|
+
const candidates = await client.searchSkills(query, cwd);
|
|
117
|
+
const scored = scorer.score(candidates);
|
|
78
118
|
return {
|
|
79
|
-
content: [{ type: "text", text:
|
|
119
|
+
content: [{ type: "text", text: JSON.stringify(scored, null, 2) }],
|
|
80
120
|
};
|
|
81
121
|
}
|
|
82
122
|
catch (err) {
|
|
83
123
|
return {
|
|
84
|
-
content: [{ type: "text", text:
|
|
124
|
+
content: [{ type: "text", text: JSON.stringify({ error: String(err) }) }],
|
|
85
125
|
};
|
|
86
126
|
}
|
|
87
127
|
});
|
|
88
128
|
// ─── sync_claude_md ───────────────────────────────────────────────────────────
|
|
89
|
-
server.tool("sync_claude_md", `.claude/skills/
|
|
129
|
+
server.tool("sync_claude_md", `.claude/skills/ 에 실제 설치된 파일을 기준으로 CLAUDE.md를 동기화합니다.`, {
|
|
90
130
|
projectPath: z.string().describe("대상 프로젝트 경로"),
|
|
91
131
|
createIfMissing: z
|
|
92
132
|
.boolean()
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAC3E,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare class PathResolver {
|
|
2
|
+
static resolveHome(inputPath: string): string;
|
|
3
|
+
static getLibraryPath(): string;
|
|
4
|
+
static getSkillsPath(projectPath: string): string;
|
|
5
|
+
static getClaudeMdPath(projectPath: string): string;
|
|
6
|
+
static normalize(inputPath: string): string;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=PathResolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PathResolver.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/config/PathResolver.ts"],"names":[],"mappings":"AAGA,qBAAa,YAAY;IACvB,MAAM,CAAC,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAQ7C,MAAM,CAAC,cAAc,IAAI,MAAM;IAI/B,MAAM,CAAC,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAIjD,MAAM,CAAC,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAInD,MAAM,CAAC,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;CAG5C"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import os from "os";
|
|
2
|
+
import path from "path";
|
|
3
|
+
export class PathResolver {
|
|
4
|
+
static resolveHome(inputPath) {
|
|
5
|
+
if (inputPath.startsWith("~/") || inputPath === "~") {
|
|
6
|
+
const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
7
|
+
return path.join(home, inputPath.slice(2));
|
|
8
|
+
}
|
|
9
|
+
return inputPath;
|
|
10
|
+
}
|
|
11
|
+
static getLibraryPath() {
|
|
12
|
+
return this.resolveHome("~/oh-my-agent-library");
|
|
13
|
+
}
|
|
14
|
+
static getSkillsPath(projectPath) {
|
|
15
|
+
return path.join(projectPath, ".claude", "skills");
|
|
16
|
+
}
|
|
17
|
+
static getClaudeMdPath(projectPath) {
|
|
18
|
+
return path.join(projectPath, "CLAUDE.md");
|
|
19
|
+
}
|
|
20
|
+
static normalize(inputPath) {
|
|
21
|
+
return path.normalize(inputPath).replace(/\\/g, "/");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=PathResolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PathResolver.js","sourceRoot":"","sources":["../../../src/infrastructure/config/PathResolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,OAAO,YAAY;IACvB,MAAM,CAAC,WAAW,CAAC,SAAiB;QAClC,IAAI,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,SAAS,KAAK,GAAG,EAAE,CAAC;YACpD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;YACzE,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,CAAC,cAAc;QACnB,OAAO,IAAI,CAAC,WAAW,CAAC,uBAAuB,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,CAAC,aAAa,CAAC,WAAmB;QACtC,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IACrD,CAAC;IAED,MAAM,CAAC,eAAe,CAAC,WAAmB;QACxC,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,CAAC,SAAS,CAAC,SAAiB;QAChC,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACvD,CAAC;CACF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface SkillFile {
|
|
2
|
+
domain: string;
|
|
3
|
+
skillName: string;
|
|
4
|
+
filePath: string;
|
|
5
|
+
content?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class LibraryScanner {
|
|
8
|
+
private libraryPath;
|
|
9
|
+
constructor(libraryPath?: string);
|
|
10
|
+
exists(): Promise<boolean>;
|
|
11
|
+
getDomains(): Promise<string[]>;
|
|
12
|
+
scanAll(includeContent?: boolean): Promise<SkillFile[]>;
|
|
13
|
+
scanDomain(domain: string, includeContent?: boolean): Promise<SkillFile[]>;
|
|
14
|
+
getSkillPath(domain: string, skillName: string): Promise<string>;
|
|
15
|
+
skillExists(domain: string, skillName: string): Promise<boolean>;
|
|
16
|
+
readSkillContent(domain: string, skillName: string): Promise<string | null>;
|
|
17
|
+
getLibraryPath(): string;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=LibraryScanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LibraryScanner.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/fs/LibraryScanner.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,WAAW,CAAS;gBAEhB,WAAW,CAAC,EAAE,MAAM;IAI1B,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAI1B,UAAU,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAY/B,OAAO,CAAC,cAAc,UAAQ,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IA2BrD,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,UAAQ,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAwBxE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAKhE,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKhE,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IASjF,cAAc,IAAI,MAAM;CAGzB"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fse from "fs-extra";
|
|
4
|
+
import { PathResolver } from "../config/PathResolver.js";
|
|
5
|
+
export class LibraryScanner {
|
|
6
|
+
libraryPath;
|
|
7
|
+
constructor(libraryPath) {
|
|
8
|
+
this.libraryPath = libraryPath ?? PathResolver.getLibraryPath();
|
|
9
|
+
}
|
|
10
|
+
async exists() {
|
|
11
|
+
return fse.pathExists(this.libraryPath);
|
|
12
|
+
}
|
|
13
|
+
async getDomains() {
|
|
14
|
+
if (!(await this.exists()))
|
|
15
|
+
return [];
|
|
16
|
+
try {
|
|
17
|
+
const entries = await fs.readdir(this.libraryPath, { withFileTypes: true });
|
|
18
|
+
return entries
|
|
19
|
+
.filter((e) => e.isDirectory())
|
|
20
|
+
.map((e) => e.name);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async scanAll(includeContent = false) {
|
|
27
|
+
const domains = await this.getDomains();
|
|
28
|
+
const skills = [];
|
|
29
|
+
for (const domain of domains) {
|
|
30
|
+
const domainPath = path.join(this.libraryPath, domain);
|
|
31
|
+
try {
|
|
32
|
+
const entries = await fs.readdir(domainPath, { withFileTypes: true });
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
35
|
+
const filePath = path.join(domainPath, entry.name);
|
|
36
|
+
const skillName = entry.name.replace(/\.md$/, "");
|
|
37
|
+
let content;
|
|
38
|
+
if (includeContent) {
|
|
39
|
+
content = await fs.readFile(filePath, "utf-8");
|
|
40
|
+
}
|
|
41
|
+
skills.push({ domain, skillName, filePath, content });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// 도메인 폴더 읽기 실패 → 스킵
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return skills;
|
|
50
|
+
}
|
|
51
|
+
async scanDomain(domain, includeContent = false) {
|
|
52
|
+
const domainPath = path.join(this.libraryPath, domain);
|
|
53
|
+
const skills = [];
|
|
54
|
+
try {
|
|
55
|
+
const entries = await fs.readdir(domainPath, { withFileTypes: true });
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
58
|
+
const filePath = path.join(domainPath, entry.name);
|
|
59
|
+
const skillName = entry.name.replace(/\.md$/, "");
|
|
60
|
+
let content;
|
|
61
|
+
if (includeContent) {
|
|
62
|
+
content = await fs.readFile(filePath, "utf-8");
|
|
63
|
+
}
|
|
64
|
+
skills.push({ domain, skillName, filePath, content });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// 도메인 없음
|
|
70
|
+
}
|
|
71
|
+
return skills;
|
|
72
|
+
}
|
|
73
|
+
async getSkillPath(domain, skillName) {
|
|
74
|
+
const fileName = skillName.endsWith(".md") ? skillName : `${skillName}.md`;
|
|
75
|
+
return path.join(this.libraryPath, domain, fileName);
|
|
76
|
+
}
|
|
77
|
+
async skillExists(domain, skillName) {
|
|
78
|
+
const skillPath = await this.getSkillPath(domain, skillName);
|
|
79
|
+
return fse.pathExists(skillPath);
|
|
80
|
+
}
|
|
81
|
+
async readSkillContent(domain, skillName) {
|
|
82
|
+
const skillPath = await this.getSkillPath(domain, skillName);
|
|
83
|
+
try {
|
|
84
|
+
return await fs.readFile(skillPath, "utf-8");
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
getLibraryPath() {
|
|
91
|
+
return this.libraryPath;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=LibraryScanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LibraryScanner.js","sourceRoot":"","sources":["../../../src/infrastructure/fs/LibraryScanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface SymlinkResult {
|
|
2
|
+
success: boolean;
|
|
3
|
+
method: "symlink" | "junction" | "copy" | "already_exists";
|
|
4
|
+
error?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class SymlinkManager {
|
|
7
|
+
createSymlink(target: string, linkPath: string): Promise<SymlinkResult>;
|
|
8
|
+
removeSymlink(linkPath: string): Promise<boolean>;
|
|
9
|
+
isValidSymlink(linkPath: string): Promise<boolean>;
|
|
10
|
+
listBrokenLinks(dir: string): Promise<string[]>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=SymlinkManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SymlinkManager.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/fs/SymlinkManager.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,gBAAgB,CAAC;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,cAAc;IACnB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IA+DvE,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAajD,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWlD,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAgBtD"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import fse from "fs-extra";
|
|
3
|
+
import path from "path";
|
|
4
|
+
export class SymlinkManager {
|
|
5
|
+
async createSymlink(target, linkPath) {
|
|
6
|
+
// 링크 디렉토리 생성
|
|
7
|
+
await fse.ensureDir(path.dirname(linkPath));
|
|
8
|
+
// 이미 존재하는 경우 확인
|
|
9
|
+
try {
|
|
10
|
+
const stat = await fs.lstat(linkPath);
|
|
11
|
+
if (stat.isSymbolicLink()) {
|
|
12
|
+
const existing = await fs.readlink(linkPath);
|
|
13
|
+
if (existing === target) {
|
|
14
|
+
return { success: true, method: "already_exists" };
|
|
15
|
+
}
|
|
16
|
+
// 다른 타겟 → 교체
|
|
17
|
+
await fs.unlink(linkPath);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
// 일반 파일/디렉토리 → 건드리지 않음
|
|
21
|
+
return {
|
|
22
|
+
success: false,
|
|
23
|
+
method: "symlink",
|
|
24
|
+
error: `링크 경로에 이미 일반 파일/디렉토리가 존재합니다: ${linkPath}`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// 파일이 없으면 정상 진행
|
|
30
|
+
}
|
|
31
|
+
// 타겟 존재 확인
|
|
32
|
+
const targetExists = await fse.pathExists(target);
|
|
33
|
+
if (!targetExists) {
|
|
34
|
+
return {
|
|
35
|
+
success: false,
|
|
36
|
+
method: "symlink",
|
|
37
|
+
error: `타겟 경로가 존재하지 않습니다: ${target}`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// POSIX 심볼릭 링크 시도
|
|
41
|
+
try {
|
|
42
|
+
await fs.symlink(target, linkPath);
|
|
43
|
+
return { success: true, method: "symlink" };
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// junction 시도 (Windows)
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
await fs.symlink(target, linkPath, "junction");
|
|
50
|
+
return { success: true, method: "junction" };
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// 폴백: 복사
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
await fse.copy(target, linkPath);
|
|
57
|
+
return { success: true, method: "copy" };
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
method: "copy",
|
|
63
|
+
error: String(err),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async removeSymlink(linkPath) {
|
|
68
|
+
try {
|
|
69
|
+
const stat = await fs.lstat(linkPath);
|
|
70
|
+
if (stat.isSymbolicLink()) {
|
|
71
|
+
await fs.unlink(linkPath);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async isValidSymlink(linkPath) {
|
|
81
|
+
try {
|
|
82
|
+
const stat = await fs.lstat(linkPath);
|
|
83
|
+
if (!stat.isSymbolicLink())
|
|
84
|
+
return false;
|
|
85
|
+
const target = await fs.readlink(linkPath);
|
|
86
|
+
return await fse.pathExists(target);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async listBrokenLinks(dir) {
|
|
93
|
+
const broken = [];
|
|
94
|
+
try {
|
|
95
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
96
|
+
for (const entry of entries) {
|
|
97
|
+
const fullPath = path.join(dir, entry.name);
|
|
98
|
+
if (entry.isSymbolicLink()) {
|
|
99
|
+
const valid = await this.isValidSymlink(fullPath);
|
|
100
|
+
if (!valid)
|
|
101
|
+
broken.push(fullPath);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// 디렉토리가 없으면 빈 배열
|
|
107
|
+
}
|
|
108
|
+
return broken;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=SymlinkManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SymlinkManager.js","sourceRoot":"","sources":["../../../src/infrastructure/fs/SymlinkManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 기술 스택 키워드 → skills.sh 스킬 레퍼런스 매핑
|
|
3
|
+
* 형식: "owner/repo@skill-name"
|
|
4
|
+
*/
|
|
5
|
+
export interface SkillRef {
|
|
6
|
+
ref: string;
|
|
7
|
+
domain: string;
|
|
8
|
+
description: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const STACK_TO_SKILL_REF: Record<string, SkillRef>;
|
|
11
|
+
/**
|
|
12
|
+
* 항상 설치하는 메타 스킬 (스킬 탐색 및 생성 지원)
|
|
13
|
+
*/
|
|
14
|
+
export declare const META_SKILLS: SkillRef[];
|
|
15
|
+
//# sourceMappingURL=SkillsRegistry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SkillsRegistry.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/skills/SkillsRegistry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAwFvD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,QAAQ,EAWjC,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export const STACK_TO_SKILL_REF = {
|
|
2
|
+
// Frontend
|
|
3
|
+
next: {
|
|
4
|
+
ref: "vercel-labs/next-skills@next-best-practices",
|
|
5
|
+
domain: "frontend",
|
|
6
|
+
description: "Next.js 모범 사례",
|
|
7
|
+
},
|
|
8
|
+
nextjs: {
|
|
9
|
+
ref: "vercel-labs/next-skills@next-best-practices",
|
|
10
|
+
domain: "frontend",
|
|
11
|
+
description: "Next.js 모범 사례",
|
|
12
|
+
},
|
|
13
|
+
react: {
|
|
14
|
+
ref: "vercel-labs/agent-skills@vercel-react-best-practices",
|
|
15
|
+
domain: "frontend",
|
|
16
|
+
description: "React 모범 사례",
|
|
17
|
+
},
|
|
18
|
+
"react-native": {
|
|
19
|
+
ref: "callstackincubator/agent-skills@react-native-best-practices",
|
|
20
|
+
domain: "frontend",
|
|
21
|
+
description: "React Native 모범 사례",
|
|
22
|
+
},
|
|
23
|
+
vue: {
|
|
24
|
+
ref: "antfu/skills@vue-testing-best-practices",
|
|
25
|
+
domain: "frontend",
|
|
26
|
+
description: "Vue 테스트 모범 사례",
|
|
27
|
+
},
|
|
28
|
+
// Testing
|
|
29
|
+
vitest: {
|
|
30
|
+
ref: "antfu/skills@vitest",
|
|
31
|
+
domain: "testing",
|
|
32
|
+
description: "Vitest 단위 테스트",
|
|
33
|
+
},
|
|
34
|
+
jest: {
|
|
35
|
+
ref: "wshobson/agents@javascript-testing-patterns",
|
|
36
|
+
domain: "testing",
|
|
37
|
+
description: "JavaScript 테스트 패턴",
|
|
38
|
+
},
|
|
39
|
+
playwright: {
|
|
40
|
+
ref: "anthropics/skills@webapp-testing",
|
|
41
|
+
domain: "testing",
|
|
42
|
+
description: "Playwright 웹 앱 테스팅",
|
|
43
|
+
},
|
|
44
|
+
cypress: {
|
|
45
|
+
ref: "wshobson/agents@e2e-testing-patterns",
|
|
46
|
+
domain: "testing",
|
|
47
|
+
description: "E2E 테스트 패턴",
|
|
48
|
+
},
|
|
49
|
+
// Backend
|
|
50
|
+
prisma: {
|
|
51
|
+
ref: "sickn33/antigravity-awesome-skills@prisma-expert",
|
|
52
|
+
domain: "backend",
|
|
53
|
+
description: "Prisma ORM 전문가",
|
|
54
|
+
},
|
|
55
|
+
express: {
|
|
56
|
+
ref: "wshobson/agents@nodejs-backend-patterns",
|
|
57
|
+
domain: "backend",
|
|
58
|
+
description: "Node.js 백엔드 패턴",
|
|
59
|
+
},
|
|
60
|
+
fastify: {
|
|
61
|
+
ref: "wshobson/agents@nodejs-backend-patterns",
|
|
62
|
+
domain: "backend",
|
|
63
|
+
description: "Node.js 백엔드 패턴",
|
|
64
|
+
},
|
|
65
|
+
graphql: {
|
|
66
|
+
ref: "wshobson/agents@nodejs-backend-patterns",
|
|
67
|
+
domain: "backend",
|
|
68
|
+
description: "Node.js 백엔드 패턴",
|
|
69
|
+
},
|
|
70
|
+
// DevOps
|
|
71
|
+
docker: {
|
|
72
|
+
ref: "sickn33/antigravity-awesome-skills@docker-expert",
|
|
73
|
+
domain: "devops",
|
|
74
|
+
description: "Docker 전문가",
|
|
75
|
+
},
|
|
76
|
+
kubernetes: {
|
|
77
|
+
ref: "wshobson/agents@k8s-manifest-generator",
|
|
78
|
+
domain: "devops",
|
|
79
|
+
description: "Kubernetes 매니페스트 생성",
|
|
80
|
+
},
|
|
81
|
+
"github-actions": {
|
|
82
|
+
ref: "wshobson/agents@github-actions-templates",
|
|
83
|
+
domain: "devops",
|
|
84
|
+
description: "GitHub Actions 템플릿",
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* 항상 설치하는 메타 스킬 (스킬 탐색 및 생성 지원)
|
|
89
|
+
*/
|
|
90
|
+
export const META_SKILLS = [
|
|
91
|
+
{
|
|
92
|
+
ref: "vercel-labs/skills@find-skills",
|
|
93
|
+
domain: "meta",
|
|
94
|
+
description: "skills.sh에서 스킬 탐색 및 설치",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
ref: "anthropics/skills@skill-creator",
|
|
98
|
+
domain: "meta",
|
|
99
|
+
description: "새로운 스킬 생성 가이드",
|
|
100
|
+
},
|
|
101
|
+
];
|
|
102
|
+
//# sourceMappingURL=SkillsRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SkillsRegistry.js","sourceRoot":"","sources":["../../../src/infrastructure/skills/SkillsRegistry.ts"],"names":[],"mappings":"AAUA,MAAM,CAAC,MAAM,kBAAkB,GAA6B;IAC1D,WAAW;IACX,IAAI,EAAE;QACJ,GAAG,EAAE,6CAA6C;QAClD,MAAM,EAAE,UAAU;QAClB,WAAW,EAAE,eAAe;KAC7B;IACD,MAAM,EAAE;QACN,GAAG,EAAE,6CAA6C;QAClD,MAAM,EAAE,UAAU;QAClB,WAAW,EAAE,eAAe;KAC7B;IACD,KAAK,EAAE;QACL,GAAG,EAAE,sDAAsD;QAC3D,MAAM,EAAE,UAAU;QAClB,WAAW,EAAE,aAAa;KAC3B;IACD,cAAc,EAAE;QACd,GAAG,EAAE,6DAA6D;QAClE,MAAM,EAAE,UAAU;QAClB,WAAW,EAAE,oBAAoB;KAClC;IACD,GAAG,EAAE;QACH,GAAG,EAAE,yCAAyC;QAC9C,MAAM,EAAE,UAAU;QAClB,WAAW,EAAE,eAAe;KAC7B;IAED,UAAU;IACV,MAAM,EAAE;QACN,GAAG,EAAE,qBAAqB;QAC1B,MAAM,EAAE,SAAS;QACjB,WAAW,EAAE,eAAe;KAC7B;IACD,IAAI,EAAE;QACJ,GAAG,EAAE,6CAA6C;QAClD,MAAM,EAAE,SAAS;QACjB,WAAW,EAAE,mBAAmB;KACjC;IACD,UAAU,EAAE;QACV,GAAG,EAAE,kCAAkC;QACvC,MAAM,EAAE,SAAS;QACjB,WAAW,EAAE,oBAAoB;KAClC;IACD,OAAO,EAAE;QACP,GAAG,EAAE,sCAAsC;QAC3C,MAAM,EAAE,SAAS;QACjB,WAAW,EAAE,YAAY;KAC1B;IAED,UAAU;IACV,MAAM,EAAE;QACN,GAAG,EAAE,kDAAkD;QACvD,MAAM,EAAE,SAAS;QACjB,WAAW,EAAE,gBAAgB;KAC9B;IACD,OAAO,EAAE;QACP,GAAG,EAAE,yCAAyC;QAC9C,MAAM,EAAE,SAAS;QACjB,WAAW,EAAE,gBAAgB;KAC9B;IACD,OAAO,EAAE;QACP,GAAG,EAAE,yCAAyC;QAC9C,MAAM,EAAE,SAAS;QACjB,WAAW,EAAE,gBAAgB;KAC9B;IACD,OAAO,EAAE;QACP,GAAG,EAAE,yCAAyC;QAC9C,MAAM,EAAE,SAAS;QACjB,WAAW,EAAE,gBAAgB;KAC9B;IAED,SAAS;IACT,MAAM,EAAE;QACN,GAAG,EAAE,kDAAkD;QACvD,MAAM,EAAE,QAAQ;QAChB,WAAW,EAAE,YAAY;KAC1B;IACD,UAAU,EAAE;QACV,GAAG,EAAE,wCAAwC;QAC7C,MAAM,EAAE,QAAQ;QAChB,WAAW,EAAE,qBAAqB;KACnC;IACD,gBAAgB,EAAE;QAChB,GAAG,EAAE,0CAA0C;QAC/C,MAAM,EAAE,QAAQ;QAChB,WAAW,EAAE,oBAAoB;KAClC;CACF,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAe;IACrC;QACE,GAAG,EAAE,gCAAgC;QACrC,MAAM,EAAE,MAAM;QACd,WAAW,EAAE,wBAAwB;KACtC;IACD;QACE,GAAG,EAAE,iCAAiC;QACtC,MAAM,EAAE,MAAM;QACd,WAAW,EAAE,eAAe;KAC7B;CACF,CAAC"}
|