@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.
- package/CLAUDE.md +73 -0
- package/README.md +186 -0
- package/commands/bylane-monitor.md +43 -0
- package/commands/bylane.md +61 -0
- package/docs/superpowers/plans/2026-04-04-bylane-implementation.md +1821 -0
- package/docs/superpowers/specs/2026-04-04-bylane-design.md +324 -0
- package/hooks/post-tool-use.md +40 -0
- package/package.json +22 -0
- package/skills/code-agent.md +69 -0
- package/skills/commit-agent.md +85 -0
- package/skills/issue-agent.md +91 -0
- package/skills/notify-agent.md +75 -0
- package/skills/orchestrator.md +70 -0
- package/skills/pr-agent.md +73 -0
- package/skills/respond-agent.md +55 -0
- package/skills/review-agent.md +65 -0
- package/skills/setup.md +101 -0
- package/skills/test-agent.md +63 -0
- package/src/branch.js +31 -0
- package/src/cli.js +67 -0
- package/src/config.js +86 -0
- package/src/monitor/index.js +26 -0
- package/src/monitor/layout.js +37 -0
- package/src/monitor/panels/header.js +24 -0
- package/src/monitor/panels/log.js +39 -0
- package/src/monitor/panels/pipeline.js +50 -0
- package/src/monitor/panels/queue.js +36 -0
- package/src/monitor/panels/status.js +35 -0
- package/src/monitor/poller.js +28 -0
- package/src/state.js +44 -0
- package/tests/branch.test.js +55 -0
- package/tests/config.test.js +55 -0
- package/tests/state.test.js +59 -0
|
@@ -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
|
+
```
|