triflux 3.3.0-dev.7 → 4.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 (91) hide show
  1. package/README.ko.md +108 -199
  2. package/README.md +108 -199
  3. package/bin/triflux.mjs +2415 -1762
  4. package/hooks/keyword-rules.json +361 -354
  5. package/hooks/pipeline-stop.mjs +5 -2
  6. package/hub/assign-callbacks.mjs +136 -136
  7. package/hub/bridge.mjs +734 -708
  8. package/hub/delegator/contracts.mjs +38 -0
  9. package/hub/delegator/index.mjs +14 -0
  10. package/hub/delegator/schema/delegator-tools.schema.json +250 -0
  11. package/hub/delegator/service.mjs +302 -0
  12. package/hub/delegator/tool-definitions.mjs +35 -0
  13. package/hub/hitl.mjs +67 -67
  14. package/hub/paths.mjs +28 -0
  15. package/hub/pipe.mjs +589 -561
  16. package/hub/pipeline/state.mjs +23 -0
  17. package/hub/public/dashboard.html +349 -0
  18. package/hub/public/tray-icon.ico +0 -0
  19. package/hub/public/tray-icon.png +0 -0
  20. package/hub/router.mjs +782 -782
  21. package/hub/schema.sql +40 -40
  22. package/hub/server.mjs +810 -637
  23. package/hub/store.mjs +706 -706
  24. package/hub/team/cli/commands/attach.mjs +37 -0
  25. package/hub/team/cli/commands/control.mjs +43 -0
  26. package/hub/team/cli/commands/debug.mjs +74 -0
  27. package/hub/team/cli/commands/focus.mjs +53 -0
  28. package/hub/team/cli/commands/interrupt.mjs +36 -0
  29. package/hub/team/cli/commands/kill.mjs +37 -0
  30. package/hub/team/cli/commands/list.mjs +24 -0
  31. package/hub/team/cli/commands/send.mjs +37 -0
  32. package/hub/team/cli/commands/start/index.mjs +87 -0
  33. package/hub/team/cli/commands/start/parse-args.mjs +32 -0
  34. package/hub/team/cli/commands/start/start-in-process.mjs +40 -0
  35. package/hub/team/cli/commands/start/start-mux.mjs +73 -0
  36. package/hub/team/cli/commands/start/start-wt.mjs +69 -0
  37. package/hub/team/cli/commands/status.mjs +87 -0
  38. package/hub/team/cli/commands/stop.mjs +31 -0
  39. package/hub/team/cli/commands/task.mjs +30 -0
  40. package/hub/team/cli/commands/tasks.mjs +13 -0
  41. package/hub/team/{cli.mjs → cli/help.mjs} +38 -99
  42. package/hub/team/cli/index.mjs +39 -0
  43. package/hub/team/cli/manifest.mjs +28 -0
  44. package/hub/team/cli/render.mjs +30 -0
  45. package/hub/team/cli/services/attach-fallback.mjs +54 -0
  46. package/hub/team/cli/services/hub-client.mjs +171 -0
  47. package/hub/team/cli/services/member-selector.mjs +30 -0
  48. package/hub/team/cli/services/native-control.mjs +115 -0
  49. package/hub/team/cli/services/runtime-mode.mjs +60 -0
  50. package/hub/team/cli/services/state-store.mjs +34 -0
  51. package/hub/team/cli/services/task-model.mjs +30 -0
  52. package/hub/team/native-supervisor.mjs +69 -63
  53. package/hub/team/native.mjs +367 -266
  54. package/hub/team/nativeProxy.mjs +217 -173
  55. package/hub/team/pane.mjs +149 -149
  56. package/hub/team/psmux.mjs +946 -946
  57. package/hub/team/session.mjs +608 -608
  58. package/hub/team/staleState.mjs +369 -299
  59. package/hub/tools.mjs +107 -107
  60. package/hub/tray.mjs +332 -0
  61. package/hub/workers/claude-worker.mjs +446 -446
  62. package/hub/workers/codex-mcp.mjs +414 -414
  63. package/hub/workers/delegator-mcp.mjs +1045 -1045
  64. package/hub/workers/factory.mjs +21 -21
  65. package/hub/workers/gemini-worker.mjs +349 -349
  66. package/hub/workers/interface.mjs +41 -41
  67. package/package.json +61 -60
  68. package/scripts/__tests__/keyword-detector.test.mjs +234 -234
  69. package/scripts/hub-ensure.mjs +102 -101
  70. package/scripts/keyword-detector.mjs +272 -272
  71. package/scripts/keyword-rules-expander.mjs +521 -521
  72. package/scripts/lib/keyword-rules.mjs +168 -168
  73. package/scripts/lib/mcp-filter.mjs +642 -642
  74. package/scripts/lib/mcp-server-catalog.mjs +118 -118
  75. package/scripts/mcp-check.mjs +126 -126
  76. package/scripts/preflight-cache.mjs +19 -0
  77. package/scripts/run.cjs +62 -62
  78. package/scripts/setup.mjs +68 -31
  79. package/scripts/test-tfx-route-no-claude-native.mjs +57 -57
  80. package/scripts/tfx-route-worker.mjs +161 -161
  81. package/scripts/tfx-route.sh +1360 -1326
  82. package/skills/tfx-auto/SKILL.md +196 -196
  83. package/skills/tfx-auto-codex/SKILL.md +77 -77
  84. package/skills/tfx-multi/SKILL.md +378 -378
  85. package/hub/team/cli-team-common.mjs +0 -348
  86. package/hub/team/cli-team-control.mjs +0 -393
  87. package/hub/team/cli-team-start.mjs +0 -516
  88. package/hub/team/cli-team-status.mjs +0 -283
  89. package/skills/auto-verify/SKILL.md +0 -145
  90. package/skills/manage-skills/SKILL.md +0 -192
  91. package/skills/verify-implementation/SKILL.md +0 -138
