@elyun/bylane 1.18.0 → 1.19.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
@@ -69,6 +69,34 @@ node src/cli.js install
69
69
 
70
70
  인터랙티브 설정 (GitHub 접근 방법, 알림 채널, 브랜치 패턴, 에이전트 모델 등).
71
71
 
72
+ ### 사전 점검
73
+
74
+ 설정이 올바른지 확인하려면:
75
+
76
+ ```bash
77
+ npx @elyun/bylane preflight
78
+ ```
79
+
80
+ 또는 Claude Code에서:
81
+
82
+ ```
83
+ /bylane preflight
84
+ ```
85
+
86
+ 점검 항목: bylane.json 존재, GitHub CLI 로그인, GITHUB_TOKEN, Slack/Telegram 연동 설정.
87
+ 문제가 있으면 항목마다 수정 방법을 안내합니다.
88
+
89
+ ```
90
+ ── byLane 사전 점검 ──
91
+
92
+ ✓ bylane 설정 v1.0
93
+ ✓ GitHub CLI (fallback) github.com
94
+ ! GitHub Token (fallback) GITHUB_TOKEN 환경변수 없음
95
+ → export GITHUB_TOKEN=ghp_xxxx
96
+ ```
97
+
98
+ 모든 에이전트 실행 전 자동으로 점검하여 연동 오류로 인한 중간 실패를 방지합니다.
99
+
72
100
  ---
73
101
 
74
102
  ## 사용법
@@ -11,7 +11,13 @@ description: byLane 메인 오케스트레이터. 자연어 의도를 파싱해
11
11
 
12
12
  ## 실행 전 체크
13
13
 
14
- 1. `.bylane/bylane.json` 로드. 없으면 즉시 `bylane-setup` 스킬 실행.
14
+ 1. 사전 점검 실행:
15
+ ```bash
16
+ npx @elyun/bylane preflight
17
+ ```
18
+ - 점검 실패 시 안내 메시지를 출력하고 워크플로우를 **중단**한다.
19
+ - `.bylane/bylane.json` 없으면 즉시 `bylane-setup` 스킬 실행.
20
+
15
21
  2. `.bylane/state/` 디렉토리 확인. 없으면 생성.
16
22
 
17
23
  ## 에이전트별 모델 결정
@@ -25,6 +25,25 @@ description: byLane 메인 커맨드. 자연어로 전체 개발 워크플로우
25
25
  /bylane status — 현재 상태 한 줄 요약
