@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,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bylane-notify-agent
|
|
3
|
+
description: 워크플로우 완료 또는 개입 필요 시 Slack/Telegram으로 알림을 보낸다.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Notify Agent
|
|
7
|
+
|
|
8
|
+
## 입력
|
|
9
|
+
|
|
10
|
+
- `type`: `completed` | `escalated` | `error`
|
|
11
|
+
- `summary`: 결과 요약 텍스트
|
|
12
|
+
- `url`: 관련 GitHub URL (PR, Issue 등)
|
|
13
|
+
|
|
14
|
+
## 실행 전 상태 기록
|
|
15
|
+
|
|
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:[]}))"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 실행 흐름
|
|
21
|
+
|
|
22
|
+
`.bylane/bylane.json`에서 알림 설정 로드.
|
|
23
|
+
|
|
24
|
+
### 터미널 출력 (항상 실행)
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
[byLane] ✅ 완료: TITLE
|
|
28
|
+
PR: PR_URL
|
|
29
|
+
소요 시간: ELAPSED
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
에러/에스컬레이션 시:
|
|
33
|
+
```
|
|
34
|
+
[byLane] ⚠️ 개입 필요: TITLE
|
|
35
|
+
이유: REASON
|
|
36
|
+
확인: PR_URL
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Slack 알림 (notifications.slack.enabled: true)
|
|
40
|
+
|
|
41
|
+
Slack MCP `slack_send_message` 도구 사용:
|
|
42
|
+
- 채널: `config.notifications.slack.channel`
|
|
43
|
+
- 완료 메시지:
|
|
44
|
+
```
|
|
45
|
+
[byLane] ✅ 완료: TITLE
|
|
46
|
+
PR: PR_URL | 소요 시간: ELAPSED
|
|
47
|
+
```
|
|
48
|
+
- 개입 필요 메시지:
|
|
49
|
+
```
|
|
50
|
+
[byLane] ⚠️ 개입 필요: TITLE
|
|
51
|
+
이유: REASON | 확인: PR_URL
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Telegram 알림 (notifications.telegram.enabled: true)
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage" \
|
|
58
|
+
-d "chat_id=$TELEGRAM_CHAT_ID&text=MESSAGE&parse_mode=Markdown"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 출력
|
|
62
|
+
|
|
63
|
+
`.bylane/state/notify-agent.json`:
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"agent": "notify-agent",
|
|
67
|
+
"status": "completed",
|
|
68
|
+
"progress": 100,
|
|
69
|
+
"notifiedChannels": ["terminal", "slack"]
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 수동 실행
|
|
74
|
+
|
|
75
|
+
`/bylane notify` → 가장 최근 워크플로우 결과로 알림 발송
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bylane-orchestrator
|
|
3
|
+
description: byLane 메인 오케스트레이터. 자연어 의도를 파싱해 에이전트 파이프라인을 실행한다.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# byLane Orchestrator
|
|
7
|
+
|
|
8
|
+
## 역할
|
|
9
|
+
|
|
10
|
+
사용자의 자연어 입력을 파싱하여 어떤 에이전트를 어떤 순서로 실행할지 결정한다.
|
|
11
|
+
|
|
12
|
+
## 실행 전 체크
|
|
13
|
+
|
|
14
|
+
1. `.bylane/bylane.json` 로드. 없으면 즉시 `bylane-setup` 스킬 실행.
|
|
15
|
+
2. `.bylane/state/` 디렉토리 확인. 없으면 생성.
|
|
16
|
+
|
|
17
|
+
## 의도 파싱 규칙
|
|
18
|
+
|
|
19
|
+
입력을 분석하여 아래 중 하나로 분류:
|
|
20
|
+
|
|
21
|
+
| 패턴 | 실행할 에이전트 체인 |
|
|
22
|
+
|---|---|
|
|
23
|
+
| "구현", "만들어", "추가해", 이슈 없음 | issue-agent → code-agent → test-agent → commit-agent → pr-agent → review-agent → notify-agent |
|
|
24
|
+
| "issue #N 구현", "이슈 #N 작업" | issue-agent(분석) → code-agent → test-agent → commit-agent → pr-agent → review-agent → notify-agent |
|
|
25
|
+
| "PR #N 리뷰", "리뷰해줘" | review-agent(PR번호 전달) |
|
|
26
|
+
| "리뷰 #N 반영", "리뷰 수락" | respond-agent(PR번호, 모드=accept 전달) |
|
|
27
|
+
| "리뷰 #N 반박" | respond-agent(PR번호, 모드=rebut 전달) |
|
|
28
|
+
| "커밋해줘" | commit-agent |
|
|
29
|
+
| "PR 만들어줘" | pr-agent |
|
|
30
|
+
| "테스트해줘" | test-agent |
|
|
31
|
+
|
|
32
|
+
## 에이전트 실행 방법
|
|
33
|
+
|
|
34
|
+
각 에이전트를 순서대로 Agent 도구로 호출한다. 이전 에이전트의 출력을 다음 에이전트의 입력으로 전달한다.
|
|
35
|
+
|
|
36
|
+
상태 기록 (각 에이전트 시작 전):
|
|
37
|
+
```bash
|
|
38
|
+
node -e "
|
|
39
|
+
import('./src/state.js').then(({writeState}) => {
|
|
40
|
+
writeState('AGENT_NAME', {
|
|
41
|
+
status: 'in_progress',
|
|
42
|
+
startedAt: new Date().toISOString(),
|
|
43
|
+
progress: 0,
|
|
44
|
+
currentTask: 'TASK_DESCRIPTION',
|
|
45
|
+
retries: 0,
|
|
46
|
+
log: []
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 피드백 루프
|
|
53
|
+
|
|
54
|
+
test-agent가 FAIL을 반환하면:
|
|
55
|
+
1. `.bylane/state/test-agent.json`에서 `failureDetails` 읽기
|
|
56
|
+
2. `.bylane/state/orchestrator.json`에서 `retries` 값 읽기
|
|
57
|
+
3. `retries < config.workflow.maxRetries`이면 code-agent를 재실행 (실패 피드백 포함, retries+1)
|
|
58
|
+
4. `retries >= maxRetries`이면 notify-agent에 "개입 필요" 메시지 전송 후 중단
|
|
59
|
+
|
|
60
|
+
respond-agent가 "수정 필요"를 반환하면 동일 로직 적용.
|
|
61
|
+
|
|
62
|
+
## 완료 처리
|
|
63
|
+
|
|
64
|
+
모든 에이전트 완료 후:
|
|
65
|
+
1. 각 에이전트 state를 `status: "completed"`로 업데이트
|
|
66
|
+
2. notify-agent 실행하여 최종 결과 전송
|
|
67
|
+
|
|
68
|
+
## 수동 실행
|
|
69
|
+
|
|
70
|
+
`/bylane [자연어]` 또는 자연어 감지 시 자동 실행
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bylane-pr-agent
|
|
3
|
+
description: 현재 브랜치의 커밋들로 GitHub Pull Request를 생성한다.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# PR Agent
|
|
7
|
+
|
|
8
|
+
## 입력
|
|
9
|
+
|
|
10
|
+
- `.bylane/state/commit-agent.json`의 `branchName`, `commitSha`
|
|
11
|
+
- `.bylane/state/issue-agent.json`의 `spec.title`, `issueNumber`
|
|
12
|
+
|
|
13
|
+
## 실행 전 상태 기록
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
node -e "import('./src/state.js').then(({writeState})=>writeState('pr-agent',{status:'in_progress',startedAt:new Date().toISOString(),progress:0,retries:0,log:[]}))"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 실행 흐름
|
|
20
|
+
|
|
21
|
+
1. 원격 브랜치 푸시:
|
|
22
|
+
```bash
|
|
23
|
+
git push -u origin BRANCH_NAME
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
2. PR 제목/본문 생성:
|
|
27
|
+
- 제목: 스펙 제목 (70자 이내)
|
|
28
|
+
- 본문:
|
|
29
|
+
```
|
|
30
|
+
## Summary
|
|
31
|
+
- [변경 요약]
|
|
32
|
+
|
|
33
|
+
## Test Plan
|
|
34
|
+
- [ ] 변경된 기능 동작 확인
|
|
35
|
+
- [ ] 기존 테스트 통과 확인
|
|
36
|
+
|
|
37
|
+
Closes #ISSUE_NUMBER
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
3. GitHub MCP로 PR 생성:
|
|
41
|
+
- `title`: 생성된 제목
|
|
42
|
+
- `body`: 생성된 본문
|
|
43
|
+
- `head`: 현재 브랜치명
|
|
44
|
+
- `base`: main
|
|
45
|
+
|
|
46
|
+
4. 상태 업데이트:
|
|
47
|
+
```bash
|
|
48
|
+
node -e "
|
|
49
|
+
import('./src/state.js').then(({writeState})=>writeState('pr-agent',{
|
|
50
|
+
status:'completed',
|
|
51
|
+
progress:100,
|
|
52
|
+
prNumber:'PR_NUMBER',
|
|
53
|
+
prUrl:'PR_URL'
|
|
54
|
+
}))
|
|
55
|
+
"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 출력
|
|
59
|
+
|
|
60
|
+
`.bylane/state/pr-agent.json`:
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"agent": "pr-agent",
|
|
64
|
+
"status": "completed",
|
|
65
|
+
"progress": 100,
|
|
66
|
+
"prNumber": 45,
|
|
67
|
+
"prUrl": "https://github.com/owner/repo/pull/45"
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 수동 실행
|
|
72
|
+
|
|
73
|
+
`/bylane pr`
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bylane-respond-agent
|
|
3
|
+
description: PR 리뷰 코멘트에 반박하거나 코드를 수정하여 반영한다.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Respond Agent
|
|
7
|
+
|
|
8
|
+
## 입력
|
|
9
|
+
|
|
10
|
+
- PR 번호
|
|
11
|
+
- 모드: `accept` (반영) 또는 `rebut` (반박)
|
|
12
|
+
|
|
13
|
+
## 실행 전 상태 기록
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
node -e "import('./src/state.js').then(({writeState})=>writeState('respond-agent',{status:'in_progress',startedAt:new Date().toISOString(),progress:0,retries:0,log:[]}))"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 실행 흐름
|
|
20
|
+
|
|
21
|
+
### accept 모드
|
|
22
|
+
|
|
23
|
+
1. GitHub MCP로 미해결 리뷰 코멘트 로드
|
|
24
|
+
2. 각 코멘트별 수정 사항 결정
|
|
25
|
+
3. 코드 수정 (code-agent 서브 실행)
|
|
26
|
+
4. test-agent로 검증
|
|
27
|
+
5. commit-agent로 수정 커밋 (`fix: address review comments`)
|
|
28
|
+
6. GitHub MCP로 각 코멘트에 "반영 완료" 답글 작성
|
|
29
|
+
|
|
30
|
+
### rebut 모드
|
|
31
|
+
|
|
32
|
+
1. GitHub MCP로 미해결 리뷰 코멘트 로드
|
|
33
|
+
2. 각 코멘트에 대해 근거를 기술한 반박 답글 작성:
|
|
34
|
+
- 의도적 설계 결정인 경우: 배경 설명
|
|
35
|
+
- 성능 트레이드오프: 구체적 수치 근거 제시
|
|
36
|
+
- 스펙 요구사항과 일치하는 경우: 이슈 링크 첨부
|
|
37
|
+
3. GitHub MCP로 반박 답글 게시
|
|
38
|
+
|
|
39
|
+
## 출력
|
|
40
|
+
|
|
41
|
+
`.bylane/state/respond-agent.json`:
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"agent": "respond-agent",
|
|
45
|
+
"status": "completed",
|
|
46
|
+
"progress": 100,
|
|
47
|
+
"mode": "accept",
|
|
48
|
+
"resolvedComments": 3,
|
|
49
|
+
"needsMoreWork": false
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 수동 실행
|
|
54
|
+
|
|
55
|
+
`/bylane respond #45` → accept/rebut 선택 프롬프트 표시
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bylane-review-agent
|
|
3
|
+
description: PR의 diff를 분석하여 코드 리뷰 코멘트를 작성한다.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Review Agent
|
|
7
|
+
|
|
8
|
+
## 입력
|
|
9
|
+
|
|
10
|
+
PR 번호 (`.bylane/state/pr-agent.json`에서 자동 로드, 또는 수동 전달)
|
|
11
|
+
|
|
12
|
+
## 실행 전 상태 기록
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
node -e "import('./src/state.js').then(({writeState})=>writeState('review-agent',{status:'in_progress',startedAt:new Date().toISOString(),progress:0,retries:0,log:[]}))"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 실행 흐름
|
|
19
|
+
|
|
20
|
+
1. GitHub MCP로 PR diff 로드
|
|
21
|
+
2. 변경된 파일별 분석:
|
|
22
|
+
- 버그 가능성 (null check, 경계값 등)
|
|
23
|
+
- 타입 오류 (TypeScript)
|
|
24
|
+
- 성능 이슈 (불필요한 리렌더링, 메모이제이션 누락)
|
|
25
|
+
- 코딩 컨벤션 위반
|
|
26
|
+
- 테스트 커버리지 누락
|
|
27
|
+
|
|
28
|
+
3. 리뷰 코멘트 심각도:
|
|
29
|
+
- **CRITICAL**: 즉시 수정 필요 (버그, 보안)
|
|
30
|
+
- **HIGH**: 수정 강력 권장
|
|
31
|
+
- **MEDIUM**: 개선 권장
|
|
32
|
+
- **LOW**: 선택적 개선
|
|
33
|
+
|
|
34
|
+
4. GitHub MCP로 리뷰 제출:
|
|
35
|
+
- CRITICAL/HIGH 없으면 → `approve`
|
|
36
|
+
- CRITICAL/HIGH 있으면 → `request_changes`
|
|
37
|
+
|
|
38
|
+
5. 상태 업데이트:
|
|
39
|
+
```bash
|
|
40
|
+
node -e "
|
|
41
|
+
import('./src/state.js').then(({writeState})=>writeState('review-agent',{
|
|
42
|
+
status:'completed',
|
|
43
|
+
progress:100,
|
|
44
|
+
approved:APPROVED_BOOL,
|
|
45
|
+
commentCount:COMMENT_COUNT
|
|
46
|
+
}))
|
|
47
|
+
"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 출력
|
|
51
|
+
|
|
52
|
+
`.bylane/state/review-agent.json`:
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"agent": "review-agent",
|
|
56
|
+
"status": "completed",
|
|
57
|
+
"progress": 100,
|
|
58
|
+
"approved": true,
|
|
59
|
+
"commentCount": 3
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 수동 실행
|
|
64
|
+
|
|
65
|
+
`/bylane review #45`
|
package/skills/setup.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bylane-setup
|
|
3
|
+
description: byLane 하네스 최초 설치 및 설정 위자드. /bylane setup 으로 실행.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# byLane Setup Wizard
|
|
7
|
+
|
|
8
|
+
사용자에게 단계별로 질문하여 `.bylane/bylane.json`을 생성한다.
|
|
9
|
+
ALWAYS complete all 6 steps before saving. NEVER skip steps.
|
|
10
|
+
|
|
11
|
+
## 실행 전 준비
|
|
12
|
+
|
|
13
|
+
1. `.bylane/` 디렉토리가 없으면 생성:
|
|
14
|
+
```bash
|
|
15
|
+
mkdir -p .bylane/state
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
2. 기존 bylane.json 있으면 현재 설정을 로드해 기본값으로 사용.
|
|
19
|
+
|
|
20
|
+
## Step 1/6 — 이슈 트래커
|
|
21
|
+
|
|
22
|
+
사용자에게 묻는다:
|
|
23
|
+
|
|
24
|
+
> 주 이슈 트래커를 선택하세요:
|
|
25
|
+
> 1. GitHub Issues (권장)
|
|
26
|
+
> 2. Linear
|
|
27
|
+
> 3. 둘 다
|
|
28
|
+
|
|
29
|
+
- `1` → `trackers.primary = "github"`, `linear.enabled = false`
|
|
30
|
+
- `2` → `trackers.primary = "linear"`, Linear API Key 입력 요청
|
|
31
|
+
- `3` → `trackers.primary = "both"`, Linear API Key 입력 요청
|
|
32
|
+
|
|
33
|
+
Linear API Key는 환경변수명(`LINEAR_API_KEY`)으로 저장. 실제 키값은 저장하지 않는다.
|
|
34
|
+
|
|
35
|
+
## Step 2/6 — 알림 채널
|
|
36
|
+
|
|
37
|
+
> 완료 알림을 받을 채널을 선택하세요:
|
|
38
|
+
> 1. Slack
|
|
39
|
+
> 2. Telegram
|
|
40
|
+
> 3. 둘 다
|
|
41
|
+
> 4. 건너뜀
|
|
42
|
+
|
|
43
|
+
- Slack 선택 시: 채널명 입력 (예: `#dev-alerts`)
|
|
44
|
+
- Telegram 선택 시: Chat ID 입력 방법 안내 후 입력받기
|
|
45
|
+
- 건너뜀 → 알림 비활성화
|
|
46
|
+
|
|
47
|
+
## Step 3/6 — 팀 모드
|
|
48
|
+
|
|
49
|
+
> 팀 모드를 활성화하시겠습니까? (y/n)
|
|
50
|
+
|
|
51
|
+
- `y` → 팀원 GitHub 핸들 입력 (쉼표 구분, 예: `@alice, @bob`)
|
|
52
|
+
- 리뷰 할당 방식 질문: `1. round-robin 2. random`
|
|
53
|
+
- `n` → `team.enabled = false`
|
|
54
|
+
|
|
55
|
+
## Step 4/6 — 권한 범위
|
|
56
|
+
|
|
57
|
+
> Claude가 자동으로 수행할 수 있는 작업 범위를 선택하세요:
|
|
58
|
+
> 1. read-only (분석/리뷰만, 코드 변경 없음)
|
|
59
|
+
> 2. write (코드 작성 + PR 생성, 머지 제외) ← 권장
|
|
60
|
+
> 3. full (머지까지 포함)
|
|
61
|
+
|
|
62
|
+
`permissions.scope`에 저장.
|
|
63
|
+
|
|
64
|
+
## Step 5/6 — 고급 설정
|
|
65
|
+
|
|
66
|
+
> 고급 설정을 변경하시겠습니까? (Enter = 기본값 사용)
|
|
67
|
+
|
|
68
|
+
각 항목을 순서대로 묻는다. Enter 입력 시 기본값 유지:
|
|
69
|
+
- `maxRetries` (기본: 3): 에이전트 재시도 최대 횟수
|
|
70
|
+
- `loopTimeoutMinutes` (기본: 30): 루프 타임아웃 (분)
|
|
71
|
+
- Figma MCP 활성화? (y/n, 기본: n)
|
|
72
|
+
|
|
73
|
+
## Step 6/6 — 브랜치 네이밍
|
|
74
|
+
|
|
75
|
+
> 브랜치 네이밍 패턴을 선택하세요:
|
|
76
|
+
> 1. {tracker}-{issue-number} 예) issues-32
|
|
77
|
+
> 2. {tracker}-{issue-number}-{custom-id} 예) issues-32-C-12
|
|
78
|
+
> 3. {type}/{issue-number}-{title-slug} 예) feature/32-add-dark-mode
|
|
79
|
+
> 4. 직접 입력
|
|
80
|
+
|
|
81
|
+
사전 정의 패턴 선택 시 해당 토큰 기본값 확인:
|
|
82
|
+
- `tracker` 기본값: `issues`
|
|
83
|
+
- `type` 기본값: `feature`
|
|
84
|
+
|
|
85
|
+
직접 입력 시 사용 가능한 토큰 목록 안내:
|
|
86
|
+
`{tracker}`, `{type}`, `{issue-number}`, `{custom-id}`, `{title-slug}`, `{date}`, `{username}`
|
|
87
|
+
|
|
88
|
+
## 저장
|
|
89
|
+
|
|
90
|
+
모든 설정 수집 후:
|
|
91
|
+
1. `.bylane/` 디렉토리 확인 및 생성:
|
|
92
|
+
```bash
|
|
93
|
+
mkdir -p .bylane/state
|
|
94
|
+
```
|
|
95
|
+
2. 수집된 설정을 `.bylane/bylane.json`에 JSON 형식으로 저장
|
|
96
|
+
3. 설정 요약을 사용자에게 출력한다.
|
|
97
|
+
4. `bylane-monitor` 실행 방법 안내:
|
|
98
|
+
```
|
|
99
|
+
모니터 대시보드: npm run monitor (byLane 디렉토리에서)
|
|
100
|
+
또는: /bylane monitor
|
|
101
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bylane-test-agent
|
|
3
|
+
description: 변경된 코드의 테스트를 실행하고 결과를 반환한다.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Test Agent
|
|
7
|
+
|
|
8
|
+
## 입력
|
|
9
|
+
|
|
10
|
+
`.bylane/state/code-agent.json`의 `changedFiles` 배열
|
|
11
|
+
|
|
12
|
+
## 실행 전 상태 기록
|
|
13
|
+
|
|
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:[]}))"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 실행 흐름
|
|
19
|
+
|
|
20
|
+
1. `.bylane/state/code-agent.json`에서 `changedFiles` 로드
|
|
21
|
+
2. 관련 테스트 파일 탐지 (`*.test.ts`, `*.spec.ts`, `*.test.tsx`, `*.test.js`)
|
|
22
|
+
3. 프로젝트 테스트 커맨드 확인 (`package.json` → `scripts.test`)
|
|
23
|
+
4. 테스트 실행:
|
|
24
|
+
```bash
|
|
25
|
+
npm test 2>&1
|
|
26
|
+
```
|
|
27
|
+
5. 결과 파싱:
|
|
28
|
+
- 모두 통과 → `status: "passed"` 저장
|
|
29
|
+
- 실패 있음 → `status: "failed"`, `failureDetails` 포함 저장
|
|
30
|
+
|
|
31
|
+
## 출력
|
|
32
|
+
|
|
33
|
+
`.bylane/state/test-agent.json`:
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"agent": "test-agent",
|
|
37
|
+
"status": "passed",
|
|
38
|
+
"progress": 100,
|
|
39
|
+
"totalTests": 12,
|
|
40
|
+
"passed": 12,
|
|
41
|
+
"failed": 0,
|
|
42
|
+
"failureDetails": []
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
실패 시:
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"agent": "test-agent",
|
|
50
|
+
"status": "failed",
|
|
51
|
+
"progress": 100,
|
|
52
|
+
"totalTests": 12,
|
|
53
|
+
"passed": 10,
|
|
54
|
+
"failed": 2,
|
|
55
|
+
"failureDetails": [
|
|
56
|
+
{ "test": "should render ThemeToggle", "error": "Cannot read property of undefined" }
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 수동 실행
|
|
62
|
+
|
|
63
|
+
`/bylane test`
|
package/src/branch.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function buildBranchName(pattern, tokens, caseStyle = null) {
|
|
2
|
+
let result = pattern
|
|
3
|
+
for (const [key, value] of Object.entries(tokens)) {
|
|
4
|
+
if (!value) {
|
|
5
|
+
// 빈 토큰과 앞의 구분자(-_/) 제거
|
|
6
|
+
result = result.replace(new RegExp(`[-_/]\\{${key}\\}`), '')
|
|
7
|
+
result = result.replace(new RegExp(`\\{${key}\\}[-_/]?`), '')
|
|
8
|
+
} else {
|
|
9
|
+
result = result.replace(new RegExp(`\\{${key}\\}`, 'g'), value)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
// 잔여 미치환 토큰 제거
|
|
13
|
+
result = result.replace(/\{[^}]+\}/g, '')
|
|
14
|
+
// 중복 구분자 정리 (슬래시는 보존)
|
|
15
|
+
result = result.replace(/[-_]{2,}/g, '-').replace(/^[-_]|[-_]$/g, '')
|
|
16
|
+
// 선행/후행 슬래시 정리
|
|
17
|
+
result = result.replace(/^\/|\/$/g, '').replace(/\/{2,}/g, '/')
|
|
18
|
+
if (caseStyle === 'kebab-case') {
|
|
19
|
+
result = result.replace(/\s+/g, '-').toLowerCase()
|
|
20
|
+
}
|
|
21
|
+
return result
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function buildBranchNameFromConfig(config, issueNumber, extra = {}) {
|
|
25
|
+
const tokens = {
|
|
26
|
+
...config.branch.tokens,
|
|
27
|
+
'issue-number': String(issueNumber),
|
|
28
|
+
...extra
|
|
29
|
+
}
|
|
30
|
+
return buildBranchName(config.branch.pattern, tokens, config.branch.caseStyle)
|
|
31
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { cpSync, mkdirSync, symlinkSync, existsSync, readdirSync } from 'fs'
|
|
3
|
+
import { join, dirname } from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
import { homedir } from 'os'
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
8
|
+
const ROOT = join(__dirname, '..')
|
|
9
|
+
const CLAUDE_DIR = join(homedir(), '.claude')
|
|
10
|
+
|
|
11
|
+
const args = process.argv.slice(2)
|
|
12
|
+
const command = args[0] || 'install'
|
|
13
|
+
const useSymlink = args.includes('--symlink')
|
|
14
|
+
|
|
15
|
+
const TARGETS = [
|
|
16
|
+
{ src: join(ROOT, 'skills'), dest: join(CLAUDE_DIR, 'skills'), label: 'Skills' },
|
|
17
|
+
{ src: join(ROOT, 'commands'), dest: join(CLAUDE_DIR, 'commands'), label: 'Commands' },
|
|
18
|
+
{ src: join(ROOT, 'hooks'), dest: join(CLAUDE_DIR, 'hooks'), label: 'Hooks' },
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
function install() {
|
|
22
|
+
console.log('\n byLane 설치 중...\n')
|
|
23
|
+
|
|
24
|
+
for (const { src, dest, label } of TARGETS) {
|
|
25
|
+
mkdirSync(dest, { recursive: true })
|
|
26
|
+
|
|
27
|
+
if (useSymlink) {
|
|
28
|
+
const files = readdirSync(src)
|
|
29
|
+
for (const file of files) {
|
|
30
|
+
const linkPath = join(dest, file)
|
|
31
|
+
const targetPath = join(src, file)
|
|
32
|
+
if (!existsSync(linkPath)) {
|
|
33
|
+
symlinkSync(targetPath, linkPath)
|
|
34
|
+
}
|
|
35
|
+
console.log(` ✓ ${label}: ${file} → ${linkPath}`)
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
cpSync(src, dest, { recursive: true })
|
|
39
|
+
const files = readdirSync(src)
|
|
40
|
+
for (const file of files) {
|
|
41
|
+
console.log(` ✓ ${label}: ${file}`)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(`
|
|
47
|
+
✅ byLane 설치 완료!
|
|
48
|
+
|
|
49
|
+
다음 단계:
|
|
50
|
+
1. Claude Code를 열고 프로젝트 디렉토리로 이동
|
|
51
|
+
2. 셋업 위자드 실행:
|
|
52
|
+
|
|
53
|
+
/bylane setup
|
|
54
|
+
|
|
55
|
+
3. 이후 사용:
|
|
56
|
+
|
|
57
|
+
/bylane 다크모드 토글 추가해줘
|
|
58
|
+
`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (command === 'install') {
|
|
62
|
+
install()
|
|
63
|
+
} else {
|
|
64
|
+
console.error(`알 수 없는 명령: ${command}`)
|
|
65
|
+
console.error('사용법: npx bylane [install] [--symlink]')
|
|
66
|
+
process.exit(1)
|
|
67
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs'
|
|
2
|
+
import { join } from 'path'
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_CONFIG = {
|
|
5
|
+
version: '1.0',
|
|
6
|
+
trackers: {
|
|
7
|
+
primary: 'github',
|
|
8
|
+
linear: { enabled: false, apiKey: '' }
|
|
9
|
+
},
|
|
10
|
+
notifications: {
|
|
11
|
+
slack: { enabled: false, channel: '' },
|
|
12
|
+
telegram: { enabled: false, chatId: '' }
|
|
13
|
+
},
|
|
14
|
+
team: {
|
|
15
|
+
enabled: false,
|
|
16
|
+
members: [],
|
|
17
|
+
reviewAssignment: 'round-robin'
|
|
18
|
+
},
|
|
19
|
+
permissions: {
|
|
20
|
+
scope: 'write',
|
|
21
|
+
allowMerge: false,
|
|
22
|
+
allowForceClose: false
|
|
23
|
+
},
|
|
24
|
+
workflow: {
|
|
25
|
+
maxRetries: 3,
|
|
26
|
+
loopTimeoutMinutes: 30,
|
|
27
|
+
autoEscalate: true
|
|
28
|
+
},
|
|
29
|
+
branch: {
|
|
30
|
+
pattern: '{tracker}-{issue-number}',
|
|
31
|
+
tokens: { tracker: 'issues', type: 'feature', 'custom-id': '' },
|
|
32
|
+
separator: '-',
|
|
33
|
+
caseStyle: 'kebab-case'
|
|
34
|
+
},
|
|
35
|
+
extensions: {
|
|
36
|
+
figma: { enabled: false, useAt: 'issue-analysis' }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function loadConfig(dir = '.bylane') {
|
|
41
|
+
const path = join(dir, 'bylane.json')
|
|
42
|
+
if (!existsSync(path)) return { ...DEFAULT_CONFIG }
|
|
43
|
+
try {
|
|
44
|
+
const raw = JSON.parse(readFileSync(path, 'utf8'))
|
|
45
|
+
return deepMerge({ ...DEFAULT_CONFIG }, raw)
|
|
46
|
+
} catch {
|
|
47
|
+
return { ...DEFAULT_CONFIG }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function deepMerge(target, source) {
|
|
52
|
+
const result = { ...target }
|
|
53
|
+
for (const key of Object.keys(source)) {
|
|
54
|
+
if (
|
|
55
|
+
source[key] !== null &&
|
|
56
|
+
typeof source[key] === 'object' &&
|
|
57
|
+
!Array.isArray(source[key]) &&
|
|
58
|
+
typeof target[key] === 'object' &&
|
|
59
|
+
target[key] !== null
|
|
60
|
+
) {
|
|
61
|
+
result[key] = deepMerge(target[key], source[key])
|
|
62
|
+
} else {
|
|
63
|
+
result[key] = source[key]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return result
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function saveConfig(config, dir = '.bylane') {
|
|
70
|
+
const path = join(dir, 'bylane.json')
|
|
71
|
+
writeFileSync(path, JSON.stringify(config, null, 2))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function validateConfig(config) {
|
|
75
|
+
const errors = []
|
|
76
|
+
if (typeof config.workflow?.maxRetries !== 'number') {
|
|
77
|
+
errors.push('workflow.maxRetries must be a number')
|
|
78
|
+
}
|
|
79
|
+
if (!['github', 'linear', 'both'].includes(config.trackers?.primary)) {
|
|
80
|
+
errors.push('trackers.primary must be "github", "linear", or "both"')
|
|
81
|
+
}
|
|
82
|
+
if (!['read-only', 'write', 'full'].includes(config.permissions?.scope)) {
|
|
83
|
+
errors.push('permissions.scope must be "read-only", "write", or "full"')
|
|
84
|
+
}
|
|
85
|
+
return errors
|
|
86
|
+
}
|