@elyun/bylane 1.17.0 → 1.18.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/README.md +262 -121
- package/commands/bylane-cleanup.md +36 -0
- package/commands/bylane-code-agent.md +3 -24
- package/commands/bylane-commit-agent.md +3 -19
- package/commands/bylane-issue-agent.md +1 -1
- package/commands/bylane-notify-agent.md +1 -1
- package/commands/bylane-orchestrator.md +4 -23
- package/commands/bylane-pr-agent.md +2 -2
- package/commands/bylane-respond-agent.md +1 -1
- package/commands/bylane-respond-loop.md +6 -18
- package/commands/bylane-review-agent.md +2 -2
- package/commands/bylane-review-loop.md +6 -17
- package/commands/bylane-test-agent.md +1 -1
- package/package.json +1 -1
- package/src/cleanup.js +152 -0
- package/src/cli.js +39 -0
- package/src/monitor/index.js +29 -0
- package/src/monitor/layout.js +1 -1
package/README.md
CHANGED
|
@@ -1,32 +1,36 @@
|
|
|
1
1
|
# byLane
|
|
2
2
|
|
|
3
|
-
> Claude Code용
|
|
3
|
+
> Claude Code용 개발 자동화 하네스
|
|
4
4
|
|
|
5
5
|
GitHub Issues에서 시작해 코드 구현, 테스트, 커밋, PR 생성, 리뷰, 리뷰 반영까지 전체 개발 워크플로우를 에이전트 기반으로 자동화합니다.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 핵심 기능
|
|
10
|
+
|
|
11
|
+
| 기능 | 설명 |
|
|
12
|
+
|------|------|
|
|
13
|
+
| **전체 워크플로우** | 자연어 한 줄로 이슈 → 코드 → 테스트 → 커밋 → PR 자동화 |
|
|
14
|
+
| **프로젝트 분석** | 코드 스타일·디자인 토큰·아키텍처 자동 분석, `.claude/instructions/` 생성 |
|
|
15
|
+
| **인라인 코드 리뷰** | 변경 라인마다 개별 코멘트 + GitHub suggestion 블록 자동 등록 |
|
|
16
|
+
| **자동 리뷰 루프** | 5분 주기로 review 요청된 PR을 자동 감지 → 리뷰 실행 |
|
|
17
|
+
| **자동 대응 루프** | 5분 주기로 내 PR 리뷰/코멘트 감지 → 자동 수정 반영 또는 반박 |
|
|
18
|
+
| **실시간 모니터** | 터미널 TUI 대시보드 (에이전트 상태, 로그, 큐, 루프 모니터링) |
|
|
19
|
+
| **에이전트별 모델** | 에이전트마다 다른 Claude 모델 지정 가능 |
|
|
20
|
+
| **GitHub 자동 감지** | MCP → CLI(`gh`) → REST API 순서로 자동 시도 |
|
|
21
|
+
| **보안 훅** | 커밋마다 시크릿/민감 파일/console.log 자동 검사 |
|
|
22
|
+
|
|
23
|
+
---
|
|
22
24
|
|
|
23
25
|
## 요구사항
|
|
24
26
|
|
|
25
27
|
- [Claude Code](https://claude.ai/code) CLI
|
|
26
28
|
- Node.js 20+
|
|
27
|
-
- GitHub MCP (Claude Code 기본 제공)
|
|
28
|
-
- Slack MCP
|
|
29
|
-
- Figma MCP (디자인 연동 사용
|
|
29
|
+
- GitHub MCP (Claude Code 기본 제공) / `gh` CLI / `GITHUB_TOKEN` 중 하나
|
|
30
|
+
- Slack MCP 또는 Telegram (알림 사용 시, 선택사항)
|
|
31
|
+
- Figma MCP (디자인 연동 사용 시, 선택사항)
|
|
32
|
+
|
|
33
|
+
---
|
|
30
34
|
|
|
31
35
|
## 설치
|
|
32
36
|
|
|
@@ -36,10 +40,11 @@ GitHub Issues에서 시작해 코드 구현, 테스트, 커밋, PR 생성, 리
|
|
|
36
40
|
npx @elyun/bylane
|
|
37
41
|
```
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
기존
|
|
43
|
+
`~/.claude/commands/`와 `~/.claude/hooks/`에 파일을 자동 복사합니다.
|
|
44
|
+
기존 파일은 `.bak`으로 백업 후 교체됩니다.
|
|
45
|
+
`.bylane/bylane.json` 사용자 설정은 **절대 덮어쓰지 않습니다**.
|
|
41
46
|
|
|
42
|
-
심볼릭
|
|
47
|
+
심볼릭 링크 설치 (레포 업데이트 시 자동 반영):
|
|
43
48
|
|
|
44
49
|
```bash
|
|
45
50
|
npx @elyun/bylane --symlink
|
|
@@ -50,34 +55,26 @@ npx @elyun/bylane --symlink
|
|
|
50
55
|
```bash
|
|
51
56
|
git clone https://github.com/el-yun/byLane.git
|
|
52
57
|
cd byLane
|
|
53
|
-
npm install
|
|
58
|
+
npm install
|
|
54
59
|
node src/cli.js install
|
|
55
60
|
```
|
|
56
61
|
|
|
57
|
-
### 셋업 위자드
|
|
62
|
+
### 셋업 위자드
|
|
58
63
|
|
|
59
|
-
Claude Code
|
|
64
|
+
설치 후 Claude Code에서 프로젝트 디렉토리로 이동:
|
|
60
65
|
|
|
61
66
|
```
|
|
62
67
|
/bylane setup
|
|
63
68
|
```
|
|
64
69
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
3. 알림 채널 (Slack / Telegram / 둘 다)
|
|
69
|
-
4. 팀 모드 설정
|
|
70
|
-
5. 권한 범위 (read-only / write / full)
|
|
71
|
-
6. 고급 설정 (재시도 횟수, 타임아웃, Figma MCP)
|
|
72
|
-
7. 브랜치 네이밍 패턴
|
|
73
|
-
8. 에이전트별 AI 모델
|
|
70
|
+
인터랙티브 설정 (GitHub 접근 방법, 알림 채널, 브랜치 패턴, 에이전트 모델 등).
|
|
71
|
+
|
|
72
|
+
---
|
|
74
73
|
|
|
75
74
|
## 사용법
|
|
76
75
|
|
|
77
76
|
### 전체 워크플로우
|
|
78
77
|
|
|
79
|
-
자연어로 작업 내용을 설명하면 오케스트레이터가 적절한 에이전트를 자동으로 실행합니다.
|
|
80
|
-
|
|
81
78
|
```
|
|
82
79
|
/bylane 다크모드 토글 버튼 추가해줘
|
|
83
80
|
/bylane issue #123 구현해줘
|
|
@@ -94,84 +91,193 @@ Claude Code를 열고 프로젝트 디렉토리에서:
|
|
|
94
91
|
/bylane test 테스트 실행
|
|
95
92
|
/bylane commit 커밋 생성
|
|
96
93
|
/bylane pr PR 생성
|
|
97
|
-
/bylane review [PR번호]
|
|
98
|
-
/bylane
|
|
99
|
-
/bylane respond [PR번호] 리뷰 반박/반영
|
|
100
|
-
/bylane respond-loop 5분 주기 자동 대응 루프
|
|
94
|
+
/bylane review [#PR번호] [scope] PR 인라인 리뷰
|
|
95
|
+
/bylane respond [#PR번호] [모드] 리뷰 반박/반영
|
|
101
96
|
/bylane notify 알림 발송
|
|
102
97
|
/bylane status 현재 상태 요약
|
|
103
98
|
```
|
|
104
99
|
|
|
105
100
|
### 자동 루프
|
|
106
101
|
|
|
107
|
-
두 루프를 동시에 실행하면
|
|
102
|
+
두 루프를 동시에 실행하면 리뷰 요청과 리뷰 대응을 완전 자동화합니다:
|
|
108
103
|
|
|
109
104
|
```bash
|
|
110
|
-
node src/review-loop.js & # 내게 요청된 PR 자동 리뷰
|
|
111
|
-
node src/respond-loop.js & # 내 PR 리뷰 자동 대응
|
|
105
|
+
node src/review-loop.js & # 내게 요청된 PR 자동 리뷰 (5분 주기)
|
|
106
|
+
node src/respond-loop.js & # 내 PR 리뷰 자동 대응 (5분 주기)
|
|
112
107
|
```
|
|
113
108
|
|
|
114
|
-
|
|
109
|
+
루프 종료:
|
|
115
110
|
|
|
116
111
|
```bash
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
112
|
+
kill $(pgrep -f review-loop.js)
|
|
113
|
+
kill $(pgrep -f respond-loop.js)
|
|
114
|
+
# 또는 모니터에서 [s]
|
|
120
115
|
```
|
|
121
116
|
|
|
117
|
+
Claude Code에서 실행:
|
|
118
|
+
|
|
122
119
|
```
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
| | |
|
|
126
|
-
| issue-agent [v] 완료 | 17:32:38 code-agent |
|
|
127
|
-
| code-agent [>] 67% | > ThemeToggle.tsx 생성 |
|
|
128
|
-
| test-agent [ ] 대기 | 17:30:09 issue-agent |
|
|
129
|
-
| ... | > spec.json 저장됨 |
|
|
130
|
-
+---------------------------+--------------------------------------+
|
|
131
|
-
| QUEUE | SYSTEM STATUS |
|
|
132
|
-
| 1 Issue #124 대기중 | GitHub OK 연결됨 |
|
|
133
|
-
| 2 PR #45 review | Slack OK #dev-alerts |
|
|
134
|
-
+---------------------------+--------------------------------------+
|
|
120
|
+
/bylane review-loop 자동 리뷰 루프 시작 (검사 범위 선택 후 시작)
|
|
121
|
+
/bylane respond-loop 자동 대응 루프 시작
|
|
135
122
|
```
|
|
136
123
|
|
|
137
|
-
|
|
124
|
+
---
|
|
138
125
|
|
|
139
|
-
|
|
126
|
+
## 모니터 대시보드
|
|
140
127
|
|
|
141
|
-
|
|
128
|
+
```bash
|
|
129
|
+
npx @elyun/bylane monitor
|
|
130
|
+
```
|
|
142
131
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
|
132
|
+
> `npx`를 사용하면 항상 최신 버전이 실행됩니다. 로컬 구버전이 실행되지 않습니다.
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
+-----------------------------+--------------------------------------+
|
|
136
|
+
| AGENT PIPELINE | AGENT LOG [LIVE] |
|
|
137
|
+
| | |
|
|
138
|
+
| [>] code-agent 12s ##-- | 17:32:38 code-agent |
|
|
139
|
+
| [ ] test-agent 대기 | > ThemeToggle.tsx 생성 |
|
|
140
|
+
| [v] issue-agent | 17:30:09 issue-agent |
|
|
141
|
+
| | > spec.json 저장됨 |
|
|
142
|
+
| LOOPS | |
|
|
143
|
+
| [>] review-loop 45s | |
|
|
144
|
+
| [-] respond-loop 미실행 | |
|
|
145
|
+
| | |
|
|
146
|
+
| SUBAGENTS | |
|
|
147
|
+
| [>] executor 8s 코드... | |
|
|
148
|
+
+-----------------------------+--------------------------------------+
|
|
149
|
+
| QUEUE | SYSTEM STATUS |
|
|
150
|
+
| PR #45 review pending | GitHub OK |
|
|
151
|
+
| PR #48 respond pending | Loops 1 running |
|
|
152
|
+
+-----------------------------+--------------------------------------+
|
|
153
|
+
[q]종료 [c]에이전트취소토글 [s]루프종료 [Tab]포커스 [j/k]로그스크롤
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### 단축키
|
|
157
|
+
|
|
158
|
+
| 키 | 동작 |
|
|
159
|
+
|----|------|
|
|
160
|
+
| `q` / `Ctrl+C` | 모니터 종료 |
|
|
161
|
+
| `r` | 상태 정리 (권한 수정, 좀비 초기화, 큐 복구) |
|
|
162
|
+
| `c` | 하위 에이전트 취소 플래그 토글 (새 Agent 호출 차단) |
|
|
163
|
+
| `s` | 실행 중인 루프 선택 → SIGTERM 전송 |
|
|
164
|
+
| `Tab` | 패널 포커스 이동 |
|
|
165
|
+
| `j` / `k` | 로그 스크롤 |
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 코드 리뷰
|
|
170
|
+
|
|
171
|
+
### 검사 범위 선택
|
|
172
|
+
|
|
173
|
+
리뷰 실행 시 검사 항목을 선택합니다. 선택 없이 Enter → 전체 검사:
|
|
148
174
|
|
|
149
|
-
|
|
175
|
+
```
|
|
176
|
+
검사 항목을 선택하세요 (쉼표 구분, Enter=전체):
|
|
177
|
+
1. grammar — 문법, 오탈자, 주석/변수명 언어 일관성
|
|
178
|
+
2. domain — 비즈니스 로직, 도메인 규칙 준수 여부
|
|
179
|
+
3. code — 코드 스타일, 컨벤션, 중복, 복잡도
|
|
180
|
+
4. security — 보안 취약점, 시크릿 노출, 인증/인가 이슈
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
인자로도 지정 가능:
|
|
184
|
+
```
|
|
185
|
+
/bylane review #45 code,security
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### 인라인 코멘트
|
|
189
|
+
|
|
190
|
+
모든 리뷰 코멘트는 **해당 코드 라인에 직접** 등록됩니다. 수정 제안이 있으면 GitHub suggestion 블록으로 작성되어 "Apply suggestion" 버튼으로 바로 적용할 수 있습니다.
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
제목
|
|
194
|
+
|
|
195
|
+
설명
|
|
150
196
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
197
|
+
```suggestion
|
|
198
|
+
// 수정 제안 코드
|
|
199
|
+
```
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### GitHub 리뷰 템플릿 우선 적용
|
|
203
|
+
|
|
204
|
+
프로젝트에 아래 파일이 있으면 해당 형식을 **최우선**으로 따릅니다:
|
|
156
205
|
|
|
157
206
|
```
|
|
158
|
-
/
|
|
159
|
-
/
|
|
207
|
+
.github/REVIEW_TEMPLATE.md
|
|
208
|
+
.github/CODE_REVIEW_TEMPLATE.md
|
|
209
|
+
docs/REVIEW_TEMPLATE.md
|
|
160
210
|
```
|
|
161
211
|
|
|
162
|
-
|
|
212
|
+
없으면 `templates/review-template.md`를 사용합니다.
|
|
213
|
+
|
|
214
|
+
---
|
|
163
215
|
|
|
164
|
-
##
|
|
216
|
+
## 리뷰 대응 (respond)
|
|
165
217
|
|
|
166
|
-
|
|
|
167
|
-
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
218
|
+
| 모드 | 동작 |
|
|
219
|
+
|------|------|
|
|
220
|
+
| `auto` (기본) | 각 코멘트를 분석해 수정 반영 또는 반박을 자동 판단, 요약 확인 후 실행 |
|
|
221
|
+
| `accept` | 모든 코멘트 수정 반영 |
|
|
222
|
+
| `rebut` | 모든 코멘트 반박 |
|
|
223
|
+
| `manual` | 코멘트별로 수정/반박/건너뜀 직접 선택 |
|
|
171
224
|
|
|
172
|
-
|
|
225
|
+
```
|
|
226
|
+
/bylane respond #45 auto 모드 (기본)
|
|
227
|
+
/bylane respond #45 manual 코멘트별 수동 선택
|
|
228
|
+
/bylane respond #45 accept 전체 반영
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
auto 모드에서는 실행 전 요약을 먼저 보여주고 확인을 받습니다:
|
|
173
232
|
|
|
174
|
-
|
|
233
|
+
```
|
|
234
|
+
코멘트 #1: [반영] null 체크 누락 → 코드 수정
|
|
235
|
+
코멘트 #2: [반박] 의도된 설계 (이슈 #12 참조)
|
|
236
|
+
진행할까요? (y/n)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## 프로젝트 분석
|
|
242
|
+
|
|
243
|
+
`/bylane analyze` 실행 시 현재 프로젝트를 자동 분석하여 Claude Code가 참조할 instruction 파일을 생성합니다.
|
|
244
|
+
|
|
245
|
+
| 파일 | 내용 |
|
|
246
|
+
|------|------|
|
|
247
|
+
| `.claude/instructions/code-style.md` | 언어, 포맷팅, 네이밍 컨벤션, import 규칙 |
|
|
248
|
+
| `.claude/instructions/design-tokens.md` | 색상, 타이포그래피, 간격, 다크모드 |
|
|
249
|
+
| `.claude/instructions/architecture.md` | 프레임워크, 상태관리, 폴더 구조 |
|
|
250
|
+
|
|
251
|
+
ESLint/Prettier/tsconfig, Tailwind config, CSS 변수 등 설정 파일을 자동 탐색하고 실제 소스 패턴을 샘플링합니다. 분석 후 `CLAUDE.md`에 import 구문을 자동 추가합니다.
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
/bylane analyze 기존 파일 있으면 확인 요청
|
|
255
|
+
/bylane analyze --force 강제 덮어쓰기
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## 에이전트 파이프라인
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
orchestrator
|
|
264
|
+
→ issue-agent 이슈 생성/분석, Figma 스펙 추출 (선택)
|
|
265
|
+
→ code-agent 코드 구현
|
|
266
|
+
→ test-agent 테스트 실행, 실패 시 code-agent 재시도
|
|
267
|
+
→ commit-agent 브랜치 생성 + 커밋
|
|
268
|
+
→ pr-agent PR 생성
|
|
269
|
+
→ review-agent 인라인 코드 리뷰
|
|
270
|
+
→ respond-agent 리뷰 반박 또는 반영
|
|
271
|
+
→ notify-agent Slack/Telegram 알림
|
|
272
|
+
|
|
273
|
+
analyze-agent 독립: 프로젝트 분석 → .claude/instructions/ 생성
|
|
274
|
+
review-loop 독립: 5분 주기 review 요청 감지
|
|
275
|
+
respond-loop 독립: 5분 주기 리뷰 코멘트 감지
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
각 에이전트는 `.bylane/state/{name}.json`에 상태를 기록합니다. 모니터가 1초마다 폴링합니다.
|
|
279
|
+
|
|
280
|
+
---
|
|
175
281
|
|
|
176
282
|
## 설정 파일 (`.bylane/bylane.json`)
|
|
177
283
|
|
|
@@ -208,6 +314,7 @@ npm run monitor
|
|
|
208
314
|
"code-agent": "claude-sonnet-4-6",
|
|
209
315
|
"review-agent": "claude-sonnet-4-6",
|
|
210
316
|
"respond-agent": "claude-opus-4-6",
|
|
317
|
+
"analyze-agent": "claude-opus-4-6",
|
|
211
318
|
"test-agent": "claude-haiku-4-5-20251001",
|
|
212
319
|
"commit-agent": "claude-haiku-4-5-20251001",
|
|
213
320
|
"pr-agent": "claude-haiku-4-5-20251001",
|
|
@@ -216,10 +323,9 @@ npm run monitor
|
|
|
216
323
|
"review": {
|
|
217
324
|
"model": "claude-sonnet-4-6",
|
|
218
325
|
"language": "ko",
|
|
219
|
-
"includeModel": true,
|
|
220
326
|
"includeCodeExample": true,
|
|
221
327
|
"templateFile": "",
|
|
222
|
-
"footer": "
|
|
328
|
+
"footer": "{model} · {date}"
|
|
223
329
|
},
|
|
224
330
|
"extensions": {
|
|
225
331
|
"figma": { "enabled": false, "useAt": "issue-analysis" }
|
|
@@ -227,64 +333,99 @@ npm run monitor
|
|
|
227
333
|
}
|
|
228
334
|
```
|
|
229
335
|
|
|
230
|
-
|
|
336
|
+
### 브랜치 네이밍 토큰
|
|
337
|
+
|
|
338
|
+
| 토큰 | 설명 |
|
|
339
|
+
|------|------|
|
|
340
|
+
| `{tracker}` | 트래커 종류 (예: `issues`) |
|
|
341
|
+
| `{type}` | 작업 타입 (예: `feature`, `fix`) |
|
|
342
|
+
| `{issue-number}` | 이슈 번호 |
|
|
343
|
+
| `{custom-id}` | 커스텀 ID |
|
|
344
|
+
| `{title-slug}` | 이슈 제목 슬러그 |
|
|
345
|
+
| `{date}` | 날짜 (YYYYMMDD) |
|
|
346
|
+
| `{username}` | GitHub 사용자명 |
|
|
231
347
|
|
|
348
|
+
값이 없는 토큰은 자동으로 제외됩니다 (`{custom-id}` 없으면 `issues-32-add-dark-mode`).
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## 주의사항
|
|
353
|
+
|
|
354
|
+
### 업데이트 시 설정 보존
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
npx @elyun/bylane # 재설치/업데이트
|
|
232
358
|
```
|
|
233
|
-
orchestrator
|
|
234
|
-
→ issue-agent (이슈 생성/분석, Figma 스펙 추출)
|
|
235
|
-
→ code-agent (코드 구현)
|
|
236
|
-
→ test-agent (테스트 실행, 실패 시 code-agent 재시도)
|
|
237
|
-
→ commit-agent (브랜치 생성 + 커밋)
|
|
238
|
-
→ pr-agent (PR 생성)
|
|
239
|
-
→ review-agent (자동 리뷰)
|
|
240
|
-
→ respond-agent (리뷰 반박 또는 반영)
|
|
241
|
-
→ notify-agent (Slack/Telegram 알림)
|
|
242
359
|
|
|
243
|
-
|
|
244
|
-
|
|
360
|
+
`.bylane/bylane.json` 사용자 설정은 절대 덮어쓰지 않습니다. 나머지 파일은 `.bak`으로 백업 후 교체됩니다.
|
|
361
|
+
|
|
362
|
+
### 모니터 버전 불일치
|
|
363
|
+
|
|
364
|
+
프로젝트의 `node_modules`에 구버전이 설치된 경우 `npm run monitor`를 사용하면 구버전이 실행됩니다.
|
|
365
|
+
항상 `npx @elyun/bylane monitor`를 사용하세요.
|
|
366
|
+
|
|
367
|
+
### 루프 중복 실행
|
|
368
|
+
|
|
369
|
+
review-loop / respond-loop는 PID를 상태 파일에 기록합니다. 모니터의 `[s]` 키 또는 `pgrep`으로 확인 후 종료하세요.
|
|
370
|
+
|
|
371
|
+
### GitHub MCP vs CLI
|
|
372
|
+
|
|
373
|
+
Claude Code 세션 내에서는 MCP가 자동 사용됩니다. 루프 폴러(`review-loop.js`, `respond-loop.js`)는 Claude 세션 외부에서 실행되므로 `gh` CLI 또는 `GITHUB_TOKEN`이 필요합니다.
|
|
374
|
+
|
|
375
|
+
### 상태 정리 (cleanup)
|
|
376
|
+
|
|
377
|
+
에이전트 크래시, 루프 강제 종료, 권한 문제 등으로 상태가 꼬인 경우 정리 명령을 실행합니다:
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
npx @elyun/bylane cleanup
|
|
245
381
|
```
|
|
246
382
|
|
|
247
|
-
|
|
383
|
+
또는 모니터에서 **`[r]`** 키를 누르면 즉시 실행됩니다.
|
|
248
384
|
|
|
249
|
-
|
|
385
|
+
| 정리 항목 | 동작 |
|
|
386
|
+
|-----------|------|
|
|
387
|
+
| `.bylane/state/` 권한 | 디렉토리 755, 파일 644로 수정 |
|
|
388
|
+
| 죽은 루프 프로세스 | PID 확인 → 없으면 `stopped`로 전환 |
|
|
389
|
+
| 30분 초과 `in_progress` | `failed`로 초기화 |
|
|
390
|
+
| `subagents.json` active | PID 없는 항목 제거 |
|
|
391
|
+
| 큐의 `reviewing`/`responding` | `pending`으로 복구 (재처리 대기) |
|
|
250
392
|
|
|
251
|
-
|
|
252
|
-
|----|------|
|
|
253
|
-
| `"auto"` (기본) | MCP → CLI → API 순서로 자동 시도 |
|
|
254
|
-
| `"mcp"` | GitHub MCP 도구만 사용 |
|
|
255
|
-
| `"cli"` | `gh` CLI만 사용 |
|
|
256
|
-
| `"api"` | REST API + `$GITHUB_TOKEN`만 사용 |
|
|
393
|
+
정리 후 상태 파일이 갱신되면 chokidar가 감지하여 모니터에 즉시 반영됩니다.
|
|
257
394
|
|
|
258
|
-
|
|
395
|
+
### 하위 에이전트 제어
|
|
259
396
|
|
|
260
|
-
`
|
|
397
|
+
모니터에서 `[c]`를 누르면 `.bylane/state/cancel.json`이 생성되어 새 하위 에이전트 호출이 차단됩니다. 다시 `[c]`를 누르면 해제됩니다.
|
|
261
398
|
|
|
262
|
-
|
|
399
|
+
---
|
|
263
400
|
|
|
264
401
|
## 보안
|
|
265
402
|
|
|
266
|
-
`npm install` 시 pre-commit 훅이 자동 등록됩니다.
|
|
403
|
+
`npm install` 시 pre-commit 훅이 자동 등록됩니다.
|
|
267
404
|
|
|
268
|
-
| 항목 |
|
|
269
|
-
|
|
270
|
-
| AWS/OpenAI/GitHub/Slack/Google 키 패턴 |
|
|
271
|
-
| `.env`, `.pem`, `credentials` 등 민감 파일 |
|
|
272
|
-
| 하드코딩된 password/api_key |
|
|
273
|
-
| `node_modules` 실수 커밋 |
|
|
274
|
-
| `console.log` |
|
|
405
|
+
| 검사 항목 | 처리 |
|
|
406
|
+
|-----------|------|
|
|
407
|
+
| AWS/OpenAI/GitHub/Slack/Google 키 패턴 | 커밋 차단 |
|
|
408
|
+
| `.env`, `.pem`, `credentials` 등 민감 파일 | 커밋 차단 |
|
|
409
|
+
| 하드코딩된 password/api_key | 커밋 차단 |
|
|
410
|
+
| `node_modules` 실수 커밋 | 커밋 차단 |
|
|
411
|
+
| `console.log` | 경고 (차단 안 함) |
|
|
275
412
|
|
|
276
|
-
우회: `git commit --no-verify`
|
|
413
|
+
우회: `git commit --no-verify` (권장하지 않음)
|
|
414
|
+
|
|
415
|
+
---
|
|
277
416
|
|
|
278
417
|
## 개발
|
|
279
418
|
|
|
280
419
|
```bash
|
|
281
420
|
npm install # 의존성 설치 + pre-commit 훅 등록
|
|
282
421
|
npm test # 테스트 실행 (19개)
|
|
283
|
-
npm run monitor # 모니터 대시보드
|
|
284
|
-
npm version minor # 버전 올리기
|
|
285
|
-
npm run release # npm 배포 (커밋/푸시 후 실행)
|
|
422
|
+
npm run monitor # 모니터 대시보드 (소스에서 직접 실행)
|
|
423
|
+
npm version minor # 버전 올리기 (커밋 + 태그 자동 생성)
|
|
424
|
+
npm run release # npm 배포 (커밋/푸시 완료 후 실행)
|
|
286
425
|
```
|
|
287
426
|
|
|
427
|
+
---
|
|
428
|
+
|
|
288
429
|
## 라이선스
|
|
289
430
|
|
|
290
431
|
MIT
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bylane-cleanup
|
|
3
|
+
description: 상태 파일 권한 수정, 좀비 에이전트/루프 초기화, 큐 복구를 한 번에 실행한다.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /bylane cleanup
|
|
7
|
+
|
|
8
|
+
## 중요
|
|
9
|
+
|
|
10
|
+
cleanup은 **터미널 명령**으로 실행하세요. Claude가 직접 실행하지 않고 명령을 안내합니다.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
아래 명령을 실행하면 상태 정리가 완료됩니다:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx @elyun/bylane cleanup
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
또는 모니터가 실행 중이라면 **`[r]` 키**를 누르면 즉시 실행됩니다.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 정리 항목
|
|
25
|
+
|
|
26
|
+
| 항목 | 동작 |
|
|
27
|
+
|------|------|
|
|
28
|
+
| `.bylane/state/` 권한 | 디렉토리 755, 파일 644로 수정 |
|
|
29
|
+
| 죽은 루프 프로세스 | PID 확인 → 없으면 `stopped`로 전환 |
|
|
30
|
+
| 30분 초과 `in_progress` | `failed`로 초기화 |
|
|
31
|
+
| `subagents.json` active | PID 없는 항목 제거 |
|
|
32
|
+
| 큐의 `reviewing`/`responding` | `pending`으로 복구 (재처리 대기) |
|
|
33
|
+
|
|
34
|
+
## 완료
|
|
35
|
+
|
|
36
|
+
안내 후 이 스킬을 즉시 종료합니다.
|
|
@@ -12,18 +12,7 @@ description: issue-agent의 스펙을 기반으로 프론트엔드 코드를 구
|
|
|
12
12
|
## 실행 전 상태 기록
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
|
|
16
|
-
import('./src/state.js').then(({writeState}) => {
|
|
17
|
-
writeState('code-agent', {
|
|
18
|
-
status: 'in_progress',
|
|
19
|
-
startedAt: new Date().toISOString(),
|
|
20
|
-
progress: 0,
|
|
21
|
-
currentTask: '코드 구현 시작',
|
|
22
|
-
retries: 0,
|
|
23
|
-
log: []
|
|
24
|
-
})
|
|
25
|
-
})
|
|
26
|
-
"
|
|
15
|
+
npx @elyun/bylane state write code-agent '{"status":"in_progress","startedAt":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","progress":0,"currentTask":"코드 구현 시작","retries":0,"log":[]}'
|
|
27
16
|
```
|
|
28
17
|
|
|
29
18
|
## 실행 흐름
|
|
@@ -36,21 +25,11 @@ import('./src/state.js').then(({writeState}) => {
|
|
|
36
25
|
4. 기존 코드베이스 패턴 파악 후 동일 스타일로 작성 (TypeScript, 테스트 파일 위치 등)
|
|
37
26
|
5. 각 파일 구현 후 `appendLog` 호출:
|
|
38
27
|
```bash
|
|
39
|
-
|
|
28
|
+
npx @elyun/bylane state append code-agent "FILENAME 구현 완료"
|
|
40
29
|
```
|
|
41
30
|
6. 구현 완료 후 상태 업데이트:
|
|
42
31
|
```bash
|
|
43
|
-
|
|
44
|
-
import('./src/state.js').then(({writeState}) => {
|
|
45
|
-
writeState('code-agent', {
|
|
46
|
-
status: 'completed',
|
|
47
|
-
progress: 100,
|
|
48
|
-
currentTask: '구현 완료',
|
|
49
|
-
retries: 0,
|
|
50
|
-
changedFiles: CHANGED_FILES_ARRAY
|
|
51
|
-
})
|
|
52
|
-
})
|
|
53
|
-
"
|
|
32
|
+
npx @elyun/bylane state write code-agent '{"status":"completed","progress":100,"currentTask":"구현 완료","retries":0,"changedFiles":CHANGED_FILES_ARRAY}'
|
|
54
33
|
```
|
|
55
34
|
|
|
56
35
|
## 코딩 원칙
|
|
@@ -14,7 +14,7 @@ description: 변경된 파일들을 conventional commit 형식으로 커밋한
|
|
|
14
14
|
## 실행 전 상태 기록
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
|
|
17
|
+
npx @elyun/bylane state write commit-agent '{"status":"in_progress","startedAt":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","progress":0,"retries":0,"log":[]}'
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
## GitHub 커밋 템플릿 탐지
|
|
@@ -36,16 +36,7 @@ ls .gitmessage .github/commit-template.txt .github/COMMIT_TEMPLATE.md 2>/dev/nul
|
|
|
36
36
|
|
|
37
37
|
1. 브랜치명 생성:
|
|
38
38
|
```bash
|
|
39
|
-
|
|
40
|
-
Promise.all([
|
|
41
|
-
import('./src/branch.js'),
|
|
42
|
-
import('./src/config.js')
|
|
43
|
-
]).then(([{buildBranchNameFromConfig},{loadConfig}]) => {
|
|
44
|
-
const config = loadConfig()
|
|
45
|
-
const branch = buildBranchNameFromConfig(config, ISSUE_NUMBER)
|
|
46
|
-
console.log(branch)
|
|
47
|
-
})
|
|
48
|
-
"
|
|
39
|
+
npx @elyun/bylane branch ISSUE_NUMBER
|
|
49
40
|
```
|
|
50
41
|
|
|
51
42
|
2. 브랜치 생성 및 체크아웃:
|
|
@@ -72,14 +63,7 @@ ls .gitmessage .github/commit-template.txt .github/COMMIT_TEMPLATE.md 2>/dev/nul
|
|
|
72
63
|
|
|
73
64
|
6. 상태 업데이트:
|
|
74
65
|
```bash
|
|
75
|
-
|
|
76
|
-
import('./src/state.js').then(({writeState})=>writeState('commit-agent',{
|
|
77
|
-
status:'completed',
|
|
78
|
-
progress:100,
|
|
79
|
-
branchName:'BRANCH_NAME',
|
|
80
|
-
commitSha:'COMMIT_SHA'
|
|
81
|
-
}))
|
|
82
|
-
"
|
|
66
|
+
npx @elyun/bylane state write commit-agent '{"status":"completed","progress":100,"branchName":"BRANCH_NAME","commitSha":"COMMIT_SHA"}'
|
|
83
67
|
```
|
|
84
68
|
|
|
85
69
|
## 출력
|
|
@@ -102,7 +102,7 @@ Figma MCP `get_file` 또는 `get_node` 도구로 프레임/컴포넌트 분석.
|
|
|
102
102
|
|
|
103
103
|
상태 기록:
|
|
104
104
|
```bash
|
|
105
|
-
|
|
105
|
+
npx @elyun/bylane state write issue-agent '{"status":"in_progress","startedAt":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","progress":0,"retries":0}'
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
## 수동 실행
|
|
@@ -14,7 +14,7 @@ description: 워크플로우 완료 또는 개입 필요 시 Slack/Telegram으
|
|
|
14
14
|
## 실행 전 상태 기록
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
|
|
17
|
+
npx @elyun/bylane state write notify-agent '{"status":"in_progress","startedAt":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","progress":0,"retries":0,"log":[]}'
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
## 실행 흐름
|
|
@@ -19,19 +19,11 @@ description: byLane 메인 오케스트레이터. 자연어 의도를 파싱해
|
|
|
19
19
|
각 에이전트 실행 전 사용할 모델을 config에서 읽는다:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
|
|
23
|
-
import('./src/config.js').then(({loadConfig, getAgentModel}) => {
|
|
24
|
-
const config = loadConfig()
|
|
25
|
-
const agents = [
|
|
26
|
-
'orchestrator','issue-agent','code-agent','test-agent',
|
|
27
|
-
'commit-agent','pr-agent','review-agent','respond-agent','notify-agent'
|
|
28
|
-
]
|
|
29
|
-
agents.forEach(a => console.log(a + ': ' + getAgentModel(config, a)))
|
|
30
|
-
// analyze-agent는 항상 opus 사용 (config 무관)
|
|
31
|
-
})
|
|
32
|
-
"
|
|
22
|
+
npx @elyun/bylane models
|
|
33
23
|
```
|
|
34
24
|
|
|
25
|
+
출력 형식: `AGENT_NAME=MODEL_ID` (한 줄씩)
|
|
26
|
+
|
|
35
27
|
에이전트 호출 시 해당 모델을 `model` 파라미터로 전달한다.
|
|
36
28
|
|
|
37
29
|
## 의도 파싱 규칙
|
|
@@ -57,18 +49,7 @@ import('./src/config.js').then(({loadConfig, getAgentModel}) => {
|
|
|
57
49
|
|
|
58
50
|
상태 기록 (각 에이전트 시작 전):
|
|
59
51
|
```bash
|
|
60
|
-
|
|
61
|
-
import('./src/state.js').then(({writeState}) => {
|
|
62
|
-
writeState('AGENT_NAME', {
|
|
63
|
-
status: 'in_progress',
|
|
64
|
-
startedAt: new Date().toISOString(),
|
|
65
|
-
progress: 0,
|
|
66
|
-
currentTask: 'TASK_DESCRIPTION',
|
|
67
|
-
retries: 0,
|
|
68
|
-
log: []
|
|
69
|
-
})
|
|
70
|
-
})
|
|
71
|
-
"
|
|
52
|
+
npx @elyun/bylane state write AGENT_NAME '{"status":"in_progress","startedAt":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","progress":0,"currentTask":"TASK_DESCRIPTION","retries":0,"log":[]}'
|
|
72
53
|
```
|
|
73
54
|
|
|
74
55
|
## 피드백 루프
|
|
@@ -43,7 +43,7 @@ ls .github/PULL_REQUEST_TEMPLATE/*.md 2>/dev/null | head -5
|
|
|
43
43
|
## 실행 전 상태 기록
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
|
|
46
|
+
npx @elyun/bylane state write pr-agent '{"status":"in_progress","startedAt":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","progress":0,"retries":0,"log":[]}'
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
## 실행 흐름
|
|
@@ -90,7 +90,7 @@ node -e "import('./src/state.js').then(({writeState})=>writeState('pr-agent',{st
|
|
|
90
90
|
|
|
91
91
|
4. 상태 업데이트:
|
|
92
92
|
```bash
|
|
93
|
-
|
|
93
|
+
npx @elyun/bylane state write pr-agent '{"status":"completed","progress":100,"prNumber":PR_NUMBER,"prUrl":"PR_URL"}'
|
|
94
94
|
```
|
|
95
95
|
|
|
96
96
|
## 출력
|
|
@@ -64,7 +64,7 @@ ls .github/REVIEW_RESPONSE_TEMPLATE.md \
|
|
|
64
64
|
## 실행 전 상태 기록
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
|
-
|
|
67
|
+
npx @elyun/bylane state write respond-agent '{"status":"in_progress","startedAt":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","progress":0,"retries":0,"log":[]}'
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
## 실행 흐름
|
|
@@ -30,15 +30,12 @@ echo "폴러 PID: $!"
|
|
|
30
30
|
pending PR이 생길 때마다 respond-agent를 실행한다:
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const q = readState('respond-queue', '.bylane/state')
|
|
36
|
-
const pending = (q?.queue ?? []).filter(p => p.status === 'pending')
|
|
37
|
-
console.log(JSON.stringify(pending))
|
|
38
|
-
})
|
|
39
|
-
"
|
|
33
|
+
# 큐 확인 (pending 항목 필터링)
|
|
34
|
+
npx @elyun/bylane state read respond-queue
|
|
40
35
|
```
|
|
41
36
|
|
|
37
|
+
출력된 JSON에서 `queue` 배열의 `status === "pending"` 항목을 선택한다.
|
|
38
|
+
|
|
42
39
|
pending 항목이 있으면 각 PR에 대해:
|
|
43
40
|
|
|
44
41
|
1. `hasChangesRequested` 여부 확인:
|
|
@@ -50,17 +47,8 @@ pending 항목이 있으면 각 PR에 대해:
|
|
|
50
47
|
3. 완료 후 큐 항목을 `status: "responded"`로 업데이트:
|
|
51
48
|
|
|
52
49
|
```bash
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const q = readState('respond-queue', '.bylane/state')
|
|
56
|
-
const queue = (q?.queue ?? []).map(p =>
|
|
57
|
-
p.number === PR_NUMBER
|
|
58
|
-
? { ...p, status: 'responded', respondedAt: new Date().toISOString() }
|
|
59
|
-
: p
|
|
60
|
-
)
|
|
61
|
-
writeState('respond-queue', { status: 'running', queue }, '.bylane/state')
|
|
62
|
-
})
|
|
63
|
-
"
|
|
50
|
+
# 현재 큐 읽기 후 PR_NUMBER 항목을 responded로 업데이트하여 다시 쓰기
|
|
51
|
+
npx @elyun/bylane state write respond-queue '{"status":"running","queue":UPDATED_QUEUE_JSON}'
|
|
64
52
|
```
|
|
65
53
|
|
|
66
54
|
4. 다음 pending 항목으로 반복. pending 없으면 5분 대기 후 재확인 (폴러 주기와 동일).
|
|
@@ -69,7 +69,7 @@ Enter 또는 아무것도 선택하지 않으면 → `all` (전체 검사)
|
|
|
69
69
|
## 실행 전 상태 기록
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
|
-
|
|
72
|
+
npx @elyun/bylane state write review-agent '{"status":"in_progress","startedAt":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","progress":0,"retries":0,"log":[]}'
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
## 실행 흐름
|
|
@@ -180,7 +180,7 @@ curl -s -X POST \
|
|
|
180
180
|
### 5. 상태 업데이트
|
|
181
181
|
|
|
182
182
|
```bash
|
|
183
|
-
|
|
183
|
+
npx @elyun/bylane state write review-agent '{"status":"completed","progress":100,"approved":APPROVED_BOOL,"commentCount":COMMENT_COUNT}'
|
|
184
184
|
```
|
|
185
185
|
|
|
186
186
|
## 출력
|
|
@@ -47,30 +47,19 @@ node src/review-loop.js
|
|
|
47
47
|
아래 루프를 실행하면서 pending PR이 생길 때마다 review-agent를 실행한다:
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
# 큐 확인
|
|
51
|
-
|
|
52
|
-
import('./src/state.js').then(({readState}) => {
|
|
53
|
-
const q = readState('review-queue', '.bylane/state')
|
|
54
|
-
const pending = (q?.queue ?? []).filter(p => p.status === 'pending')
|
|
55
|
-
console.log(JSON.stringify(pending))
|
|
56
|
-
})
|
|
57
|
-
"
|
|
50
|
+
# 큐 확인 (pending 항목 필터링)
|
|
51
|
+
npx @elyun/bylane state read review-queue
|
|
58
52
|
```
|
|
59
53
|
|
|
54
|
+
출력된 JSON에서 `queue` 배열의 `status === "pending"` 항목을 선택한다.
|
|
55
|
+
|
|
60
56
|
pending 항목이 있으면 각 PR에 대해:
|
|
61
57
|
1. `bylane-review-agent` skill 실행 (PR 번호 전달)
|
|
62
58
|
2. 리뷰 완료 후 큐 항목을 `status: "reviewed"`로 업데이트:
|
|
63
59
|
|
|
64
60
|
```bash
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const q = readState('review-queue', '.bylane/state')
|
|
68
|
-
const queue = (q?.queue ?? []).map(p =>
|
|
69
|
-
p.number === PR_NUMBER ? { ...p, status: 'reviewed', reviewedAt: new Date().toISOString() } : p
|
|
70
|
-
)
|
|
71
|
-
writeState('review-queue', { status: 'running', queue }, '.bylane/state')
|
|
72
|
-
})
|
|
73
|
-
"
|
|
61
|
+
# 현재 큐 읽기 후 PR_NUMBER 항목을 reviewed로 업데이트하여 다시 쓰기
|
|
62
|
+
npx @elyun/bylane state write review-queue '{"status":"running","queue":UPDATED_QUEUE_JSON}'
|
|
74
63
|
```
|
|
75
64
|
|
|
76
65
|
3. 다음 pending 항목으로 반복
|
|
@@ -12,7 +12,7 @@ description: 변경된 코드의 테스트를 실행하고 결과를 반환한
|
|
|
12
12
|
## 실행 전 상태 기록
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
|
|
15
|
+
npx @elyun/bylane state write test-agent '{"status":"in_progress","startedAt":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","progress":0,"retries":0,"log":[]}'
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
## 실행 흐름
|
package/package.json
CHANGED
package/src/cleanup.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cleanup.js
|
|
3
|
+
* 상태 파일 정리, 권한 수정, 좀비 프로세스/에이전트 초기화
|
|
4
|
+
* monitor [r] 키 또는 `npx @elyun/bylane cleanup`으로 실행
|
|
5
|
+
*/
|
|
6
|
+
import { readdirSync, chmodSync, existsSync, unlinkSync, readFileSync, writeFileSync, mkdirSync } from 'fs'
|
|
7
|
+
import { join } from 'path'
|
|
8
|
+
import { readState, writeState } from './state.js'
|
|
9
|
+
|
|
10
|
+
const STATE_DIR = '.bylane/state'
|
|
11
|
+
const STALE_MS = 30 * 60 * 1000 // 30분 이상 in_progress → 초기화
|
|
12
|
+
const SUBAGENTS_FILE = join(STATE_DIR, 'subagents.json')
|
|
13
|
+
|
|
14
|
+
/** 프로세스가 살아있는지 확인 (kill -0) */
|
|
15
|
+
function isPidAlive(pid) {
|
|
16
|
+
try {
|
|
17
|
+
process.kill(Number(pid), 0)
|
|
18
|
+
return true
|
|
19
|
+
} catch {
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** subagents.json에서 죽은 PID 제거 */
|
|
25
|
+
function cleanSubagents() {
|
|
26
|
+
if (!existsSync(SUBAGENTS_FILE)) return []
|
|
27
|
+
let data
|
|
28
|
+
try { data = JSON.parse(readFileSync(SUBAGENTS_FILE, 'utf8')) } catch { return [] }
|
|
29
|
+
|
|
30
|
+
const before = (data.active ?? []).length
|
|
31
|
+
const active = (data.active ?? []).filter(a => {
|
|
32
|
+
if (!a.pid) return false
|
|
33
|
+
return isPidAlive(a.pid)
|
|
34
|
+
})
|
|
35
|
+
const cleaned = before - active.length
|
|
36
|
+
|
|
37
|
+
if (cleaned > 0) {
|
|
38
|
+
writeFileSync(SUBAGENTS_FILE, JSON.stringify({ ...data, active }, null, 2))
|
|
39
|
+
}
|
|
40
|
+
return cleaned > 0 ? [`subagents.json: active ${before}개 → ${active.length}개 (${cleaned}개 제거)`] : []
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 디렉토리 및 파일 권한 수정 */
|
|
44
|
+
function fixPermissions() {
|
|
45
|
+
const fixed = []
|
|
46
|
+
try {
|
|
47
|
+
chmodSync(STATE_DIR, 0o755)
|
|
48
|
+
} catch {}
|
|
49
|
+
|
|
50
|
+
const files = existsSync(STATE_DIR)
|
|
51
|
+
? readdirSync(STATE_DIR).filter(f => f.endsWith('.json'))
|
|
52
|
+
: []
|
|
53
|
+
|
|
54
|
+
for (const file of files) {
|
|
55
|
+
try {
|
|
56
|
+
chmodSync(join(STATE_DIR, file), 0o644)
|
|
57
|
+
} catch (e) {
|
|
58
|
+
fixed.push(`권한 수정 실패: ${file} — ${e.message}`)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return fixed
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 전체 정리 실행
|
|
66
|
+
* @returns {{ fixed: string[], killed: string[], reset: string[], cleared: string[] }}
|
|
67
|
+
*/
|
|
68
|
+
export function runCleanup() {
|
|
69
|
+
const result = { fixed: [], killed: [], reset: [], cleared: [] }
|
|
70
|
+
|
|
71
|
+
if (!existsSync(STATE_DIR)) {
|
|
72
|
+
mkdirSync(STATE_DIR, { recursive: true })
|
|
73
|
+
return result
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 1. 파일 권한 수정
|
|
77
|
+
result.fixed.push(...fixPermissions())
|
|
78
|
+
|
|
79
|
+
// 2. 상태 파일 순회
|
|
80
|
+
const files = readdirSync(STATE_DIR).filter(f => f.endsWith('.json'))
|
|
81
|
+
|
|
82
|
+
for (const file of files) {
|
|
83
|
+
const name = file.replace('.json', '')
|
|
84
|
+
|
|
85
|
+
// 특수 파일 건너뜀
|
|
86
|
+
if (name === 'cancel') continue
|
|
87
|
+
|
|
88
|
+
// subagents 별도 처리
|
|
89
|
+
if (name === 'subagents') {
|
|
90
|
+
result.cleared.push(...cleanSubagents())
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const state = readState(name, STATE_DIR)
|
|
95
|
+
if (!state) continue
|
|
96
|
+
|
|
97
|
+
// 루프: PID가 죽었으면 stopped로 전환
|
|
98
|
+
if (name.endsWith('-loop') && state.pid && state.status === 'running') {
|
|
99
|
+
if (!isPidAlive(state.pid)) {
|
|
100
|
+
writeState(name, { ...state, status: 'stopped', stoppedAt: new Date().toISOString() }, STATE_DIR)
|
|
101
|
+
result.killed.push(`${name}: PID ${state.pid} 없음 → stopped`)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// in_progress 상태가 30분 이상 → failed로 초기화
|
|
106
|
+
if (state.status === 'in_progress' && state.startedAt) {
|
|
107
|
+
const age = Date.now() - new Date(state.startedAt).getTime()
|
|
108
|
+
if (age > STALE_MS) {
|
|
109
|
+
writeState(name, {
|
|
110
|
+
...state,
|
|
111
|
+
status: 'failed',
|
|
112
|
+
error: `stale: ${Math.floor(age / 60000)}분 초과 in_progress`
|
|
113
|
+
}, STATE_DIR)
|
|
114
|
+
result.reset.push(`${name}: ${Math.floor(age / 60000)}분 in_progress → failed`)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 큐 파일: responding/reviewing 상태가 남아있으면 pending으로 복구
|
|
119
|
+
if ((name === 'review-queue' || name === 'respond-queue') && Array.isArray(state.queue)) {
|
|
120
|
+
const fixed = state.queue.map(item =>
|
|
121
|
+
item.status === 'reviewing' || item.status === 'responding'
|
|
122
|
+
? { ...item, status: 'pending', recoveredAt: new Date().toISOString() }
|
|
123
|
+
: item
|
|
124
|
+
)
|
|
125
|
+
const changedCount = fixed.filter((item, i) => item.status !== state.queue[i].status).length
|
|
126
|
+
if (changedCount > 0) {
|
|
127
|
+
writeState(name, { ...state, queue: fixed }, STATE_DIR)
|
|
128
|
+
result.reset.push(`${name}: ${changedCount}개 진행중 항목 → pending 복구`)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return result
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** 결과를 사람이 읽기 쉬운 문자열로 출력 */
|
|
137
|
+
export function formatCleanupResult(result) {
|
|
138
|
+
const lines = []
|
|
139
|
+
const total = Object.values(result).reduce((s, arr) => s + arr.length, 0)
|
|
140
|
+
|
|
141
|
+
if (total === 0) {
|
|
142
|
+
lines.push('정리할 항목 없음.')
|
|
143
|
+
return lines.join('\n')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (result.killed.length) lines.push(' [종료]', ...result.killed.map(s => ` · ${s}`))
|
|
147
|
+
if (result.reset.length) lines.push(' [초기화]', ...result.reset.map(s => ` · ${s}`))
|
|
148
|
+
if (result.cleared.length) lines.push(' [정리]', ...result.cleared.map(s => ` · ${s}`))
|
|
149
|
+
if (result.fixed.length) lines.push(' [오류]', ...result.fixed.map(s => ` · ${s}`))
|
|
150
|
+
|
|
151
|
+
return lines.join('\n')
|
|
152
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -141,6 +141,45 @@ function install() {
|
|
|
141
141
|
|
|
142
142
|
if (command === 'install') {
|
|
143
143
|
install()
|
|
144
|
+
} else if (command === 'models') {
|
|
145
|
+
// models → 에이전트별 모델 목록 출력 (KEY=VALUE 형식)
|
|
146
|
+
const { loadConfig, getAgentModel } = await import('./config.js')
|
|
147
|
+
const config = loadConfig()
|
|
148
|
+
const agents = ['orchestrator','issue-agent','code-agent','test-agent',
|
|
149
|
+
'commit-agent','pr-agent','review-agent','respond-agent','notify-agent','analyze-agent']
|
|
150
|
+
agents.forEach(a => console.log(`${a}=${getAgentModel(config, a)}`))
|
|
151
|
+
} else if (command === 'branch') {
|
|
152
|
+
// branch ISSUE_NUMBER → 브랜치명 출력
|
|
153
|
+
const issueNumber = Number(args[1])
|
|
154
|
+
if (!issueNumber) { console.error('사용법: bylane branch <issueNumber>'); process.exit(1) }
|
|
155
|
+
const { buildBranchNameFromConfig } = await import('./branch.js')
|
|
156
|
+
const { loadConfig } = await import('./config.js')
|
|
157
|
+
console.log(buildBranchNameFromConfig(loadConfig(), issueNumber))
|
|
158
|
+
} else if (command === 'state') {
|
|
159
|
+
// state write AGENT '{"status":"in_progress",...}'
|
|
160
|
+
// state append AGENT "메시지"
|
|
161
|
+
// state read AGENT
|
|
162
|
+
const subCmd = args[1]
|
|
163
|
+
const agentName = args[2]
|
|
164
|
+
const payload = args[3]
|
|
165
|
+
const { writeState, appendLog, readState } = await import('./state.js')
|
|
166
|
+
|
|
167
|
+
if (subCmd === 'write' && agentName && payload) {
|
|
168
|
+
writeState(agentName, JSON.parse(payload))
|
|
169
|
+
} else if (subCmd === 'append' && agentName && payload) {
|
|
170
|
+
appendLog(agentName, payload)
|
|
171
|
+
} else if (subCmd === 'read' && agentName) {
|
|
172
|
+
console.log(JSON.stringify(readState(agentName), null, 2))
|
|
173
|
+
} else {
|
|
174
|
+
console.error('사용법: bylane state <write|append|read> <agentName> [payload]')
|
|
175
|
+
process.exit(1)
|
|
176
|
+
}
|
|
177
|
+
} else if (command === 'cleanup') {
|
|
178
|
+
const { runCleanup, formatCleanupResult } = await import('./cleanup.js')
|
|
179
|
+
console.log('\n byLane 상태 정리 중...\n')
|
|
180
|
+
const result = runCleanup()
|
|
181
|
+
console.log(formatCleanupResult(result))
|
|
182
|
+
console.log('\n 완료.\n')
|
|
144
183
|
} else if (command === 'monitor') {
|
|
145
184
|
// 항상 현재 패키지의 모니터 실행 (버전 일치 보장)
|
|
146
185
|
const monitorPath = join(__dirname, 'monitor', 'index.js')
|
package/src/monitor/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { createLayout } from './layout.js'
|
|
|
4
4
|
import { createPoller } from './poller.js'
|
|
5
5
|
import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync } from 'fs'
|
|
6
6
|
import { join } from 'path'
|
|
7
|
+
import { runCleanup, formatCleanupResult } from '../cleanup.js'
|
|
7
8
|
|
|
8
9
|
const { screen, header, pipeline, log, queue, status, onCleanup } = createLayout()
|
|
9
10
|
const poller = createPoller()
|
|
@@ -101,6 +102,34 @@ screen.key('s', () => {
|
|
|
101
102
|
})
|
|
102
103
|
})
|
|
103
104
|
|
|
105
|
+
// 'r' — 상태 정리 (권한 수정, 좀비 초기화, 큐 복구)
|
|
106
|
+
screen.key('r', () => {
|
|
107
|
+
header.update({
|
|
108
|
+
workflowTitle: '상태 정리 중...',
|
|
109
|
+
time: new Date().toLocaleTimeString('ko-KR', { hour12: false }),
|
|
110
|
+
elapsed: `${Math.floor((Date.now() - startTime) / 1000)}s`
|
|
111
|
+
})
|
|
112
|
+
screen.render()
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const result = runCleanup()
|
|
116
|
+
const summary = formatCleanupResult(result)
|
|
117
|
+
const total = Object.values(result).reduce((s, a) => s + a.length, 0)
|
|
118
|
+
header.update({
|
|
119
|
+
workflowTitle: total > 0 ? `정리 완료 (${total}건)` : '정리 완료 — 이상 없음',
|
|
120
|
+
time: new Date().toLocaleTimeString('ko-KR', { hour12: false }),
|
|
121
|
+
elapsed: `${Math.floor((Date.now() - startTime) / 1000)}s`
|
|
122
|
+
})
|
|
123
|
+
} catch (e) {
|
|
124
|
+
header.update({
|
|
125
|
+
workflowTitle: `정리 실패: ${e.message}`,
|
|
126
|
+
time: new Date().toLocaleTimeString('ko-KR', { hour12: false }),
|
|
127
|
+
elapsed: `${Math.floor((Date.now() - startTime) / 1000)}s`
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
screen.render()
|
|
131
|
+
})
|
|
132
|
+
|
|
104
133
|
// 'c' — 하위 에이전트 취소 플래그 토글
|
|
105
134
|
screen.key('c', () => {
|
|
106
135
|
if (existsSync(CANCEL_FILE)) {
|
package/src/monitor/layout.js
CHANGED
|
@@ -62,7 +62,7 @@ export function createLayout() {
|
|
|
62
62
|
left: 0,
|
|
63
63
|
width: '100%',
|
|
64
64
|
height: 1,
|
|
65
|
-
content: ' [q]종료 [c]에이전트취소토글 [s]루프종료 [Tab]포커스 [j/k]로그스크롤',
|
|
65
|
+
content: ' [q]종료 [r]상태정리 [c]에이전트취소토글 [s]루프종료 [Tab]포커스 [j/k]로그스크롤',
|
|
66
66
|
style: { fg: 'black', bg: 'cyan' }
|
|
67
67
|
})
|
|
68
68
|
screen.append(footer)
|