@elyun/bylane 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.
@@ -0,0 +1,1821 @@
1
+ # byLane Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Claude Code 위에서 동작하는 프론트엔드 개발 자동화 하네스를 Skills 파일 세트 + Node.js 유틸리티로 구현한다.
6
+
7
+ **Architecture:** 오케스트레이터 skill이 자연어 의도를 파싱해 8개의 워커 에이전트를 순차/병렬로 호출한다. 각 에이전트는 `.bylane/state/*.json`에 상태를 기록하고, 별도 Node.js 프로세스인 모니터 대시보드가 이를 폴링해 2열 TUI로 표시한다. 모든 에이전트는 수동 단독 실행도 가능하다.
8
+
9
+ **Tech Stack:** Claude Code Skills (Markdown), Node.js 20+, blessed (TUI), Vitest (테스트), GitHub MCP, Figma MCP (선택), Slack MCP, Telegram MCP
10
+
11
+ ---
12
+
13
+ ## 파일 구조
14
+
15
+ ```
16
+ byLane/
17
+ ├── package.json # Node.js 패키지 (monitor + 유틸)
18
+ ├── src/
19
+ │ ├── state.js # 에이전트 상태 read/write
20
+ │ ├── config.js # bylane.json 로드/검증/저장
21
+ │ ├── branch.js # 브랜치명 패턴 엔진
22
+ │ └── monitor/
23
+ │ ├── index.js # TUI 엔트리포인트 (bin)
24
+ │ ├── layout.js # blessed 레이아웃 정의
25
+ │ ├── poller.js # state 파일 폴링 (1초)
26
+ │ └── panels/
27
+ │ ├── header.js # 헤더 패널
28
+ │ ├── pipeline.js # Agent Pipeline 패널 (좌상)
29
+ │ ├── log.js # Agent Log 패널 (우상)
30
+ │ ├── queue.js # Queue 패널 (좌하)
31
+ │ └── status.js # System Status 패널 (우하)
32
+ ├── tests/
33
+ │ ├── state.test.js
34
+ │ ├── config.test.js
35
+ │ └── branch.test.js
36
+ ├── skills/
37
+ │ ├── orchestrator.md # 전체 워크플로우 총괄
38
+ │ ├── setup.md # 셋업 위자드
39
+ │ ├── issue-agent.md
40
+ │ ├── code-agent.md
41
+ │ ├── test-agent.md
42
+ │ ├── commit-agent.md
43
+ │ ├── pr-agent.md
44
+ │ ├── review-agent.md
45
+ │ ├── respond-agent.md
46
+ │ └── notify-agent.md
47
+ ├── hooks/
48
+ │ └── post-tool-use.md # 외부 이벤트 감지
49
+ ├── commands/
50
+ │ ├── bylane.md # /bylane 메인 커맨드
51
+ │ └── bylane-monitor.md # /bylane monitor 대시보드
52
+ └── CLAUDE.md
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Phase 1: 프로젝트 스캐폴드 + 유틸리티 기반
58
+
59
+ ### Task 1: package.json 및 프로젝트 초기화
60
+
61
+ **Files:**
62
+ - Create: `package.json`
63
+ - Create: `src/state.js`
64
+ - Create: `src/config.js`
65
+ - Create: `src/branch.js`
66
+
67
+ - [ ] **Step 1: package.json 작성**
68
+
69
+ ```json
70
+ {
71
+ "name": "bylane",
72
+ "version": "1.0.0",
73
+ "description": "Frontend development harness for Claude Code",
74
+ "type": "module",
75
+ "bin": {
76
+ "bylane-monitor": "./src/monitor/index.js"
77
+ },
78
+ "scripts": {
79
+ "monitor": "node src/monitor/index.js",
80
+ "test": "vitest run",
81
+ "test:watch": "vitest"
82
+ },
83
+ "dependencies": {
84
+ "blessed": "^0.1.81",
85
+ "chokidar": "^3.6.0"
86
+ },
87
+ "devDependencies": {
88
+ "vitest": "^1.6.0"
89
+ }
90
+ }
91
+ ```
92
+
93
+ - [ ] **Step 2: 디렉토리 생성**
94
+
95
+ ```bash
96
+ mkdir -p src/monitor/panels tests skills hooks commands .bylane/state
97
+ ```
98
+
99
+ - [ ] **Step 3: npm install**
100
+
101
+ ```bash
102
+ npm install
103
+ ```
104
+
105
+ Expected: `node_modules/` 생성, `blessed`, `chokidar`, `vitest` 설치됨
106
+
107
+ - [ ] **Step 4: Commit**
108
+
109
+ ```bash
110
+ git add package.json package-lock.json
111
+ git commit -m "chore: 프로젝트 초기화 및 의존성 설치"
112
+ ```
113
+
114
+ ---
115
+
116
+ ### Task 2: 상태 관리 모듈 (`src/state.js`)
117
+
118
+ **Files:**
119
+ - Create: `tests/state.test.js`
120
+ - Create: `src/state.js`
121
+
122
+ - [ ] **Step 1: 실패 테스트 작성**
123
+
124
+ ```js
125
+ // tests/state.test.js
126
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest'
127
+ import { readState, writeState, clearState, listStates } from '../src/state.js'
128
+ import { mkdirSync, rmSync, existsSync } from 'fs'
129
+ import { join } from 'path'
130
+
131
+ const TEST_DIR = '.bylane-test/state'
132
+
133
+ beforeEach(() => mkdirSync(TEST_DIR, { recursive: true }))
134
+ afterEach(() => rmSync('.bylane-test', { recursive: true, force: true }))
135
+
136
+ describe('writeState', () => {
137
+ it('에이전트 상태를 JSON 파일에 저장한다', () => {
138
+ writeState('code-agent', { status: 'in_progress', progress: 50 }, TEST_DIR)
139
+ const result = readState('code-agent', TEST_DIR)
140
+ expect(result.status).toBe('in_progress')
141
+ expect(result.progress).toBe(50)
142
+ })
143
+ })
144
+
145
+ describe('readState', () => {
146
+ it('존재하지 않는 에이전트는 null을 반환한다', () => {
147
+ const result = readState('nonexistent', TEST_DIR)
148
+ expect(result).toBeNull()
149
+ })
150
+
151
+ it('저장된 상태에 agent 이름과 updatedAt이 포함된다', () => {
152
+ writeState('issue-agent', { status: 'completed' }, TEST_DIR)
153
+ const result = readState('issue-agent', TEST_DIR)
154
+ expect(result.agent).toBe('issue-agent')
155
+ expect(result.updatedAt).toBeDefined()
156
+ })
157
+ })
158
+
159
+ describe('clearState', () => {
160
+ it('특정 에이전트 상태 파일을 삭제한다', () => {
161
+ writeState('pr-agent', { status: 'idle' }, TEST_DIR)
162
+ clearState('pr-agent', TEST_DIR)
163
+ expect(readState('pr-agent', TEST_DIR)).toBeNull()
164
+ })
165
+ })
166
+
167
+ describe('listStates', () => {
168
+ it('모든 에이전트 상태 목록을 반환한다', () => {
169
+ writeState('code-agent', { status: 'completed' }, TEST_DIR)
170
+ writeState('test-agent', { status: 'idle' }, TEST_DIR)
171
+ const list = listStates(TEST_DIR)
172
+ expect(list).toHaveLength(2)
173
+ expect(list.map(s => s.agent)).toContain('code-agent')
174
+ })
175
+ })
176
+ ```
177
+
178
+ - [ ] **Step 2: 테스트 실패 확인**
179
+
180
+ ```bash
181
+ npm test -- tests/state.test.js
182
+ ```
183
+
184
+ Expected: FAIL — `Cannot find module '../src/state.js'`
185
+
186
+ - [ ] **Step 3: state.js 구현**
187
+
188
+ ```js
189
+ // src/state.js
190
+ import { readFileSync, writeFileSync, unlinkSync, readdirSync, existsSync } from 'fs'
191
+ import { join } from 'path'
192
+
193
+ const DEFAULT_DIR = '.bylane/state'
194
+
195
+ export function writeState(agentName, data, dir = DEFAULT_DIR) {
196
+ const payload = {
197
+ ...data,
198
+ agent: agentName,
199
+ updatedAt: new Date().toISOString(),
200
+ log: data.log ?? []
201
+ }
202
+ writeFileSync(join(dir, `${agentName}.json`), JSON.stringify(payload, null, 2))
203
+ }
204
+
205
+ export function readState(agentName, dir = DEFAULT_DIR) {
206
+ const path = join(dir, `${agentName}.json`)
207
+ if (!existsSync(path)) return null
208
+ try {
209
+ return JSON.parse(readFileSync(path, 'utf8'))
210
+ } catch {
211
+ return null
212
+ }
213
+ }
214
+
215
+ export function clearState(agentName, dir = DEFAULT_DIR) {
216
+ const path = join(dir, `${agentName}.json`)
217
+ if (existsSync(path)) unlinkSync(path)
218
+ }
219
+
220
+ export function listStates(dir = DEFAULT_DIR) {
221
+ if (!existsSync(dir)) return []
222
+ return readdirSync(dir)
223
+ .filter(f => f.endsWith('.json'))
224
+ .map(f => readState(f.replace('.json', ''), dir))
225
+ .filter(Boolean)
226
+ }
227
+
228
+ export function appendLog(agentName, message, dir = DEFAULT_DIR) {
229
+ const state = readState(agentName, dir) ?? { agent: agentName, status: 'idle', log: [] }
230
+ const entry = { ts: new Date().toISOString(), msg: message }
231
+ writeState(agentName, { ...state, log: [...(state.log ?? []), entry] }, dir)
232
+ }
233
+ ```
234
+
235
+ - [ ] **Step 4: 테스트 통과 확인**
236
+
237
+ ```bash
238
+ npm test -- tests/state.test.js
239
+ ```
240
+
241
+ Expected: PASS — 5 tests passed
242
+
243
+ - [ ] **Step 5: Commit**
244
+
245
+ ```bash
246
+ git add src/state.js tests/state.test.js
247
+ git commit -m "feat: 에이전트 상태 관리 모듈 구현"
248
+ ```
249
+
250
+ ---
251
+
252
+ ### Task 3: 설정 모듈 (`src/config.js`)
253
+
254
+ **Files:**
255
+ - Create: `tests/config.test.js`
256
+ - Create: `src/config.js`
257
+
258
+ - [ ] **Step 1: 실패 테스트 작성**
259
+
260
+ ```js
261
+ // tests/config.test.js
262
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest'
263
+ import { loadConfig, saveConfig, validateConfig, DEFAULT_CONFIG } from '../src/config.js'
264
+ import { mkdirSync, rmSync, writeFileSync } from 'fs'
265
+
266
+ const TEST_DIR = '.bylane-test'
267
+
268
+ beforeEach(() => mkdirSync(TEST_DIR, { recursive: true }))
269
+ afterEach(() => rmSync(TEST_DIR, { recursive: true, force: true }))
270
+
271
+ describe('DEFAULT_CONFIG', () => {
272
+ it('기본 maxRetries는 3이다', () => {
273
+ expect(DEFAULT_CONFIG.workflow.maxRetries).toBe(3)
274
+ })
275
+
276
+ it('기본 primary tracker는 github이다', () => {
277
+ expect(DEFAULT_CONFIG.trackers.primary).toBe('github')
278
+ })
279
+ })
280
+
281
+ describe('loadConfig', () => {
282
+ it('파일이 없으면 DEFAULT_CONFIG를 반환한다', () => {
283
+ const config = loadConfig(TEST_DIR)
284
+ expect(config.workflow.maxRetries).toBe(3)
285
+ })
286
+
287
+ it('존재하는 설정 파일을 로드한다', () => {
288
+ writeFileSync(`${TEST_DIR}/bylane.json`, JSON.stringify({
289
+ ...DEFAULT_CONFIG,
290
+ workflow: { ...DEFAULT_CONFIG.workflow, maxRetries: 5 }
291
+ }))
292
+ const config = loadConfig(TEST_DIR)
293
+ expect(config.workflow.maxRetries).toBe(5)
294
+ })
295
+ })
296
+
297
+ describe('saveConfig', () => {
298
+ it('설정을 bylane.json에 저장한다', () => {
299
+ saveConfig({ ...DEFAULT_CONFIG }, TEST_DIR)
300
+ const loaded = loadConfig(TEST_DIR)
301
+ expect(loaded.version).toBe('1.0')
302
+ })
303
+ })
304
+
305
+ describe('validateConfig', () => {
306
+ it('유효한 설정은 에러가 없다', () => {
307
+ const errors = validateConfig(DEFAULT_CONFIG)
308
+ expect(errors).toHaveLength(0)
309
+ })
310
+
311
+ it('maxRetries가 숫자가 아니면 에러를 반환한다', () => {
312
+ const bad = { ...DEFAULT_CONFIG, workflow: { ...DEFAULT_CONFIG.workflow, maxRetries: 'abc' } }
313
+ const errors = validateConfig(bad)
314
+ expect(errors.length).toBeGreaterThan(0)
315
+ })
316
+ })
317
+ ```
318
+
319
+ - [ ] **Step 2: 테스트 실패 확인**
320
+
321
+ ```bash
322
+ npm test -- tests/config.test.js
323
+ ```
324
+
325
+ Expected: FAIL — `Cannot find module '../src/config.js'`
326
+
327
+ - [ ] **Step 3: config.js 구현**
328
+
329
+ ```js
330
+ // src/config.js
331
+ import { readFileSync, writeFileSync, existsSync } from 'fs'
332
+ import { join } from 'path'
333
+
334
+ export const DEFAULT_CONFIG = {
335
+ version: '1.0',
336
+ trackers: {
337
+ primary: 'github',
338
+ linear: { enabled: false, apiKey: '' }
339
+ },
340
+ notifications: {
341
+ slack: { enabled: false, channel: '' },
342
+ telegram: { enabled: false, chatId: '' }
343
+ },
344
+ team: {
345
+ enabled: false,
346
+ members: [],
347
+ reviewAssignment: 'round-robin'
348
+ },
349
+ permissions: {
350
+ scope: 'write',
351
+ allowMerge: false,
352
+ allowForceClose: false
353
+ },
354
+ workflow: {
355
+ maxRetries: 3,
356
+ loopTimeoutMinutes: 30,
357
+ autoEscalate: true
358
+ },
359
+ branch: {
360
+ pattern: '{tracker}-{issue-number}',
361
+ tokens: { tracker: 'issues', type: 'feature', 'custom-id': '' },
362
+ separator: '-',
363
+ caseStyle: 'kebab-case'
364
+ },
365
+ extensions: {
366
+ figma: { enabled: false, useAt: 'issue-analysis' }
367
+ }
368
+ }
369
+
370
+ export function loadConfig(dir = '.bylane') {
371
+ const path = join(dir, 'bylane.json')
372
+ if (!existsSync(path)) return { ...DEFAULT_CONFIG }
373
+ try {
374
+ return JSON.parse(readFileSync(path, 'utf8'))
375
+ } catch {
376
+ return { ...DEFAULT_CONFIG }
377
+ }
378
+ }
379
+
380
+ export function saveConfig(config, dir = '.bylane') {
381
+ const path = join(dir, 'bylane.json')
382
+ writeFileSync(path, JSON.stringify(config, null, 2))
383
+ }
384
+
385
+ export function validateConfig(config) {
386
+ const errors = []
387
+ if (typeof config.workflow?.maxRetries !== 'number') {
388
+ errors.push('workflow.maxRetries must be a number')
389
+ }
390
+ if (!['github', 'linear', 'both'].includes(config.trackers?.primary)) {
391
+ errors.push('trackers.primary must be "github", "linear", or "both"')
392
+ }
393
+ if (!['read-only', 'write', 'full'].includes(config.permissions?.scope)) {
394
+ errors.push('permissions.scope must be "read-only", "write", or "full"')
395
+ }
396
+ return errors
397
+ }
398
+ ```
399
+
400
+ - [ ] **Step 4: 테스트 통과 확인**
401
+
402
+ ```bash
403
+ npm test -- tests/config.test.js
404
+ ```
405
+
406
+ Expected: PASS — 6 tests passed
407
+
408
+ - [ ] **Step 5: Commit**
409
+
410
+ ```bash
411
+ git add src/config.js tests/config.test.js
412
+ git commit -m "feat: bylane.json 설정 로드/저장/검증 모듈 구현"
413
+ ```
414
+
415
+ ---
416
+
417
+ ### Task 4: 브랜치 네이밍 엔진 (`src/branch.js`)
418
+
419
+ **Files:**
420
+ - Create: `tests/branch.test.js`
421
+ - Create: `src/branch.js`
422
+
423
+ - [ ] **Step 1: 실패 테스트 작성**
424
+
425
+ ```js
426
+ // tests/branch.test.js
427
+ import { describe, it, expect } from 'vitest'
428
+ import { buildBranchName } from '../src/branch.js'
429
+
430
+ describe('buildBranchName', () => {
431
+ it('{tracker}-{issue-number} 패턴 기본 케이스', () => {
432
+ const result = buildBranchName(
433
+ '{tracker}-{issue-number}',
434
+ { tracker: 'issues', 'issue-number': '32' }
435
+ )
436
+ expect(result).toBe('issues-32')
437
+ })
438
+
439
+ it('{custom-id}가 비어있으면 해당 토큰과 앞 구분자를 제외한다', () => {
440
+ const result = buildBranchName(
441
+ '{tracker}-{issue-number}-{custom-id}',
442
+ { tracker: 'issues', 'issue-number': '32', 'custom-id': '' }
443
+ )
444
+ expect(result).toBe('issues-32')
445
+ })
446
+
447
+ it('{custom-id}가 있으면 포함한다', () => {
448
+ const result = buildBranchName(
449
+ '{tracker}-{issue-number}-{custom-id}',
450
+ { tracker: 'issues', 'issue-number': '32', 'custom-id': 'C-12' }
451
+ )
452
+ expect(result).toBe('issues-32-C-12')
453
+ })
454
+
455
+ it('{type}/{issue-number}-{title-slug} 패턴', () => {
456
+ const result = buildBranchName(
457
+ '{type}/{issue-number}-{title-slug}',
458
+ { type: 'feature', 'issue-number': '32', 'title-slug': 'add-dark-mode' }
459
+ )
460
+ expect(result).toBe('feature/32-add-dark-mode')
461
+ })
462
+
463
+ it('kebab-case 변환: 공백을 하이픈으로', () => {
464
+ const result = buildBranchName(
465
+ '{type}-{title-slug}',
466
+ { type: 'feat', 'title-slug': 'Add Dark Mode' },
467
+ 'kebab-case'
468
+ )
469
+ expect(result).toBe('feat-add-dark-mode')
470
+ })
471
+ })
472
+ ```
473
+
474
+ - [ ] **Step 2: 테스트 실패 확인**
475
+
476
+ ```bash
477
+ npm test -- tests/branch.test.js
478
+ ```
479
+
480
+ Expected: FAIL — `Cannot find module '../src/branch.js'`
481
+
482
+ - [ ] **Step 3: branch.js 구현**
483
+
484
+ ```js
485
+ // src/branch.js
486
+ export function buildBranchName(pattern, tokens, caseStyle = 'kebab-case') {
487
+ // 빈 토큰과 앞의 구분자(-) 제거: -token이 비어있으면 -token 전체를 제거
488
+ let result = pattern
489
+ for (const [key, value] of Object.entries(tokens)) {
490
+ if (!value) {
491
+ // 해당 토큰과 앞의 구분자 제거 (-, _ 등)
492
+ result = result.replace(new RegExp(`[-_]\\{${key}\\}`), '')
493
+ result = result.replace(new RegExp(`\\{${key}\\}[-_]?`), '')
494
+ } else {
495
+ result = result.replace(new RegExp(`\\{${key}\\}`, 'g'), value)
496
+ }
497
+ }
498
+ // 잔여 미치환 토큰 제거
499
+ result = result.replace(/\{[^}]+\}/g, '')
500
+ // 중복 구분자 정리
501
+ result = result.replace(/[-_]{2,}/g, '-').replace(/^[-_]|[-_]$/g, '')
502
+ if (caseStyle === 'kebab-case') {
503
+ result = result.replace(/\s+/g, '-').toLowerCase()
504
+ }
505
+ return result
506
+ }
507
+
508
+ export function buildBranchNameFromConfig(config, issueNumber, extra = {}) {
509
+ const tokens = {
510
+ ...config.branch.tokens,
511
+ 'issue-number': String(issueNumber),
512
+ ...extra
513
+ }
514
+ return buildBranchName(config.branch.pattern, tokens, config.branch.caseStyle)
515
+ }
516
+ ```
517
+
518
+ - [ ] **Step 4: 테스트 통과 확인**
519
+
520
+ ```bash
521
+ npm test -- tests/branch.test.js
522
+ ```
523
+
524
+ Expected: PASS — 5 tests passed
525
+
526
+ - [ ] **Step 5: Commit**
527
+
528
+ ```bash
529
+ git add src/branch.js tests/branch.test.js
530
+ git commit -m "feat: 브랜치 네이밍 패턴 엔진 구현"
531
+ ```
532
+
533
+ ---
534
+
535
+ ## Phase 2: 셋업 위자드 Skill
536
+
537
+ ### Task 5: 셋업 위자드 스킬 파일 (`skills/setup.md`)
538
+
539
+ **Files:**
540
+ - Create: `skills/setup.md`
541
+
542
+ - [ ] **Step 1: setup.md 작성**
543
+
544
+ ```markdown
545
+ ---
546
+ name: bylane-setup
547
+ description: byLane 하네스 최초 설치 및 설정 위자드. /bylane setup 으로 실행.
548
+ ---
549
+
550
+ # byLane Setup Wizard
551
+
552
+ 사용자에게 단계별로 질문하여 `.bylane/bylane.json`을 생성한다.
553
+ ALWAYS complete all 6 steps before saving. NEVER skip steps.
554
+
555
+ ## 실행 전 준비
556
+
557
+ 1. `.bylane/` 디렉토리가 없으면 생성:
558
+ ```bash
559
+ mkdir -p .bylane/state
560
+ ```
561
+
562
+ 2. 기존 bylane.json 있으면 현재 설정을 로드해 기본값으로 사용.
563
+
564
+ ## Step 1/6 — 이슈 트래커
565
+
566
+ 사용자에게 묻는다:
567
+
568
+ > 주 이슈 트래커를 선택하세요:
569
+ > 1. GitHub Issues (권장)
570
+ > 2. Linear
571
+ > 3. 둘 다
572
+
573
+ - `1` → `trackers.primary = "github"`, `linear.enabled = false`
574
+ - `2` → `trackers.primary = "linear"`, Linear API Key 입력 요청
575
+ - `3` → `trackers.primary = "both"`, Linear API Key 입력 요청
576
+
577
+ Linear API Key는 환경변수명(`LINEAR_API_KEY`)으로 저장. 실제 키값은 저장하지 않는다.
578
+
579
+ ## Step 2/6 — 알림 채널
580
+
581
+ > 완료 알림을 받을 채널을 선택하세요:
582
+ > 1. Slack
583
+ > 2. Telegram
584
+ > 3. 둘 다
585
+ > 4. 건너뜀
586
+
587
+ - Slack 선택 시: 채널명 입력 (예: `#dev-alerts`)
588
+ - Telegram 선택 시: Chat ID 입력 방법 안내 후 입력받기
589
+ - 건너뜀 → 알림 비활성화
590
+
591
+ ## Step 3/6 — 팀 모드
592
+
593
+ > 팀 모드를 활성화하시겠습니까? (y/n)
594
+
595
+ - `y` → 팀원 GitHub 핸들 입력 (쉼표 구분, 예: `@alice, @bob`)
596
+ - 리뷰 할당 방식 질문: `1. round-robin 2. random`
597
+ - `n` → `team.enabled = false`
598
+
599
+ ## Step 4/6 — 권한 범위
600
+
601
+ > Claude가 자동으로 수행할 수 있는 작업 범위를 선택하세요:
602
+ > 1. read-only (분석/리뷰만, 코드 변경 없음)
603
+ > 2. write (코드 작성 + PR 생성, 머지 제외) ← 권장
604
+ > 3. full (머지까지 포함)
605
+
606
+ `permissions.scope`에 저장.
607
+
608
+ ## Step 5/6 — 고급 설정
609
+
610
+ > 고급 설정을 변경하시겠습니까? (Enter = 기본값 사용)
611
+
612
+ 각 항목을 순서대로 묻는다. Enter 입력 시 기본값 유지:
613
+ - `maxRetries` (기본: 3): 에이전트 재시도 최대 횟수
614
+ - `loopTimeoutMinutes` (기본: 30): 루프 타임아웃 (분)
615
+ - Figma MCP 활성화? (y/n, 기본: n)
616
+
617
+ ## Step 6/6 — 브랜치 네이밍
618
+
619
+ > 브랜치 네이밍 패턴을 선택하세요:
620
+ > 1. {tracker}-{issue-number} 예) issues-32
621
+ > 2. {tracker}-{issue-number}-{custom-id} 예) issues-32-C-12
622
+ > 3. {type}/{issue-number}-{title-slug} 예) feature/32-add-dark-mode
623
+ > 4. 직접 입력
624
+
625
+ 사전 정의 패턴 선택 시 해당 토큰 기본값 확인:
626
+ - `tracker` 기본값: `issues`
627
+ - `type` 기본값: `feature`
628
+
629
+ 직접 입력 시 사용 가능한 토큰 목록 안내:
630
+ `{tracker}`, `{type}`, `{issue-number}`, `{custom-id}`, `{title-slug}`, `{date}`, `{username}`
631
+
632
+ ## 저장
633
+
634
+ 모든 설정 수집 후:
635
+ 1. `src/config.js`의 `saveConfig()` 패턴으로 `.bylane/bylane.json` 저장:
636
+ ```bash
637
+ node -e "
638
+ import('./src/config.js').then(({saveConfig}) => {
639
+ saveConfig(CONFIG_OBJECT)
640
+ console.log('✓ .bylane/bylane.json 저장됨')
641
+ })
642
+ "
643
+ ```
644
+ 2. 설정 요약을 사용자에게 출력한다.
645
+ 3. `bylane-monitor` 실행 방법 안내: `npm run monitor` 또는 `/bylane monitor`
646
+ ```
647
+
648
+ - [ ] **Step 2: setup.md 검토 — 모든 6단계 커버 여부 확인**
649
+
650
+ 각 단계(1~6)가 모두 포함되어 있고, 저장 로직이 명확한지 검토한다. 누락된 항목 없음.
651
+
652
+ - [ ] **Step 3: Commit**
653
+
654
+ ```bash
655
+ git add skills/setup.md
656
+ git commit -m "feat: byLane 셋업 위자드 스킬 작성"
657
+ ```
658
+
659
+ ---
660
+
661
+ ## Phase 3: 에이전트 Skills
662
+
663
+ ### Task 6: 오케스트레이터 스킬 (`skills/orchestrator.md`)
664
+
665
+ **Files:**
666
+ - Create: `skills/orchestrator.md`
667
+
668
+ - [ ] **Step 1: orchestrator.md 작성**
669
+
670
+ ```markdown
671
+ ---
672
+ name: bylane-orchestrator
673
+ description: byLane 메인 오케스트레이터. 자연어 의도를 파싱해 에이전트 파이프라인을 실행한다.
674
+ ---
675
+
676
+ # byLane Orchestrator
677
+
678
+ ## 역할
679
+
680
+ 사용자의 자연어 입력을 파싱하여 어떤 에이전트를 어떤 순서로 실행할지 결정한다.
681
+
682
+ ## 실행 전 체크
683
+
684
+ 1. `.bylane/bylane.json` 로드. 없으면 즉시 `bylane-setup` 스킬 실행.
685
+ 2. `.bylane/state/` 디렉토리 확인. 없으면 생성.
686
+
687
+ ## 의도 파싱 규칙
688
+
689
+ 입력을 분석하여 아래 중 하나로 분류:
690
+
691
+ | 패턴 | 실행할 에이전트 체인 |
692
+ |---|---|
693
+ | "구현", "만들어", "추가해", 이슈 없음 | issue-agent → code-agent → test-agent → commit-agent → pr-agent → review-agent → notify-agent |
694
+ | "issue #N 구현", "이슈 #N 작업" | issue-agent(분석) → code-agent → test-agent → commit-agent → pr-agent → review-agent → notify-agent |
695
+ | "PR #N 리뷰", "리뷰해줘" | review-agent(PR번호 전달) |
696
+ | "리뷰 #N 반영", "리뷰 수락" | respond-agent(PR번호, 모드=accept 전달) |
697
+ | "리뷰 #N 반박" | respond-agent(PR번호, 모드=rebut 전달) |
698
+ | "커밋해줘" | commit-agent |
699
+ | "PR 만들어줘" | pr-agent |
700
+ | "테스트해줘" | test-agent |
701
+
702
+ ## 에이전트 실행 방법
703
+
704
+ 각 에이전트를 순서대로 Agent 도구로 호출한다. 이전 에이전트의 출력을 다음 에이전트의 입력으로 전달한다.
705
+
706
+ 상태 기록 (각 에이전트 시작 전):
707
+ ```bash
708
+ node -e "
709
+ import('./src/state.js').then(({writeState}) => {
710
+ writeState('AGENT_NAME', {
711
+ status: 'in_progress',
712
+ startedAt: new Date().toISOString(),
713
+ progress: 0,
714
+ currentTask: 'TASK_DESCRIPTION',
715
+ retries: 0,
716
+ log: []
717
+ })
718
+ })
719
+ "
720
+ ```
721
+
722
+ ## 피드백 루프
723
+
724
+ test-agent가 FAIL을 반환하면:
725
+ 1. `retries` 값을 읽는다.
726
+ 2. `retries < config.workflow.maxRetries`이면 code-agent를 재실행 (실패 피드백 포함).
727
+ 3. `retries >= maxRetries`이면 notify-agent에 "개입 필요" 메시지 전송 후 중단.
728
+
729
+ respond-agent가 "수정 필요"를 반환하면 동일 로직 적용.
730
+
731
+ ## 완료 처리
732
+
733
+ 모든 에이전트 완료 후:
734
+ 1. 각 에이전트 state를 `status: "completed"`로 업데이트
735
+ 2. notify-agent 실행하여 최종 결과 전송
736
+ ```
737
+
738
+ - [ ] **Step 2: Commit**
739
+
740
+ ```bash
741
+ git add skills/orchestrator.md
742
+ git commit -m "feat: 오케스트레이터 스킬 작성"
743
+ ```
744
+
745
+ ---
746
+
747
+ ### Task 7: 이슈 에이전트 스킬 (`skills/issue-agent.md`)
748
+
749
+ **Files:**
750
+ - Create: `skills/issue-agent.md`
751
+
752
+ - [ ] **Step 1: issue-agent.md 작성**
753
+
754
+ ```markdown
755
+ ---
756
+ name: bylane-issue-agent
757
+ description: GitHub Issue 생성 및 분석. Figma 링크 감지 시 스펙 추출.
758
+ ---
759
+
760
+ # Issue Agent
761
+
762
+ ## 입력
763
+
764
+ - 자유 텍스트 (새 이슈 생성용) OR GitHub Issue 번호 (#N)
765
+
766
+ ## 실행 흐름
767
+
768
+ ### 새 이슈 생성 모드 (텍스트 입력)
769
+
770
+ 1. 입력 텍스트에서 다음을 추출:
771
+ - 제목 (50자 이내)
772
+ - 상세 설명
773
+ - 구현 체크리스트 (예상 가능한 경우)
774
+ - Figma URL (있는 경우)
775
+
776
+ 2. GitHub MCP로 이슈 생성:
777
+ - `title`: 추출된 제목
778
+ - `body`: Markdown 형식 설명 + 체크리스트
779
+ - `labels`: `bylane-auto` 라벨 추가
780
+
781
+ 3. Figma MCP 활성화 여부 확인 (`.bylane/bylane.json` → `extensions.figma.enabled`):
782
+ - `true`이고 Figma URL이 있으면 → **Figma 분석** 단계 실행
783
+ - `false`이면 → 텍스트 기반 스펙만 생성
784
+
785
+ ### 기존 이슈 분석 모드 (Issue 번호 입력)
786
+
787
+ 1. GitHub MCP로 이슈 내용 로드
788
+ 2. 본문에서 Figma URL 추출 시도
789
+ 3. 스펙 생성 (아래 참조)
790
+
791
+ ### Figma 분석 (활성화된 경우)
792
+
793
+ Figma MCP `get_file` 또는 `get_node` 도구로 해당 프레임/컴포넌트 분석:
794
+ - 컴포넌트 계층 구조
795
+ - 색상 토큰
796
+ - 타이포그래피
797
+ - 스페이싱 값
798
+
799
+ 추출 결과를 스펙에 포함.
800
+
801
+ ## 출력: 구현 스펙 JSON
802
+
803
+ `.bylane/state/issue-agent.json`에 저장:
804
+
805
+ ```json
806
+ {
807
+ "agent": "issue-agent",
808
+ "status": "completed",
809
+ "issueNumber": 123,
810
+ "issueUrl": "https://github.com/...",
811
+ "spec": {
812
+ "title": "다크모드 토글 버튼 추가",
813
+ "description": "...",
814
+ "checklist": ["ThemeToggle 컴포넌트 생성", "useTheme hook 구현"],
815
+ "figmaSpec": {
816
+ "enabled": true,
817
+ "components": [...],
818
+ "colorTokens": {...}
819
+ }
820
+ }
821
+ }
822
+ ```
823
+
824
+ ## 수동 실행
825
+
826
+ `/bylane issue #123` 또는 `/bylane issue 다크모드 토글 추가해줘`
827
+ ```
828
+
829
+ - [ ] **Step 2: Commit**
830
+
831
+ ```bash
832
+ git add skills/issue-agent.md
833
+ git commit -m "feat: issue-agent 스킬 작성 (Figma MCP 포함)"
834
+ ```
835
+
836
+ ---
837
+
838
+ ### Task 8: 코드/테스트/커밋 에이전트 스킬
839
+
840
+ **Files:**
841
+ - Create: `skills/code-agent.md`
842
+ - Create: `skills/test-agent.md`
843
+ - Create: `skills/commit-agent.md`
844
+
845
+ - [ ] **Step 1: code-agent.md 작성**
846
+
847
+ ```markdown
848
+ ---
849
+ name: bylane-code-agent
850
+ description: issue-agent의 스펙을 기반으로 프론트엔드 코드를 구현한다.
851
+ ---
852
+
853
+ # Code Agent
854
+
855
+ ## 입력
856
+
857
+ `.bylane/state/issue-agent.json`에서 `spec` 읽기. 없으면 사용자에게 스펙 텍스트 직접 입력 요청.
858
+
859
+ ## 실행 흐름
860
+
861
+ 1. 스펙의 `checklist` 항목을 순서대로 구현
862
+ 2. Figma 스펙이 있으면 (`spec.figmaSpec.enabled === true`):
863
+ - `colorTokens`를 CSS 변수 또는 Tailwind config 값으로 변환
864
+ - 컴포넌트 구조를 Figma 계층에 맞게 구현
865
+ 3. 기존 코드베이스 패턴 파악 후 동일 스타일로 작성 (TypeScript, 테스트 파일 위치 등)
866
+ 4. 구현 완료 후 상태 업데이트:
867
+ ```bash
868
+ node -e "import('./src/state.js').then(({writeState})=>writeState('code-agent',{status:'completed',progress:100,changedFiles:CHANGED_FILES_ARRAY}))"
869
+ ```
870
+
871
+ ## 코딩 원칙
872
+
873
+ - 함수형 컴포넌트 + hooks 우선
874
+ - 파일당 단일 책임
875
+ - 200줄 초과 시 분리 고려
876
+ - 불변성 패턴 유지
877
+
878
+ ## 수동 실행
879
+
880
+ `/bylane code #123`
881
+ ```
882
+
883
+ - [ ] **Step 2: test-agent.md 작성**
884
+
885
+ ```markdown
886
+ ---
887
+ name: bylane-test-agent
888
+ description: 변경된 코드의 테스트를 실행하고 결과를 반환한다.
889
+ ---
890
+
891
+ # Test Agent
892
+
893
+ ## 입력
894
+
895
+ `.bylane/state/code-agent.json`의 `changedFiles` 배열
896
+
897
+ ## 실행 흐름
898
+
899
+ 1. 변경 파일 목록 확인
900
+ 2. 관련 테스트 파일 탐지 (`*.test.ts`, `*.spec.ts`, `*.test.tsx`)
901
+ 3. 테스트 실행:
902
+ ```bash
903
+ npx vitest run --reporter=verbose 2>&1
904
+ ```
905
+ 또는 프로젝트의 테스트 커맨드 사용 (`package.json` → `scripts.test`)
906
+
907
+ 4. 결과 파싱:
908
+ - 모두 통과 → `status: "passed"` 저장 후 commit-agent로 진행
909
+ - 실패 있음 → `status: "failed"`, `failureDetails` 포함 저장 후 orchestrator에 반환
910
+
911
+ ## 출력
912
+
913
+ `.bylane/state/test-agent.json`:
914
+ ```json
915
+ {
916
+ "agent": "test-agent",
917
+ "status": "passed",
918
+ "totalTests": 12,
919
+ "passed": 12,
920
+ "failed": 0,
921
+ "failureDetails": []
922
+ }
923
+ ```
924
+
925
+ ## 수동 실행
926
+
927
+ `/bylane test`
928
+ ```
929
+
930
+ - [ ] **Step 3: commit-agent.md 작성**
931
+
932
+ ```markdown
933
+ ---
934
+ name: bylane-commit-agent
935
+ description: 변경된 파일들을 conventional commit 형식으로 커밋한다.
936
+ ---
937
+
938
+ # Commit Agent
939
+
940
+ ## 입력
941
+
942
+ `.bylane/state/code-agent.json`의 `changedFiles`
943
+ `.bylane/state/issue-agent.json`의 `spec.title`, `issueNumber`
944
+
945
+ ## 실행 흐름
946
+
947
+ 1. 브랜치명 생성:
948
+ ```bash
949
+ node -e "
950
+ import('./src/branch.js').then(({buildBranchNameFromConfig}) => {
951
+ import('./src/config.js').then(({loadConfig}) => {
952
+ const config = loadConfig()
953
+ const branch = buildBranchNameFromConfig(config, ISSUE_NUMBER)
954
+ console.log(branch)
955
+ })
956
+ })
957
+ "
958
+ ```
959
+
960
+ 2. 브랜치 생성 및 체크아웃:
961
+ ```bash
962
+ git checkout -b BRANCH_NAME
963
+ ```
964
+
965
+ 3. 변경 파일 스테이징:
966
+ ```bash
967
+ git add CHANGED_FILES
968
+ ```
969
+
970
+ 4. 커밋 메시지 생성 규칙:
971
+ - `feat: ` + 스펙 제목 (신규 기능)
972
+ - `fix: ` + 스펙 제목 (버그 수정)
973
+ - 본문에 `Closes #ISSUE_NUMBER` 포함
974
+
975
+ 5. 커밋 실행:
976
+ ```bash
977
+ git commit -m "feat: SPEC_TITLE
978
+
979
+ Closes #ISSUE_NUMBER"
980
+ ```
981
+
982
+ ## 수동 실행
983
+
984
+ `/bylane commit`
985
+ ```
986
+
987
+ - [ ] **Step 4: Commit**
988
+
989
+ ```bash
990
+ git add skills/code-agent.md skills/test-agent.md skills/commit-agent.md
991
+ git commit -m "feat: code/test/commit 에이전트 스킬 작성"
992
+ ```
993
+
994
+ ---
995
+
996
+ ### Task 9: PR/리뷰/응답/알림 에이전트 스킬
997
+
998
+ **Files:**
999
+ - Create: `skills/pr-agent.md`
1000
+ - Create: `skills/review-agent.md`
1001
+ - Create: `skills/respond-agent.md`
1002
+ - Create: `skills/notify-agent.md`
1003
+
1004
+ - [ ] **Step 1: pr-agent.md 작성**
1005
+
1006
+ ```markdown
1007
+ ---
1008
+ name: bylane-pr-agent
1009
+ description: 현재 브랜치의 커밋들로 GitHub Pull Request를 생성한다.
1010
+ ---
1011
+
1012
+ # PR Agent
1013
+
1014
+ ## 입력
1015
+
1016
+ `.bylane/state/commit-agent.json`의 `branchName`, `commitShas`
1017
+ `.bylane/state/issue-agent.json`의 `spec.title`, `issueNumber`
1018
+
1019
+ ## 실행 흐름
1020
+
1021
+ 1. 원격 브랜치 푸시:
1022
+ ```bash
1023
+ git push -u origin BRANCH_NAME
1024
+ ```
1025
+
1026
+ 2. PR 제목/본문 생성:
1027
+ - 제목: 스펙 제목 (70자 이내)
1028
+ - 본문: 변경 요약 + `Closes #ISSUE_NUMBER` + 테스트 체크리스트
1029
+
1030
+ 3. GitHub MCP로 PR 생성:
1031
+ - `title`: 생성된 제목
1032
+ - `body`: 생성된 본문
1033
+ - `head`: 현재 브랜치
1034
+ - `base`: main (또는 config 기본 브랜치)
1035
+ - `draft`: false
1036
+
1037
+ 4. `.bylane/state/pr-agent.json`에 PR URL, PR 번호 저장
1038
+
1039
+ ## 수동 실행
1040
+
1041
+ `/bylane pr`
1042
+ ```
1043
+
1044
+ - [ ] **Step 2: review-agent.md 작성**
1045
+
1046
+ ```markdown
1047
+ ---
1048
+ name: bylane-review-agent
1049
+ description: PR의 diff를 분석하여 코드 리뷰 코멘트를 작성한다.
1050
+ ---
1051
+
1052
+ # Review Agent
1053
+
1054
+ ## 입력
1055
+
1056
+ PR 번호 (`.bylane/state/pr-agent.json`에서 자동 로드, 또는 수동 전달)
1057
+
1058
+ ## 실행 흐름
1059
+
1060
+ 1. GitHub MCP로 PR diff 로드
1061
+ 2. 변경된 파일별 분석:
1062
+ - 버그 가능성
1063
+ - 타입 오류
1064
+ - 성능 이슈
1065
+ - 코딩 컨벤션 위반
1066
+ - 테스트 커버리지 누락
1067
+ 3. 리뷰 코멘트 작성 기준:
1068
+ - CRITICAL: 즉시 수정 필요
1069
+ - HIGH: 수정 강력 권장
1070
+ - MEDIUM: 개선 권장
1071
+ - LOW: 선택적 개선
1072
+ 4. GitHub MCP로 리뷰 제출 (approve / request_changes / comment)
1073
+ 5. CRITICAL/HIGH 없으면 → `approved: true`
1074
+ CRITICAL/HIGH 있으면 → `approved: false`, 코멘트 목록 포함
1075
+
1076
+ ## 수동 실행
1077
+
1078
+ `/bylane review #45`
1079
+ ```
1080
+
1081
+ - [ ] **Step 3: respond-agent.md 작성**
1082
+
1083
+ ```markdown
1084
+ ---
1085
+ name: bylane-respond-agent
1086
+ description: PR 리뷰 코멘트에 반박하거나 코드를 수정하여 반영한다.
1087
+ ---
1088
+
1089
+ # Respond Agent
1090
+
1091
+ ## 입력
1092
+
1093
+ - PR 번호
1094
+ - 모드: `accept` (반영) 또는 `rebut` (반박)
1095
+
1096
+ ## 실행 흐름
1097
+
1098
+ ### accept 모드
1099
+
1100
+ 1. GitHub MCP로 미해결 리뷰 코멘트 로드
1101
+ 2. 각 코멘트별 수정 사항 결정
1102
+ 3. code-agent를 서브 에이전트로 호출하여 수정 구현
1103
+ 4. test-agent로 검증
1104
+ 5. commit-agent로 수정 커밋
1105
+ 6. GitHub MCP로 각 코멘트에 "반영 완료" 답글 작성
1106
+
1107
+ ### rebut 모드
1108
+
1109
+ 1. GitHub MCP로 미해결 리뷰 코멘트 로드
1110
+ 2. 각 코멘트에 대해 기존 구현의 근거를 기술한 반박 답글 작성
1111
+ 3. 반박 근거:
1112
+ - 의도적 설계 결정인 경우 배경 설명
1113
+ - 성능 트레이드오프가 있는 경우 수치 근거 제시
1114
+ - 스펙 요구사항과 일치하는 경우 이슈 링크 첨부
1115
+
1116
+ ## 수동 실행
1117
+
1118
+ `/bylane respond #45` → accept/rebut 선택 프롬프트 표시
1119
+ ```
1120
+
1121
+ - [ ] **Step 4: notify-agent.md 작성**
1122
+
1123
+ ```markdown
1124
+ ---
1125
+ name: bylane-notify-agent
1126
+ description: 워크플로우 완료 또는 개입 필요 시 Slack/Telegram으로 알림을 보낸다.
1127
+ ---
1128
+
1129
+ # Notify Agent
1130
+
1131
+ ## 입력
1132
+
1133
+ - `type`: `completed` | `escalated` | `error`
1134
+ - `summary`: 결과 요약 텍스트
1135
+ - `url`: 관련 GitHub URL (PR, Issue 등)
1136
+
1137
+ ## 실행 흐름
1138
+
1139
+ `.bylane/bylane.json`에서 알림 설정 로드:
1140
+
1141
+ ### Slack 알림 (enabled: true)
1142
+
1143
+ Slack MCP `slack_send_message` 도구 사용:
1144
+ - 채널: `config.notifications.slack.channel`
1145
+ - 메시지 형식:
1146
+ ```
1147
+ [byLane] ✅ 완료: TITLE
1148
+ PR: PR_URL
1149
+ 소요 시간: ELAPSED
1150
+ ```
1151
+ 에러/에스컬레이션 시:
1152
+ ```
1153
+ [byLane] ⚠️ 개입 필요: TITLE
1154
+ 이유: REASON
1155
+ 확인: PR_URL
1156
+ ```
1157
+
1158
+ ### Telegram 알림 (enabled: true)
1159
+
1160
+ Telegram MCP 또는 Telegram Bot API (curl):
1161
+ ```bash
1162
+ curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage" \
1163
+ -d "chat_id=$TELEGRAM_CHAT_ID&text=MESSAGE&parse_mode=Markdown"
1164
+ ```
1165
+
1166
+ ### 터미널 출력 (항상)
1167
+
1168
+ 알림 채널 설정 여부와 무관하게 Claude Code 터미널에 결과 출력.
1169
+
1170
+ ## 수동 실행
1171
+
1172
+ `/bylane notify` → 가장 최근 워크플로우 결과로 알림 발송
1173
+ ```
1174
+
1175
+ - [ ] **Step 5: Commit**
1176
+
1177
+ ```bash
1178
+ git add skills/pr-agent.md skills/review-agent.md skills/respond-agent.md skills/notify-agent.md
1179
+ git commit -m "feat: pr/review/respond/notify 에이전트 스킬 작성"
1180
+ ```
1181
+
1182
+ ---
1183
+
1184
+ ## Phase 4: 모니터 TUI 대시보드
1185
+
1186
+ ### Task 10: blessed 레이아웃 기반 TUI (`src/monitor/`)
1187
+
1188
+ **Files:**
1189
+ - Create: `src/monitor/panels/header.js`
1190
+ - Create: `src/monitor/panels/pipeline.js`
1191
+ - Create: `src/monitor/panels/log.js`
1192
+ - Create: `src/monitor/panels/queue.js`
1193
+ - Create: `src/monitor/panels/status.js`
1194
+ - Create: `src/monitor/layout.js`
1195
+ - Create: `src/monitor/poller.js`
1196
+ - Create: `src/monitor/index.js`
1197
+
1198
+ - [ ] **Step 1: header.js 작성**
1199
+
1200
+ ```js
1201
+ // src/monitor/panels/header.js
1202
+ import blessed from 'blessed'
1203
+
1204
+ export function createHeader(screen) {
1205
+ const box = blessed.box({
1206
+ top: 0,
1207
+ left: 0,
1208
+ width: '100%',
1209
+ height: 3,
1210
+ content: ' byLane Monitor Idle --:--:--',
1211
+ tags: true,
1212
+ border: { type: 'line' },
1213
+ style: { fg: 'white', bg: 'blue', border: { fg: 'cyan' } }
1214
+ })
1215
+ screen.append(box)
1216
+
1217
+ return {
1218
+ update({ workflowTitle, elapsed, time }) {
1219
+ const title = workflowTitle ?? 'Idle'
1220
+ box.setContent(` {bold}byLane Monitor{/bold} ${title} ${elapsed ?? ''} ${time}`)
1221
+ screen.render()
1222
+ }
1223
+ }
1224
+ }
1225
+ ```
1226
+
1227
+ - [ ] **Step 2: pipeline.js 작성**
1228
+
1229
+ ```js
1230
+ // src/monitor/panels/pipeline.js
1231
+ import blessed from 'blessed'
1232
+
1233
+ const AGENTS = [
1234
+ 'orchestrator', 'issue-agent', 'code-agent', 'test-agent',
1235
+ 'commit-agent', 'pr-agent', 'review-agent', 'respond-agent', 'notify-agent'
1236
+ ]
1237
+
1238
+ const STATUS_ICON = {
1239
+ idle: '[○]',
1240
+ in_progress: '[▶]',
1241
+ completed: '[✓]',
1242
+ failed: '[✗]',
1243
+ escalated: '[!]'
1244
+ }
1245
+
1246
+ export function createPipelinePanel(screen) {
1247
+ const box = blessed.box({
1248
+ top: 3,
1249
+ left: 0,
1250
+ width: '50%',
1251
+ height: '60%-3',
1252
+ label: ' AGENT PIPELINE ',
1253
+ tags: true,
1254
+ border: { type: 'line' },
1255
+ style: { border: { fg: 'cyan' } }
1256
+ })
1257
+ screen.append(box)
1258
+
1259
+ return {
1260
+ update(states) {
1261
+ const lines = AGENTS.map(name => {
1262
+ const s = states[name]
1263
+ if (!s) return ` ${STATUS_ICON.idle} ${name.padEnd(16)} 대기`
1264
+ const icon = STATUS_ICON[s.status] ?? STATUS_ICON.idle
1265
+ const elapsed = s.startedAt
1266
+ ? `${Math.floor((Date.now() - new Date(s.startedAt)) / 1000)}s`
1267
+ : ''
1268
+ const bar = s.progress > 0
1269
+ ? `${'█'.repeat(Math.floor(s.progress / 10))}${'░'.repeat(10 - Math.floor(s.progress / 10))} ${s.progress}%`
1270
+ : ''
1271
+ return ` ${icon} ${name.padEnd(16)} ${elapsed.padEnd(6)} ${bar}`
1272
+ })
1273
+ const retries = states['orchestrator']?.retries ?? 0
1274
+ const maxRetries = states['orchestrator']?.maxRetries ?? 3
1275
+ lines.push('', ` Retries: ${retries}/${maxRetries}`)
1276
+ box.setContent(lines.join('\n'))
1277
+ screen.render()
1278
+ }
1279
+ }
1280
+ }
1281
+ ```
1282
+
1283
+ - [ ] **Step 3: log.js 작성**
1284
+
1285
+ ```js
1286
+ // src/monitor/panels/log.js
1287
+ import blessed from 'blessed'
1288
+
1289
+ export function createLogPanel(screen) {
1290
+ const box = blessed.box({
1291
+ top: 3,
1292
+ left: '50%',
1293
+ width: '50%',
1294
+ height: '60%-3',
1295
+ label: ' AGENT LOG [LIVE] ',
1296
+ tags: true,
1297
+ scrollable: true,
1298
+ alwaysScroll: true,
1299
+ border: { type: 'line' },
1300
+ style: { border: { fg: 'cyan' } },
1301
+ keys: true,
1302
+ vi: true
1303
+ })
1304
+ screen.append(box)
1305
+ const lines = []
1306
+
1307
+ return {
1308
+ update(states) {
1309
+ const newLines = []
1310
+ for (const state of Object.values(states)) {
1311
+ for (const entry of (state.log ?? []).slice(-5)) {
1312
+ const ts = new Date(entry.ts).toLocaleTimeString('ko-KR', { hour12: false })
1313
+ newLines.push(` ${ts} {cyan-fg}${state.agent}{/cyan-fg}`)
1314
+ newLines.push(` → ${entry.msg}`)
1315
+ }
1316
+ }
1317
+ // 최근 50줄만 유지
1318
+ const all = [...lines, ...newLines].slice(-50)
1319
+ lines.length = 0
1320
+ lines.push(...all)
1321
+ box.setContent(lines.join('\n'))
1322
+ box.scrollTo(lines.length)
1323
+ screen.render()
1324
+ }
1325
+ }
1326
+ }
1327
+ ```
1328
+
1329
+ - [ ] **Step 4: queue.js 작성**
1330
+
1331
+ ```js
1332
+ // src/monitor/panels/queue.js
1333
+ import blessed from 'blessed'
1334
+ import { existsSync, readFileSync } from 'fs'
1335
+
1336
+ export function createQueuePanel(screen) {
1337
+ const box = blessed.box({
1338
+ top: '60%',
1339
+ left: 0,
1340
+ width: '50%',
1341
+ height: '40%',
1342
+ label: ' QUEUE ',
1343
+ tags: true,
1344
+ border: { type: 'line' },
1345
+ style: { border: { fg: 'cyan' } }
1346
+ })
1347
+ screen.append(box)
1348
+
1349
+ return {
1350
+ update() {
1351
+ const queuePath = '.bylane/queue.json'
1352
+ const queue = existsSync(queuePath)
1353
+ ? JSON.parse(readFileSync(queuePath, 'utf8'))
1354
+ : []
1355
+ const header = ` ${'#'.padEnd(3)} ${'TYPE'.padEnd(12)} ${'TARGET'.padEnd(10)} STATUS`
1356
+ const rows = queue.slice(0, 8).map((item, i) =>
1357
+ ` ${String(i + 1).padEnd(3)} ${item.type.padEnd(12)} ${item.target.padEnd(10)} ${item.status}`
1358
+ )
1359
+ box.setContent([header, ...rows].join('\n'))
1360
+ screen.render()
1361
+ }
1362
+ }
1363
+ }
1364
+ ```
1365
+
1366
+ - [ ] **Step 5: status.js 작성**
1367
+
1368
+ ```js
1369
+ // src/monitor/panels/status.js
1370
+ import blessed from 'blessed'
1371
+ import { loadConfig } from '../../config.js'
1372
+
1373
+ export function createStatusPanel(screen) {
1374
+ const box = blessed.box({
1375
+ top: '60%',
1376
+ left: '50%',
1377
+ width: '50%',
1378
+ height: '40%',
1379
+ label: ' SYSTEM STATUS ',
1380
+ tags: true,
1381
+ border: { type: 'line' },
1382
+ style: { border: { fg: 'cyan' } }
1383
+ })
1384
+ screen.append(box)
1385
+
1386
+ return {
1387
+ update() {
1388
+ const config = loadConfig()
1389
+ const check = (v) => v ? '{green-fg}✓{/green-fg}' : '{red-fg}✗{/red-fg}'
1390
+ const lines = [
1391
+ ` GitHub ${check(true)} 연결됨`,
1392
+ ` Linear ${check(config.trackers.linear.enabled)} ${config.trackers.linear.enabled ? '활성' : '비활성'}`,
1393
+ ` Figma MCP ${check(config.extensions.figma.enabled)} ${config.extensions.figma.enabled ? '활성' : '비활성'}`,
1394
+ ` Slack ${check(config.notifications.slack.enabled)} ${config.notifications.slack.channel || '미설정'}`,
1395
+ ` Telegram ${check(config.notifications.telegram.enabled)} ${config.notifications.telegram.chatId || '미설정'}`,
1396
+ ``,
1397
+ ` 팀 모드 ${check(config.team.enabled)} ${config.team.enabled ? `활성 (${config.team.members.length}명)` : '비활성'}`,
1398
+ ` 권한 범위 ${config.permissions.scope}`
1399
+ ]
1400
+ box.setContent(lines.join('\n'))
1401
+ screen.render()
1402
+ }
1403
+ }
1404
+ }
1405
+ ```
1406
+
1407
+ - [ ] **Step 6: poller.js 작성**
1408
+
1409
+ ```js
1410
+ // src/monitor/poller.js
1411
+ import { watch } from 'chokidar'
1412
+ import { listStates } from '../state.js'
1413
+
1414
+ export function createPoller(stateDir = '.bylane/state', intervalMs = 1000) {
1415
+ const callbacks = new Set()
1416
+
1417
+ const emit = () => {
1418
+ const states = {}
1419
+ for (const s of listStates(stateDir)) {
1420
+ states[s.agent] = s
1421
+ }
1422
+ for (const cb of callbacks) cb(states)
1423
+ }
1424
+
1425
+ const watcher = watch(`${stateDir}/*.json`, { persistent: true })
1426
+ watcher.on('change', emit)
1427
+ watcher.on('add', emit)
1428
+
1429
+ // 폴백 인터벌
1430
+ const interval = setInterval(emit, intervalMs)
1431
+
1432
+ return {
1433
+ onChange(cb) { callbacks.add(cb) },
1434
+ stop() {
1435
+ clearInterval(interval)
1436
+ watcher.close()
1437
+ }
1438
+ }
1439
+ }
1440
+ ```
1441
+
1442
+ - [ ] **Step 7: layout.js + index.js 작성**
1443
+
1444
+ ```js
1445
+ // src/monitor/layout.js
1446
+ import blessed from 'blessed'
1447
+ import { createHeader } from './panels/header.js'
1448
+ import { createPipelinePanel } from './panels/pipeline.js'
1449
+ import { createLogPanel } from './panels/log.js'
1450
+ import { createQueuePanel } from './panels/queue.js'
1451
+ import { createStatusPanel } from './panels/status.js'
1452
+
1453
+ export function createLayout() {
1454
+ const screen = blessed.screen({
1455
+ smartCSR: true,
1456
+ title: 'byLane Monitor'
1457
+ })
1458
+
1459
+ const header = createHeader(screen)
1460
+ const pipeline = createPipelinePanel(screen)
1461
+ const log = createLogPanel(screen)
1462
+ const queue = createQueuePanel(screen)
1463
+ const status = createStatusPanel(screen)
1464
+
1465
+ // 푸터
1466
+ const footer = blessed.box({
1467
+ bottom: 0,
1468
+ left: 0,
1469
+ width: '100%',
1470
+ height: 1,
1471
+ content: ' [q]종료 [p]일시정지 [c]작업취소 [Tab]포커스 [↑↓]로그스크롤 [?]도움말',
1472
+ style: { fg: 'black', bg: 'cyan' }
1473
+ })
1474
+ screen.append(footer)
1475
+
1476
+ screen.key(['q', 'C-c'], () => process.exit(0))
1477
+
1478
+ return { screen, header, pipeline, log, queue, status }
1479
+ }
1480
+ ```
1481
+
1482
+ ```js
1483
+ // src/monitor/index.js
1484
+ #!/usr/bin/env node
1485
+ import { createLayout } from './layout.js'
1486
+ import { createPoller } from './poller.js'
1487
+
1488
+ const { screen, header, pipeline, log, queue, status } = createLayout()
1489
+ const poller = createPoller()
1490
+
1491
+ let startTime = Date.now()
1492
+ const clockInterval = setInterval(() => {
1493
+ header.update({
1494
+ time: new Date().toLocaleTimeString('ko-KR', { hour12: false }),
1495
+ elapsed: `${Math.floor((Date.now() - startTime) / 1000)}s`
1496
+ })
1497
+ }, 1000)
1498
+
1499
+ poller.onChange((states) => {
1500
+ pipeline.update(states)
1501
+ log.update(states)
1502
+ queue.update()
1503
+ status.update()
1504
+ })
1505
+
1506
+ screen.render()
1507
+ ```
1508
+
1509
+ - [ ] **Step 8: bin 권한 설정**
1510
+
1511
+ ```bash
1512
+ chmod +x src/monitor/index.js
1513
+ ```
1514
+
1515
+ - [ ] **Step 9: 모니터 실행 테스트**
1516
+
1517
+ ```bash
1518
+ # 테스트용 더미 상태 파일 생성
1519
+ mkdir -p .bylane/state
1520
+ node -e "
1521
+ import('./src/state.js').then(({writeState}) => {
1522
+ writeState('code-agent', { status: 'in_progress', progress: 67, currentTask: 'ThemeToggle.tsx 구현', retries: 0 })
1523
+ writeState('issue-agent', { status: 'completed', progress: 100, retries: 0 })
1524
+ })
1525
+ "
1526
+ npm run monitor
1527
+ ```
1528
+
1529
+ Expected: 2열 TUI 대시보드 표시, `q` 로 종료
1530
+
1531
+ - [ ] **Step 10: Commit**
1532
+
1533
+ ```bash
1534
+ git add src/monitor/
1535
+ git commit -m "feat: blessed 기반 TUI 모니터 대시보드 구현"
1536
+ ```
1537
+
1538
+ ---
1539
+
1540
+ ## Phase 5: Hooks + Commands + CLAUDE.md
1541
+
1542
+ ### Task 11: Hooks 및 Commands 파일 작성
1543
+
1544
+ **Files:**
1545
+ - Create: `hooks/post-tool-use.md`
1546
+ - Create: `commands/bylane.md`
1547
+ - Create: `commands/bylane-monitor.md`
1548
+ - Create: `CLAUDE.md`
1549
+
1550
+ - [ ] **Step 1: hooks/post-tool-use.md 작성**
1551
+
1552
+ ```markdown
1553
+ ---
1554
+ name: bylane-post-tool-use
1555
+ description: GitHub 이벤트 감지 후 적절한 byLane 에이전트를 자동 트리거한다.
1556
+ trigger: post-tool-use
1557
+ ---
1558
+
1559
+ # byLane External Event Hook
1560
+
1561
+ GitHub MCP 도구 사용 후 결과를 분석하여 자동으로 에이전트를 트리거한다.
1562
+
1563
+ ## 감지 규칙
1564
+
1565
+ ### PR 오픈 감지
1566
+
1567
+ GitHub MCP `create_pull_request` 또는 `list_pull_requests` 결과에서
1568
+ `state: "open"` + `user.login`이 팀 멤버인 PR 발견 시:
1569
+
1570
+ → `.bylane/bylane.json`의 `team.enabled`가 `true`이면 `bylane-review-agent` 실행
1571
+
1572
+ ### 리뷰 코멘트 수신 감지
1573
+
1574
+ GitHub MCP 결과에서 내 PR에 `review_state: "changes_requested"` 발견 시:
1575
+
1576
+ → `bylane-respond-agent` 실행 (PR 번호 전달)
1577
+
1578
+ ### CI 실패 감지
1579
+
1580
+ GitHub MCP `get_pull_request_status`에서 `state: "failure"` 발견 시:
1581
+
1582
+ → `.bylane/state/code-agent.json`의 retries 확인
1583
+ → `retries < maxRetries`이면 `bylane-code-agent` 재실행
1584
+
1585
+ ## 비활성화
1586
+
1587
+ `.bylane/bylane.json`에 `"hookAutoTrigger": false` 추가 시 이 hook 비활성화
1588
+ ```
1589
+
1590
+ - [ ] **Step 2: commands/bylane.md 작성**
1591
+
1592
+ ```markdown
1593
+ ---
1594
+ name: bylane
1595
+ description: byLane 메인 커맨드. 자연어로 전체 개발 워크플로우를 실행한다.
1596
+ ---
1597
+
1598
+ # /bylane
1599
+
1600
+ ## 사용법
1601
+
1602
+ ```
1603
+ /bylane [자연어 명령]
1604
+ /bylane setup
1605
+ /bylane monitor
1606
+ /bylane issue [#번호 | 텍스트]
1607
+ /bylane code [#번호]
1608
+ /bylane test
1609
+ /bylane commit
1610
+ /bylane pr
1611
+ /bylane review [PR번호]
1612
+ /bylane respond [PR번호]
1613
+ /bylane notify
1614
+ /bylane status
1615
+ ```
1616
+
1617
+ ## 실행 흐름
1618
+
1619
+ 1. 인자가 없거나 자연어이면 → `bylane-orchestrator` 스킬 실행
1620
+ 2. 첫 번째 단어가 서브커맨드이면 → 해당 스킬 직접 실행:
1621
+ - `setup` → `bylane-setup`
1622
+ - `monitor` → 아래 참조
1623
+ - `issue` → `bylane-issue-agent`
1624
+ - `code` → `bylane-code-agent`
1625
+ - `test` → `bylane-test-agent`
1626
+ - `commit` → `bylane-commit-agent`
1627
+ - `pr` → `bylane-pr-agent`
1628
+ - `review` → `bylane-review-agent`
1629
+ - `respond` → `bylane-respond-agent`
1630
+ - `notify` → `bylane-notify-agent`
1631
+ - `status` → `.bylane/state/` 파일 읽어 한 줄 요약 출력
1632
+
1633
+ ## monitor 서브커맨드
1634
+
1635
+ ```bash
1636
+ npm run monitor --prefix $(find ~/.claude -name "bylane" -type d | head -1)
1637
+ ```
1638
+ 또는 `bylane-monitor` bin이 PATH에 있으면:
1639
+ ```bash
1640
+ bylane-monitor
1641
+ ```
1642
+ ```
1643
+
1644
+ - [ ] **Step 3: commands/bylane-monitor.md 작성**
1645
+
1646
+ ```markdown
1647
+ ---
1648
+ name: bylane-monitor
1649
+ description: byLane 실시간 TUI 모니터 대시보드를 실행한다.
1650
+ ---
1651
+
1652
+ # /bylane monitor
1653
+
1654
+ ## 설명
1655
+
1656
+ 현재 진행 중인 모든 byLane 에이전트의 상태를 2열 그리드 TUI로 실시간 표시.
1657
+
1658
+ ## 실행
1659
+
1660
+ ```bash
1661
+ node src/monitor/index.js
1662
+ ```
1663
+
1664
+ `npm run monitor`로도 실행 가능.
1665
+
1666
+ ## 키보드 단축키
1667
+
1668
+ | 키 | 동작 |
1669
+ |---|---|
1670
+ | `q` / `Ctrl+C` | 종료 |
1671
+ | `p` | 현재 에이전트 일시정지 |
1672
+ | `c` | 현재 작업 취소 |
1673
+ | `Tab` | 패널 포커스 전환 |
1674
+ | `↑` / `↓` | 로그 패널 스크롤 |
1675
+ | `?` | 도움말 |
1676
+ ```
1677
+
1678
+ - [ ] **Step 4: CLAUDE.md 작성**
1679
+
1680
+ ```markdown
1681
+ # CLAUDE.md
1682
+
1683
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
1684
+
1685
+ ## 프로젝트 개요
1686
+
1687
+ byLane — Claude Code용 프론트엔드 개발 자동화 하네스.
1688
+ 오케스트레이터 + 워커 에이전트 패턴으로 이슈 생성부터 PR 머지까지 자동화.
1689
+
1690
+ ## 커맨드
1691
+
1692
+ ```bash
1693
+ # 의존성 설치
1694
+ npm install
1695
+
1696
+ # 테스트 실행
1697
+ npm test
1698
+
1699
+ # 모니터 대시보드 실행
1700
+ npm run monitor
1701
+
1702
+ # 테스트 더미 데이터 생성
1703
+ node -e "import('./src/state.js').then(({writeState})=>writeState('code-agent',{status:'in_progress',progress:50}))"
1704
+ ```
1705
+
1706
+ ## 아키텍처
1707
+
1708
+ - `src/state.js` — `.bylane/state/*.json` 읽기/쓰기 유틸
1709
+ - `src/config.js` — `.bylane/bylane.json` 로드/저장/검증
1710
+ - `src/branch.js` — 브랜치명 패턴 엔진 (`{tracker}-{issue-number}-{custom-id}` 등)
1711
+ - `src/monitor/` — blessed 기반 TUI 대시보드 (2열 그리드)
1712
+ - `skills/` — Claude Code 에이전트 skill 파일들 (Markdown)
1713
+ - `hooks/` — 외부 이벤트 자동 감지 훅
1714
+ - `commands/` — `/bylane` 슬래시 커맨드 정의
1715
+
1716
+ ## 에이전트 파이프라인
1717
+
1718
+ orchestrator → issue-agent → code-agent → test-agent → commit-agent → pr-agent → review-agent → respond-agent → notify-agent
1719
+
1720
+ 각 에이전트는 `.bylane/state/{name}.json`에 상태 기록.
1721
+ 모니터 대시보드가 1초마다 폴링하여 표시.
1722
+
1723
+ ## 상태 파일 스키마
1724
+
1725
+ ```json
1726
+ {
1727
+ "agent": "code-agent",
1728
+ "status": "in_progress | completed | failed | idle",
1729
+ "startedAt": "ISO8601",
1730
+ "progress": 0-100,
1731
+ "retries": 0,
1732
+ "log": [{ "ts": "ISO8601", "msg": "string" }]
1733
+ }
1734
+ ```
1735
+
1736
+ ## 브랜치 네이밍 토큰
1737
+
1738
+ `{tracker}`, `{type}`, `{issue-number}`, `{custom-id}`, `{title-slug}`, `{date}`, `{username}`
1739
+ 빈 토큰은 자동으로 제외됨 (e.g. `{custom-id}` 없으면 `issues-32-C-12` → `issues-32`)
1740
+ ```
1741
+
1742
+ - [ ] **Step 5: Commit**
1743
+
1744
+ ```bash
1745
+ git add hooks/ commands/ CLAUDE.md
1746
+ git commit -m "feat: hooks, commands, CLAUDE.md 작성"
1747
+ ```
1748
+
1749
+ ---
1750
+
1751
+ ### Task 12: 전체 테스트 실행 및 최종 커밋
1752
+
1753
+ **Files:**
1754
+ - 없음 (검증 단계)
1755
+
1756
+ - [ ] **Step 1: 전체 테스트 실행**
1757
+
1758
+ ```bash
1759
+ npm test
1760
+ ```
1761
+
1762
+ Expected:
1763
+ ```
1764
+ ✓ tests/state.test.js (5 tests)
1765
+ ✓ tests/config.test.js (6 tests)
1766
+ ✓ tests/branch.test.js (5 tests)
1767
+
1768
+ Test Files 3 passed (3)
1769
+ Tests 16 passed (16)
1770
+ ```
1771
+
1772
+ - [ ] **Step 2: 모니터 스모크 테스트**
1773
+
1774
+ ```bash
1775
+ node -e "
1776
+ import('./src/state.js').then(({writeState}) => {
1777
+ writeState('issue-agent', { status: 'completed', progress: 100, retries: 0, log: [{ts: new Date().toISOString(), msg: 'spec.json 저장됨'}] })
1778
+ writeState('code-agent', { status: 'in_progress', progress: 67, retries: 1, log: [{ts: new Date().toISOString(), msg: 'ThemeToggle.tsx 구현 중'}] })
1779
+ console.log('더미 상태 생성됨')
1780
+ })
1781
+ "
1782
+ npm run monitor
1783
+ ```
1784
+
1785
+ Expected: TUI 대시보드 표시, 2개 에이전트 상태 표시, `q` 로 종료
1786
+
1787
+ - [ ] **Step 3: skills 디렉토리 파일 수 확인**
1788
+
1789
+ ```bash
1790
+ ls skills/ | wc -l
1791
+ ```
1792
+
1793
+ Expected: `10` (orchestrator + setup + 8 agents)
1794
+
1795
+ - [ ] **Step 4: 최종 커밋**
1796
+
1797
+ ```bash
1798
+ git add -A
1799
+ git status # 누락 파일 없는지 확인
1800
+ git commit -m "chore: byLane 1.0 초기 구현 완료"
1801
+ ```
1802
+
1803
+ ---
1804
+
1805
+ ## 설치 방법 (사용자 가이드)
1806
+
1807
+ 이 레포지토리를 Claude Code 프로젝트에 하네스로 연결하는 방법:
1808
+
1809
+ ```bash
1810
+ # 1. byLane 클론
1811
+ git clone https://github.com/YOUR/byLane ~/.claude/plugins/bylane
1812
+
1813
+ # 2. 의존성 설치
1814
+ cd ~/.claude/plugins/bylane && npm install
1815
+
1816
+ # 3. Claude Code 설정에 스킬/커맨드 경로 등록
1817
+ # ~/.claude/settings.json 에 skills, hooks, commands 경로 추가
1818
+
1819
+ # 4. 프론트엔드 프로젝트에서 셋업 실행
1820
+ /bylane setup
1821
+ ```