@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 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
- - **프로젝트 분석** — 코드 스타일·디자인 토큰·아키텍처를 자동 분석해 `.claude/instructions/`에 저장, CLAUDE.md에 자동 import
10
- - **전체 워크플로우 자동화** — `/bylane 다크모드 토글 추가해줘` 한 줄로 이슈 생성부터 PR까지
11
- - **개별 실행 가능** — 각 에이전트를 단독으로 실행 가능
12
- - **실시간 모니터링** — 2열 그리드 터미널 TUI 대시보드
13
- - **자동 리뷰 루프** 5분 주기로 review 요청 PR 자동 리뷰
14
- - **자동 대응 루프** 5분 주기로 PR 리뷰/코멘트 자동 대응 (accept/rebut)
15
- - **피드백 루프** 테스트 실패 자동 재시도 (설정 가능)
16
- - **GitHub 접근 방법** MCP / CLI(`gh`) / REST API 자동 감지
17
- - **리뷰 템플릿** 코드 예시, 모델 명시, 커스텀 푸터 설정 가능
18
- - **에이전트별 모델 설정** 에이전트마다 다른 AI 모델 지정 가능
19
- - **Figma MCP 연동** 이슈의 Figma 링크에서 스펙 자동 추출 (선택적)
20
- - **Slack / Telegram 알림** 완료 또는 개입 필요 알림
21
- - **pre-commit 보안 훅** 시크릿/민감 파일/console.log 자동 검사
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 기본 제공), `gh` CLI, 또는 `GITHUB_TOKEN` 중 하나
28
- - Slack MCP / Telegram (알림 사용 )
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
- Skills, Commands, Hooks 파일을 `~/.claude/` 하위 디렉토리에 자동으로 복사합니다.
40
- 기존 파일이 있으면 `.bak`으로 백업 후 교체합니다.
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 # pre-commit 보안 훅 자동 등록
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
- 7단계 인터랙티브 설정:
66
- 1. GitHub 접근 방법 (MCP / CLI / API / auto)
67
- 2. 이슈 트래커 (GitHub Issues / Linear / 둘 다)
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번호] PR 리뷰
98
- /bylane review-loop 5분 주기 자동 리뷰 루프
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
- 두 루프를 동시에 실행하면 review 요청과 리뷰 대응을 모두 자동화합니다:
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
- npm run monitor
118
- # 또는
119
- /bylane monitor
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
- | AGENT PIPELINE | AGENT LOG [LIVE] |
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
- ## 프로젝트 분석 (analyze)
124
+ ---
138
125
 
139
- `/bylane analyze`를 실행하면 현재 프로젝트를 자동 분석하여 Claude Code가 참조할 수 있는 instruction 파일을 생성합니다.
126
+ ## 모니터 대시보드
140
127
 
141
- ### 생성되는 파일
128
+ ```bash
129
+ npx @elyun/bylane monitor
130
+ ```
142
131
 
143
- | 파일 | 내용 |
144
- |---|---|
145
- | `.claude/instructions/code-style.md` | 언어, 포맷팅, 네이밍 컨벤션, import 규칙, 금지 패턴 |
146
- | `.claude/instructions/design-tokens.md` | 색상 팔레트, 타이포그래피, 간격, 브레이크포인트, 다크모드 |
147
- | `.claude/instructions/architecture.md` | 프레임워크, 렌더링 전략, 상태관리, 데이터 페칭, 폴더 구조 |
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
- 1. ESLint/Prettier/tsconfig, Tailwind config, CSS 변수, package.json 등 설정 파일을 자동 탐색
152
- 2. 소스 파일을 샘플링하여 실제 사용 패턴 파악
153
- 3. 분석 결과를 `.claude/instructions/`에 Markdown 파일로 저장
154
- 4. `CLAUDE.md`에 `@.claude/instructions/*.md` import 구문 자동 추가
155
- - `CLAUDE.md`가 없으면 `/init`으로 먼저 생성
197
+ ```suggestion
198
+ // 수정 제안 코드
199
+ ```
200
+ ```
201
+
202
+ ### GitHub 리뷰 템플릿 우선 적용
203
+
204
+ 프로젝트에 아래 파일이 있으면 해당 형식을 **최우선**으로 따릅니다:
156
205
 
157
206
  ```
