vibe-collab 0.8.11 → 0.8.14
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 +98 -54
- package/dist/cli/commands/auth.js +4 -3
- package/dist/mcp/server.js +51 -0
- package/dist/mcp/tools/_actor.d.ts +6 -0
- package/dist/mcp/tools/_actor.js +16 -0
- package/dist/mcp/tools/analyzeRequest.js +21 -3
- package/dist/mcp/tools/gitPush.d.ts +3 -0
- package/dist/mcp/tools/gitPush.js +40 -0
- package/dist/mcp/tools/recordCheckpoint.js +47 -6
- package/dist/mcp/tools/saveContext.d.ts +9 -0
- package/dist/mcp/tools/saveContext.js +30 -0
- package/dist/mcp/tools/startSession.js +21 -5
- package/dist/mcp/tools/startWork.js +51 -17
- package/dist/state/reader.d.ts +1 -0
- package/dist/state/reader.js +11 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
← JWT 토큰 발급 ←──────────────────────
|
|
18
18
|
→ ~/.vibe/auth.json 저장
|
|
19
19
|
|
|
20
|
-
Claude Code / Cursor /
|
|
20
|
+
Claude Code / Cursor / Gemini CLI / Antigravity ...
|
|
21
21
|
→ MCP 도구 호출 (vibe_start_session 등)
|
|
22
22
|
→ AI 요청 ──────────────────────────→ /api/ai/complete (JWT 인증)
|
|
23
23
|
← AI 응답 ←────────────────────────── Anthropic API 호출 후 반환
|
|
@@ -25,47 +25,52 @@
|
|
|
25
25
|
|
|
26
26
|
- **AI API 키 불필요** — 서버(vibeorchestratorserver.vercel.app)가 관리합니다
|
|
27
27
|
- **GitHub 로그인 한 번** — 이후 모든 AI 기능 자동 사용
|
|
28
|
-
- **MCP
|
|
28
|
+
- **10개 MCP 도구** — git commit/push까지 AI가 직접 실행, 사용자는 채팅만
|
|
29
29
|
|
|
30
30
|
---
|
|
31
31
|
|
|
32
32
|
## 빠른 시작
|
|
33
33
|
|
|
34
|
-
###
|
|
34
|
+
### 프로젝트 팀장 (최초 1회)
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
37
|
npm install -g vibe-collab
|
|
38
|
-
```
|
|
39
38
|
|
|
40
|
-
|
|
39
|
+
vibe auth login # GitHub 로그인
|
|
40
|
+
cd /path/to/your/project
|
|
41
|
+
vibe init # CHARTER.md + .vibe/ 생성
|
|
42
|
+
vibe connect # AI 도구 자동 감지 + MCP 설정
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
git add .vibe CHARTER.md
|
|
45
|
+
git commit -m "chore: init vibe"
|
|
46
|
+
git push
|
|
44
47
|
```
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
### 3단계 — 프로젝트 초기화
|
|
49
|
+
### 새 협업자 합류 (최초 1회)
|
|
49
50
|
|
|
50
51
|
```bash
|
|
51
|
-
|
|
52
|
-
vibe init
|
|
53
|
-
```
|
|
52
|
+
npm install -g vibe-collab
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
- `.vscode/mcp.json` — VS Code MCP 설정
|
|
54
|
+
git clone <repo-url> # .vibe/ 폴더도 함께 옴
|
|
55
|
+
vibe auth login # 본인 GitHub 계정으로 로그인
|
|
56
|
+
vibe connect # AI 도구에 MCP 설정 추가
|
|
57
|
+
```
|
|
60
58
|
|
|
61
|
-
|
|
59
|
+
> `vibe init`은 다시 실행하지 않아도 됩니다. `.vibe/config.json`과 `CHARTER.md`는 이미 레포에 있습니다.
|
|
62
60
|
|
|
63
|
-
|
|
61
|
+
### 매일 작업 (자동)
|
|
64
62
|
|
|
65
|
-
```bash
|
|
66
|
-
# CLI로도 직접 시작 가능
|
|
67
|
-
vibe start
|
|
68
63
|
```
|
|
64
|
+
AI 채팅창 열기
|
|
65
|
+
→ AI가 vibe_start_session 자동 호출 (팀 현황 파악)
|
|
66
|
+
→ "~~ 기능 추가해줘" 요청
|
|
67
|
+
→ AI가 vibe_analyze_request → vibe_start_work 자동 진행
|
|
68
|
+
(기존 이슈 브랜치면 git fetch + checkout + pull도 자동)
|
|
69
|
+
→ 코드 작업 → vibe_record_checkpoint → vibe_git_push
|
|
70
|
+
(git commit & push도 AI가 MCP를 통해 자동 실행)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**git pull, vibe init, vibe start 같은 명령어를 매번 실행할 필요 없습니다.**
|
|
69
74
|
|
|
70
75
|
---
|
|
71
76
|
|
|
@@ -76,18 +81,13 @@ vibe start
|
|
|
76
81
|
| `vibe auth login` | GitHub OAuth 로그인 |
|
|
77
82
|
| `vibe auth logout` | 로그아웃 |
|
|
78
83
|
| `vibe auth status` | 로그인 상태 및 만료일 확인 |
|
|
79
|
-
| `vibe init` | 프로젝트 초기화
|
|
80
|
-
| `vibe connect` |
|
|
81
|
-
| `vibe connect --ai <tool>` | 특정 AI 도구에 MCP 설정 추가
|
|
82
|
-
| `vibe start` | 작업 시작 또는 이어서 진행 |
|
|
84
|
+
| `vibe init` | 프로젝트 초기화 — **팀장이 최초 1회만 실행** |
|
|
85
|
+
| `vibe connect` | AI 도구 자동 감지 후 MCP 설정 추가 |
|
|
86
|
+
| `vibe connect --ai <tool>` | 특정 AI 도구에 MCP 설정 추가 |
|
|
83
87
|
| `vibe status` | 팀 현황 출력 |
|
|
84
88
|
| `vibe serve` | MCP 서버 시작 (AI 도구에서 자동 실행됨) |
|
|
85
|
-
| `vibe update` | vibe-collab을 최신 버전으로 업데이트 |
|
|
86
|
-
|
|
87
|
-
### `vibe connect --ai` 지원 도구
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
`--ai <tool>` 지정 시 해당 도구의 수동 설정 가이드만 출력됩니다.
|
|
90
|
+
### `vibe connect` 지원 도구
|
|
91
91
|
|
|
92
92
|
| 명령어 | 도구 | 설정 파일 |
|
|
93
93
|
|--------|------|-----------|
|
|
@@ -97,53 +97,98 @@ vibe start
|
|
|
97
97
|
| `vibe connect --ai copilot` | Copilot | `.vscode/mcp.json` |
|
|
98
98
|
| `vibe connect --ai kiro` | Kiro | `.kiro/settings/mcp.json` |
|
|
99
99
|
| `vibe connect --ai roocode` | Roo Code | `.roo/mcp.json` |
|
|
100
|
-
| `vibe connect --ai trae` | Trae | `.trae/mcp.json`
|
|
100
|
+
| `vibe connect --ai trae` | Trae | `.trae/mcp.json` |
|
|
101
101
|
| `vibe connect --ai continue` | Continue | `.continue/mcpServers/mcp.json` |
|
|
102
|
-
| `vibe connect --ai opencode` | OpenCode | `.opencode/config.toml` |
|
|
103
|
-
| `vibe connect --ai codex` | Codex CLI | `.codex/config.toml` |
|
|
104
|
-
| `vibe connect --ai qoder` | Qoder | `.qoder/mcp.json` |
|
|
105
|
-
| `vibe connect --ai codebuddy` | CodeBuddy | `.codebuddy/mcp.json` |
|
|
106
|
-
| `vibe connect --ai droid` | Droid | `.droid/mcp.json` |
|
|
107
102
|
| `vibe connect --ai windsurf` | Windsurf | `~/.codeium/windsurf/mcp_config.json` (전역) |
|
|
108
103
|
| `vibe connect --ai gemini` | Gemini CLI | `~/.gemini/settings.json` (전역) |
|
|
109
104
|
| `vibe connect --ai antigravity` | Antigravity | `~/.gemini/antigravity/mcp_config.json` (전역) |
|
|
110
|
-
| `vibe connect --ai all` | 위 전체
|
|
105
|
+
| `vibe connect --ai all` | 위 전체 | — |
|
|
111
106
|
|
|
112
|
-
> **Windsurf / Gemini CLI / Antigravity**: 전역
|
|
107
|
+
> **Windsurf / Gemini CLI / Antigravity**: 전역 설정에 기록됩니다. `--cwd <프로젝트 경로>`가 자동으로 주입되어 어느 디렉토리에서 실행해도 올바른 프로젝트를 찾습니다.
|
|
113
108
|
|
|
114
109
|
---
|
|
115
110
|
|
|
116
|
-
## MCP 도구 (
|
|
111
|
+
## MCP 도구 (10개)
|
|
117
112
|
|
|
118
|
-
Claude Code, Cursor,
|
|
113
|
+
Claude Code, Cursor, Gemini CLI 등 MCP를 지원하는 모든 AI 도구에서 사용할 수 있습니다.
|
|
119
114
|
|
|
120
115
|
| 도구 | 설명 |
|
|
121
116
|
|------|------|
|
|
122
|
-
| `vibe_start_session` | 세션 시작 — CHARTER, 팀 현황,
|
|
123
|
-
| `vibe_analyze_request` |
|
|
124
|
-
| `vibe_start_work` |
|
|
125
|
-
| `vibe_record_checkpoint` | 단계 완료 기록 |
|
|
126
|
-
| `
|
|
117
|
+
| `vibe_start_session` | 세션 시작 — CHARTER, 팀 현황, 워크플로우 규칙 반환 |
|
|
118
|
+
| `vibe_analyze_request` | 요청 분석 → 관련 이슈 연결 또는 신규 제안, 이전 대화 기록 복원 |
|
|
119
|
+
| `vibe_start_work` | 작업 시작 — 기존 브랜치면 자동 checkout+pull, 신규면 브랜치 생성 |
|
|
120
|
+
| `vibe_record_checkpoint` | 단계 완료 기록 (code_complete, qa_passed 등) |
|
|
121
|
+
| `vibe_git_push` | git add -A → commit → push 자동 실행 (state.json 포함) |
|
|
122
|
+
| `vibe_save_context` | 결정/진행 상황/피드백을 이슈별로 저장 → 다음 세션에서 자동 복원 |
|
|
123
|
+
| `vibe_request_qa` | 코드 리뷰 (정적 분석 + AI 검사) |
|
|
127
124
|
| `vibe_create_pr` | PR 자동 생성 |
|
|
128
125
|
| `vibe_request_merge_review` | 머지 전 충돌 검사 |
|
|
129
126
|
| `vibe_execute_merge` | Squash merge + CHARTER 자동 갱신 |
|
|
130
127
|
|
|
128
|
+
### AI가 따르는 워크플로우
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
① 사용자 요청 → vibe_analyze_request
|
|
132
|
+
② 작업 확정 → vibe_start_work (기존 브랜치 자동 checkout+pull)
|
|
133
|
+
③ 코드 완료 → vibe_record_checkpoint(stage: "code_complete")
|
|
134
|
+
④ 커밋+푸시 → vibe_git_push (state.json 포함 전체 자동)
|
|
135
|
+
⑤ 코드 검토 → vibe_request_qa
|
|
136
|
+
⑥ 검토 통과 → vibe_create_pr
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 세션 간 대화 기록 유지 (`vibe_save_context`)
|
|
142
|
+
|
|
143
|
+
AI가 중요한 결정이나 피드백을 `.vibe/logs/{issueNumber}.md`에 저장합니다.
|
|
144
|
+
|
|
145
|
+
```markdown
|
|
146
|
+
# 이슈 #5 작업 로그
|
|
147
|
+
|
|
148
|
+
## [2026-03-01 14:30] alice (claude-code) — 결정
|
|
149
|
+
JWT 방식 채택. Session 방식은 서버 부하 문제로 기각.
|
|
150
|
+
|
|
151
|
+
## [2026-03-02 09:00] bob (gemini-cli) — 사용자 피드백
|
|
152
|
+
에러 메시지를 더 친절하게 바꿔달라고 함.
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
같은 이슈를 다음 세션이나 다른 AI 에이전트가 이어받을 때, `vibe_analyze_request`가 이 기록을 자동으로 읽어서 컨텍스트에 포함합니다.
|
|
156
|
+
|
|
131
157
|
---
|
|
132
158
|
|
|
133
159
|
## 여러 AI 동시 협업 예시
|
|
134
160
|
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
|
|
161
|
+
```
|
|
162
|
+
[Claude Code — alice] [Gemini CLI — bob]
|
|
163
|
+
vibe_start_session vibe_start_session
|
|
164
|
+
→ 이슈 #5 담당 → 이슈 #6 담당
|
|
138
165
|
|
|
139
|
-
|
|
140
|
-
|
|
166
|
+
코드 수정 코드 수정
|
|
167
|
+
vibe_record_checkpoint vibe_record_checkpoint
|
|
168
|
+
vibe_git_push vibe_git_push
|
|
141
169
|
|
|
142
|
-
|
|
170
|
+
두 세션이 동일한 .vibe/state.json 공유 → 충돌 없이 병렬 개발
|
|
143
171
|
```
|
|
144
172
|
|
|
145
173
|
---
|
|
146
174
|
|
|
175
|
+
## 협업 시 `.vibe` 폴더 관리
|
|
176
|
+
|
|
177
|
+
`.vibe/` 폴더는 반드시 Git으로 관리해야 합니다.
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
.vibe/
|
|
181
|
+
config.json ← GitHub 레포 정보 (vibe init 1회)
|
|
182
|
+
state.json ← 팀 협업 상태 (매 작업마다 업데이트)
|
|
183
|
+
intents/ ← 이슈별 분석 기록
|
|
184
|
+
logs/ ← 이슈별 대화 기록 (vibe_save_context)
|
|
185
|
+
CHARTER.md ← 레포 컨벤션 (vibe init 1회, merge 시 자동 갱신)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
> `state.json`은 `vibe_git_push`가 자동으로 커밋에 포함합니다.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
147
192
|
## 프록시 서버
|
|
148
193
|
|
|
149
194
|
AI 요청은 **https://vibeorchestratorserver.vercel.app** 을 통해 처리됩니다.
|
|
@@ -151,7 +196,6 @@ AI 요청은 **https://vibeorchestratorserver.vercel.app** 을 통해 처리됩
|
|
|
151
196
|
- `/api/ai/complete` — JWT 인증 후 Anthropic API 호출
|
|
152
197
|
- `/api/auth/cli-token` — GitHub OAuth 후 CLI 토큰 발급
|
|
153
198
|
|
|
154
|
-
|
|
155
199
|
---
|
|
156
200
|
|
|
157
201
|
## 라이선스
|
|
@@ -139,10 +139,11 @@ export async function authCommand(action) {
|
|
|
139
139
|
case 'login': {
|
|
140
140
|
const existing = readAuthData();
|
|
141
141
|
if (existing) {
|
|
142
|
-
console.log(chalk.yellow(
|
|
142
|
+
console.log(chalk.yellow(`현재 로그인: @${existing.user.login} (${existing.user.name})\n` +
|
|
143
143
|
`만료: ${new Date(existing.expiresAt).toLocaleString('ko-KR')}\n\n` +
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
`다른 계정으로 전환하려면 계속 진행하세요...`));
|
|
145
|
+
// 기존 토큰 제거 후 재로그인 (계정 전환 지원)
|
|
146
|
+
deleteAuthData();
|
|
146
147
|
}
|
|
147
148
|
await loginFlow();
|
|
148
149
|
process.exit(0);
|
package/dist/mcp/server.js
CHANGED
|
@@ -11,6 +11,8 @@ import { requestQA } from './tools/requestQA.js';
|
|
|
11
11
|
import { createPR } from './tools/createPR.js';
|
|
12
12
|
import { requestMergeReview } from './tools/requestMergeReview.js';
|
|
13
13
|
import { executeMerge } from './tools/executeMerge.js';
|
|
14
|
+
import { gitPush } from './tools/gitPush.js';
|
|
15
|
+
import { saveContext } from './tools/saveContext.js';
|
|
14
16
|
// cwd에서 위로 올라가며 .vibe/state.json이 있는 프로젝트 루트를 탐색
|
|
15
17
|
function findProjectRoot(startDir) {
|
|
16
18
|
let dir = startDir;
|
|
@@ -239,6 +241,49 @@ export async function startMCPServer(explicitPath) {
|
|
|
239
241
|
required: ['prNumber', 'actor'],
|
|
240
242
|
},
|
|
241
243
|
},
|
|
244
|
+
{
|
|
245
|
+
name: 'vibe_git_push',
|
|
246
|
+
description: '코드 변경 후 반드시 이 도구로 커밋&푸시하세요. git add -A (state.json 포함) → commit → push를 자동 실행합니다. 터미널 git 명령 사용 금지.',
|
|
247
|
+
inputSchema: {
|
|
248
|
+
type: 'object',
|
|
249
|
+
properties: {
|
|
250
|
+
commitMessage: {
|
|
251
|
+
type: 'string',
|
|
252
|
+
description: '커밋 메시지',
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
required: ['commitMessage'],
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: 'vibe_save_context',
|
|
260
|
+
description: '중요한 결정, 진행 상황, 사용자 피드백을 이슈별로 저장합니다. 다음 세션이나 다른 AI가 이어받을 때 이 기록이 자동으로 복원됩니다. 중요한 대화 내용이 있을 때마다 호출하세요.',
|
|
261
|
+
inputSchema: {
|
|
262
|
+
type: 'object',
|
|
263
|
+
properties: {
|
|
264
|
+
issueNumber: { type: 'number', description: '이슈 번호' },
|
|
265
|
+
actor: {
|
|
266
|
+
type: 'object',
|
|
267
|
+
properties: {
|
|
268
|
+
name: { type: 'string' },
|
|
269
|
+
githubId: { type: 'string' },
|
|
270
|
+
agent: { type: 'string' },
|
|
271
|
+
},
|
|
272
|
+
required: ['name', 'githubId', 'agent'],
|
|
273
|
+
},
|
|
274
|
+
type: {
|
|
275
|
+
type: 'string',
|
|
276
|
+
enum: ['decision', 'progress', 'user_feedback'],
|
|
277
|
+
description: 'decision: 기술적 결정, progress: 진행 상황, user_feedback: 사용자 의견',
|
|
278
|
+
},
|
|
279
|
+
content: {
|
|
280
|
+
type: 'string',
|
|
281
|
+
description: '저장할 내용 (간결하게 요약)',
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
required: ['issueNumber', 'actor', 'type', 'content'],
|
|
285
|
+
},
|
|
286
|
+
},
|
|
242
287
|
],
|
|
243
288
|
}));
|
|
244
289
|
// CallTool 핸들러
|
|
@@ -271,6 +316,12 @@ export async function startMCPServer(explicitPath) {
|
|
|
271
316
|
case 'vibe_execute_merge':
|
|
272
317
|
result = await executeMerge(repoPath, args);
|
|
273
318
|
break;
|
|
319
|
+
case 'vibe_git_push':
|
|
320
|
+
result = await gitPush(repoPath, args);
|
|
321
|
+
break;
|
|
322
|
+
case 'vibe_save_context':
|
|
323
|
+
result = await saveContext(repoPath, args);
|
|
324
|
+
break;
|
|
274
325
|
default:
|
|
275
326
|
result = JSON.stringify({ error: `알 수 없는 도구: ${name}` });
|
|
276
327
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { readAuthData } from '../../cli/commands/auth.js';
|
|
2
|
+
/**
|
|
3
|
+
* AI가 넘긴 actor 파라미터를 ~/.vibe/auth.json으로 override.
|
|
4
|
+
* 로그인된 계정 정보가 있으면 무조건 그걸 사용 (AI가 틀린 정보 넘겨도 무시).
|
|
5
|
+
*/
|
|
6
|
+
export function resolveActor(input) {
|
|
7
|
+
const auth = readAuthData();
|
|
8
|
+
if (!auth)
|
|
9
|
+
return input;
|
|
10
|
+
return {
|
|
11
|
+
name: auth.user.name || auth.user.login || input.name,
|
|
12
|
+
githubId: auth.user.login || input.githubId,
|
|
13
|
+
agent: input.agent,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=_actor.js.map
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { callAI, getAIProvider } from '../../ai/client.js';
|
|
2
|
-
import { readState } from '../../state/reader.js';
|
|
2
|
+
import { readState, readContextLog } from '../../state/reader.js';
|
|
3
|
+
import { saveIntentLog } from '../../state/writer.js';
|
|
4
|
+
import { resolveActor } from './_actor.js';
|
|
3
5
|
export async function analyzeRequest(repoPath, input) {
|
|
4
|
-
const { userRequest
|
|
6
|
+
const { userRequest } = input;
|
|
7
|
+
const actor = resolveActor(input.actor);
|
|
5
8
|
const state = await readState(repoPath);
|
|
6
9
|
const openIssues = state.issues.filter((i) => i.status === 'open' || i.status === 'in_progress');
|
|
7
10
|
// 이슈 없으면 바로 new 반환
|
|
@@ -33,6 +36,19 @@ export async function analyzeRequest(repoPath, input) {
|
|
|
33
36
|
if (parsed.matched && parsed.confidence >= 0.7 && parsed.issueNumber) {
|
|
34
37
|
const matchedIssue = openIssues.find((i) => i.number === parsed.issueNumber);
|
|
35
38
|
if (matchedIssue) {
|
|
39
|
+
// intent 로그 저장 + 기존 컨텍스트 로그 읽기
|
|
40
|
+
const [, contextLog] = await Promise.all([
|
|
41
|
+
saveIntentLog(repoPath, {
|
|
42
|
+
issueNumber: matchedIssue.number,
|
|
43
|
+
author: actor,
|
|
44
|
+
timestamp: new Date().toISOString(),
|
|
45
|
+
decided: [`사용자 요청: ${userRequest.substring(0, 200)}`],
|
|
46
|
+
rejected: [],
|
|
47
|
+
warnings: [],
|
|
48
|
+
nextSteps: [`vibe_start_work(issueNumber: ${matchedIssue.number}) 호출`],
|
|
49
|
+
}),
|
|
50
|
+
readContextLog(repoPath, matchedIssue.number),
|
|
51
|
+
]);
|
|
36
52
|
return JSON.stringify({
|
|
37
53
|
type: 'existing',
|
|
38
54
|
issue: {
|
|
@@ -40,8 +56,10 @@ export async function analyzeRequest(repoPath, input) {
|
|
|
40
56
|
title: matchedIssue.title,
|
|
41
57
|
branch: matchedIssue.branch,
|
|
42
58
|
stage: matchedIssue.stage,
|
|
59
|
+
assignee: matchedIssue.assignee,
|
|
43
60
|
},
|
|
44
|
-
|
|
61
|
+
previousContext: contextLog || '이전 대화 기록 없음',
|
|
62
|
+
checkpointMessage: `관련된 작업 항목이 있습니다:\n\n #${matchedIssue.number} "${matchedIssue.title}"\n 현재 상태: ${matchedIssue.status === 'in_progress' ? '작업 중' : '시작 전'}${matchedIssue.assignee ? ` (담당: ${matchedIssue.assignee})` : ''}${contextLog ? `\n\n이전 대화 기록:\n${contextLog}` : ''}\n\n이 작업에 이어서 작업할까요, 아니면 새로 시작할까요?\n [1] 이어서 작업\n [2] 새로 시작`,
|
|
45
63
|
});
|
|
46
64
|
}
|
|
47
65
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
export async function gitPush(repoPath, input) {
|
|
3
|
+
const { commitMessage } = input;
|
|
4
|
+
try {
|
|
5
|
+
// 1. 모든 변경 파일 스테이징 (.vibe/state.json 포함)
|
|
6
|
+
execSync('git add -A', { cwd: repoPath, stdio: 'pipe' });
|
|
7
|
+
// 2. 스테이징된 변경 사항 확인
|
|
8
|
+
const staged = execSync('git status --porcelain', {
|
|
9
|
+
cwd: repoPath,
|
|
10
|
+
encoding: 'utf-8',
|
|
11
|
+
stdio: 'pipe',
|
|
12
|
+
}).trim();
|
|
13
|
+
if (!staged) {
|
|
14
|
+
return JSON.stringify({ success: true, message: '커밋할 변경 사항이 없습니다.' });
|
|
15
|
+
}
|
|
16
|
+
// 3. 커밋
|
|
17
|
+
const safeMessage = commitMessage.replace(/"/g, '\\"').replace(/`/g, '\\`');
|
|
18
|
+
execSync(`git commit -m "${safeMessage}"`, { cwd: repoPath, stdio: 'pipe' });
|
|
19
|
+
// 4. 푸시 (업스트림 자동 설정)
|
|
20
|
+
execSync('git push -u origin HEAD', { cwd: repoPath, stdio: 'pipe' });
|
|
21
|
+
// 5. 커밋 해시 조회
|
|
22
|
+
const sha = execSync('git rev-parse --short HEAD', {
|
|
23
|
+
cwd: repoPath,
|
|
24
|
+
encoding: 'utf-8',
|
|
25
|
+
stdio: 'pipe',
|
|
26
|
+
}).trim();
|
|
27
|
+
return JSON.stringify({
|
|
28
|
+
success: true,
|
|
29
|
+
sha,
|
|
30
|
+
message: `커밋 및 푸시 완료 (${sha}): ${commitMessage}`,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
35
|
+
// stderr 내용 추출
|
|
36
|
+
const msg = raw.split('\n').slice(0, 3).join(' ').trim();
|
|
37
|
+
return JSON.stringify({ error: `git 작업 실패: ${msg}` });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=gitPush.js.map
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
1
2
|
import { readState } from '../../state/reader.js';
|
|
2
3
|
import { appendWorkLog, updateIssueStage, setActiveWork } from '../../state/writer.js';
|
|
4
|
+
import { resolveActor } from './_actor.js';
|
|
3
5
|
function stageToKorean(stage) {
|
|
4
6
|
const map = {
|
|
5
7
|
not_started: '시작 전',
|
|
@@ -15,14 +17,45 @@ function stageToKorean(stage) {
|
|
|
15
17
|
};
|
|
16
18
|
return map[stage] ?? stage;
|
|
17
19
|
}
|
|
18
|
-
|
|
20
|
+
/**
|
|
21
|
+
* code_complete 시 자동으로 git add -A → commit → push 실행.
|
|
22
|
+
* state.json이 항상 커밋에 포함됨.
|
|
23
|
+
*/
|
|
24
|
+
function autoGitPush(repoPath, commitMessage) {
|
|
25
|
+
try {
|
|
26
|
+
execSync('git add -A', { cwd: repoPath, stdio: 'pipe' });
|
|
27
|
+
const staged = execSync('git status --porcelain', {
|
|
28
|
+
cwd: repoPath,
|
|
29
|
+
encoding: 'utf-8',
|
|
30
|
+
stdio: 'pipe',
|
|
31
|
+
}).trim();
|
|
32
|
+
if (!staged) {
|
|
33
|
+
return '(커밋할 변경 사항 없음)';
|
|
34
|
+
}
|
|
35
|
+
const safeMsg = commitMessage.replace(/"/g, '\\"').replace(/`/g, '\\`');
|
|
36
|
+
execSync(`git commit -m "${safeMsg}"`, { cwd: repoPath, stdio: 'pipe' });
|
|
37
|
+
execSync('git push -u origin HEAD', { cwd: repoPath, stdio: 'pipe' });
|
|
38
|
+
const sha = execSync('git rev-parse --short HEAD', {
|
|
39
|
+
cwd: repoPath,
|
|
40
|
+
encoding: 'utf-8',
|
|
41
|
+
stdio: 'pipe',
|
|
42
|
+
}).trim();
|
|
43
|
+
return `커밋 & 푸시 완료 (${sha})`;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
const msg = error instanceof Error ? error.message.split('\n')[0] : String(error);
|
|
47
|
+
return `git 자동 푸시 실패 (수동으로 vibe_git_push 호출하세요): ${msg}`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function buildCheckpointMessage(stage, details, gitResult) {
|
|
19
51
|
const { filesChanged, violations, retryCount, notes } = details;
|
|
20
52
|
switch (stage) {
|
|
21
53
|
case 'code_complete': {
|
|
22
54
|
const filesText = filesChanged && filesChanged.length > 0
|
|
23
55
|
? `\n변경된 파일:\n${filesChanged.map((f) => ` · ${f}`).join('\n')}`
|
|
24
56
|
: '';
|
|
25
|
-
|
|
57
|
+
const gitText = gitResult ? `\n\n📦 자동 커밋 결과: ${gitResult}` : '';
|
|
58
|
+
return `✅ 코드 작성 완료 — 커밋 & 푸시 자동 완료${filesText}${gitText}\n\n검토를 시작하겠습니다. 계속할까요?\n [예, 검토해주세요] [아니오, 나중에 할게요]`;
|
|
26
59
|
}
|
|
27
60
|
case 'qa_passed':
|
|
28
61
|
return `✅ 검토 완료 — 팀 공유 준비됨\n\n검토 결과:\n · 요청하신 기능이 모두 구현됐습니다 ✓\n · 기존 기능에 영향 없음 ✓\n · 코드 규칙 준수 ✓\n\n지금 팀에 공유하시겠습니까?\n [예, 공유하기] [아니오, 나중에]`;
|
|
@@ -48,7 +81,8 @@ function buildCheckpointMessage(stage, details) {
|
|
|
48
81
|
}
|
|
49
82
|
}
|
|
50
83
|
export async function recordCheckpoint(repoPath, input) {
|
|
51
|
-
const { stage,
|
|
84
|
+
const { stage, issueNumber, details } = input;
|
|
85
|
+
const actor = resolveActor(input.actor);
|
|
52
86
|
// 1. workLog 추가
|
|
53
87
|
await appendWorkLog(repoPath, {
|
|
54
88
|
actor,
|
|
@@ -69,8 +103,15 @@ export async function recordCheckpoint(repoPath, input) {
|
|
|
69
103
|
}
|
|
70
104
|
// 3. issues stage 업데이트
|
|
71
105
|
await updateIssueStage(repoPath, issueNumber, { stage });
|
|
72
|
-
// 4.
|
|
73
|
-
|
|
74
|
-
|
|
106
|
+
// 4. code_complete 시 자동 git push (state.json 포함)
|
|
107
|
+
let gitResult;
|
|
108
|
+
if (stage === 'code_complete') {
|
|
109
|
+
const commitMsg = details.commitMessage
|
|
110
|
+
|| `[Vibe] #${issueNumber} 코드 완료`;
|
|
111
|
+
gitResult = autoGitPush(repoPath, commitMsg);
|
|
112
|
+
}
|
|
113
|
+
// 5. 체크포인트 메시지 생성
|
|
114
|
+
const checkpointMessage = buildCheckpointMessage(stage, details, gitResult);
|
|
115
|
+
return JSON.stringify({ checkpointMessage, gitResult });
|
|
75
116
|
}
|
|
76
117
|
//# sourceMappingURL=recordCheckpoint.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Actor } from '../../state/types.js';
|
|
2
|
+
type ContextType = 'decision' | 'progress' | 'user_feedback';
|
|
3
|
+
export declare function saveContext(repoPath: string, input: {
|
|
4
|
+
issueNumber: number;
|
|
5
|
+
actor: Actor;
|
|
6
|
+
type: ContextType;
|
|
7
|
+
content: string;
|
|
8
|
+
}): Promise<string>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { mkdir, appendFile, readFile } from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { resolveActor } from './_actor.js';
|
|
4
|
+
const typeLabel = {
|
|
5
|
+
decision: '결정',
|
|
6
|
+
progress: '진행 상황',
|
|
7
|
+
user_feedback: '사용자 피드백',
|
|
8
|
+
};
|
|
9
|
+
export async function saveContext(repoPath, input) {
|
|
10
|
+
const { issueNumber, type, content } = input;
|
|
11
|
+
const actor = resolveActor(input.actor);
|
|
12
|
+
const logsDir = path.join(repoPath, '.vibe', 'logs');
|
|
13
|
+
await mkdir(logsDir, { recursive: true });
|
|
14
|
+
const logPath = path.join(logsDir, `${issueNumber}.md`);
|
|
15
|
+
// 파일이 없으면 헤더 먼저 생성
|
|
16
|
+
try {
|
|
17
|
+
await readFile(logPath, 'utf-8');
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
await appendFile(logPath, `# 이슈 #${issueNumber} 작업 로그\n`, 'utf-8');
|
|
21
|
+
}
|
|
22
|
+
const now = new Date().toLocaleString('ko-KR');
|
|
23
|
+
const entry = `\n## [${now}] ${actor.githubId} (${actor.agent}) — ${typeLabel[type]}\n${content}\n`;
|
|
24
|
+
await appendFile(logPath, entry, 'utf-8');
|
|
25
|
+
return JSON.stringify({
|
|
26
|
+
success: true,
|
|
27
|
+
message: `컨텍스트 저장 완료 (이슈 #${issueNumber}, ${typeLabel[type]})`,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=saveContext.js.map
|
|
@@ -73,6 +73,15 @@ export async function startSession(repoPath, input) {
|
|
|
73
73
|
: '일시중단된 작업 없음';
|
|
74
74
|
return `=== VIBE ORCHESTRATOR 컨텍스트 로드 완료 ===
|
|
75
75
|
|
|
76
|
+
╔══════════════════════════════════════════╗
|
|
77
|
+
║ AI 필독 — 이 세션의 절대 규칙 ║
|
|
78
|
+
╠══════════════════════════════════════════╣
|
|
79
|
+
║ • 모든 작업 시작 전 vibe_ 도구 호출 필수 ║
|
|
80
|
+
║ • git commit/push → vibe_git_push 사용 ║
|
|
81
|
+
║ • 터미널 git 명령으로 커밋 절대 금지 ║
|
|
82
|
+
║ • vibe_ 없이 진행 = 팀 기록 파괴 ║
|
|
83
|
+
╚══════════════════════════════════════════╝
|
|
84
|
+
|
|
76
85
|
[프로젝트]
|
|
77
86
|
이름: ${state.project || '(미설정)'}
|
|
78
87
|
최종 업데이트: ${state.lastUpdated || '(없음)'}
|
|
@@ -97,17 +106,24 @@ ${pausedText}
|
|
|
97
106
|
|
|
98
107
|
[필수 워크플로우 — 순서 위반 금지]
|
|
99
108
|
① 사용자 요청 접수 → vibe_analyze_request 호출
|
|
100
|
-
② 작업 항목 확정 → vibe_start_work 호출
|
|
109
|
+
② 작업 항목 확정 → vibe_start_work 호출 (기존 브랜치 자동 checkout+pull 포함)
|
|
101
110
|
③ 코드 수정 완료 → vibe_record_checkpoint(stage: "code_complete") 즉시 호출 ← 절대 생략 금지
|
|
102
|
-
|
|
111
|
+
→ code_complete 호출 시 git commit & push가 자동으로 실행됩니다 (별도 git 명령 불필요)
|
|
103
112
|
⑤ 코드 검토 → vibe_request_qa 호출
|
|
104
113
|
⑥ 검토 통과 → vibe_create_pr 호출
|
|
105
114
|
⑦ 각 단계 완료 후 사용자에게 결과 전달 및 다음 단계 확인
|
|
106
115
|
|
|
116
|
+
[추가 권장 — 언제든지]
|
|
117
|
+
• 사용자가 중요한 피드백을 줄 때 → vibe_save_context(type: "user_feedback")
|
|
118
|
+
• 기술적 결정을 내릴 때 → vibe_save_context(type: "decision")
|
|
119
|
+
• 작업 중단 전 → vibe_save_context(type: "progress")로 진행 상황 요약
|
|
120
|
+
→ 이 기록은 다음 세션/다른 AI가 이어받을 때 자동 복원됩니다
|
|
121
|
+
|
|
107
122
|
[절대 금지]
|
|
108
|
-
-
|
|
109
|
-
-
|
|
110
|
-
-
|
|
123
|
+
- vibe_ 도구 없이 코드 수정 또는 git 작업 진행 (팀 기록 파괴)
|
|
124
|
+
- 코드 수정 후 vibe_record_checkpoint 건너뜀 (누락 금지)
|
|
125
|
+
- 터미널로 git commit/push 직접 실행 (vibe_record_checkpoint가 자동 처리)
|
|
126
|
+
- .vibe/state.json을 커밋에서 제외 (팀 기록 손실)
|
|
111
127
|
- CHARTER 규칙 위반 코드 작성
|
|
112
128
|
- Issue, PR, Merge, Branch 같은 기술 용어 사용자에게 노출
|
|
113
129
|
==========================================`;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { readState, readIntentLog, readConfig, readContextLog } from '../../state/reader.js';
|
|
2
3
|
import { writeState, setActiveWork, updateIssueStage, appendWorkLog, } from '../../state/writer.js';
|
|
3
4
|
import { branchExists, createBranch, getRecentCommits, getChangedFiles } from '../../github/branches.js';
|
|
4
5
|
import { createIssue } from '../../github/issues.js';
|
|
6
|
+
import { resolveActor } from './_actor.js';
|
|
5
7
|
function slugify(title) {
|
|
6
8
|
return (title
|
|
7
9
|
.toLowerCase()
|
|
@@ -10,8 +12,22 @@ function slugify(title) {
|
|
|
10
12
|
.replace(/^-|-$/g, '')
|
|
11
13
|
.substring(0, 30) || 'task');
|
|
12
14
|
}
|
|
15
|
+
/** 로컬 git으로 브랜치 전환 + pull. 실패해도 에러 문자열 반환 (throw 안 함) */
|
|
16
|
+
function gitCheckoutAndPull(repoPath, branch) {
|
|
17
|
+
try {
|
|
18
|
+
execSync('git fetch origin', { cwd: repoPath, stdio: 'pipe' });
|
|
19
|
+
execSync(`git checkout ${branch}`, { cwd: repoPath, stdio: 'pipe' });
|
|
20
|
+
execSync(`git pull origin ${branch}`, { cwd: repoPath, stdio: 'pipe' });
|
|
21
|
+
return `브랜치 "${branch}"로 전환 및 최신 코드 반영 완료`;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
const msg = error instanceof Error ? error.message.split('\n')[0] : String(error);
|
|
25
|
+
return `브랜치 자동 전환 실패 (수동으로 실행: git checkout ${branch} && git pull): ${msg}`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
13
28
|
export async function startWork(repoPath, input) {
|
|
14
|
-
const
|
|
29
|
+
const actor = resolveActor(input.actor);
|
|
30
|
+
const { createNew, newIssueTitle } = input;
|
|
15
31
|
let { issueNumber } = input;
|
|
16
32
|
const config = await readConfig(repoPath);
|
|
17
33
|
if (!config) {
|
|
@@ -23,7 +39,6 @@ export async function startWork(repoPath, input) {
|
|
|
23
39
|
if (createNew && newIssueTitle) {
|
|
24
40
|
issueNumber = await createIssue(owner, repo, newIssueTitle, '');
|
|
25
41
|
issueTitle = newIssueTitle;
|
|
26
|
-
// state에 추가
|
|
27
42
|
const st = await readState(repoPath);
|
|
28
43
|
st.issues.push({
|
|
29
44
|
number: issueNumber,
|
|
@@ -42,27 +57,43 @@ export async function startWork(repoPath, input) {
|
|
|
42
57
|
const issue = st.issues.find((i) => i.number === issueNumber);
|
|
43
58
|
issueTitle = issue?.title ?? `이슈 #${issueNumber}`;
|
|
44
59
|
}
|
|
45
|
-
// 2.
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
// 2. 기존 이슈에 브랜치가 이미 있는지 확인
|
|
61
|
+
const currentState = await readState(repoPath);
|
|
62
|
+
const existingIssue = currentState.issues.find((i) => i.number === issueNumber);
|
|
63
|
+
const existingBranch = existingIssue?.branch ?? null;
|
|
64
|
+
let branchName;
|
|
65
|
+
let gitStatusMessage;
|
|
66
|
+
if (existingBranch) {
|
|
67
|
+
// 기존 브랜치 그대로 사용 + 로컬에서 checkout & pull
|
|
68
|
+
branchName = existingBranch;
|
|
69
|
+
gitStatusMessage = gitCheckoutAndPull(repoPath, existingBranch);
|
|
51
70
|
}
|
|
52
|
-
|
|
71
|
+
else {
|
|
72
|
+
// 새 브랜치 생성
|
|
73
|
+
branchName = `vibe/${actor.githubId}/${issueNumber}/${slugify(issueTitle)}`;
|
|
74
|
+
const exists = await branchExists(owner, repo, branchName);
|
|
75
|
+
if (!exists) {
|
|
76
|
+
await createBranch(owner, repo, branchName, defaultBranch);
|
|
77
|
+
}
|
|
78
|
+
gitStatusMessage = `새 브랜치 생성됨: ${branchName}`;
|
|
79
|
+
}
|
|
80
|
+
// 3. 기존 커밋/변경 파일
|
|
53
81
|
const [commits, changedFiles] = await Promise.all([
|
|
54
|
-
getRecentCommits(owner, repo, branchName, 5),
|
|
55
|
-
getChangedFiles(owner, repo, branchName, defaultBranch),
|
|
82
|
+
getRecentCommits(owner, repo, branchName, 5).catch(() => []),
|
|
83
|
+
getChangedFiles(owner, repo, branchName, defaultBranch).catch(() => []),
|
|
56
84
|
]);
|
|
57
85
|
const existingChanges = commits.length > 0
|
|
58
86
|
? `이미 ${commits.length}개 저장이 있습니다:\n${commits.map((c) => ` · ${c}`).join('\n')}`
|
|
59
87
|
: '이전 저장 없음';
|
|
60
|
-
//
|
|
61
|
-
const intentLog = await
|
|
88
|
+
// 4. intent 로그 + 채팅 컨텍스트 로그
|
|
89
|
+
const [intentLog, contextLog] = await Promise.all([
|
|
90
|
+
readIntentLog(repoPath, issueNumber),
|
|
91
|
+
readContextLog(repoPath, issueNumber),
|
|
92
|
+
]);
|
|
62
93
|
const intentText = intentLog
|
|
63
94
|
? `결정 사항: ${intentLog.decided.join(', ')}\n거절된 방안: ${intentLog.rejected.join(', ')}`
|
|
64
95
|
: '없음 (새 작업)';
|
|
65
|
-
//
|
|
96
|
+
// 5. state 업데이트
|
|
66
97
|
await setActiveWork(repoPath, {
|
|
67
98
|
issueNumber,
|
|
68
99
|
actor: actor.githubId,
|
|
@@ -78,7 +109,7 @@ export async function startWork(repoPath, input) {
|
|
|
78
109
|
assignee: actor.githubId,
|
|
79
110
|
stage: 'work_started',
|
|
80
111
|
});
|
|
81
|
-
//
|
|
112
|
+
// 6. workLog
|
|
82
113
|
await appendWorkLog(repoPath, {
|
|
83
114
|
actor,
|
|
84
115
|
stage: 'work_started',
|
|
@@ -89,9 +120,12 @@ export async function startWork(repoPath, input) {
|
|
|
89
120
|
});
|
|
90
121
|
return JSON.stringify({
|
|
91
122
|
branchName,
|
|
123
|
+
gitStatus: gitStatusMessage,
|
|
92
124
|
existingChanges,
|
|
93
125
|
intentLog: intentText,
|
|
94
|
-
|
|
126
|
+
contextLog: contextLog || '이전 대화 기록 없음',
|
|
127
|
+
assignee: actor.githubId,
|
|
128
|
+
readyMessage: `작업 공간이 준비됐습니다.\n브랜치: ${branchName}\n${gitStatusMessage}\n\n코드 작성을 시작하세요. 완료 후 vibe_record_checkpoint → vibe_git_push 순으로 호출하세요.\n중요한 결정이나 사용자 피드백이 있으면 vibe_save_context로 기록하세요.`,
|
|
95
129
|
});
|
|
96
130
|
}
|
|
97
131
|
//# sourceMappingURL=startWork.js.map
|
package/dist/state/reader.d.ts
CHANGED
|
@@ -3,3 +3,4 @@ export declare function readState(repoPath: string): Promise<VibeState>;
|
|
|
3
3
|
export declare function readIntentLog(repoPath: string, issueNumber: number): Promise<IntentLog | null>;
|
|
4
4
|
export declare function readCharter(repoPath: string): Promise<string>;
|
|
5
5
|
export declare function readConfig(repoPath: string): Promise<VibeConfig | null>;
|
|
6
|
+
export declare function readContextLog(repoPath: string, issueNumber: number): Promise<string>;
|
package/dist/state/reader.js
CHANGED
|
@@ -47,4 +47,15 @@ export async function readConfig(repoPath) {
|
|
|
47
47
|
return null;
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
+
export async function readContextLog(repoPath, issueNumber) {
|
|
51
|
+
const logPath = path.join(repoPath, '.vibe', 'logs', `${issueNumber}.md`);
|
|
52
|
+
try {
|
|
53
|
+
const content = await readFile(logPath, 'utf-8');
|
|
54
|
+
// 너무 길면 마지막 3000자만 (컨텍스트 과부하 방지)
|
|
55
|
+
return content.length > 3000 ? '...(이전 내용 생략)\n' + content.slice(-3000) : content;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
50
61
|
//# sourceMappingURL=reader.js.map
|
package/package.json
CHANGED