@qualisoft/ai-skills 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 (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +234 -0
  3. package/bin/cli.mjs +331 -0
  4. package/package.json +53 -0
  5. package/skills/erd-visual/SKILL.md +294 -0
  6. package/skills/erd-visual/build.mjs +404 -0
  7. package/skills/erd-visual/engine/dbml.mjs +133 -0
  8. package/skills/erd-visual/engine/ingest.mjs +273 -0
  9. package/skills/erd-visual/engine/layout.mjs +165 -0
  10. package/skills/erd-visual/engine/overview.mjs +314 -0
  11. package/skills/erd-visual/engine/render.mjs +144 -0
  12. package/skills/erd-visual/engine/router.mjs +401 -0
  13. package/skills/erd-visual/engine/verify.mjs +138 -0
  14. package/skills/erd-visual/engine/wire.mjs +115 -0
  15. package/skills/erd-visual/fixtures/crm-large.dbml +1337 -0
  16. package/skills/erd-visual/fixtures/edge-cases.dbml +43 -0
  17. package/skills/erd-visual/fixtures/map-dynamics.json +32 -0
  18. package/skills/erd-visual/fixtures/shop-basic.dbml +78 -0
  19. package/skills/erd-visual/fixtures/src-json/api.json +38 -0
  20. package/skills/erd-visual/fixtures/src-prisma/schema.prisma +44 -0
  21. package/skills/erd-visual/fixtures/src-sql/shop.sql +62 -0
  22. package/skills/erd-visual/readers/csv.mjs +81 -0
  23. package/skills/erd-visual/readers/index.mjs +55 -0
  24. package/skills/erd-visual/readers/jsonschema.mjs +86 -0
  25. package/skills/erd-visual/readers/prisma.mjs +102 -0
  26. package/skills/erd-visual/readers/sql.mjs +205 -0
  27. package/skills/erd-visual/readers/xlsx.mjs +220 -0
  28. package/skills/erd-visual/readers/xml.mjs +130 -0
  29. package/skills/erd-visual/readers/zip.mjs +74 -0
  30. package/skills/erd-visual/viewer/index.html +539 -0
  31. package/skills/project-build/SKILL.md +171 -0
  32. package/skills/project-build/templates/build-rules.md +88 -0
  33. package/skills/project-build/templates/change-log.md +29 -0
  34. package/skills/project-build/templates/coverage.md +49 -0
  35. package/skills/project-build/templates/parity.html +375 -0
  36. package/skills/project-build/templates/theme.css +45 -0
  37. package/skills/project-design/MEDIUMS.md +104 -0
  38. package/skills/project-design/QUESTIONS.md +126 -0
  39. package/skills/project-design/SKILL.md +365 -0
  40. package/skills/project-design/templates/audit.html +689 -0
  41. package/skills/project-design/templates/change-log.md +53 -0
  42. package/skills/project-design/templates/design-rules.md +118 -0
  43. package/skills/project-design/templates/mockup-app.html +96 -0
  44. package/skills/project-design/templates/mockup-slides.html +82 -0
  45. package/skills/project-design/templates/mockup-web.html +45 -0
  46. package/skills/project-design/templates/styleguide.html +436 -0
  47. package/skills/project-design/templates/tokens.css +69 -0
  48. package/skills/project-design/templates/tone-options.html +138 -0
  49. package/skills/project-init/GUIDE.md +370 -0
  50. package/skills/project-init/RUNBOOK.md +192 -0
  51. package/skills/project-init/SKILL.md +382 -0
  52. package/skills/project-init/build.mjs +2898 -0
  53. package/skills/project-init/evals/RUBRIC.md +81 -0
  54. package/skills/project-init/evals/cases/conflicting.expect.json +17 -0
  55. package/skills/project-init/evals/cases/conflicting.md +9 -0
  56. package/skills/project-init/evals/cases/vague-idea.expect.json +11 -0
  57. package/skills/project-init/evals/cases/vague-idea.md +7 -0
  58. package/skills/project-init/evals/cases/well-formed.expect.json +18 -0
  59. package/skills/project-init/evals/cases/well-formed.md +28 -0
  60. package/skills/project-init/markdown.mjs +0 -0
  61. package/skills/project-init/modules/a11y.json +46 -0
  62. package/skills/project-init/modules/ai.json +77 -0
  63. package/skills/project-init/modules/audience.json +48 -0
  64. package/skills/project-init/modules/backend.json +129 -0
  65. package/skills/project-init/modules/brand.json +78 -0
  66. package/skills/project-init/modules/core.json +132 -0
  67. package/skills/project-init/modules/design.json +72 -0
  68. package/skills/project-init/modules/engineering.json +86 -0
  69. package/skills/project-init/modules/mobile.json +22 -0
  70. package/skills/project-init/modules/ops.json +122 -0
  71. package/skills/project-init/modules/process.json +104 -0
  72. package/skills/project-init/modules/product.json +37 -0
  73. package/skills/project-init/modules/ux.json +52 -0
  74. package/skills/project-init/modules/web.json +129 -0
  75. package/skills/project-init/presets/ai-product.json +12 -0
  76. package/skills/project-init/presets/internal-system.json +15 -0
  77. package/skills/project-init/presets/mobile-app.json +20 -0
  78. package/skills/project-init/presets/web-corporate.json +116 -0
  79. package/skills/project-init/schema.json +80 -0
  80. package/skills/project-init/templates/log.md +54 -0
  81. package/skills/project-init/templates/readme.md +62 -0
  82. package/skills/project-init/templates/reference.md +34 -0
  83. package/skills/project-init/templates/register.md +63 -0
  84. package/skills/project-init/templates/spec.md +46 -0
  85. package/skills/project-interview/INTERVIEW.md +221 -0
  86. package/skills/project-interview/SKILL.md +156 -0
  87. package/skills/project-interview/fixtures/brief.md +49 -0
  88. package/skills/project-interview/fixtures/decisions.md +13 -0
  89. package/skills/project-interview/fixtures/open-questions.md +9 -0
  90. package/skills/project-interview/templates/brief.md +179 -0
  91. package/skills/project-interview/templates/decisions.md +55 -0
  92. package/skills/project-interview/templates/open-questions.md +40 -0
  93. package/skills/project-interview/validate.mjs +49 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Qualisoft
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,234 @@
1
+ # @qualisoft/ai-skills
2
+
3
+ **기획 인터뷰 → 문서 체계 → 디자인 시안 → 구현.** 프로젝트 한 건을 끝까지 끌고 가는 AI 에이전트 스킬 5종입니다.
4
+
5
+ Claude Code · Codex · Gemini CLI · Cursor 어디서든 같은 스킬을 씁니다. **외부 의존성이 없습니다** — 마크다운과 Node 내장 모듈만 사용합니다.
6
+
7
+ ```bash
8
+ npx @qualisoft/ai-skills install
9
+ ```
10
+
11
+ 설치된 AI 도구를 찾아 스킬을 연결합니다. 그 다음 에이전트에게 평소처럼 말하면 됩니다.
12
+
13
+ > "이런 서비스를 만들고 싶은데 기획안부터 정리해줘"
14
+
15
+ ---
16
+
17
+ ## 무엇이 들어 있나
18
+
19
+ 스킬은 **순서대로 물립니다.** 앞 단계의 산출물이 뒤 단계의 입력이 됩니다.
20
+
21
+ | | 스킬 | 하는 일 | 산출물 |
22
+ | :---: | --- | --- | --- |
23
+ | 1 | **`project-interview`** | 아이디어만 있는 상태에서 **선택형 인터뷰**로 기획안을 만든다 | `Docs/_intake/*.md` 기획안, 결정·미결 기록 |
24
+ | 2 | **`project-init`** | 기획안을 흡수해 **산출물 정의서 · 요구사항 · WBS** 세 축을 세우고 공정별 정의서를 만든다. 검증기가 표준을 강제한다 | `Docs/*.md` + 단일 `index.html` |
25
+ | 3 | **`project-design`** | 설계도를 근거로 디자인 규칙과 시안을 만든다. 웹 · 앱 · 어드민 · PPT · 인쇄물 | `Docs/_design/` 토큰 · 시안 · 스타일 가이드 |
26
+ | 4 | **`project-build`** | 시안을 실제 코드로 옮긴다. **시안 ↔ 구현 대조기**로 어긋남을 판정한다 | `src/`, `Docs/_build/parity.html` |
27
+ | — | **`erd-visual`** | Excel · CSV · DDL · XML · Prisma · JSON Schema를 읽어 **시각 ERD**를 만든다. 독립적으로 쓴다 | 서버 없이 열리는 ERD HTML |
28
+
29
+ `erd-visual`만 독립이고, 1→4는 이어서 씁니다. 물론 2번만 써도 됩니다.
30
+
31
+ ---
32
+
33
+ ## 설치
34
+
35
+ ### 한 번에
36
+
37
+ ```bash
38
+ npx @qualisoft/ai-skills install
39
+ ```
40
+
41
+ `~/.claude`, `~/.codex` 같은 설정 디렉터리가 **실제로 있는 도구만** 골라 설치합니다.
42
+
43
+ ### 도구를 지정해서
44
+
45
+ ```bash
46
+ npx @qualisoft/ai-skills install --tool=claude,codex
47
+ npx @qualisoft/ai-skills install --tool=all
48
+ ```
49
+
50
+ | id | 도구 | 설치 방식 |
51
+ | --- | --- | --- |
52
+ | `claude` | Claude Code | `~/.claude/skills/` — 네이티브 |
53
+ | `codex` | Codex CLI | `~/.codex/skills/` — 네이티브 |
54
+ | `gemini` | Gemini CLI | 공용 위치 + `~/.gemini/GEMINI.md` 에 목록 주입 |
55
+ | `cursor` | Cursor | 공용 위치 + `~/.cursor/rules/` 에 목록 주입 |
56
+ | `agents` | 범용 | 공용 위치 + 현재 폴더 `AGENTS.md` 에 목록 주입 |
57
+
58
+ ### 그 밖의 명령
59
+
60
+ ```bash
61
+ npx @qualisoft/ai-skills status # 어디에 무엇이 설치됐는지
62
+ npx @qualisoft/ai-skills list # 스킬 설명 전문
63
+ npx @qualisoft/ai-skills install --dry-run # 바뀔 것만 미리 본다
64
+ npx @qualisoft/ai-skills install --copy # 링크 대신 복사 (Windows 기본)
65
+ npx @qualisoft/ai-skills install --force # 같은 이름 디렉터리를 덮어쓴다
66
+ npx @qualisoft/ai-skills uninstall
67
+ ```
68
+
69
+ **같은 이름의 스킬이 이미 있으면 건너뜁니다.** 직접 고쳐 쓰던 스킬을 지우지 않기 위해서입니다. 바꿀 생각이면 백업하고 `--force`를 붙이세요.
70
+
71
+ ---
72
+
73
+ ## 어떻게 세 도구에서 다 되나
74
+
75
+ 스킬의 실체는 **마크다운 지시문 + Node 스크립트**입니다. 도구마다 다른 것은 실행 방식이 아니라 **스킬을 발견하는 방식**뿐입니다. 그래서 설치기가 하는 일도 그 하나입니다.
76
+
77
+ ```
78
+ Claude Code · Codex 스킬 디렉터리를 직접 읽는다 → 심볼릭 링크만 걸면 끝
79
+ ~/.claude/skills/<이름>/SKILL.md
80
+
81
+ Gemini · Cursor · 그 외 스킬 개념이 없다 → 공용 위치에 두고
82
+ ~/.ai-skills/qualisoft/ 지시문 파일에 목록을 적는다
83
+ + GEMINI.md / AGENTS.md
84
+ ```
85
+
86
+ 주입되는 블록은 표식으로 감싸여 있어(`<!-- qualisoft-ai-skills:begin -->`) **기존 지시문을 건드리지 않고** 그 부분만 갈아끼웁니다. `uninstall`은 블록만 빼고 나머지는 남깁니다.
87
+
88
+ > **경로 정확도** — `claude` · `codex` · `agents`는 실제로 확인한 경로입니다. `gemini` · `cursor`는 각 도구 문서를 근거로 넣었고 버전에 따라 다를 수 있습니다. 어긋나면 `bin/cli.mjs` 상단 `TARGETS` 한 곳만 고치면 됩니다.
89
+
90
+ ---
91
+
92
+ ## 무엇이 다른가
93
+
94
+ ### 1. 검증기가 표준을 강제한다
95
+
96
+ `project-init`은 문서를 만들고 나서 검사합니다. **검사에 실패하면 `index.html`을 만들지 않습니다.**
97
+
98
+ ```
99
+ ✗ 04-project-brief.md
100
+ 필수 섹션 누락: 한 줄로 말하면
101
+ 1건 실패 — 빌드 중단
102
+ ```
103
+
104
+ 프론트매터 누락, 깨진 `[[링크]]`, 정의되지 않은 추적 ID, 문서 종류별 필수 섹션 — 지침이 아니라 규칙입니다. 지침은 지켜지지 않습니다.
105
+
106
+ ### 2. 산출물이 없는 과업은 WBS에 올리지 않는다
107
+
108
+ 세 축이 서로를 가리킵니다.
109
+
110
+ ```
111
+ 요구사항 (R-01) ──연계 과업──▶ WBS 과업 (W-01)
112
+ │ │
113
+ └──────연계 산출물──────▶ 산출물 (DLV-01)
114
+ ```
115
+
116
+ 과업을 만들 때 "이걸 하면 무엇이 남는가"를 먼저 묻습니다. 안 남으면 과업이 아닙니다. **이 규칙 하나가 과업 폭증을 막습니다** — 과업 수는 산출물 수를 넘을 수 없습니다.
117
+
118
+ 공정은 **상시 + 5단계**입니다. 대형 SI의 9공정이 여기로 접힙니다.
119
+
120
+ ```
121
+ 00. 관제탑 · 01. 상시 · 사업관리
122
+ 10. 착수·분석 → 20. 설계 → 30. 구축 → 40. 검증 → 50. 오픈·안정화
123
+ ```
124
+
125
+ ### 3. 프로젝트 크기만큼만 문서가 나온다
126
+
127
+ 세 축(`scale` · `risk` · `delivery`)으로 문서 개수가 정해집니다. 2주짜리 랜딩과 7개월짜리 시스템이 같은 문서 세트를 받지 않습니다.
128
+
129
+ ```
130
+ solo 9개 혼자, 몇 주
131
+ small 15개 1~2명, 1~2개월
132
+ large 19~21개 다인원, 6개월 이상
133
+ ```
134
+
135
+ ### 4. 고객이 읽을 수 있게 쓴다 (ELI5)
136
+
137
+ 모든 정의서는 `## 한 줄로 말하면` 으로 시작합니다. 없으면 빌드가 막힙니다.
138
+
139
+ ```markdown
140
+ ## 한 줄로 말하면
141
+
142
+ > 계약 내용이 바뀌면 담당자 팀즈로 자동 메시지가 갑니다.
143
+
144
+ **왜 필요한가** — 지금은 직접 들어가 봐야 변경을 압니다.
145
+ 모르고 지나가면 잘못된 조건으로 영업이 나갑니다.
146
+
147
+ **비유하자면** — 택배 배송 알림과 같습니다.
148
+ ```
149
+
150
+ ### 5. 상용 라이브러리를 쓰지 않는다
151
+
152
+ `npm ls`가 비어 있습니다. `fs` · `path` · `url` · `zlib` 만 씁니다. 설치 시간도, 취약점 알림도, 라이선스 검토도 없습니다.
153
+
154
+ ---
155
+
156
+ ## 쓰는 법
157
+
158
+ 설치 후에는 평소처럼 말하면 됩니다. 각 스킬의 `description`에 트리거 문구가 들어 있어 에이전트가 알아서 고릅니다.
159
+
160
+ | 이렇게 말하면 | 이 스킬이 붙는다 |
161
+ | --- | --- |
162
+ | "아이디어가 있는데 정리해줘", "기획안 만들어줘" | `project-interview` |
163
+ | "PRD 작성", "산출물 정의서", "WBS 정리", "Docs 빌드" | `project-init` |
164
+ | "디자인 시안 뽑아줘", "이 레퍼런스처럼", "PPT 만들어줘" | `project-design` |
165
+ | "구현 시작", "시안대로 만들어줘", "스캐폴딩" | `project-build` |
166
+ | "ERD 그려줘", "이 엑셀로 DB 구조 시각화" | `erd-visual` |
167
+
168
+ Claude Code · Codex 에서는 `/project-init` 처럼 직접 호출할 수도 있습니다.
169
+
170
+ ### 전체 흐름 예시
171
+
172
+ ```bash
173
+ # 1. 아이디어만 있는 상태
174
+ "사내 계약관리 시스템을 만들려는데 기획안부터 정리해줘"
175
+ → Docs/_intake/2026-09-11-contract-system.md
176
+
177
+ # 2. 문서 체계
178
+ "이 기획안 흡수해서 문서 만들어줘"
179
+ → Docs/*.md + index.html (산출물 정의서 · 요구사항 · WBS 포함)
180
+
181
+ # 3. 시안
182
+ "어드민 화면 시안 만들어줘. Linear 같은 톤으로"
183
+ → Docs/_design/ 토큰 · 시안 · 스타일 가이드
184
+
185
+ # 4. 구현
186
+ "시안대로 구현 시작해줘"
187
+ → src/ + Docs/_build/parity.html (시안 ↔ 구현 대조)
188
+ ```
189
+
190
+ `Docs/index.html`을 브라우저로 열면 1~4단계 진행률이 한 화면에 나옵니다.
191
+
192
+ ---
193
+
194
+ ## 직접 고쳐 쓰기
195
+
196
+ 설치는 **심볼릭 링크**입니다. 저장소의 `skills/`를 고치면 모든 도구에 즉시 반영됩니다.
197
+
198
+ ```bash
199
+ git clone https://github.com/qualisoft-service/project-ai-skills
200
+ cd project-ai-skills
201
+ node bin/cli.mjs install --tool=all --force
202
+
203
+ # skills/project-init/modules/*.json 을 고치면 곧바로 적용된다
204
+ node skills/project-init/build.mjs selftest
205
+ ```
206
+
207
+ `project-init`의 확장 지점은 셋입니다.
208
+
209
+ | 무엇을 바꾸나 | 어디를 고치나 |
210
+ | --- | --- |
211
+ | 문서 묶음 추가·병합 | `skills/project-init/modules/*.json` |
212
+ | 자주 쓰는 조합 고정 | `skills/project-init/presets/*.json` |
213
+ | 검증 규칙 (필수 섹션 등) | `skills/project-init/schema.json` |
214
+
215
+ **프리셋을 먼저 만들지 마세요.** 모듈 조합으로 되는지 본 다음, 자주 쓰는 조합만 프리셋으로 굳힙니다.
216
+
217
+ ---
218
+
219
+ ## 요구 사항
220
+
221
+ - **Node.js 18 이상** (`node -v`로 확인)
222
+ - macOS · Linux · Windows. Windows는 링크 대신 복사로 자동 전환됩니다
223
+
224
+ ## 저장소 · 문의
225
+
226
+ - 소스 — <https://github.com/qualisoft-service/project-ai-skills>
227
+ - 버그·제안 — <https://github.com/qualisoft-service/project-ai-skills/issues>
228
+
229
+ 경로가 맞지 않는 도구를 발견하면 이슈로 알려주세요. `bin/cli.mjs` 의 `TARGETS`
230
+ 한 곳만 고치면 됩니다.
231
+
232
+ ## 라이선스
233
+
234
+ MIT
package/bin/cli.mjs ADDED
@@ -0,0 +1,331 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @qualisoft/ai-skills — 스킬을 각 AI 도구가 찾는 자리에 놓는다.
4
+ *
5
+ * 스킬 자체는 마크다운과 node 내장 모듈만 쓰는 스크립트다. 따라서 도구마다
6
+ * 다른 것은 **실행 방식이 아니라 발견 방식**뿐이다. 이 CLI 가 하는 일도
7
+ * 그 하나다 — 도구가 보는 디렉터리에 링크를 걸거나, 스킬 목록을 지시문에 적는다.
8
+ */
9
+ import {
10
+ existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync,
11
+ lstatSync, rmSync, symlinkSync, cpSync, realpathSync,
12
+ } from "node:fs";
13
+ import { homedir, platform } from "node:os";
14
+ import { join, dirname, resolve } from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+
17
+ const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
18
+ const SKILLS_DIR = join(PKG_ROOT, "skills");
19
+ const HOME = homedir();
20
+
21
+ /* ── 설치 대상 ──────────────────────────────────────────────────────
22
+ * mode: "native" — 도구가 스킬 디렉터리를 직접 읽는다. 링크만 걸면 끝.
23
+ * mode: "inject" — 스킬 개념이 없는 도구. 공용 위치에 두고 지시문에 목록을 적는다.
24
+ *
25
+ * 경로가 바뀌면 여기만 고친다. verified 는 실제로 확인한 경로인지를 뜻한다. */
26
+ const TARGETS = [
27
+ {
28
+ id: "claude",
29
+ label: "Claude Code",
30
+ mode: "native",
31
+ dir: join(HOME, ".claude", "skills"),
32
+ verified: true,
33
+ },
34
+ {
35
+ id: "codex",
36
+ label: "Codex CLI",
37
+ mode: "native",
38
+ dir: join(HOME, ".codex", "skills"),
39
+ verified: true,
40
+ },
41
+ {
42
+ id: "gemini",
43
+ label: "Gemini CLI",
44
+ mode: "inject",
45
+ dir: join(HOME, ".ai-skills", "qualisoft"),
46
+ instructions: join(HOME, ".gemini", "GEMINI.md"),
47
+ verified: false,
48
+ },
49
+ {
50
+ id: "cursor",
51
+ label: "Cursor",
52
+ mode: "inject",
53
+ dir: join(HOME, ".ai-skills", "qualisoft"),
54
+ instructions: join(HOME, ".cursor", "rules", "qualisoft-ai-skills.mdc"),
55
+ verified: false,
56
+ },
57
+ {
58
+ id: "agents",
59
+ label: "AGENTS.md (범용)",
60
+ mode: "inject",
61
+ dir: join(HOME, ".ai-skills", "qualisoft"),
62
+ instructions: resolve("AGENTS.md"),
63
+ verified: true,
64
+ },
65
+ ];
66
+
67
+ const MARK_BEGIN = "<!-- qualisoft-ai-skills:begin -->";
68
+ const MARK_END = "<!-- qualisoft-ai-skills:end -->";
69
+
70
+ /* ── 스킬 읽기 ─────────────────────────────────────────────────── */
71
+
72
+ /** SKILL.md 프론트매터에서 name 과 description 을 뽑는다. */
73
+ function readSkill(name) {
74
+ const file = join(SKILLS_DIR, name, "SKILL.md");
75
+ if (!existsSync(file)) return null;
76
+ const text = readFileSync(file, "utf8");
77
+ const fm = /^---\n([\s\S]*?)\n---/.exec(text);
78
+ if (!fm) return { name, description: "" };
79
+ const get = (key) => {
80
+ const m = new RegExp("^" + key + ": (.+)$", "m").exec(fm[1]);
81
+ return m ? m[1].trim() : "";
82
+ };
83
+ return { name: get("name") || name, description: get("description") };
84
+ }
85
+
86
+ function allSkills() {
87
+ if (!existsSync(SKILLS_DIR)) return [];
88
+ return readdirSync(SKILLS_DIR, { withFileTypes: true })
89
+ .filter((e) => e.isDirectory())
90
+ .map((e) => readSkill(e.name))
91
+ .filter(Boolean)
92
+ .sort((a, b) => a.name.localeCompare(b.name));
93
+ }
94
+
95
+ /* ── 설치 ─────────────────────────────────────────────────────── */
96
+
97
+ /** 이미 우리 스킬을 가리키는 링크인가. 남의 디렉터리를 지우지 않기 위한 확인. */
98
+ function isOurs(path, src) {
99
+ try {
100
+ return realpathSync(path) === realpathSync(src);
101
+ } catch {
102
+ return false;
103
+ }
104
+ }
105
+
106
+ function place(src, dest, useCopy, force) {
107
+ const st = lstatSync(dest, { throwIfNoEntry: false });
108
+ if (st) {
109
+ if (!useCopy && isOurs(dest, src)) return "이미 연결됨";
110
+ // 남이 놓은 실디렉터리는 지우지 않는다. 사용자의 수정이 들어 있을 수 있다.
111
+ if (st.isDirectory() && !st.isSymbolicLink() && !force) {
112
+ return "건너뜀 — 기존 디렉터리가 있습니다 (--force 로 덮어씀)";
113
+ }
114
+ rmSync(dest, { recursive: true, force: true });
115
+ }
116
+ if (useCopy) {
117
+ cpSync(src, dest, { recursive: true });
118
+ return "복사";
119
+ }
120
+ symlinkSync(src, dest, "dir");
121
+ return "연결";
122
+ }
123
+
124
+ /** 지시문 파일의 표식 사이 블록만 갈아끼운다. 나머지 내용은 건드리지 않는다. */
125
+ function injectInstructions(file, skills, baseDir) {
126
+ const block = [
127
+ MARK_BEGIN,
128
+ "",
129
+ "## Qualisoft 프로젝트 스킬",
130
+ "",
131
+ "설치 위치 — `" + baseDir + "`",
132
+ "각 스킬은 그 아래 `<스킬이름>/SKILL.md` 에 있다.",
133
+ "",
134
+ "요청이 아래 설명과 맞으면 **먼저 해당 `SKILL.md` 를 읽고 그 지시를 따른다.**",
135
+ "스킬은 마크다운과 node 내장 모듈만 쓰므로 추가 설치가 필요 없다.",
136
+ "",
137
+ ...skills.flatMap((s) => [
138
+ "### " + s.name,
139
+ "",
140
+ "`" + join(baseDir, s.name, "SKILL.md") + "`",
141
+ "",
142
+ s.description,
143
+ "",
144
+ ]),
145
+ "**순서가 있다** — `project-interview`(기획안) → `project-init`(문서 체계)",
146
+ "→ `project-design`(시안) → `project-build`(구현).",
147
+ "`erd-visual` 은 독립적으로 쓴다.",
148
+ "",
149
+ MARK_END,
150
+ ].join("\n");
151
+
152
+ mkdirSync(dirname(file), { recursive: true });
153
+ let text = existsSync(file) ? readFileSync(file, "utf8") : "";
154
+ const begin = text.indexOf(MARK_BEGIN);
155
+ const end = text.indexOf(MARK_END);
156
+ if (begin !== -1 && end !== -1) {
157
+ text = text.slice(0, begin) + block + text.slice(end + MARK_END.length);
158
+ } else {
159
+ text = (text.trimEnd() + "\n\n" + block + "\n").trimStart();
160
+ }
161
+ writeFileSync(file, text, "utf8");
162
+ }
163
+
164
+ function install(targets, { useCopy, dryRun, force }) {
165
+ const skills = allSkills();
166
+ const skipped = [];
167
+ if (!skills.length) {
168
+ console.error("skills/ 가 비어 있습니다. 패키지가 손상되었습니다.");
169
+ process.exit(1);
170
+ }
171
+
172
+ for (const t of targets) {
173
+ console.log("\n" + t.label + " → " + t.dir + (t.verified ? "" : " (경로 미확인)"));
174
+ if (dryRun) {
175
+ skills.forEach((s) => console.log(" · " + s.name + " (건너뜀 — dry-run)"));
176
+ continue;
177
+ }
178
+ mkdirSync(t.dir, { recursive: true });
179
+ for (const s of skills) {
180
+ const how = place(join(SKILLS_DIR, s.name), join(t.dir, s.name), useCopy, force);
181
+ if (how.startsWith("건너뜀")) skipped.push(join(t.dir, s.name));
182
+ console.log(" · " + s.name.padEnd(20) + how);
183
+ }
184
+ if (t.mode === "inject") {
185
+ injectInstructions(t.instructions, skills, t.dir);
186
+ console.log(" 지시문: " + t.instructions);
187
+ }
188
+ }
189
+
190
+ console.log("\n완료. 스킬 " + skills.length + "개.");
191
+ if (targets.some((t) => t.mode === "native")) {
192
+ console.log("Claude Code · Codex 는 다음 실행 때 스킬을 인식합니다.");
193
+ }
194
+ if (skipped.length) {
195
+ console.log("\n건너뛴 " + skipped.length + "개 — 같은 이름의 디렉터리가 이미 있습니다:");
196
+ skipped.forEach((p) => console.log(" " + p));
197
+ console.log("직접 만든 스킬이면 그대로 두고, 이 패키지 것으로 바꾸려면 --force 를 붙입니다.");
198
+ console.log("먼저 백업하세요: cp -R <경로> <경로>.bak");
199
+ }
200
+ }
201
+
202
+ /** 지시문에서 우리 블록만 뺀다. 나머지 내용은 그대로 둔다. */
203
+ function removeInstructions(file) {
204
+ if (!existsSync(file)) return false;
205
+ const text = readFileSync(file, "utf8");
206
+ const begin = text.indexOf(MARK_BEGIN);
207
+ const end = text.indexOf(MARK_END);
208
+ if (begin === -1 || end === -1) return false;
209
+ const left = (text.slice(0, begin) + text.slice(end + MARK_END.length)).replace(/\n{3,}/g, "\n\n");
210
+ writeFileSync(file, left.trim() ? left : "", "utf8");
211
+ return true;
212
+ }
213
+
214
+ function uninstall(targets) {
215
+ const skills = allSkills();
216
+ for (const t of targets) {
217
+ console.log("\n" + t.label);
218
+ for (const s of skills) {
219
+ const dest = join(t.dir, s.name);
220
+ if (!lstatSync(dest, { throwIfNoEntry: false })) continue;
221
+ // 우리가 놓은 것만 지운다. 사용자가 직접 만든 디렉터리는 남긴다.
222
+ if (isOurs(dest, join(SKILLS_DIR, s.name))) {
223
+ rmSync(dest, { recursive: true, force: true });
224
+ console.log(" · " + s.name.padEnd(20) + "제거");
225
+ } else {
226
+ console.log(" · " + s.name.padEnd(20) + "건너뜀 — 이 패키지가 놓은 것이 아닙니다");
227
+ }
228
+ }
229
+ // 없는 스킬을 가리키는 지시문을 남기면 에이전트가 빈 경로를 읽으려 한다
230
+ if (t.mode === "inject" && removeInstructions(t.instructions)) {
231
+ console.log(" 지시문 블록 제거: " + t.instructions);
232
+ }
233
+ }
234
+ }
235
+
236
+ function status() {
237
+ const skills = allSkills();
238
+ console.log("패키지: " + PKG_ROOT);
239
+ console.log("스킬 " + skills.length + "개\n");
240
+ const w = Math.max(...skills.map((s) => s.name.length), 8);
241
+ console.log("스킬".padEnd(w) + " " + TARGETS.map((t) => t.id.padEnd(8)).join(""));
242
+ for (const s of skills) {
243
+ const cells = TARGETS.map((t) => {
244
+ const dest = join(t.dir, s.name);
245
+ if (!lstatSync(dest, { throwIfNoEntry: false })) return "-".padEnd(8);
246
+ // link = 이 패키지가 놓은 것, other = 다른 내용이 이미 있음
247
+ return (isOurs(dest, join(SKILLS_DIR, s.name)) ? "link" : "other").padEnd(8);
248
+ });
249
+ console.log(s.name.padEnd(w) + " " + cells.join(""));
250
+ }
251
+ }
252
+
253
+ function list() {
254
+ for (const s of allSkills()) {
255
+ console.log("\n" + s.name);
256
+ console.log(" " + s.description.replace(/(.{1,88})(\s|$)/g, "$1\n ").trimEnd());
257
+ }
258
+ }
259
+
260
+ /* ── 진입점 ───────────────────────────────────────────────────── */
261
+
262
+ const argv = process.argv.slice(2);
263
+ const cmd = argv.find((a) => !a.startsWith("-")) || "help";
264
+ const flag = (name) => argv.includes("--" + name);
265
+ const opt = (name) => {
266
+ const hit = argv.find((a) => a.startsWith("--" + name + "="));
267
+ return hit ? hit.slice(name.length + 3) : null;
268
+ };
269
+
270
+ function pickTargets() {
271
+ const want = opt("tool");
272
+ if (want === "all") return TARGETS;
273
+ if (want) {
274
+ const ids = want.split(",").map((x) => x.trim());
275
+ const picked = TARGETS.filter((t) => ids.includes(t.id));
276
+ const unknown = ids.filter((i) => !TARGETS.some((t) => t.id === i));
277
+ if (unknown.length) {
278
+ console.error("모르는 도구: " + unknown.join(", "));
279
+ console.error("가능한 값: " + TARGETS.map((t) => t.id).join(", ") + ", all");
280
+ process.exit(1);
281
+ }
282
+ return picked;
283
+ }
284
+ // 지정이 없으면 설정 디렉터리가 실제로 있는 도구만 고른다
285
+ const found = TARGETS.filter((t) =>
286
+ t.mode === "native"
287
+ ? existsSync(dirname(t.dir))
288
+ : t.id !== "agents" && existsSync(dirname(t.instructions)),
289
+ );
290
+ if (!found.length) {
291
+ console.error("설치된 AI 도구를 찾지 못했습니다. --tool=<id> 로 직접 지정하세요.");
292
+ console.error("가능한 값: " + TARGETS.map((t) => t.id).join(", ") + ", all");
293
+ process.exit(1);
294
+ }
295
+ return found;
296
+ }
297
+
298
+ switch (cmd) {
299
+ case "install":
300
+ install(pickTargets(), {
301
+ useCopy: flag("copy") || platform() === "win32",
302
+ dryRun: flag("dry-run"),
303
+ force: flag("force"),
304
+ });
305
+ break;
306
+ case "uninstall":
307
+ uninstall(pickTargets());
308
+ break;
309
+ case "status":
310
+ status();
311
+ break;
312
+ case "list":
313
+ list();
314
+ break;
315
+ default:
316
+ console.log(`@qualisoft/ai-skills — 기획부터 구현까지의 프로젝트 스킬 5종
317
+
318
+ 사용법
319
+ npx @qualisoft/ai-skills install 설치된 도구를 찾아 전부 설치
320
+ npx @qualisoft/ai-skills install --tool=claude,codex
321
+ npx @qualisoft/ai-skills install --tool=all
322
+ npx @qualisoft/ai-skills install --copy 링크 대신 복사 (Windows 기본)
323
+ npx @qualisoft/ai-skills install --dry-run 무엇이 바뀔지만 본다
324
+ npx @qualisoft/ai-skills install --force 기존 같은 이름 디렉터리를 덮어씀
325
+ npx @qualisoft/ai-skills status 어디에 무엇이 설치됐는지
326
+ npx @qualisoft/ai-skills list 스킬 목록과 설명
327
+ npx @qualisoft/ai-skills uninstall
328
+
329
+ 도구 id
330
+ ${TARGETS.map((t) => " " + t.id.padEnd(9) + t.label + (t.verified ? "" : " (경로 미확인)")).join("\n")}`);
331
+ }
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@qualisoft/ai-skills",
3
+ "version": "1.0.0",
4
+ "description": "기획 인터뷰부터 문서 체계·디자인 시안·구현까지 이어지는 AI 에이전트 스킬 5종. Claude Code · Codex · Gemini 등 어느 도구에서나 쓴다. 외부 의존성 없음.",
5
+ "keywords": [
6
+ "ai-skills",
7
+ "claude-code",
8
+ "claude",
9
+ "codex",
10
+ "gemini",
11
+ "agent-skills",
12
+ "wbs",
13
+ "prd",
14
+ "erd",
15
+ "dbml",
16
+ "project-management",
17
+ "design-system",
18
+ "korean"
19
+ ],
20
+ "license": "MIT",
21
+ "author": "Qualisoft",
22
+ "homepage": "https://github.com/qualisoft-service/project-ai-skills#readme",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/qualisoft-service/project-ai-skills.git"
26
+ },
27
+ "bugs": {
28
+ "url": "https://github.com/qualisoft-service/project-ai-skills/issues"
29
+ },
30
+ "type": "module",
31
+ "bin": {
32
+ "qualisoft-ai-skills": "bin/cli.mjs"
33
+ },
34
+ "exports": {
35
+ ".": "./bin/cli.mjs",
36
+ "./skills/*": "./skills/*"
37
+ },
38
+ "files": [
39
+ "bin/",
40
+ "skills/",
41
+ "README.md",
42
+ "LICENSE"
43
+ ],
44
+ "engines": {
45
+ "node": ">=18"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "scripts": {
51
+ "test": "node bin/cli.mjs list > /dev/null && node skills/project-init/build.mjs selftest"
52
+ }
53
+ }