26
26
  ```
27
27
 
28
+ ## 사전 점검 (모든 명령 전 자동 실행)
29
+
30
+ 서브커맨드를 라우팅하기 전 아래 점검을 실행한다:
31
+
32
+ ```bash
33
+ npx @elyun/bylane preflight
34
+ ```
35
+
36
+ 점검 항목:
37
+ - `.bylane/bylane.json` 존재 여부 → 없으면 `/bylane setup` 안내 후 중단
38
+ - GitHub 접근 방법 (`github.method` 기준):
39
+ - `cli`: `gh auth status` → 로그인 안 됐으면 `gh auth login` 안내
40
+ - `api`: `GITHUB_TOKEN` 환경변수 → 없으면 설정 방법 안내
41
+ - `auto`/`mcp`: CLI + Token 둘 다 확인, 어느 것도 없으면 안내
42
+ - 알림 채널 (활성화된 경우만): Slack 채널 설정 여부, Telegram 토큰 여부
43
+
44
+ 문제가 있으면 각 항목마다 수정 방법을 출력하고 중단한다.
45
+ `setup`, `status`, `preflight` 서브커맨드는 점검 없이 바로 실행한다.
46
+
28
47
  ## 실행 흐름
29
48
 
30
49
  첫 번째 인자가 서브커맨드인지 확인.
@@ -47,6 +66,7 @@ description: byLane 메인 커맨드. 자연어로 전체 개발 워크플로우
47
66
  | `respond-loop` | `bylane-respond-loop` |
48
67
  | `notify` | `bylane-notify-agent` |
49
68
  | `status` | `.bylane/state/` 파일 읽어 한 줄 요약 출력 |
69
+ | `preflight` | 연동 상태 점검 및 문제 안내 |
50
70
 
51
71
  ## monitor 서브커맨드
52
72
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elyun/bylane",
3
- "version": "1.18.0",
3
+ "version": "1.19.0",
4
4
  "description": "Frontend development harness for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -141,6 +141,11 @@ function install() {
141
141
 
142
142
  if (command === 'install') {
143
143
  install()
144
+ } else if (command === 'preflight') {
145
+ const { runPreflight, formatPreflight } = await import('./preflight.js')
146
+ const result = runPreflight()
147
+ console.log(formatPreflight(result))
148
+ if (!result.passed) process.exit(1)
144
149
  } else if (command === 'models') {
145
150
  // models → 에이전트별 모델 목록 출력 (KEY=VALUE 형식)
146
151
  const { loadConfig, getAgentModel } = await import('./config.js')
@@ -0,0 +1,47 @@
1
+ /**
2
+ * loop-utils.js
3
+ * 루프 프로세스 공통 유틸
4
+ */
5
+ import { execSync } from 'child_process'
6
+ import { readState, writeState } from './state.js'
7
+
8
+ /**
9
+ * 동일 루프가 이미 실행 중이면 기존 프로세스를 종료하고 기다린다.
10
+ * @param {string} loopName e.g. 'review-loop'
11
+ * @param {string} stateDir
12
+ */
13
+ export function killExistingLoop(loopName, stateDir = '.bylane/state') {
14
+ const existing = readState(loopName, stateDir)
15
+ if (!existing || existing.status !== 'running' || !existing.pid) return
16
+
17
+ const pid = Number(existing.pid)
18
+ if (pid === process.pid) return // 자기 자신이면 무시
19
+
20
+ // PID가 살아있는지 확인
21
+ try {
22
+ process.kill(pid, 0)
23
+ } catch {
24
+ // 이미 종료된 프로세스 — 상태만 정리
25
+ writeState(loopName, { ...existing, status: 'stopped', stoppedAt: new Date().toISOString() }, stateDir)
26
+ return
27
+ }
28
+
29
+ // 기존 프로세스 종료
30
+ console.log(`[${loopName}] 기존 프로세스(PID: ${pid}) 종료 후 재시작합니다.`)
31
+ try {
32
+ process.kill(pid, 'SIGTERM')
33
+ } catch {
34
+ // 종료 실패 시 무시하고 계속
35
+ }
36
+
37
+ // 최대 2초 대기 (100ms 간격 폴링)
38
+ const deadline = Date.now() + 2000
39
+ while (Date.now() < deadline) {
40
+ try {
41
+ process.kill(pid, 0)
42
+ execSync('sleep 0.1')
43
+ } catch {
44
+ break // 종료 완료
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * preflight.js
3
+ * GitHub CLI/MCP/API, 알림 채널 연동 상태를 점검하고 문제가 있으면 가이드를 출력한다.
4
+ * `npx @elyun/bylane preflight` 또는 에이전트 시작 전 자동 실행.
5
+ */
6
+ import { execSync } from 'child_process'
7
+ import { existsSync } from 'fs'
8
+ import { loadConfig } from './config.js'
9
+
10
+ const CONFIG_PATH = '.bylane/bylane.json'
11
+
12
+ function run(cmd) {
13
+ try {
14
+ return { ok: true, out: execSync(cmd, { encoding: 'utf8', stdio: ['pipe','pipe','pipe'] }).trim() }
15
+ } catch (e) {
16
+ return { ok: false, out: e.stderr?.toString().trim() ?? '' }
17
+ }
18
+ }
19
+
20
+ function checkGhCli() {
21
+ const which = run('which gh')
22
+ if (!which.ok) return { ok: false, reason: 'gh CLI 미설치', fix: 'brew install gh # 또는 https://cli.github.com' }
23
+
24
+ const auth = run('gh auth status')
25
+ if (!auth.ok) return { ok: false, reason: 'gh CLI 로그인 필요', fix: 'gh auth login' }
26
+
27
+ return { ok: true, detail: auth.out.split('\n')[0] }
28
+ }
29
+
30
+ function checkGithubToken() {
31
+ const token = process.env.GITHUB_TOKEN
32
+ if (!token) return { ok: false, reason: 'GITHUB_TOKEN 환경변수 없음', fix: 'export GITHUB_TOKEN=ghp_xxxx' }
33
+ if (token.length < 10) return { ok: false, reason: 'GITHUB_TOKEN 값이 너무 짧음', fix: '올바른 토큰을 설정하세요' }
34
+ return { ok: true, detail: `설정됨 (${token.slice(0,4)}…)` }
35
+ }
36
+
37
+ function checkSlack(config) {
38
+ if (!config.notifications?.slack?.enabled) return null
39
+ const channel = config.notifications.slack.channel
40
+ if (!channel) return { ok: false, reason: 'slack.channel 미설정', fix: '.bylane/bylane.json의 notifications.slack.channel 설정' }
41
+ return { ok: true, detail: `채널: ${channel}` }
42
+ }
43
+
44
+ function checkTelegram(config) {
45
+ if (!config.notifications?.telegram?.enabled) return null
46
+ const botToken = process.env.TELEGRAM_BOT_TOKEN
47
+ const chatId = process.env.TELEGRAM_CHAT_ID || config.notifications.telegram.chatId
48
+ if (!botToken) return { ok: false, reason: 'TELEGRAM_BOT_TOKEN 환경변수 없음', fix: 'export TELEGRAM_BOT_TOKEN=xxx' }
49
+ if (!chatId) return { ok: false, reason: 'TELEGRAM_CHAT_ID 미설정', fix: 'export TELEGRAM_CHAT_ID=xxx 또는 bylane.json에 설정' }
50
+ return { ok: true, detail: `chat_id: ${chatId}` }
51
+ }
52
+
53
+ /**
54
+ * 전체 점검 실행
55
+ * @returns {{ passed: boolean, results: Array<{name, ok, detail?, reason?, fix?}> }}
56
+ */
57
+ export function runPreflight() {
58
+ const results = []
59
+
60
+ // 1. bylane.json 존재 여부
61
+ if (!existsSync(CONFIG_PATH)) {
62
+ results.push({
63
+ name: 'bylane 설정',
64
+ ok: false,
65
+ reason: '.bylane/bylane.json 없음',
66
+ fix: '/bylane setup 을 실행하여 초기 설정을 완료하세요.'
67
+ })
68
+ return { passed: false, results }
69
+ }
70
+
71
+ let config
72
+ try { config = loadConfig() } catch (e) {
73
+ results.push({ name: 'bylane 설정', ok: false, reason: `bylane.json 파싱 오류: ${e.message}`, fix: '/bylane setup 으로 재생성' })
74
+ return { passed: false, results }
75
+ }
76
+ results.push({ name: 'bylane 설정', ok: true, detail: `v${config.version ?? '?'}` })
77
+
78
+ // 2. GitHub 접근
79
+ const method = config.github?.method ?? 'auto'
80
+
81
+ if (method === 'cli') {
82
+ const r = checkGhCli()
83
+ results.push({ name: 'GitHub CLI', ...r })
84
+ } else if (method === 'api') {
85
+ const r = checkGithubToken()
86
+ results.push({ name: 'GitHub Token', ...r })
87
+ } else if (method === 'auto' || method === 'mcp') {
88
+ // auto/mcp: CLI도 확인해두면 유용
89
+ const cli = checkGhCli()
90
+ const token = checkGithubToken()
91
+ const anyOk = cli.ok || token.ok
92
+
93
+ if (method === 'mcp') {
94
+ results.push({
95
+ name: 'GitHub MCP',
96
+ ok: true,
97
+ detail: 'Claude Code 세션에서 자동 사용 (별도 확인 불필요)'
98
+ })
99
+ }
100
+ results.push({ name: 'GitHub CLI (fallback)', ...cli })
101
+ results.push({ name: 'GitHub Token (fallback)', ...token })
102
+
103
+ if (!anyOk && method === 'auto') {
104
+ results.find(r => r.name === 'GitHub CLI (fallback)').critical = true
105
+ }
106
+ }
107
+
108
+ // 3. 알림 채널 (설정된 경우만)
109
+ const slack = checkSlack(config)
110
+ if (slack) results.push({ name: 'Slack 알림', ...slack })
111
+
112
+ const telegram = checkTelegram(config)
113
+ if (telegram) results.push({ name: 'Telegram 알림', ...telegram })
114
+
115
+ const passed = results.every(r => r.ok)
116
+ return { passed, results }
117
+ }
118
+
119
+ /** 결과를 읽기 좋은 텍스트로 포맷 */
120
+ export function formatPreflight({ passed, results }) {
121
+ const lines = []
122
+ lines.push('')
123
+ lines.push(' ── byLane 사전 점검 ──')
124
+ lines.push('')
125
+
126
+ for (const r of results) {
127
+ const icon = r.ok ? ' ✓' : (r.critical ? ' ✗' : ' !')
128
+ const name = r.name.padEnd(22)
129
+ const desc = r.ok ? (r.detail ?? 'OK') : r.reason
130
+ lines.push(`${icon} ${name} ${desc}`)
131
+ if (!r.ok && r.fix) {
132
+ lines.push(` → ${r.fix}`)
133
+ }
134
+ }
135
+
136
+ lines.push('')
137
+ if (passed) {
138
+ lines.push(' 모든 항목 정상. 워크플로우를 시작합니다.')
139
+ } else {
140
+ lines.push(' 일부 항목에 문제가 있습니다. 위 안내를 참고하여 설정을 완료하세요.')
141
+ lines.push(' 설정 완료 후 명령을 다시 실행하거나 /bylane setup 을 실행하세요.')
142
+ }
143
+ lines.push('')
144
+
145
+ return lines.join('\n')
146
+ }
@@ -8,11 +8,13 @@ import { execSync } from 'child_process'
8
8
  import { mkdirSync } from 'fs'
9
9
  import { writeState, readState, appendLog } from './state.js'
10
10
  import { loadConfig } from './config.js'
11
+ import { killExistingLoop } from './loop-utils.js'
11
12
 
12
13
  const INTERVAL_MS = 5 * 60 * 1000
13
14
  const STATE_DIR = '.bylane/state'
14
15
 
15
16
  mkdirSync(STATE_DIR, { recursive: true })
17
+ killExistingLoop('respond-loop', STATE_DIR)
16
18
 
17
19
  function fetchMyPRsWithReviews(method, owner, repo) {
18
20
  // CLI
@@ -7,11 +7,13 @@ import { execSync } from 'child_process'
7
7
  import { mkdirSync } from 'fs'
8
8
  import { writeState, readState, appendLog } from './state.js'
9
9
  import { loadConfig } from './config.js'
10
+ import { killExistingLoop } from './loop-utils.js'
10
11
 
11
12
  const INTERVAL_MS = 5 * 60 * 1000
12
13
  const STATE_DIR = '.bylane/state'
13
14
 
14
15
  mkdirSync(STATE_DIR, { recursive: true })
16
+ killExistingLoop('review-loop', STATE_DIR)
15
17
 
16
18
  function fetchPendingReviews(method, owner, repo) {
17
19
  // CLI