158
- /bylane analyze # 분석 후 파일 생성 (기존 파일 확인 요청)
159
- /bylane analyze --force # 기존 instruction 파일 강제 덮어쓰기
207
+ .github/REVIEW_TEMPLATE.md
208
+ .github/CODE_REVIEW_TEMPLATE.md
209
+ docs/REVIEW_TEMPLATE.md
160
210
  ```
161
211
 
162
- 커스텀 내용은 `.claude/instructions/structure.md`를 직접 작성하고 CLAUDE.md에 `@.claude/instructions/structure.md`를 추가하면 재분석 시에도 유지됩니다.
212
+ 없으면 `templates/review-template.md`를 사용합니다.
213
+
214
+ ---
163
215
 
164
- ## 브랜치 네이밍 패턴
216
+ ## 리뷰 대응 (respond)
165
217
 
166
- | 패턴 | 예시 |
167
- |---|---|
168
- | `{tracker}-{issue-number}` | `issues-32` |
169
- | `{tracker}-{issue-number}-{custom-id}` | `issues-32-C-12` |
170
- | `{type}/{issue-number}-{title-slug}` | `feature/32-add-dark-mode` |
218
+ | 모드 | 동작 |
219
+ |------|------|
220
+ | `auto` (기본) | 코멘트를 분석해 수정 반영 또는 반박을 자동 판단, 요약 확인 후 실행 |
221
+ | `accept` | 모든 코멘트 수정 반영 |
222
+ | `rebut` | 모든 코멘트 반박 |
223
+ | `manual` | 코멘트별로 수정/반박/건너뜀 직접 선택 |
171
224
 
172
- 사용 가능한 토큰: `{tracker}`, `{type}`, `{issue-number}`, `{custom-id}`, `{title-slug}`, `{date}`, `{username}`
225
+ ```
226
+ /bylane respond #45 auto 모드 (기본)
227
+ /bylane respond #45 manual 코멘트별 수동 선택
228
+ /bylane respond #45 accept 전체 반영
229
+ ```
230
+
231
+ auto 모드에서는 실행 전 요약을 먼저 보여주고 확인을 받습니다:
173
232
 
174
- 빈 토큰은 자동으로 제외됩니다 (`{custom-id}` 없으면 `issues-32`).
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": "Reviewed by byLane · model: {model}"
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
- review-loop (독립 실행: 5분 주기 리뷰 요청 감지)
244
- respond-loop (독립 실행: 5분 주기 리뷰 코멘트 감지)
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
- ## GitHub 접근 방법
383
+ 또는 모니터에서 **`[r]`** 키를 누르면 즉시 실행됩니다.
248
384
 
249
- `github.method` 설정으로 제어:
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
- `templates/review-template.md`를 복사해 커스터마이즈한 `review.templateFile`에 경로를 지정합니다.
397
+ 모니터에서 `[c]`를 누르면 `.bylane/state/cancel.json`이 생성되어 하위 에이전트 호출이 차단됩니다. 다시 `[c]`를 누르면 해제됩니다.
261
398
 
262
- 기본 구성: 심각도 레이블 (CRITICAL/HIGH/MEDIUM/LOW), Before/After 코드 예시, 사용 모델 명시, 커스텀 푸터.
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 키 패턴 | CRITICAL (차단) |
271
- | `.env`, `.pem`, `credentials` 등 민감 파일 | CRITICAL (차단) |
272
- | 하드코딩된 password/api_key | CRITICAL (차단) |
273
- | `node_modules` 실수 커밋 | CRITICAL (차단) |
274
- | `console.log` | WARN (경고만) |
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
- node -e "
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
- node -e "import('./src/state.js').then(({appendLog})=>appendLog('code-agent','FILENAME 구현 완료'))"
28
+ npx @elyun/bylane state append code-agent "FILENAME 구현 완료"
40
29
  ```
41
30
  6. 구현 완료 후 상태 업데이트:
42
31
  ```bash
43
- node -e "
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
- node -e "import('./src/state.js').then(({writeState})=>writeState('commit-agent',{status:'in_progress',startedAt:new Date().toISOString(),progress:0,retries:0,log:[]}))"
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
- node -e "
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
- node -e "
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
- node -e "import('./src/state.js').then(({writeState})=>writeState('issue-agent',{status:'in_progress',startedAt:new Date().toISOString(),progress:0,retries:0}))"
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
- node -e "import('./src/state.js').then(({writeState})=>writeState('notify-agent',{status:'in_progress',startedAt:new Date().toISOString(),progress:0,retries:0,log:[]}))"
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
- node -e "
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
- node -e "
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
- node -e "import('./src/state.js').then(({writeState})=>writeState('pr-agent',{status:'in_progress',startedAt:new Date().toISOString(),progress:0,retries:0,log:[]}))"
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
- node -e "import('./src/state.js').then(({writeState})=>writeState('pr-agent',{status:'completed',progress:100,prNumber:PR_NUMBER,prUrl:'PR_URL'}))"
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
- node -e "import('./src/state.js').then(({writeState})=>writeState('respond-agent',{status:'in_progress',startedAt:new Date().toISOString(),progress:0,retries:0,log:[]}))"
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
- node -e "
34
- import('./src/state.js').then(({readState}) => {
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
- node -e "
54
- import('./src/state.js').then(({readState, writeState}) => {
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
- node -e "import('./src/state.js').then(({writeState})=>writeState('review-agent',{status:'in_progress',startedAt:new Date().toISOString(),progress:0,retries:0,log:[]}))"
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
- node -e "import('./src/state.js').then(({writeState})=>writeState('review-agent',{status:'completed',progress:100,approved:APPROVED_BOOL,commentCount:COMMENT_COUNT}))"
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
- node -e "
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
- node -e "
66
- import('./src/state.js').then(({readState, writeState}) => {
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
- node -e "import('./src/state.js').then(({writeState})=>writeState('test-agent',{status:'in_progress',startedAt:new Date().toISOString(),progress:0,retries:0,log:[]}))"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elyun/bylane",
3
- "version": "1.17.0",
3
+ "version": "1.18.0",
4
4
  "description": "Frontend development harness for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
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')
@@ -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)) {
@@ -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)