@@ -1,283 +0,0 @@
1
- // hub/team/cli-team-status.mjs — team 상태/조회 로직
2
- import {
3
- capturePaneOutput,
4
- detectMultiplexer,
5
- getSessionAttachedCount,
6
- hasWindowsTerminal,
7
- hasWindowsTerminalSession,
8
- listSessions,
9
- } from "./session.mjs";
10
- import { AMBER, BOLD, DIM, GRAY, GREEN, RED, RESET, WHITE } from "./shared.mjs";
11
- import {
12
- TEAM_PROFILE,
13
- getDefaultHubUrl,
14
- getHubInfo,
15
- isNativeMode,
16
- isTeamAlive,
17
- isWtMode,
18
- loadTeamState,
19
- nativeGetStatus,
20
- ok,
21
- saveTeamState,
22
- } from "./cli-team-common.mjs";
23
-
24
- async function fetchHubTaskList(state) {
25
- const hubBase = (state?.hubUrl || getDefaultHubUrl()).replace(/\/mcp$/, "");
26
- const teamName = state?.native?.teamName || state?.sessionName || null;
27
- if (!teamName) return [];
28
- try {
29
- const res = await fetch(`${hubBase}/bridge/team/task-list`, {
30
- method: "POST",
31
- headers: { "Content-Type": "application/json" },
32
- body: JSON.stringify({ team_name: teamName }),
33
- signal: AbortSignal.timeout(2000),
34
- });
35
- const data = await res.json();
36
- return data?.ok ? (data.data?.tasks || []) : [];
37
- } catch {
38
- return [];
39
- }
40
- }
41
-
42
- function renderTasks(tasks = []) {
43
- if (!tasks.length) {
44
- console.log(`\n ${DIM}태스크 없음${RESET}\n`);
45
- return;
46
- }
47
-
48
- console.log(`\n ${AMBER}${BOLD}⬡ Team Tasks${RESET}\n`);
49
- for (const t of tasks) {
50
- const dep = t.depends_on?.length ? ` ${DIM}(deps: ${t.depends_on.join(",")})${RESET}` : "";
51
- const owner = t.owner ? ` ${GRAY}[${t.owner}]${RESET}` : "";
52
- console.log(` ${WHITE}${t.id}${RESET} ${t.status.padEnd(11)} ${t.title}${owner}${dep}`);
53
- }
54
- console.log("");
55
- }
56
-
57
- function formatCompletionSuffix(member) {
58
- if (!member?.completionStatus) return "";
59
- if (member.completionStatus === "abnormal") {
60
- const reason = member.completionReason || "unknown";
61
- return ` ${RED}[abnormal:${reason}]${RESET}`;
62
- }
63
- if (member.completionStatus === "normal") {
64
- return ` ${GREEN}[route-ok]${RESET}`;
65
- }
66
- if (member.completionStatus === "unchecked") {
67
- return ` ${GRAY}[route-unchecked]${RESET}`;
68
- }
69
- return "";
70
- }
71
-
72
- export async function teamStatus() {
73
- const state = loadTeamState();
74
- if (!state) {
75
- console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
76
- return;
77
- }
78
-
79
- const alive = isTeamAlive(state);
80
- const status = alive ? `${GREEN}● active${RESET}` : `${RED}● dead${RESET}`;
81
- const uptime = alive ? `${Math.round((Date.now() - state.startedAt) / 60000)}분` : "-";
82
-
83
- console.log(`\n ${AMBER}${BOLD}⬡ tfx multi${RESET} ${status}\n`);
84
- console.log(` 세션: ${state.sessionName}`);
85
- console.log(` 모드: ${state.teammateMode || "tmux"}`);
86
- console.log(` 리드: ${state.lead || "claude"}`);
87
- console.log(` 워커: ${(state.agents || []).join(", ")}`);
88
- console.log(` Uptime: ${uptime}`);
89
- console.log(` 태스크: ${(state.tasks || []).length}`);
90
- if (isWtMode(state) && !hasWindowsTerminalSession()) {
91
- console.log(` ${DIM}WT_SESSION 미감지: 생존성은 heuristics로 판정됨${RESET}`);
92
- }
93
-
94
- const members = state.members || [];
95
- if (members.length) {
96
- console.log("");
97
- for (const m of members) {
98
- const roleTag = m.role === "lead" ? "lead" : "worker";
99
- console.log(` - ${m.name} (${m.cli}) ${DIM}${roleTag}${RESET} ${DIM}${m.pane}${RESET}`);
100
- }
101
- }
102
-
103
- if (isNativeMode(state) && alive) {
104
- const native = await nativeGetStatus(state);
105
- const nativeMembers = native?.data?.members || [];
106
- if (nativeMembers.length) {
107
- console.log("");
108
- for (const m of nativeMembers) {
109
- console.log(` • ${m.name}: ${m.status}${formatCompletionSuffix(m)}${m.lastPreview ? ` ${DIM}${m.lastPreview}${RESET}` : ""}`);
110
- }
111
- }
112
- }
113
-
114
- if (alive) {
115
- const hubTasks = await fetchHubTaskList(state);
116
- if (hubTasks.length > 0) {
117
- const completed = hubTasks.filter((t) => t.status === "completed").length;
118
- const failed = hubTasks.filter((t) => t.status === "failed").length;
119
-
120
- console.log(`\n ${BOLD}Hub Tasks${RESET} ${DIM}(${completed}/${hubTasks.length} done)${RESET}`);
121
- for (const t of hubTasks) {
122
- const icon = t.status === "completed" ? `${GREEN}✓${RESET}`
123
- : t.status === "in_progress" ? `${AMBER}●${RESET}`
124
- : t.status === "failed" ? `${RED}✗${RESET}`
125
- : `${GRAY}○${RESET}`;
126
- const owner = t.owner ? ` ${GRAY}[${t.owner}]${RESET}` : "";
127
- const subject = t.subject || t.description?.slice(0, 50) || "";
128
- console.log(` ${icon} ${subject}${owner}`);
129
- }
130
- if (failed > 0) console.log(` ${RED}⚠ ${failed}건 실패${RESET}`);
131
- }
132
- }
133
-
134
- console.log("");
135
- }
136
-
137
- export function teamTasks() {
138
- const state = loadTeamState();
139
- if (!state || !isTeamAlive(state)) {
140
- console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
141
- return;
142
- }
143
- renderTasks(state.tasks || []);
144
- }
145
-
146
- export function teamTaskUpdate() {
147
- const state = loadTeamState();
148
- if (!state || !isTeamAlive(state)) {
149
- console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
150
- return;
151
- }
152
-
153
- const action = (process.argv[4] || "").toLowerCase();
154
- const taskId = (process.argv[5] || "").toUpperCase();
155
- const nextStatus = action === "done" || action === "complete" || action === "completed"
156
- ? "completed"
157
- : action === "progress" || action === "in-progress" || action === "in_progress"
158
- ? "in_progress"
159
- : action === "pending"
160
- ? "pending"
161
- : null;
162
-
163
- if (!nextStatus || !taskId) {
164
- console.log(`\n 사용법: ${WHITE}tfx multi task <pending|progress|done> <T1>${RESET}\n`);
165
- return;
166
- }
167
-
168
- const tasks = state.tasks || [];
169
- const target = tasks.find((t) => String(t.id).toUpperCase() === taskId);
170
- if (!target) {
171
- console.log(`\n ${DIM}태스크를 찾을 수 없음: ${taskId}${RESET}\n`);
172
- return;
173
- }
174
-
175
- target.status = nextStatus;
176
- saveTeamState(state);
177
- ok(`${target.id} 상태 갱신: ${nextStatus}`);
178
- console.log("");
179
- }
180
-
181
- export async function teamDebug() {
182
- const state = loadTeamState();
183
- const linesIdx = process.argv.findIndex((a) => a === "--lines" || a === "-n");
184
- const lines = linesIdx !== -1 ? Math.max(3, parseInt(process.argv[linesIdx + 1] || "20", 10) || 20) : 20;
185
- const mux = detectMultiplexer() || "none";
186
- const hub = await getHubInfo();
187
-
188
- console.log(`\n ${AMBER}${BOLD}⬡ Team Debug${RESET}\n`);
189
- console.log(` platform: ${process.platform}`);
190
- console.log(` node: ${process.version}`);
191
- console.log(` tty: stdout=${!!process.stdout.isTTY}, stdin=${!!process.stdin.isTTY}`);
192
- console.log(` mux: ${mux}`);
193
- console.log(` hub-pid: ${hub ? `${hub.pid}` : "-"}`);
194
- console.log(` hub-url: ${hub?.url || "-"}`);
195
-
196
- const sessions = listSessions();
197
- console.log(` sessions: ${sessions.length ? sessions.join(", ") : "-"}`);
198
-
199
- if (!state) {
200
- console.log(`\n ${DIM}team-state 없음 (활성 세션 없음)${RESET}\n`);
201
- return;
202
- }
203
-
204
- console.log(`\n ${BOLD}state${RESET}`);
205
- console.log(` session: ${state.sessionName}`);
206
- console.log(` profile: ${state.profile || TEAM_PROFILE}`);
207
- console.log(` mode: ${state.teammateMode || "tmux"}`);
208
- console.log(` lead: ${state.lead}`);
209
- console.log(` agents: ${(state.agents || []).join(", ")}`);
210
- console.log(` alive: ${isTeamAlive(state) ? "yes" : "no"}`);
211
- const attached = getSessionAttachedCount(state.sessionName);
212
- console.log(` attached: ${attached == null ? "-" : attached}`);
213
-
214
- if (isWtMode(state)) {
215
- const wtState = state.wt || {};
216
- console.log(`\n ${BOLD}wt-session${RESET}`);
217
- console.log(` window: ${wtState.windowId ?? 0}`);
218
- console.log(` layout: ${wtState.layout || state.layout || "-"}`);
219
- console.log(` panes: ${wtState.paneCount ?? (state.members || []).length}`);
220
- console.log(` wt.exe: ${hasWindowsTerminal() ? "yes" : "no"}`);
221
- console.log(` WT_SESSION:${hasWindowsTerminalSession() ? "yes" : "no"}`);
222
- console.log("");
223
- return;
224
- }
225
-
226
- if (isNativeMode(state)) {
227
- const native = await nativeGetStatus(state);
228
- const members = native?.data?.members || [];
229
- console.log(`\n ${BOLD}native-members${RESET}`);
230
- if (!members.length) {
231
- console.log(` ${DIM}(no data)${RESET}`);
232
- } else {
233
- for (const m of members) {
234
- console.log(` - ${m.name}: ${m.status}${formatCompletionSuffix(m)}${m.lastPreview ? ` ${DIM}${m.lastPreview}${RESET}` : ""}`);
235
- }
236
- }
237
- console.log("");
238
- return;
239
- }
240
-
241
- const members = state.members || [];
242
- console.log(`\n ${BOLD}pane-tail${RESET} ${DIM}(last ${lines} lines)${RESET}`);
243
- if (!members.length) {
244
- console.log(` ${DIM}(members 없음)${RESET}`);
245
- } else {
246
- for (const m of members) {
247
- const tail = capturePaneOutput(m.pane, lines) || "(empty)";
248
- console.log(`\n [${m.name}] ${m.pane}`);
249
- const tailLines = tail.split("\n").slice(-lines);
250
- for (const line of tailLines) {
251
- console.log(` ${line}`);
252
- }
253
- }
254
- }
255
- console.log("");
256
- }
257
-
258
- export function teamList() {
259
- const state = loadTeamState();
260
- if (state && isNativeMode(state) && isTeamAlive(state)) {
261
- console.log(`\n ${AMBER}${BOLD}⬡ 팀 세션 목록${RESET}\n`);
262
- console.log(` ${GREEN}●${RESET} ${state.sessionName} ${DIM}(in-process)${RESET}`);
263
- console.log("");
264
- return;
265
- }
266
- if (state && isWtMode(state) && isTeamAlive(state)) {
267
- console.log(`\n ${AMBER}${BOLD}⬡ 팀 세션 목록${RESET}\n`);
268
- console.log(` ${GREEN}●${RESET} ${state.sessionName} ${DIM}(wt)${RESET}`);
269
- console.log("");
270
- return;
271
- }
272
-
273
- const sessions = listSessions();
274
- if (sessions.length === 0) {
275
- console.log(`\n ${DIM}활성 팀 세션 없음${RESET}\n`);
276
- return;
277
- }
278
- console.log(`\n ${AMBER}${BOLD}⬡ 팀 세션 목록${RESET}\n`);
279
- for (const sessionName of sessions) {
280
- console.log(` ${GREEN}●${RESET} ${sessionName}`);
281
- }
282
- console.log("");
283
- }
@@ -1,145 +0,0 @@
1
- ---
2
- name: auto-verify
3
- description: OMC 실행 모드 완료 후 자동으로 verify 스킬을 생성/업데이트하고, 통합 검증을 실행합니다. autopilot/team/ralph 완료 시점 또는 커밋 전에 사용.
4
- argument-hint: "[선택사항: skip-manage | skip-verify | full]"
5
- ---
6
-
7
- # auto-verify — OMC 연동 자동 검증
8
-
9
- ## 목적
10
-
11
- OMC 워크플로우(autopilot, team, ralph, executor 등) 완료 후 **한 번의 호출로** 스킬 관리 + 검증을 자동 수행합니다:
12
-
13
- 1. `/manage-skills` 실행 → 세션 변경사항 기반 verify 스킬 자동 생성/업데이트
14
- 2. `/verify-implementation` 실행 → 생성된 스킬 포함 전체 검증
15
- 3. 이슈 발견 시 수정 제안 → 사용자 승인 → 재검증
16
-
17
- ## OMC 호환
18
-
19
- ```
20
- [OMC 실행 모드]
21
-
22
- ├─ /autopilot 완료 후 → /auto-verify
23
- ├─ /team team-verify 단계 → /auto-verify
24
- ├─ /ralph 각 이터레이션 후 → /auto-verify
25
- ├─ executor/deep-executor 완료 후 → /auto-verify
26
- └─ 커밋 전 (수동) → /auto-verify
27
- ```
28
-
29
- **OMC team-verify 단계에서의 실행 순서:**
30
-
31
- ```
32
- team-verify:
33
- 1. /auto-verify ← 프로젝트 고유 규칙 검증 (이 스킬)
34
- 2. OMC verifier (sonnet) ← 범용 완료성 검증
35
- 3. OMC code-reviewer (opus) ← 심층 코드 리뷰 (20+ 파일 시)
36
- 4. ask_codex (security) ← 보안 분석 (MCP, 필요 시)
37
- ```
38
-
39
- ## 워크플로우
40
-
41
- ### Step 1: 모드 감지 및 옵션 파싱
42
-
43
- 인수 확인:
44
- - `skip-manage`: manage-skills 단계 건너뛰기 (이미 실행한 경우)
45
- - `skip-verify`: verify-implementation 건너뛰기 (스킬 업데이트만)
46
- - `full` 또는 인수 없음: 전체 실행 (기본값)
47
-
48
- 현재 OMC 상태 확인 (선택적):
49
-
50
- ```bash
51
- # OMC 실행 모드 상태 확인 (있으면 컨텍스트로 활용)
52
- cat .omc/state/team-state.json 2>/dev/null
53
- cat .omc/state/autopilot-state.json 2>/dev/null
54
- cat .omc/state/ralph-state.json 2>/dev/null
55
- ```
56
-
57
- ### Step 2: 변경사항 규모 평가
58
-
59
- ```bash
60
- # 변경된 파일 수 확인
61
- git diff HEAD --name-only | wc -l
62
- git diff main...HEAD --name-only 2>/dev/null | wc -l
63
- ```
64
-
65
- 규모에 따른 동작 조정:
66
- - **소규모 (1-5 파일)**: manage-skills + verify-implementation 순차 실행
67
- - **중규모 (6-20 파일)**: manage-skills 실행 → verify-implementation 실행
68
- - **대규모 (20+ 파일)**: manage-skills 실행 → verify-implementation 실행 후 OMC code-reviewer 권장
69
-
70
- ### Step 3: manage-skills 실행
71
-
72
- `skip-manage`가 아닌 경우:
73
-
74
- 1. `/manage-skills` 스킬의 워크플로우를 실행
75
- 2. 새로 생성/업데이트된 스킬 목록 기록
76
- 3. CLAUDE.md Skills 테이블 동기화 확인
77
-
78
- **표시:**
79
-
80
- ```markdown
81
- ## 스킬 관리 완료
82
-
83
- - 분석된 파일: N개
84
- - 새 스킬 생성: X개 (verify-<name1>, verify-<name2>)
85
- - 기존 스킬 업데이트: Y개
86
- - 면제: Z개
87
-
88
- 다음 단계: 통합 검증 실행...
89
- ```
90
-
91
- ### Step 4: verify-implementation 실행
92
-
93
- `skip-verify`가 아닌 경우:
94
-
95
- 1. `/verify-implementation` 스킬의 워크플로우를 실행
96
- 2. Step 3에서 새로 생성된 스킬도 포함하여 전체 검증
97
- 3. 통합 리포트 생성
98
-
99
- ### Step 5: OMC 에이전트 연계 권장
100
-
101
- 검증 결과에 따라 OMC 에이전트 후속 작업을 권장:
102
-
103
- ```markdown
104
- ## 추가 권장 사항
105
-
106
- | 조건 | 권장 액션 |
107
- |------|----------|
108
- | 보안 관련 이슈 발견 | `ask_codex`(security-reviewer, xhigh)로 심층 분석 |
109
- | 아키텍처 위반 발견 | `ask_codex`(architect, high)로 구조 리뷰 |
110
- | UI/프론트 이슈 발견 | `ask_gemini`(designer)로 UI 리뷰 |
111
- | 20+ 파일 변경 | OMC `/code-review`로 전체 리뷰 |
112
- | 이슈 없음 | 커밋 준비 완료 ✓ |
113
- ```
114
-
115
- ### Step 6: 최종 보고서
116
-
117
- ```markdown
118
- ## Auto-Verify 완료
119
-
120
- ### 스킬 관리
121
- - 새 스킬: X개 | 업데이트: Y개
122
-
123
- ### 검증 결과
124
- | 스킬 | 상태 | 이슈 |
125
- |------|------|------|
126
- | verify-<name> | PASS/FAIL | N개 |
127
-
128
- ### 전체: PASS ✓ / X개 이슈 발견
129
- ### 다음 단계: [커밋 준비 완료 | 이슈 수정 필요 | OMC 심층 리뷰 권장]
130
- ```
131
-
132
- ## 예외사항
133
-
134
- 1. **OMC 실행 모드 상태 파일 없음** — 정상 (독립 실행 지원)
135
- 2. **verify 스킬 0개** — manage-skills만 실행하여 최초 스킬 생성
136
- 3. **git 이력 없음** — 현재 파일 전체 스캔으로 fallback
137
- 4. **MCP 미사용 환경** — OMC 에이전트 권장만 표시, MCP 권장 생략
138
-
139
- ## 관련 파일
140
-
141
- | File | Purpose |
142
- |------|---------|
143
- | `.claude/skills/manage-skills/SKILL.md` | 스킬 생성/관리 |
144
- | `.claude/skills/verify-implementation/SKILL.md` | 통합 검증 실행 |
145
- | `CLAUDE.md` | Skills 섹션 (자동 동기화 대상) |
@@ -1,192 +0,0 @@
1
- ---
2
- name: manage-skills
3
- description: 세션 변경사항을 분석하여 검증 스킬 누락을 탐지합니다. 기존 스킬을 동적으로 탐색하고, 새 스킬을 생성하거나 기존 스킬을 업데이트한 뒤 CLAUDE.md를 관리합니다.
4
- disable-model-invocation: true
5
- argument-hint: "[선택사항: 특정 스킬 이름 또는 집중할 영역]"
6
- ---
7
-
8
- # manage-skills — 세션 기반 스킬 유지보수
9
-
10
- ## 목적
11
-
12
- 현재 세션에서 변경된 내용을 분석하여 검증 스킬의 드리프트를 탐지하고 수정합니다:
13
-
14
- 1. **커버리지 누락** — 어떤 verify 스킬에서도 참조하지 않는 변경된 파일
15
- 2. **유효하지 않은 참조** — 삭제되거나 이동된 파일을 참조하는 스킬
16
- 3. **누락된 검사** — 기존 검사에서 다루지 않는 새로운 패턴/규칙
17
- 4. **오래된 값** — 더 이상 일치하지 않는 설정값 또는 탐지 명령어
18
-
19
- ## OMC 호환
20
-
21
- 이 스킬은 OMC(oh-my-claudecode) 에이전트 시스템과 **보완적**으로 작동합니다:
22
-
23
- - **OMC verifier**: 세션 단위 일회성 검증 (매번 코드 분석)
24
- - **verify 스킬**: 프로젝트별 영구 규칙 기반 검증 (누적 지식)
25
- - OMC `code-reviewer`, `quality-reviewer` 등은 범용 리뷰 → verify 스킬은 **프로젝트 고유 규칙** 검증
26
- - 두 시스템을 함께 사용 시: verify 스킬로 프로젝트 규칙 검증 → OMC verifier로 일반 품질 검증
27
-
28
- **MCP-First 환경 적용**: verify 스킬의 리포트를 Codex/Gemini CLI에 전달하여 심층 분석 위임 가능.
29
-
30
- ## 실행 시점
31
-
32
- - 새로운 패턴이나 규칙을 도입하는 기능을 구현한 후
33
- - 기존 verify 스킬을 수정하고 일관성을 점검하고 싶을 때
34
- - PR 전에 verify 스킬이 변경된 영역을 커버하는지 확인할 때
35
- - 검증 실행 시 예상했던 이슈를 놓쳤을 때
36
- - 주기적으로 스킬을 코드베이스 변화에 맞춰 정렬할 때
37
-
38
- ## 등록된 검증 스킬
39
-
40
- 현재 프로젝트에 등록된 검증 스킬 목록입니다. 새 스킬 생성/삭제 시 이 목록을 업데이트합니다.
41
-
42
- (아직 등록된 검증 스킬이 없습니다)
43
-
44
- <!-- 스킬이 추가되면 아래 형식으로 등록:
45
- | 스킬 | 설명 | 커버 파일 패턴 |
46
- |------|------|---------------|
47
- | `verify-example` | 예시 검증 | `src/example/**/*.ts` |
48
- -->
49
-
50
- ## 워크플로우
51
-
52
- ### Step 1: 세션 변경사항 분석
53
-
54
- 현재 세션에서 변경된 모든 파일을 수집합니다:
55
-
56
- ```bash
57
- # 커밋되지 않은 변경사항
58
- git diff HEAD --name-only
59
-
60
- # 현재 브랜치의 커밋 (main에서 분기된 경우)
61
- git log --oneline main..HEAD 2>/dev/null
62
-
63
- # main에서 분기된 이후의 모든 변경사항
64
- git diff main...HEAD --name-only 2>/dev/null
65
- ```
66
-
67
- 중복을 제거한 목록으로 합칩니다. 선택적 인수로 스킬 이름이나 영역이 지정된 경우 관련 파일만 필터링합니다.
68
-
69
- **표시:** 최상위 디렉토리(첫 1-2 경로 세그먼트) 기준으로 파일을 그룹화합니다:
70
-
71
- ```markdown
72
- ## 세션 변경사항 감지
73
-
74
- **이 세션에서 N개 파일 변경됨:**
75
-
76
- | 디렉토리 | 파일 |
77
- |----------|------|
78
- | src/components | `Button.tsx`, `Modal.tsx` |
79
- | src/server | `router.ts`, `handler.ts` |
80
- ```
81
-
82
- ### Step 2: 등록된 스킬과 변경 파일 매핑
83
-
84
- **등록된 검증 스킬** 섹션에 나열된 스킬을 참조하여 파일-스킬 매핑을 구축합니다.
85
-
86
- 등록된 스킬이 0개인 경우, Step 4로 바로 이동합니다. 모든 변경 파일이 "UNCOVERED"로 처리됩니다.
87
-
88
- 등록된 스킬이 1개 이상인 경우, 각 스킬의 SKILL.md를 읽고 Related Files, Workflow 섹션에서 파일 경로 패턴을 추출합니다.
89
-
90
- ```markdown
91
- ### 파일 → 스킬 매핑
92
-
93
- | 스킬 | 트리거 파일 | 액션 |
94
- |------|-----------|------|
95
- | verify-api | `router.ts`, `handler.ts` | CHECK |
96
- | (스킬 없음) | `package.json` | UNCOVERED |
97
- ```
98
-
99
- ### Step 3: 커버리지 갭 분석
100
-
101
- 영향받은 각 스킬에 대해 SKILL.md를 읽고 점검합니다:
102
-
103
- 1. **누락된 파일 참조** — 관련 변경 파일이 Related Files에 없는 경우
104
- 2. **오래된 탐지 명령어** — grep/glob 패턴이 현재 파일 구조와 일치하는지
105
- 3. **커버되지 않은 새 패턴** — 스킬이 검사하지 않는 새로운 규칙
106
- 4. **삭제된 파일의 잔여 참조** — 존재하지 않는 파일 참조
107
- 5. **변경된 값** — 특정 값(식별자, 설정 키)이 수정되었는지
108
-
109
- ### Step 4: CREATE vs UPDATE 결정
110
-
111
- ```
112
- 커버되지 않은 각 파일 그룹에 대해:
113
- IF 기존 스킬의 도메인과 관련:
114
- → UPDATE (커버리지 확장)
115
- ELIF 3개+ 관련 파일이 공통 패턴 공유:
116
- → CREATE (새 verify 스킬)
117
- ELSE:
118
- → 면제 (스킬 불필요)
119
- ```
120
-
121
- `AskUserQuestion`으로 사용자에게 확인합니다.
122
-
123
- ### Step 5: 기존 스킬 업데이트
124
-
125
- **규칙:**
126
- - 추가/수정만 — 작동하는 기존 검사는 절대 제거하지 않음
127
- - Related Files 테이블에 새 파일 경로 추가
128
- - 새 탐지 명령어 추가
129
- - 삭제 확인된 파일의 참조 제거
130
- - 변경된 값 업데이트
131
-
132
- ### Step 6: 새 스킬 생성
133
-
134
- **반드시 사용자에게 스킬 이름 확인 후 생성.**
135
-
136
- **이름 규칙:**
137
- - `verify-`로 시작 (예: `verify-auth`, `verify-api`)
138
- - kebab-case 사용
139
-
140
- **필수 섹션:**
141
- - Frontmatter: name, description
142
- - **Purpose** — 2-5개 검증 카테고리
143
- - **When to Run** — 3-5개 트리거 조건
144
- - **Related Files** — 실제 파일 경로 테이블 (`ls`로 검증)
145
- - **Workflow** — 검사 단계 (도구, 패턴, PASS/FAIL 기준, 수정 방법)
146
- - **Output Format** — 마크다운 테이블
147
- - **Exceptions** — 2-3개 면제 케이스
148
-
149
- **연관 파일 자동 업데이트:**
150
- 1. `manage-skills/SKILL.md` — 등록된 검증 스킬 테이블
151
- 2. `verify-implementation/SKILL.md` — 실행 대상 스킬 테이블
152
- 3. `CLAUDE.md` — Skills 섹션 테이블
153
-
154
- ### Step 7: 검증
155
-
156
- 1. 수정된 SKILL.md 다시 읽어 마크다운 형식 확인
157
- 2. Related Files 경로 존재 확인: `ls <path> 2>/dev/null || echo "MISSING"`
158
- 3. 탐지 명령어 드라이런으로 문법 검증
159
- 4. 등록 테이블 동기화 확인
160
-
161
- ### Step 8: 요약 보고서
162
-
163
- ```markdown
164
- ## 세션 스킬 유지보수 보고서
165
-
166
- ### 분석된 변경 파일: N개
167
- ### 업데이트된 스킬: X개
168
- ### 생성된 스킬: Y개
169
- ### 업데이트된 연관 파일: [목록]
170
- ### 미커버 변경사항: [면제 사유]
171
- ```
172
-
173
- ## 관련 파일
174
-
175
- | File | Purpose |
176
- |------|---------|
177
- | `.claude/skills/verify-implementation/SKILL.md` | 통합 검증 스킬 |
178
- | `.claude/skills/manage-skills/SKILL.md` | 이 파일 |
179
- | `CLAUDE.md` | 프로젝트 지침 (Skills 섹션) |
180
-
181
- ## 예외사항
182
-
183
- 다음은 **문제가 아닙니다**:
184
-
185
- 1. **Lock 파일 및 생성된 파일** — `package-lock.json`, `yarn.lock`, 빌드 출력물
186
- 2. **일회성 설정 변경** — 버전 범프, 린터 설정 사소한 변경
187
- 3. **문서 파일** — `README.md`, `CHANGELOG.md`, `LICENSE`
188
- 4. **테스트 픽스처** — `fixtures/`, `test-data/` 디렉토리
189
- 5. **CLAUDE.md 자체** — 문서 업데이트이며 코드 패턴이 아님
190
- 6. **벤더/서드파티 코드** — `vendor/`, `node_modules/`
191
- 7. **CI/CD 설정** — `.github/`, `Dockerfile`
192
- 8. **AI 설정 파일** — `.claude/`, `.omc/`, `.codex/`, `.gemini/` (OMC 호환)