oh-my-agent 1.0.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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +119 -0
  3. package/dist/application/DiagnoseProjectUseCase.d.ts +17 -0
  4. package/dist/application/DiagnoseProjectUseCase.d.ts.map +1 -0
  5. package/dist/application/DiagnoseProjectUseCase.js +34 -0
  6. package/dist/application/DiagnoseProjectUseCase.js.map +1 -0
  7. package/dist/application/OrganizeSkillsUseCase.d.ts +24 -0
  8. package/dist/application/OrganizeSkillsUseCase.d.ts.map +1 -0
  9. package/dist/application/OrganizeSkillsUseCase.js +57 -0
  10. package/dist/application/OrganizeSkillsUseCase.js.map +1 -0
  11. package/dist/application/SetupSkillsUseCase.d.ts +25 -0
  12. package/dist/application/SetupSkillsUseCase.d.ts.map +1 -0
  13. package/dist/application/SetupSkillsUseCase.js +58 -0
  14. package/dist/application/SetupSkillsUseCase.js.map +1 -0
  15. package/dist/application/SyncClaudeMdUseCase.d.ts +19 -0
  16. package/dist/application/SyncClaudeMdUseCase.d.ts.map +1 -0
  17. package/dist/application/SyncClaudeMdUseCase.js +62 -0
  18. package/dist/application/SyncClaudeMdUseCase.js.map +1 -0
  19. package/dist/domain/project/Project.d.ts +8 -0
  20. package/dist/domain/project/Project.d.ts.map +1 -0
  21. package/dist/domain/project/Project.js +2 -0
  22. package/dist/domain/project/Project.js.map +1 -0
  23. package/dist/domain/project/ProjectAnalyzer.d.ts +8 -0
  24. package/dist/domain/project/ProjectAnalyzer.d.ts.map +1 -0
  25. package/dist/domain/project/ProjectAnalyzer.js +87 -0
  26. package/dist/domain/project/ProjectAnalyzer.js.map +1 -0
  27. package/dist/domain/skill/Skill.d.ts +12 -0
  28. package/dist/domain/skill/Skill.d.ts.map +1 -0
  29. package/dist/domain/skill/Skill.js +2 -0
  30. package/dist/domain/skill/Skill.js.map +1 -0
  31. package/dist/domain/skill/SkillClassifier.d.ts +12 -0
  32. package/dist/domain/skill/SkillClassifier.d.ts.map +1 -0
  33. package/dist/domain/skill/SkillClassifier.js +60 -0
  34. package/dist/domain/skill/SkillClassifier.js.map +1 -0
  35. package/dist/domain/skill/SkillDomain.d.ts +12 -0
  36. package/dist/domain/skill/SkillDomain.d.ts.map +1 -0
  37. package/dist/domain/skill/SkillDomain.js +142 -0
  38. package/dist/domain/skill/SkillDomain.js.map +1 -0
  39. package/dist/index.d.ts +3 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +125 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/infrastructure/config/PathResolver.d.ts +8 -0
  44. package/dist/infrastructure/config/PathResolver.d.ts.map +1 -0
  45. package/dist/infrastructure/config/PathResolver.js +24 -0
  46. package/dist/infrastructure/config/PathResolver.js.map +1 -0
  47. package/dist/infrastructure/fs/ClaudeMdUpdater.d.ts +21 -0
  48. package/dist/infrastructure/fs/ClaudeMdUpdater.d.ts.map +1 -0
  49. package/dist/infrastructure/fs/ClaudeMdUpdater.js +101 -0
  50. package/dist/infrastructure/fs/ClaudeMdUpdater.js.map +1 -0
  51. package/dist/infrastructure/fs/LibraryScanner.d.ts +19 -0
  52. package/dist/infrastructure/fs/LibraryScanner.d.ts.map +1 -0
  53. package/dist/infrastructure/fs/LibraryScanner.js +94 -0
  54. package/dist/infrastructure/fs/LibraryScanner.js.map +1 -0
  55. package/dist/infrastructure/fs/SymlinkManager.d.ts +12 -0
  56. package/dist/infrastructure/fs/SymlinkManager.d.ts.map +1 -0
  57. package/dist/infrastructure/fs/SymlinkManager.js +111 -0
  58. package/dist/infrastructure/fs/SymlinkManager.js.map +1 -0
  59. package/package.json +55 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tkyoun0421
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # oh-my-agent
2
+
3
+ > AI 스킬을 자동으로 진단·배포·관리하는 MCP 서버
4
+
5
+ 프로젝트의 기술 스택을 분석해 적합한 AI 스킬을 추천하고, `~/oh-my-agent-library`에서 `.claude/skills/`로 심볼릭 링크를 생성해 Claude Code에서 즉시 활용할 수 있게 합니다.
6
+
7
+ ## 설치 및 사용
8
+
9
+ ### Claude Code에 전역 등록
10
+
11
+ ```bash
12
+ claude mcp add oh-my-agent npx -y oh-my-agent
13
+ ```
14
+
15
+ ### 또는 프로젝트별 `.mcp.json` 설정
16
+
17
+ ```json
18
+ {
19
+ "mcpServers": {
20
+ "oh-my-agent": {
21
+ "command": "npx",
22
+ "args": ["-y", "oh-my-agent"]
23
+ }
24
+ }
25
+ }
26
+ ```
27
+
28
+ ## 라이브러리 구조 준비
29
+
30
+ 스킬 파일(.md)을 아래 구조로 배치합니다:
31
+
32
+ ```
33
+ ~/oh-my-agent-library/
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 ...
40
+ ```
41
+
42
+ ## 제공 도구 (MCP Tools)
43
+
44
+ ### `diagnose_project`
45
+
46
+ 프로젝트의 `package.json`을 분석해 기술 스택을 감지하고 추천 스킬 목록을 반환합니다.
47
+
48
+ ```
49
+ 입력: projectPath?, includeDevDeps?
50
+ 출력: { detectedStack, recommendedSkills[], summary }
51
+ ```
52
+
53
+ ### `setup_skills`
54
+
55
+ 라이브러리의 스킬을 프로젝트 `.claude/skills/`에 심볼릭 링크로 배포하고 `CLAUDE.md`를 업데이트합니다.
56
+
57
+ ```
58
+ 입력: projectPath, skills[{domain, skillName}], updateClaudeMd?, dryRun?
59
+ 출력: { results[], claudeMdUpdated, summary }
60
+ ```
61
+
62
+ ### `organize_skills`
63
+
64
+ 라이브러리 내 스킬 파일을 분석해 오분류된 스킬을 감지하고 자동으로 올바른 도메인으로 이동합니다.
65
+
66
+ ```
67
+ 입력: libraryPath?, autoFix?, targetDomain?
68
+ 출력: { misplaced[], correctlyPlaced, autoFixed, summary }
69
+ ```
70
+
71
+ ### `sync_claude_md`
72
+
73
+ `.claude/skills/`를 스캔해 깨진 링크를 감지하고 `CLAUDE.md`의 스킬 섹션을 동기화합니다.
74
+
75
+ ```
76
+ 입력: projectPath, createIfMissing?, templateStyle?
77
+ 출력: { action, installedSkills[], addedToMd[], removedFromMd[], brokenLinks[] }
78
+ ```
79
+
80
+ ## CLAUDE.md 자동 관리
81
+
82
+ `sync_claude_md` 실행 시 아래 마커 구간이 자동으로 생성·업데이트됩니다:
83
+
84
+ ```markdown
85
+ <!-- oh-my-agent:skills:start -->
86
+ ## Active Skills
87
+
88
+ - **frontend/next-best-practices** — Next.js 15 모범 사례
89
+ - **testing/vitest-practices** — Vitest 단위 테스트
90
+
91
+ > 이 섹션은 oh-my-agent가 자동 관리합니다.
92
+ > 마지막 동기화: 2026-02-23T00:00:00.000Z
93
+ <!-- oh-my-agent:skills:end -->
94
+ ```
95
+
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 |
111
+
112
+ ## 요구사항
113
+
114
+ - Node.js 18 이상
115
+ - Claude Code
116
+
117
+ ## 라이선스
118
+
119
+ MIT
@@ -0,0 +1,17 @@
1
+ import type { SkillReference } from "../domain/skill/Skill.js";
2
+ export interface DiagnoseInput {
3
+ projectPath?: string;
4
+ includeDevDeps?: boolean;
5
+ }
6
+ export interface DiagnoseOutput {
7
+ detectedStack: string[];
8
+ recommendedSkills: Array<SkillReference & {
9
+ available: boolean;
10
+ }>;
11
+ summary: string;
12
+ }
13
+ export declare class DiagnoseProjectUseCase {
14
+ private analyzer;
15
+ execute(input: DiagnoseInput): Promise<DiagnoseOutput>;
16
+ }
17
+ //# sourceMappingURL=DiagnoseProjectUseCase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DiagnoseProjectUseCase.d.ts","sourceRoot":"","sources":["../../src/application/DiagnoseProjectUseCase.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE/D,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,iBAAiB,EAAE,KAAK,CAAC,cAAc,GAAG;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAClE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAyB;IAEnC,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;CAqC7D"}
@@ -0,0 +1,34 @@
1
+ import { ProjectAnalyzer } from "../domain/project/ProjectAnalyzer.js";
2
+ import { LibraryScanner } from "../infrastructure/fs/LibraryScanner.js";
3
+ import { PathResolver } from "../infrastructure/config/PathResolver.js";
4
+ export class DiagnoseProjectUseCase {
5
+ analyzer = new ProjectAnalyzer();
6
+ async execute(input) {
7
+ const projectPath = input.projectPath ?? process.cwd();
8
+ const includeDevDeps = input.includeDevDeps ?? true;
9
+ const project = await this.analyzer.analyze(projectPath, includeDevDeps);
10
+ const recommendedSkills = this.analyzer.getRecommendedSkills(project.detectedStack);
11
+ // 라이브러리에서 실제 존재 여부 확인
12
+ const scanner = new LibraryScanner(PathResolver.getLibraryPath());
13
+ const libraryExists = await scanner.exists();
14
+ const skillsWithAvailability = await Promise.all(recommendedSkills.map(async (skill) => ({
15
+ ...skill,
16
+ available: libraryExists
17
+ ? await scanner.skillExists(skill.domain, skill.skillName)
18
+ : false,
19
+ })));
20
+ const stackStr = project.detectedStack.length > 0
21
+ ? project.detectedStack.join(", ")
22
+ : "감지된 스택 없음";
23
+ const availableCount = skillsWithAvailability.filter((s) => s.available).length;
24
+ const summary = `감지된 스택: ${stackStr} | ` +
25
+ `추천 스킬: ${recommendedSkills.length}개 ` +
26
+ `(라이브러리 사용 가능: ${availableCount}개)`;
27
+ return {
28
+ detectedStack: project.detectedStack,
29
+ recommendedSkills: skillsWithAvailability,
30
+ summary,
31
+ };
32
+ }
33
+ }
34
+ //# sourceMappingURL=DiagnoseProjectUseCase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DiagnoseProjectUseCase.js","sourceRoot":"","sources":["../../src/application/DiagnoseProjectUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,0CAA0C,CAAC;AAcxE,MAAM,OAAO,sBAAsB;IACzB,QAAQ,GAAG,IAAI,eAAe,EAAE,CAAC;IAEzC,KAAK,CAAC,OAAO,CAAC,KAAoB;QAChC,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QACvD,MAAM,cAAc,GAAG,KAAK,CAAC,cAAc,IAAI,IAAI,CAAC;QAEpD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;QACzE,MAAM,iBAAiB,GAAG,IAAI,CAAC,QAAQ,CAAC,oBAAoB,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QAEpF,sBAAsB;QACtB,MAAM,OAAO,GAAG,IAAI,cAAc,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC,CAAC;QAClE,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC;QAE7C,MAAM,sBAAsB,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9C,iBAAiB,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;YACtC,GAAG,KAAK;YACR,SAAS,EAAE,aAAa;gBACtB,CAAC,CAAC,MAAM,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,SAAS,CAAC;gBAC1D,CAAC,CAAC,KAAK;SACV,CAAC,CAAC,CACJ,CAAC;QAEF,MAAM,QAAQ,GACZ,OAAO,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC;YAC9B,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;YAClC,CAAC,CAAC,WAAW,CAAC;QAElB,MAAM,cAAc,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC;QAChF,MAAM,OAAO,GACX,WAAW,QAAQ,KAAK;YACxB,UAAU,iBAAiB,CAAC,MAAM,IAAI;YACtC,iBAAiB,cAAc,IAAI,CAAC;QAEtC,OAAO;YACL,aAAa,EAAE,OAAO,CAAC,aAAa;YACpC,iBAAiB,EAAE,sBAAsB;YACzC,OAAO;SACR,CAAC;IACJ,CAAC;CACF"}
@@ -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":"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"}
@@ -0,0 +1,25 @@
1
+ import type { SkillReference } from "../domain/skill/Skill.js";
2
+ export interface SetupSkillsInput {
3
+ projectPath: string;
4
+ skills: SkillReference[];
5
+ updateClaudeMd?: boolean;
6
+ dryRun?: boolean;
7
+ }
8
+ export interface SkillSetupResult {
9
+ domain: string;
10
+ skillName: string;
11
+ success: boolean;
12
+ method?: string;
13
+ error?: string;
14
+ dryRun?: boolean;
15
+ }
16
+ export interface SetupSkillsOutput {
17
+ results: SkillSetupResult[];
18
+ claudeMdUpdated: boolean;
19
+ summary: string;
20
+ }
21
+ export declare class SetupSkillsUseCase {
22
+ private symlinkManager;
23
+ execute(input: SetupSkillsInput): Promise<SetupSkillsOutput>;
24
+ }
25
+ //# sourceMappingURL=SetupSkillsUseCase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SetupSkillsUseCase.d.ts","sourceRoot":"","sources":["../../src/application/SetupSkillsUseCase.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE/D,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,eAAe,EAAE,OAAO,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,cAAc,CAAwB;IAExC,OAAO,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CA8DnE"}
@@ -0,0 +1,58 @@
1
+ import path from "path";
2
+ import { SymlinkManager } from "../infrastructure/fs/SymlinkManager.js";
3
+ import { ClaudeMdUpdater } from "../infrastructure/fs/ClaudeMdUpdater.js";
4
+ import { LibraryScanner } from "../infrastructure/fs/LibraryScanner.js";
5
+ import { PathResolver } from "../infrastructure/config/PathResolver.js";
6
+ export class SetupSkillsUseCase {
7
+ symlinkManager = new SymlinkManager();
8
+ async execute(input) {
9
+ const { projectPath, skills, updateClaudeMd = true, dryRun = false } = input;
10
+ const scanner = new LibraryScanner(PathResolver.getLibraryPath());
11
+ const skillsPath = PathResolver.getSkillsPath(projectPath);
12
+ const results = [];
13
+ for (const skill of skills) {
14
+ const librarySkillPath = await scanner.getSkillPath(skill.domain, skill.skillName);
15
+ const exists = await scanner.skillExists(skill.domain, skill.skillName);
16
+ if (!exists) {
17
+ results.push({
18
+ ...skill,
19
+ success: false,
20
+ error: `라이브러리에 스킬이 없습니다: ${skill.domain}/${skill.skillName}`,
21
+ });
22
+ continue;
23
+ }
24
+ const linkPath = path.join(skillsPath, `${skill.skillName}.md`);
25
+ if (dryRun) {
26
+ results.push({ ...skill, success: true, dryRun: true });
27
+ continue;
28
+ }
29
+ const result = await this.symlinkManager.createSymlink(librarySkillPath, linkPath);
30
+ results.push({
31
+ ...skill,
32
+ success: result.success,
33
+ method: result.method,
34
+ error: result.error,
35
+ });
36
+ }
37
+ let claudeMdUpdated = false;
38
+ if (updateClaudeMd && !dryRun) {
39
+ const successfulSkills = results.filter((r) => r.success);
40
+ if (successfulSkills.length > 0) {
41
+ const updater = new ClaudeMdUpdater(projectPath);
42
+ const updateResult = await updater.update(successfulSkills.map((s) => ({
43
+ domain: s.domain,
44
+ skillName: s.skillName,
45
+ })), true);
46
+ claudeMdUpdated = updateResult.action !== "no_change";
47
+ }
48
+ }
49
+ const successCount = results.filter((r) => r.success).length;
50
+ const failCount = results.filter((r) => !r.success).length;
51
+ const summary = `총 ${results.length}개 스킬 처리 ` +
52
+ `(성공: ${successCount}, 실패: ${failCount})` +
53
+ (dryRun ? " [dry-run]" : "") +
54
+ (claudeMdUpdated ? " | CLAUDE.md 업데이트됨" : "");
55
+ return { results, claudeMdUpdated, summary };
56
+ }
57
+ }
58
+ //# sourceMappingURL=SetupSkillsUseCase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SetupSkillsUseCase.js","sourceRoot":"","sources":["../../src/application/SetupSkillsUseCase.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { InstalledSkill } from "../infrastructure/fs/ClaudeMdUpdater.js";
2
+ export interface SyncClaudeMdInput {
3
+ projectPath: string;
4
+ createIfMissing?: boolean;
5
+ templateStyle?: "minimal" | "full";
6
+ }
7
+ export interface SyncClaudeMdOutput {
8
+ action: string;
9
+ installedSkills: InstalledSkill[];
10
+ addedToMd: string[];
11
+ removedFromMd: string[];
12
+ brokenLinks: string[];
13
+ }
14
+ export declare class SyncClaudeMdUseCase {
15
+ private symlinkManager;
16
+ execute(input: SyncClaudeMdInput): Promise<SyncClaudeMdOutput>;
17
+ private extractDomainFromPath;
18
+ }
19
+ //# sourceMappingURL=SyncClaudeMdUseCase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SyncClaudeMdUseCase.d.ts","sourceRoot":"","sources":["../../src/application/SyncClaudeMdUseCase.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAE9E,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,cAAc,CAAwB;IAExC,OAAO,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA8CpE,OAAO,CAAC,qBAAqB;CAU9B"}
@@ -0,0 +1,62 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ import fse from "fs-extra";
4
+ import { ClaudeMdUpdater } from "../infrastructure/fs/ClaudeMdUpdater.js";
5
+ import { SymlinkManager } from "../infrastructure/fs/SymlinkManager.js";
6
+ import { PathResolver } from "../infrastructure/config/PathResolver.js";
7
+ export class SyncClaudeMdUseCase {
8
+ symlinkManager = new SymlinkManager();
9
+ async execute(input) {
10
+ const { projectPath, createIfMissing = true } = input;
11
+ const skillsPath = PathResolver.getSkillsPath(projectPath);
12
+ // .claude/skills/ 디렉토리 스캔
13
+ const installedSkills = [];
14
+ const brokenLinks = [];
15
+ const skillsDirExists = await fse.pathExists(skillsPath);
16
+ if (skillsDirExists) {
17
+ const entries = await fs.readdir(skillsPath, { withFileTypes: true });
18
+ for (const entry of entries) {
19
+ if (!entry.name.endsWith(".md"))
20
+ continue;
21
+ const fullPath = path.join(skillsPath, entry.name);
22
+ const skillName = entry.name.replace(/\.md$/, "");
23
+ if (entry.isSymbolicLink()) {
24
+ const valid = await this.symlinkManager.isValidSymlink(fullPath);
25
+ if (!valid) {
26
+ brokenLinks.push(fullPath);
27
+ continue;
28
+ }
29
+ // 심볼릭 링크 타겟에서 도메인 추출
30
+ const target = await fs.readlink(fullPath);
31
+ const domain = this.extractDomainFromPath(target);
32
+ installedSkills.push({ domain, skillName });
33
+ }
34
+ else if (entry.isFile()) {
35
+ // 일반 파일도 포함
36
+ installedSkills.push({ domain: "unknown", skillName });
37
+ }
38
+ }
39
+ }
40
+ // CLAUDE.md 업데이트
41
+ const updater = new ClaudeMdUpdater(projectPath);
42
+ const updateResult = await updater.update(installedSkills, createIfMissing);
43
+ return {
44
+ action: updateResult.action,
45
+ installedSkills,
46
+ addedToMd: updateResult.addedToMd,
47
+ removedFromMd: updateResult.removedFromMd,
48
+ brokenLinks,
49
+ };
50
+ }
51
+ extractDomainFromPath(targetPath) {
52
+ // ~/oh-my-agent-library/{domain}/{skillName}.md 형식에서 도메인 추출
53
+ const normalized = targetPath.replace(/\\/g, "/");
54
+ const parts = normalized.split("/");
55
+ const idx = parts.findIndex((p) => p === "oh-my-agent-library");
56
+ if (idx !== -1 && parts[idx + 1]) {
57
+ return parts[idx + 1];
58
+ }
59
+ return "unknown";
60
+ }
61
+ }
62
+ //# sourceMappingURL=SyncClaudeMdUseCase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SyncClaudeMdUseCase.js","sourceRoot":"","sources":["../../src/application/SyncClaudeMdUseCase.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export interface Project {
2
+ projectPath: string;
3
+ name?: string;
4
+ detectedStack: string[];
5
+ dependencies: Record<string, string>;
6
+ devDependencies: Record<string, string>;
7
+ }
8
+ //# sourceMappingURL=Project.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Project.d.ts","sourceRoot":"","sources":["../../../src/domain/project/Project.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,OAAO;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=Project.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Project.js","sourceRoot":"","sources":["../../../src/domain/project/Project.ts"],"names":[],"mappings":""}
@@ -0,0 +1,8 @@
1
+ import type { Project } from "./Project.js";
2
+ import type { SkillReference } from "../skill/Skill.js";
3
+ export declare class ProjectAnalyzer {
4
+ analyze(projectPath: string, includeDevDeps?: boolean): Promise<Project>;
5
+ private detectStack;
6
+ getRecommendedSkills(detectedStack: string[]): SkillReference[];
7
+ }
8
+ //# sourceMappingURL=ProjectAnalyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectAnalyzer.d.ts","sourceRoot":"","sources":["../../../src/domain/project/ProjectAnalyzer.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAQxD,qBAAa,eAAe;IACpB,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,UAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAwB3E,OAAO,CAAC,WAAW;IAmDnB,oBAAoB,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE;CAiBhE"}
@@ -0,0 +1,87 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ import fse from "fs-extra";
4
+ import { STACK_TO_SKILL } from "../skill/SkillDomain.js";
5
+ export class ProjectAnalyzer {
6
+ async analyze(projectPath, includeDevDeps = true) {
7
+ const pkgPath = path.join(projectPath, "package.json");
8
+ let pkg = {};
9
+ if (await fse.pathExists(pkgPath)) {
10
+ const raw = await fs.readFile(pkgPath, "utf-8");
11
+ pkg = JSON.parse(raw);
12
+ }
13
+ const deps = pkg.dependencies ?? {};
14
+ const devDeps = includeDevDeps ? (pkg.devDependencies ?? {}) : {};
15
+ const allDeps = { ...deps, ...devDeps };
16
+ const detectedStack = this.detectStack(allDeps);
17
+ return {
18
+ projectPath,
19
+ name: pkg.name,
20
+ detectedStack,
21
+ dependencies: deps,
22
+ devDependencies: pkg.devDependencies ?? {},
23
+ };
24
+ }
25
+ detectStack(allDeps) {
26
+ const depKeys = Object.keys(allDeps).map((k) => k.toLowerCase());
27
+ const detected = new Set();
28
+ // 직접 패키지 이름 매칭
29
+ const directMatches = {
30
+ next: "next",
31
+ react: "react",
32
+ "react-native": "react-native",
33
+ vue: "vue",
34
+ nuxt: "nuxt",
35
+ svelte: "svelte",
36
+ "@angular/core": "angular",
37
+ vitest: "vitest",
38
+ jest: "jest",
39
+ cypress: "cypress",
40
+ "@playwright/test": "playwright",
41
+ playwright: "playwright",
42
+ prisma: "prisma",
43
+ "@prisma/client": "prisma",
44
+ express: "express",
45
+ fastify: "fastify",
46
+ "@trpc/server": "trpc",
47
+ "@trpc/client": "trpc",
48
+ graphql: "graphql",
49
+ "apollo-server": "graphql",
50
+ jsonwebtoken: "jsonwebtoken",
51
+ jose: "jose",
52
+ docker: "docker",
53
+ };
54
+ for (const [pkg, stack] of Object.entries(directMatches)) {
55
+ if (depKeys.includes(pkg.toLowerCase())) {
56
+ detected.add(stack);
57
+ }
58
+ }
59
+ // 패턴 기반 감지
60
+ if (depKeys.some((k) => k.startsWith("@testing-library/"))) {
61
+ detected.add("react");
62
+ }
63
+ if (depKeys.some((k) => k.includes("jest"))) {
64
+ detected.add("jest");
65
+ }
66
+ if (depKeys.some((k) => k.includes("vitest"))) {
67
+ detected.add("vitest");
68
+ }
69
+ return Array.from(detected);
70
+ }
71
+ getRecommendedSkills(detectedStack) {
72
+ const seen = new Set();
73
+ const skills = [];
74
+ for (const stack of detectedStack) {
75
+ const mapping = STACK_TO_SKILL[stack];
76
+ if (mapping) {
77
+ const key = `${mapping.domain}/${mapping.skillName}`;
78
+ if (!seen.has(key)) {
79
+ seen.add(key);
80
+ skills.push(mapping);
81
+ }
82
+ }
83
+ }
84
+ return skills;
85
+ }
86
+ }
87
+ //# sourceMappingURL=ProjectAnalyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectAnalyzer.js","sourceRoot":"","sources":["../../../src/domain/project/ProjectAnalyzer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export interface Skill {
2
+ domain: string;
3
+ skillName: string;
4
+ filePath: string;
5
+ description?: string;
6
+ confidence?: number;
7
+ }
8
+ export interface SkillReference {
9
+ domain: string;
10
+ skillName: string;
11
+ }
12
+ //# sourceMappingURL=Skill.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=Skill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Skill.js","sourceRoot":"","sources":["../../../src/domain/skill/Skill.ts"],"names":[],"mappings":""}