@elyun/bylane 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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`
@@ -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
+ }