@elyun/bylane 1.17.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 +290 -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 +11 -24
- 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/commands/bylane.md +20 -0
- package/package.json +1 -1
- package/src/cleanup.js +152 -0
- package/src/cli.js +44 -0
- package/src/loop-utils.js +47 -0
- package/src/monitor/index.js +29 -0
- package/src/monitor/layout.js +1 -1
- package/src/preflight.js +146 -0
- package/src/respond-loop.js +2 -0
- package/src/review-loop.js +2 -0
package/src/preflight.js
ADDED
|
@@ -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
|
+
}
|
package/src/respond-loop.js
CHANGED
|
@@ -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
|
package/src/review-loop.js
CHANGED
|
@@ -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
|