@dmsdc-ai/aigentry-devkit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +500 -0
- package/bin/aigentry-devkit.js +94 -0
- package/config/CLAUDE.md +744 -0
- package/config/envrc/global.envrc +3 -0
- package/config/settings.json.template +12 -0
- package/hooks/hooks.json +16 -0
- package/hooks/session-start +37 -0
- package/hud/simple-status.sh +126 -0
- package/install.ps1 +203 -0
- package/install.sh +213 -0
- package/mcp-servers/deliberation/index.js +2429 -0
- package/mcp-servers/deliberation/package.json +16 -0
- package/mcp-servers/deliberation/session-monitor.sh +316 -0
- package/package.json +50 -0
- package/skills/clipboard-image/SKILL.md +31 -0
- package/skills/deliberation/SKILL.md +135 -0
- package/skills/deliberation-executor/SKILL.md +86 -0
- package/skills/env-manager/SKILL.md +231 -0
- package/skills/youtube-analyzer/SKILL.md +56 -0
- package/skills/youtube-analyzer/scripts/analyze_youtube.py +383 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-deliberation",
|
|
3
|
+
"version": "2.5.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mcp-deliberation": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node index.js"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
14
|
+
"ws": "^8.18.0"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# Session Monitor — 단일 deliberation 세션 전용 터미널 뷰
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# bash session-monitor.sh <session_id> <project_slug>
|
|
7
|
+
#
|
|
8
|
+
# MCP 서버가 deliberation_start 시 자동으로 tmux 윈도우에서 실행합니다.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
SESSION_ID="${1:?session_id 필요}"
|
|
12
|
+
PROJECT="${2:?project_slug 필요}"
|
|
13
|
+
STATE_FILE="$HOME/.local/lib/mcp-deliberation/state/$PROJECT/sessions/$SESSION_ID.json"
|
|
14
|
+
|
|
15
|
+
# 색상
|
|
16
|
+
RED='\033[0;31m'
|
|
17
|
+
GREEN='\033[0;32m'
|
|
18
|
+
YELLOW='\033[1;33m'
|
|
19
|
+
BLUE='\033[0;34m'
|
|
20
|
+
CYAN='\033[0;36m'
|
|
21
|
+
MAGENTA='\033[0;35m'
|
|
22
|
+
BOLD='\033[1m'
|
|
23
|
+
DIM='\033[2m'
|
|
24
|
+
NC='\033[0m'
|
|
25
|
+
BOX_CONTENT_WIDTH=60
|
|
26
|
+
BOX_RULE="$(printf '%*s' "$((BOX_CONTENT_WIDTH + 2))" '' | tr ' ' '═')"
|
|
27
|
+
|
|
28
|
+
fit_to_width() {
|
|
29
|
+
TEXT="$1" WIDTH="$2" node -e "
|
|
30
|
+
const raw = String(process.env.TEXT ?? '')
|
|
31
|
+
.replace(/\s+/g, ' ')
|
|
32
|
+
.trim();
|
|
33
|
+
const target = Number(process.env.WIDTH ?? '60');
|
|
34
|
+
|
|
35
|
+
const isWide = (cp) =>
|
|
36
|
+
cp >= 0x1100 && (
|
|
37
|
+
cp <= 0x115f || cp === 0x2329 || cp === 0x232a ||
|
|
38
|
+
(cp >= 0x2e80 && cp <= 0xa4cf && cp !== 0x303f) ||
|
|
39
|
+
(cp >= 0xac00 && cp <= 0xd7a3) ||
|
|
40
|
+
(cp >= 0xf900 && cp <= 0xfaff) ||
|
|
41
|
+
(cp >= 0xfe10 && cp <= 0xfe19) ||
|
|
42
|
+
(cp >= 0xfe30 && cp <= 0xfe6f) ||
|
|
43
|
+
(cp >= 0xff00 && cp <= 0xff60) ||
|
|
44
|
+
(cp >= 0xffe0 && cp <= 0xffe6) ||
|
|
45
|
+
(cp >= 0x1f300 && cp <= 0x1f64f) ||
|
|
46
|
+
(cp >= 0x1f900 && cp <= 0x1f9ff) ||
|
|
47
|
+
(cp >= 0x20000 && cp <= 0x3fffd)
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const charWidth = (ch) => {
|
|
51
|
+
const cp = ch.codePointAt(0);
|
|
52
|
+
if (!cp || cp < 32 || (cp >= 0x7f && cp < 0xa0)) return 0;
|
|
53
|
+
return isWide(cp) ? 2 : 1;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const src = Array.from(raw);
|
|
57
|
+
const out = [];
|
|
58
|
+
let width = 0;
|
|
59
|
+
let truncated = false;
|
|
60
|
+
for (const ch of src) {
|
|
61
|
+
const w = charWidth(ch);
|
|
62
|
+
if (width + w > target) {
|
|
63
|
+
truncated = true;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
out.push(ch);
|
|
67
|
+
width += w;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (truncated && target >= 3) {
|
|
71
|
+
while (out.length > 0 && width + 3 > target) {
|
|
72
|
+
const last = out.pop();
|
|
73
|
+
width -= charWidth(last);
|
|
74
|
+
}
|
|
75
|
+
out.push('.', '.', '.');
|
|
76
|
+
width += 3;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
process.stdout.write(out.join('') + ' '.repeat(Math.max(0, target - width)));
|
|
80
|
+
" 2>/dev/null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
print_box_border() {
|
|
84
|
+
printf "%b%s%b\n" "$BOLD" "$1${BOX_RULE}$2" "$NC"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
print_box_row() {
|
|
88
|
+
local text="$1"
|
|
89
|
+
local color="${2:-$NC}"
|
|
90
|
+
local fitted
|
|
91
|
+
fitted="$(fit_to_width "$text" "$BOX_CONTENT_WIDTH")"
|
|
92
|
+
printf "%b║%b %b%s%b %b║%b\n" "$BOLD" "$NC" "$color" "$fitted" "$NC" "$BOLD" "$NC"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
pane_in_copy_mode() {
|
|
96
|
+
if [ -z "$TMUX" ] || [ -z "${TMUX_PANE:-}" ]; then
|
|
97
|
+
return 1
|
|
98
|
+
fi
|
|
99
|
+
[ "$(tmux display-message -p -t "$TMUX_PANE" "#{pane_in_mode}" 2>/dev/null)" = "1" ]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
state_signature() {
|
|
103
|
+
if [ ! -f "$STATE_FILE" ]; then
|
|
104
|
+
echo "MISSING"
|
|
105
|
+
return
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
node -e "
|
|
109
|
+
const fs = require('fs');
|
|
110
|
+
try {
|
|
111
|
+
const s = JSON.parse(fs.readFileSync('$STATE_FILE','utf-8'));
|
|
112
|
+
const logs = Array.isArray(s.log) ? s.log : [];
|
|
113
|
+
const last = logs.length > 0 ? logs[logs.length - 1] : {};
|
|
114
|
+
const sig = [
|
|
115
|
+
s.status ?? '',
|
|
116
|
+
s.current_round ?? '',
|
|
117
|
+
s.max_rounds ?? '',
|
|
118
|
+
s.current_speaker ?? '',
|
|
119
|
+
Array.isArray(s.speakers) ? s.speakers.join(',') : '',
|
|
120
|
+
logs.length,
|
|
121
|
+
last.timestamp ?? '',
|
|
122
|
+
s.updated ?? '',
|
|
123
|
+
s.synthesis ? s.synthesis.length : 0
|
|
124
|
+
].join('|');
|
|
125
|
+
console.log(sig);
|
|
126
|
+
} catch {
|
|
127
|
+
console.log('PARSE_ERROR');
|
|
128
|
+
}
|
|
129
|
+
" 2>/dev/null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
get_field() {
|
|
133
|
+
node -e "
|
|
134
|
+
try {
|
|
135
|
+
const d = JSON.parse(require('fs').readFileSync('$STATE_FILE','utf-8'));
|
|
136
|
+
const keys = '$1'.split('.');
|
|
137
|
+
let val = d;
|
|
138
|
+
for (const k of keys) val = val?.[k];
|
|
139
|
+
if (Array.isArray(val)) console.log(val.length);
|
|
140
|
+
else console.log(val ?? '?');
|
|
141
|
+
} catch { console.log('?'); }
|
|
142
|
+
" 2>/dev/null
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
render() {
|
|
146
|
+
if [ ! -f "$STATE_FILE" ]; then
|
|
147
|
+
echo -e "${DIM}세션 파일 대기 중: $STATE_FILE${NC}"
|
|
148
|
+
return
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
local topic=$(get_field "topic")
|
|
152
|
+
local status=$(get_field "status")
|
|
153
|
+
local round=$(get_field "current_round")
|
|
154
|
+
local max_rounds=$(get_field "max_rounds")
|
|
155
|
+
local participant_count=$(get_field "speakers")
|
|
156
|
+
local speaker=$(get_field "current_speaker")
|
|
157
|
+
local responses=$(get_field "log")
|
|
158
|
+
|
|
159
|
+
# 상태 색상
|
|
160
|
+
local status_color="$YELLOW"
|
|
161
|
+
case "$status" in
|
|
162
|
+
active) status_color="$GREEN" ;;
|
|
163
|
+
completed) status_color="$CYAN" ;;
|
|
164
|
+
awaiting_synthesis) status_color="$BLUE" ;;
|
|
165
|
+
esac
|
|
166
|
+
|
|
167
|
+
# 프로그레스 바
|
|
168
|
+
if ! [[ "$participant_count" =~ ^[0-9]+$ ]]; then participant_count=2; fi
|
|
169
|
+
if [ "$participant_count" -lt 1 ]; then participant_count=1; fi
|
|
170
|
+
|
|
171
|
+
local total=$((max_rounds * participant_count))
|
|
172
|
+
local filled=$responses
|
|
173
|
+
[ "$filled" -gt "$total" ] 2>/dev/null && filled=$total
|
|
174
|
+
local bar=""
|
|
175
|
+
for ((i=0; i<filled; i++)); do bar+="█"; done
|
|
176
|
+
for ((i=filled; i<total; i++)); do bar+="░"; done
|
|
177
|
+
|
|
178
|
+
# 헤더
|
|
179
|
+
print_box_border "╔" "╗"
|
|
180
|
+
print_box_row "$topic" "$YELLOW"
|
|
181
|
+
print_box_border "╠" "╣"
|
|
182
|
+
print_box_row "Session: $SESSION_ID" "$MAGENTA"
|
|
183
|
+
print_box_row "Project: $PROJECT" "$CYAN"
|
|
184
|
+
print_box_row "Status: $status" "$status_color"
|
|
185
|
+
print_box_row "Round: $round/$max_rounds | Next: $speaker" "$BOLD"
|
|
186
|
+
print_box_row "Progress: [$bar] $responses/$total" "$GREEN"
|
|
187
|
+
print_box_border "╚" "╝"
|
|
188
|
+
echo ""
|
|
189
|
+
|
|
190
|
+
# 토론 기록
|
|
191
|
+
node -e "
|
|
192
|
+
const fs = require('fs');
|
|
193
|
+
try {
|
|
194
|
+
const s = JSON.parse(fs.readFileSync('$STATE_FILE','utf-8'));
|
|
195
|
+
|
|
196
|
+
if (s.synthesis) {
|
|
197
|
+
console.log('\x1b[1m── Synthesis ──\x1b[0m');
|
|
198
|
+
console.log('');
|
|
199
|
+
const lines = s.synthesis.split('\n').slice(0, 20);
|
|
200
|
+
lines.forEach(l => console.log(' ' + l));
|
|
201
|
+
if (s.synthesis.split('\n').length > 20) console.log(' ...(truncated)');
|
|
202
|
+
console.log('');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (s.log.length === 0) {
|
|
206
|
+
console.log('\x1b[2m 아직 응답이 없습니다. ' + s.current_speaker + ' 차례 대기 중...\x1b[0m');
|
|
207
|
+
process.exit(0);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
console.log('\x1b[1m── Debate Log ──\x1b[0m');
|
|
211
|
+
console.log('');
|
|
212
|
+
|
|
213
|
+
const palette = ['\x1b[34m', '\x1b[33m', '\x1b[35m', '\x1b[36m', '\x1b[32m', '\x1b[31m'];
|
|
214
|
+
const icons = ['🔵', '🟡', '🟣', '🟢', '🟠', '⚪'];
|
|
215
|
+
const hash = (name) => {
|
|
216
|
+
let out = 0;
|
|
217
|
+
for (let i = 0; i < name.length; i += 1) out = (out * 31 + name.charCodeAt(i)) >>> 0;
|
|
218
|
+
return out;
|
|
219
|
+
};
|
|
220
|
+
const styleFor = (name) => {
|
|
221
|
+
const idx = hash(String(name ?? '')) % palette.length;
|
|
222
|
+
return { color: palette[idx], icon: icons[idx % icons.length] };
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
for (const entry of s.log) {
|
|
226
|
+
const { color, icon } = styleFor(entry.speaker);
|
|
227
|
+
console.log(color + '\x1b[1m' + icon + ' ' + entry.speaker + ' — Round ' + entry.round + '\x1b[0m');
|
|
228
|
+
|
|
229
|
+
const lines = entry.content.split('\n');
|
|
230
|
+
lines.forEach(l => console.log(' ' + l));
|
|
231
|
+
console.log('');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (s.status === 'active') {
|
|
235
|
+
const nextColor = styleFor(s.current_speaker).color;
|
|
236
|
+
console.log(nextColor + ' ⏳ Waiting for ' + s.current_speaker + ' (Round ' + s.current_round + ')...\x1b[0m');
|
|
237
|
+
} else if (s.status === 'awaiting_synthesis') {
|
|
238
|
+
console.log('\x1b[36m 🏁 모든 라운드 종료. 합성 대기 중...\x1b[0m');
|
|
239
|
+
}
|
|
240
|
+
} catch(e) {
|
|
241
|
+
console.log(' 읽기 실패: ' + e.message);
|
|
242
|
+
}
|
|
243
|
+
" 2>/dev/null
|
|
244
|
+
|
|
245
|
+
echo ""
|
|
246
|
+
|
|
247
|
+
# 완료 시 카운트다운
|
|
248
|
+
if [ "$status" = "completed" ]; then
|
|
249
|
+
echo -e "${CYAN}${BOLD} ✅ Deliberation 완료!${NC}"
|
|
250
|
+
echo -e "${DIM} 이 터미널은 30초 후 자동으로 닫힙니다...${NC}"
|
|
251
|
+
for i in $(seq 30 -1 1); do
|
|
252
|
+
printf "\r${DIM} 닫히기까지 %2d초...${NC}" "$i"
|
|
253
|
+
sleep 1
|
|
254
|
+
# 파일이 삭제되었으면 즉시 종료
|
|
255
|
+
[ ! -f "$STATE_FILE" ] && break
|
|
256
|
+
done
|
|
257
|
+
echo ""
|
|
258
|
+
exit 0
|
|
259
|
+
fi
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
draw_frame() {
|
|
263
|
+
# 전체 클리어 대신 홈 이동 + 하단 잔여 영역만 정리해서 깜빡임 감소
|
|
264
|
+
printf '\033[H'
|
|
265
|
+
render
|
|
266
|
+
printf '\033[J'
|
|
267
|
+
echo -e "${DIM}Auto-refresh on change (poll 2s) | Scroll: mouse wheel / PgUp (tmux copy-mode) | Ctrl+C${NC}"
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
# 커서 숨김 (종료 시 복구)
|
|
271
|
+
printf '\033[?25l'
|
|
272
|
+
trap 'printf "\033[?25h"' EXIT INT TERM
|
|
273
|
+
printf '\033[2J\033[H'
|
|
274
|
+
|
|
275
|
+
# tmux에서 스크롤(휠/업다운) 동작을 위해 mouse/copy history 옵션 활성화
|
|
276
|
+
if [ -n "$TMUX" ]; then
|
|
277
|
+
tmux set-option -g mouse on >/dev/null 2>&1 || true
|
|
278
|
+
tmux set-option -g history-limit 200000 >/dev/null 2>&1 || true
|
|
279
|
+
fi
|
|
280
|
+
|
|
281
|
+
# 메인 루프
|
|
282
|
+
last_sig=""
|
|
283
|
+
seen_file=0
|
|
284
|
+
while true; do
|
|
285
|
+
if pane_in_copy_mode; then
|
|
286
|
+
# 사용자가 스크롤 중이면 렌더 업데이트를 멈춰 화면 점프를 방지
|
|
287
|
+
sleep 1
|
|
288
|
+
continue
|
|
289
|
+
fi
|
|
290
|
+
|
|
291
|
+
if [ ! -f "$STATE_FILE" ]; then
|
|
292
|
+
if [ "$seen_file" -eq 1 ]; then
|
|
293
|
+
printf '\033[H\033[J'
|
|
294
|
+
echo -e "${RED}세션이 삭제되었습니다.${NC}"
|
|
295
|
+
sleep 3
|
|
296
|
+
exit 0
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
if [ "$last_sig" != "MISSING" ]; then
|
|
300
|
+
draw_frame
|
|
301
|
+
last_sig="MISSING"
|
|
302
|
+
fi
|
|
303
|
+
|
|
304
|
+
sleep 2
|
|
305
|
+
continue
|
|
306
|
+
fi
|
|
307
|
+
|
|
308
|
+
seen_file=1
|
|
309
|
+
sig="$(state_signature)"
|
|
310
|
+
if [ "$sig" != "$last_sig" ]; then
|
|
311
|
+
draw_frame
|
|
312
|
+
last_sig="$sig"
|
|
313
|
+
fi
|
|
314
|
+
|
|
315
|
+
sleep 2
|
|
316
|
+
done
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dmsdc-ai/aigentry-devkit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Cross-platform installer and tooling bundle for aigentry-devkit",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/dmsdc-ai/aigentry-devkit.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/dmsdc-ai/aigentry-devkit#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/dmsdc-ai/aigentry-devkit/issues"
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public",
|
|
16
|
+
"registry": "https://registry.npmjs.org/"
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"aigentry-devkit": "bin/aigentry-devkit.js"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"bin/aigentry-devkit.js",
|
|
26
|
+
"config/**",
|
|
27
|
+
"hooks/**",
|
|
28
|
+
"hud/**",
|
|
29
|
+
"install.sh",
|
|
30
|
+
"install.ps1",
|
|
31
|
+
"mcp-servers/deliberation/index.js",
|
|
32
|
+
"mcp-servers/deliberation/package.json",
|
|
33
|
+
"mcp-servers/deliberation/session-monitor.sh",
|
|
34
|
+
"skills/clipboard-image/**",
|
|
35
|
+
"skills/deliberation/**",
|
|
36
|
+
"skills/deliberation-executor/**",
|
|
37
|
+
"skills/env-manager/**",
|
|
38
|
+
"skills/youtube-analyzer/**",
|
|
39
|
+
"LICENSE",
|
|
40
|
+
"README.md"
|
|
41
|
+
],
|
|
42
|
+
"keywords": [
|
|
43
|
+
"ai",
|
|
44
|
+
"devkit",
|
|
45
|
+
"claude",
|
|
46
|
+
"codex",
|
|
47
|
+
"mcp",
|
|
48
|
+
"deliberation"
|
|
49
|
+
]
|
|
50
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clipboard-image
|
|
3
|
+
description: Capture and view clipboard image. Triggers on "clipboard", "paste image", "클립보드", "이미지 붙여넣기", "캡처 확인"
|
|
4
|
+
allowed-tools: Bash, Read
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Clipboard Image Viewer
|
|
8
|
+
|
|
9
|
+
Capture the current clipboard image and display it for analysis.
|
|
10
|
+
|
|
11
|
+
## Steps
|
|
12
|
+
|
|
13
|
+
1. Save clipboard image to a temp file:
|
|
14
|
+
```bash
|
|
15
|
+
DISPLAY=:1 xclip -selection clipboard -t image/png -o > /tmp/clipboard-image.png 2>/dev/null
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
2. Check if the image was captured successfully:
|
|
19
|
+
```bash
|
|
20
|
+
file /tmp/clipboard-image.png
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
3. If successful, use the Read tool to view `/tmp/clipboard-image.png` (Claude Code can read images natively).
|
|
24
|
+
|
|
25
|
+
4. If no image in clipboard, inform the user:
|
|
26
|
+
- "클립보드에 이미지가 없습니다. 스크린샷을 복사한 후 다시 시도해주세요."
|
|
27
|
+
|
|
28
|
+
## Troubleshooting
|
|
29
|
+
|
|
30
|
+
- If DISPLAY=:1 doesn't work, try DISPLAY=:0
|
|
31
|
+
- If xclip fails, check: `DISPLAY=:1 xclip -selection clipboard -t TARGETS -o`
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deliberation
|
|
3
|
+
description: |
|
|
4
|
+
AI 간 deliberation(토론) 세션을 관리합니다. 멀티 세션 병렬 토론 지원.
|
|
5
|
+
MCP deliberation 서버를 통해 MCP를 지원하는 모든 CLI가 구조화된 토론을 진행합니다.
|
|
6
|
+
"deliberation", "deliberate", "토론", "토론 시작", "deliberation 시작",
|
|
7
|
+
"저장소 전략 토론", "컨셉 토론", "debate" 키워드 시 자동 트리거됩니다.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# AI Deliberation 스킬 (v2.4 — Multi-Session)
|
|
11
|
+
|
|
12
|
+
Claude/Codex를 포함해 MCP를 지원하는 임의 CLI들이 구조화된 토론을 진행합니다.
|
|
13
|
+
**여러 토론을 동시에 병렬 진행할 수 있습니다.**
|
|
14
|
+
**이 스킬은 토론/합의 전용이며, 실제 구현은 `deliberation-executor`로 handoff합니다.**
|
|
15
|
+
|
|
16
|
+
## MCP 서버 위치
|
|
17
|
+
- **서버**: `~/.local/lib/mcp-deliberation/index.js` (v2.4.0)
|
|
18
|
+
- **상태**: `~/.local/lib/mcp-deliberation/state/{프로젝트명}/sessions/{session_id}.json`
|
|
19
|
+
- **등록**: 각 CLI 환경의 MCP 설정에 `deliberation` 서버 등록
|
|
20
|
+
- **브라우저 탭 스캔**: macOS 자동화 + CDP(Windows/Linux는 remote-debugging port 권장)
|
|
21
|
+
|
|
22
|
+
## 사용 가능한 MCP 도구
|
|
23
|
+
|
|
24
|
+
| 도구 | 설명 | session_id |
|
|
25
|
+
|------|------|:---:|
|
|
26
|
+
| `deliberation_start` | 새 토론 시작 → **session_id 반환** | 반환 |
|
|
27
|
+
| `deliberation_speaker_candidates` | 참가 가능한 speaker 후보 목록 조회 | 불필요 |
|
|
28
|
+
| `deliberation_list_active` | 진행 중인 모든 세션 목록 | 불필요 |
|
|
29
|
+
| `deliberation_status` | 토론 상태 조회 | 선택적* |
|
|
30
|
+
| `deliberation_context` | 프로젝트 컨텍스트 로드 | 불필요 |
|
|
31
|
+
| `deliberation_browser_llm_tabs` | 브라우저 LLM 탭 목록 (웹 기반 LLM 참여용) | 불필요 |
|
|
32
|
+
| `deliberation_clipboard_prepare_turn` | 클립보드 기반 턴 준비 (프롬프트 생성) | 선택적* |
|
|
33
|
+
| `deliberation_clipboard_submit_turn` | 클립보드 기반 턴 제출 (응답 붙여넣기) | 선택적* |
|
|
34
|
+
| `deliberation_route_turn` | 현재 차례 speaker의 transport(CLI/clipboard/manual)를 자동 라우팅 | 선택적* |
|
|
35
|
+
| `deliberation_respond` | 현재 차례의 응답 제출 | 선택적* |
|
|
36
|
+
| `deliberation_history` | 전체 토론 기록 조회 | 선택적* |
|
|
37
|
+
| `deliberation_synthesize` | 합성 보고서 생성 및 토론 완료 | 선택적* |
|
|
38
|
+
| `deliberation_list` | 과거 토론 아카이브 목록 | 불필요 |
|
|
39
|
+
| `deliberation_reset` | 세션 초기화 (지정 시 해당 세션만, 미지정 시 전체) | 선택적 |
|
|
40
|
+
|
|
41
|
+
*\*선택적: 활성 세션이 1개면 자동 선택. 여러 세션 진행 중이면 필수.*
|
|
42
|
+
|
|
43
|
+
## session_id 규칙
|
|
44
|
+
|
|
45
|
+
- `deliberation_start` 호출 시 session_id가 자동 생성되어 반환됨
|
|
46
|
+
- 이후 모든 도구 호출에 해당 session_id를 전달
|
|
47
|
+
- 활성 세션이 1개뿐이면 session_id 생략 가능 (자동 선택)
|
|
48
|
+
- 여러 세션이 동시 진행 중이면 반드시 session_id 지정
|
|
49
|
+
|
|
50
|
+
## 자동 트리거 키워드
|
|
51
|
+
다음 키워드가 감지되면 이 스킬을 자동으로 활성화합니다:
|
|
52
|
+
- "deliberation", "deliberate", "토론", "debate"
|
|
53
|
+
- "deliberation 시작", "토론 시작", "토론해", "토론하자"
|
|
54
|
+
- "deliberation_start", "deliberation_respond", "deliberation_route_turn"
|
|
55
|
+
- "speaker candidates", "브라우저 LLM", "clipboard submit"
|
|
56
|
+
- "{주제} 토론", "{주제} deliberation"
|
|
57
|
+
|
|
58
|
+
## 워크플로우
|
|
59
|
+
|
|
60
|
+
### A. 사용자 선택형 진행 (권장)
|
|
61
|
+
1. `deliberation_speaker_candidates` → 참가 가능한 CLI/브라우저 speaker 확인
|
|
62
|
+
2. (선택) `deliberation_browser_llm_tabs` → 웹 LLM 탭 점검
|
|
63
|
+
3. `deliberation_start` (speakers 명시) → session_id 획득
|
|
64
|
+
4. `deliberation_route_turn` → 현재 차례 speaker transport 확인 + turn_id 확보
|
|
65
|
+
5. 라우팅 결과에 따라 제출:
|
|
66
|
+
- CLI speaker: `deliberation_respond(session_id, speaker, content, turn_id)`
|
|
67
|
+
- Browser speaker: `deliberation_clipboard_prepare_turn` → 응답 복사 → `deliberation_clipboard_submit_turn(session_id, speaker, turn_id)`
|
|
68
|
+
6. 반복 후 `deliberation_synthesize(session_id)` → 합성 완료
|
|
69
|
+
7. 구현이 필요하면 `deliberation-executor` 스킬로 handoff
|
|
70
|
+
예: "session_id {id} 합의안 구현해줘"
|
|
71
|
+
|
|
72
|
+
### B. 병렬 세션 운영
|
|
73
|
+
1. `deliberation_start` (topic: "주제A") → session_id_A
|
|
74
|
+
2. `deliberation_start` (topic: "주제B") → session_id_B
|
|
75
|
+
3. `deliberation_list_active` → 진행 중 세션 확인
|
|
76
|
+
4. 각 세션을 `session_id`로 명시해 독립 진행
|
|
77
|
+
5. 각각 `deliberation_synthesize`로 개별 종료
|
|
78
|
+
|
|
79
|
+
### C. 자동 진행 (스크립트)
|
|
80
|
+
```bash
|
|
81
|
+
# 새 토론
|
|
82
|
+
bash auto-deliberate.sh "저장소 전략"
|
|
83
|
+
|
|
84
|
+
# 5라운드로 진행
|
|
85
|
+
bash auto-deliberate.sh "API 설계" 5
|
|
86
|
+
|
|
87
|
+
# 기존 세션 재개
|
|
88
|
+
bash auto-deliberate.sh --resume <session_id>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### D. 모니터링
|
|
92
|
+
```bash
|
|
93
|
+
# 모든 활성 세션 모니터링
|
|
94
|
+
bash deliberation-monitor.sh
|
|
95
|
+
|
|
96
|
+
# 특정 세션만
|
|
97
|
+
bash deliberation-monitor.sh <session_id>
|
|
98
|
+
|
|
99
|
+
# tmux에서
|
|
100
|
+
bash deliberation-monitor.sh --tmux
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 역할 규칙
|
|
104
|
+
|
|
105
|
+
### 역할 예시 A: 비판적 분석가
|
|
106
|
+
- 제안의 약점을 먼저 찾는다
|
|
107
|
+
- 구체적 근거와 수치를 요구한다
|
|
108
|
+
- 리스크를 명시하되 대안을 함께 제시한다
|
|
109
|
+
|
|
110
|
+
### 역할 예시 B: 현실적 실행가
|
|
111
|
+
- 실행 가능성을 우선 평가한다
|
|
112
|
+
- 구체적 기술 스택과 구현 방안을 제시한다
|
|
113
|
+
- 비용/복잡도/일정을 현실적으로 산정한다
|
|
114
|
+
|
|
115
|
+
## 응답 형식
|
|
116
|
+
|
|
117
|
+
매 턴의 응답은 다음 구조를 따릅니다:
|
|
118
|
+
|
|
119
|
+
```markdown
|
|
120
|
+
**상대 평가:** (동의/반박/보완)
|
|
121
|
+
**핵심 입장:** (구체적 제안)
|
|
122
|
+
**근거:** (2-3개)
|
|
123
|
+
**리스크/우려:** (약점 1-2개)
|
|
124
|
+
**상대에게 질문:** (1-2개)
|
|
125
|
+
**합의 가능 포인트:** (동의할 수 있는 것)
|
|
126
|
+
**미합의 포인트:** (결론 안 난 것)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## 주의사항
|
|
130
|
+
1. 여러 deliberation을 동시에 병렬 진행 가능
|
|
131
|
+
2. session_id는 `deliberation_start` 응답에서 확인
|
|
132
|
+
3. 토론 결과는 Obsidian vault에 자동 아카이브 (프로젝트 폴더 존재 시)
|
|
133
|
+
4. `deliberation-{session_id}.md`가 프로젝트 루트에 실시간 동기화됨
|
|
134
|
+
5. `Transport closed` 발생 시 현재 CLI 세션 재시작 후 재시도 (stdio 연결은 세션 바인딩)
|
|
135
|
+
6. 멀티 세션 운영 중 `pkill -f mcp-deliberation` 사용 금지 (다른 세션 연결까지 끊길 수 있음)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deliberation-executor
|
|
3
|
+
description: |
|
|
4
|
+
deliberation 합의안을 실제 구현으로 전환하는 실행 전용 스킬.
|
|
5
|
+
synthesis/결론을 작업 단위로 분해하고 코드 수정, 테스트, 검증까지 진행합니다.
|
|
6
|
+
"합의안 구현", "토론 결과 구현", "deliberation 구현", "synthesis 구현", "executor" 요청 시 사용합니다.
|
|
7
|
+
allowed-tools: Bash, Read, Edit, Write, Glob, Grep
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Deliberation Executor
|
|
11
|
+
|
|
12
|
+
토론(deliberation) 결과를 실제 코드 변경으로 연결하는 실행 스킬입니다.
|
|
13
|
+
|
|
14
|
+
## 입력 기준
|
|
15
|
+
|
|
16
|
+
다음 중 하나를 받아 구현을 시작합니다:
|
|
17
|
+
|
|
18
|
+
1. `deliberation-{session_id}.md` 파일
|
|
19
|
+
2. `deliberation_synthesize` 결과 텍스트
|
|
20
|
+
3. 사용자가 직접 붙여넣은 합의안/결론
|
|
21
|
+
|
|
22
|
+
입력이 불완전하면 즉시 확인:
|
|
23
|
+
- 목표 산출물(코드/문서/스크립트)
|
|
24
|
+
- 완료 조건(테스트 통과, 동작 확인, 품질 기준)
|
|
25
|
+
- 범위(이번 턴에서 처리할 것과 제외할 것)
|
|
26
|
+
|
|
27
|
+
## 실행 워크플로우
|
|
28
|
+
|
|
29
|
+
### 1) 합의안 -> 실행 백로그 변환
|
|
30
|
+
|
|
31
|
+
합의안을 3~8개 작업으로 분해합니다:
|
|
32
|
+
- `task_id`
|
|
33
|
+
- `목표`
|
|
34
|
+
- `영향 파일/모듈`
|
|
35
|
+
- `검증 방법`
|
|
36
|
+
- `완료 기준`
|
|
37
|
+
|
|
38
|
+
작업은 작고 독립적으로 나눕니다. 모호한 작업은 바로 구현하지 않고 먼저 구체화합니다.
|
|
39
|
+
|
|
40
|
+
### 2) 적재적소 스킬/에이전트 라우팅
|
|
41
|
+
|
|
42
|
+
요청 성격에 따라 필요한 보조 스킬을 함께 사용합니다:
|
|
43
|
+
|
|
44
|
+
- 환경변수/배포 설정/direnv: `env-manager`
|
|
45
|
+
- 스크린샷/클립보드 이미지 분석: `clipboard-image`
|
|
46
|
+
- YouTube 링크 분석/요약 근거 추출: `youtube-analyzer`
|
|
47
|
+
- 설계 불확실성이 큰 경우: 짧은 `deliberation` 세션 재개 후 합의 업데이트
|
|
48
|
+
|
|
49
|
+
기본 원칙:
|
|
50
|
+
- 구현은 이 스킬에서 진행
|
|
51
|
+
- 조사/분석성 하위 작업만 보조 스킬로 위임
|
|
52
|
+
|
|
53
|
+
### 3) 구현 루프
|
|
54
|
+
|
|
55
|
+
각 task마다 아래 순서를 지킵니다:
|
|
56
|
+
|
|
57
|
+
1. 관련 파일 탐색 (`rg`, `rg --files`)
|
|
58
|
+
2. 최소 변경으로 코드 수정
|
|
59
|
+
3. 영향 범위 테스트/검증 실행
|
|
60
|
+
4. 실패 시 원인 수정 후 재검증
|
|
61
|
+
5. task 상태 업데이트
|
|
62
|
+
|
|
63
|
+
### 4) 결과 보고 형식
|
|
64
|
+
|
|
65
|
+
최종 보고에는 반드시 포함:
|
|
66
|
+
|
|
67
|
+
- 완료된 task 목록
|
|
68
|
+
- 변경 파일 목록
|
|
69
|
+
- 실행한 검증 명령과 결과 요약
|
|
70
|
+
- 남은 리스크/미완료 task
|
|
71
|
+
- 다음 액션(필요 시)
|
|
72
|
+
|
|
73
|
+
## 품질 규칙
|
|
74
|
+
|
|
75
|
+
- 합의안에 없는 기능 확장은 금지(범위 드리프트 방지)
|
|
76
|
+
- 테스트가 있으면 반드시 실행하고 결과를 남김
|
|
77
|
+
- 테스트가 없으면 최소 실행 검증 절차를 명시
|
|
78
|
+
- 실패를 숨기지 말고 재현 가능한 형태로 보고
|
|
79
|
+
|
|
80
|
+
## 빠른 시작 예시
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
"deliberation 합의안 구현해줘. session_id는 abc123"
|
|
84
|
+
"토론 결과를 코드로 반영하고 테스트까지 돌려줘"
|
|
85
|
+
"synthesis 구현 진행하고 남은 리스크도 정리해줘"
|
|
86
|
+
```
|