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 CHANGED
@@ -17,7 +17,7 @@
17
17
  ← JWT 토큰 발급 ←──────────────────────
18
18
  → ~/.vibe/auth.json 저장
19
19
 
20
- Claude Code / Cursor / VS Code
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 자동 연결** `vibe init` 번으로 Claude Code, Cursor, VS Code 동시 설정
28
+ - **10개 MCP 도구**git commit/push까지 AI가 직접 실행, 사용자는 채팅만
29
29
 
30
30
  ---
31
31
 
32
32
  ## 빠른 시작
33
33
 
34
- ### 1단계 설치
34
+ ### 프로젝트 팀장 (최초 1회)
35
35
 
36
36
  ```bash
37
37
  npm install -g vibe-collab
38
- ```
39
38
 
40
- ### 2단계 로그인
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
- ```bash
43
- vibe auth login
44
+ git add .vibe CHARTER.md
45
+ git commit -m "chore: init vibe"
46
+ git push
44
47
  ```
45
48
 
46
- 브라우저가 열리면 GitHub 계정으로 로그인하세요. 완료되면 터미널로 돌아옵니다.
47
-
48
- ### 3단계 — 프로젝트 초기화
49
+ ### 협업자 합류 (최초 1회)
49
50
 
50
51
  ```bash
51
- cd /path/to/your/project
52
- vibe init
53
- ```
52
+ npm install -g vibe-collab
54
53
 
55
- GitHub 레포 URL을 입력하면 자동으로 설정됩니다:
56
- - `CHARTER.md` AI가 분석한 레포 컨벤션
57
- - `.vibe/state.json` 협업 상태
58
- - `.claude/settings.json` — Claude Code MCP 설정
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
- ### 4단계 작업 시작
59
+ > `vibe init`은 다시 실행하지 않아도 됩니다. `.vibe/config.json`과 `CHARTER.md`는 이미 레포에 있습니다.
62
60
 
63
- Claude Code, Cursor 등 AI 도구에서 `vibe_start_session`을 호출하면 팀 컨텍스트가 자동으로 주입됩니다.
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` | 프로젝트 초기화 (CHARTER, state.json 생성 + MCP 자동 감지) |
80
- | `vibe connect` | 프로젝트 내 AI 도구 자동 감지 후 MCP 설정 추가 |
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 도구 (8개)
111
+ ## MCP 도구 (10개)
117
112
 
118
- Claude Code, Cursor, VS Code 등 MCP를 지원하는 모든 AI 도구에서 사용할 수 있습니다.
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
- | `vibe_request_qa` | 코드 리뷰 요청 (정적 분석 + AI 검사) |
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
- ```bash
136
- # 터미널 A Claude Code로 작업
137
- # vibe_start_session { userName: "김지혁", githubId: "jihyuk-kim" }
161
+ ```
162
+ [Claude Codealice] [Gemini CLI bob]
163
+ vibe_start_session vibe_start_session
164
+ → 이슈 #5 담당 → 이슈 #6 담당
138
165
 
139
- # 터미널 B — Cursor로 다른 기능 동시 작업
140
- # vibe_start_session { userName: "이민수", githubId: "minsoo-lee" }
166
+ 코드 수정 코드 수정
167
+ vibe_record_checkpoint vibe_record_checkpoint
168
+ vibe_git_push vibe_git_push
141
169
 
142
- # 두 세션이 동일한 state.json 공유 → 충돌 없이 병렬 개발
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(`이미 로그인되어 있습니다: @${existing.user.login}\n` +
142
+ console.log(chalk.yellow(`현재 로그인: @${existing.user.login} (${existing.user.name})\n` +
143
143
  `만료: ${new Date(existing.expiresAt).toLocaleString('ko-KR')}\n\n` +
144
- `재로그인하려면: vibe auth logout 후 vibe auth login`));
145
- return;
144
+ `다른 계정으로 전환하려면 계속 진행하세요...`));
145
+ // 기존 토큰 제거 후 재로그인 (계정 전환 지원)
146
+ deleteAuthData();
146
147
  }
147
148
  await loginFlow();
148
149
  process.exit(0);
@@ -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,6 @@
1
+ import type { Actor } from '../../state/types.js';
2
+ /**
3
+ * AI가 넘긴 actor 파라미터를 ~/.vibe/auth.json으로 override.
4
+ * 로그인된 계정 정보가 있으면 무조건 그걸 사용 (AI가 틀린 정보 넘겨도 무시).
5
+ */
6
+ export declare function resolveActor(input: Actor): Actor;
@@ -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, actor } = input;
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
- checkpointMessage: `관련된 작업 항목이 있습니다:\n\n #${matchedIssue.number} "${matchedIssue.title}"\n 현재 상태: ${matchedIssue.status === 'in_progress' ? '작업 ' : '시작 전'}\n\n이 작업에 이어서 작업할까요, 아니면 새로 시작할까요?\n [1] 이어서 작업\n [2] 새로 시작`,
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,3 @@
1
+ export declare function gitPush(repoPath: string, input: {
2
+ commitMessage: string;
3
+ }): Promise<string>;
@@ -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
- function buildCheckpointMessage(stage, details) {
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
- return `✅ 코드 작성 완료${filesText}\n\n⚠️ 지금 바로 .vibe/state.json을 포함한 모든 변경 파일을 git commit & push 하세요.\n 커밋 명령: git add -A && git commit -m "..." && git push\n (.vibe/state.json이 커밋에 빠지면 팀 기록이 누락됩니다)\n\n커밋 완료 검토를 시작하겠습니다. 계속할까요?\n [예, 검토해주세요] [아니오, 나중에 할게요]`;
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, actor, issueNumber, details } = input;
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
- const checkpointMessage = buildCheckpointMessage(stage, details);
74
- return JSON.stringify({ checkpointMessage });
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
- vibe_record_checkpoint 완료 직후 git add .vibe/state.json (코드 파일과 함께) git commit → git push ← state.json 반드시 포함
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
- - 코드를 수정한 vibe_record_checkpoint를 호출하지 않는 (팀 기록 누락)
109
- - git commit 전에 vibe_record_checkpoint 건너뛰는 (state.json이 커밋에서 누락됨)
110
- - .vibe/state.json을 git add/commit에서 제외하는 ( 기록 손실)
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 { readState, readIntentLog, readConfig } from '../../state/reader.js';
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 { actor, createNew, newIssueTitle } = input;
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 branchName = `vibe/${actor.githubId}/${issueNumber}/${slugify(issueTitle)}`;
47
- // 3. 브랜치 없으면 생성
48
- const exists = await branchExists(owner, repo, branchName);
49
- if (!exists) {
50
- await createBranch(owner, repo, branchName, defaultBranch);
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
- // 4. 기존 커밋/변경 파일
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
- // 5. intent 로그
61
- const intentLog = await readIntentLog(repoPath, issueNumber);
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
- // 6. state 업데이트
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
- // 7. workLog
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
- readyMessage: `작업 공간이 준비됐습니다.\n브랜치: ${branchName}\n\n코드 작성을 시작하세요.`,
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
@@ -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>;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-collab",
3
- "version": "0.8.11",
3
+ "version": "0.8.14",
4
4
  "description": "누가 어떤 AI를 써도, 항상 한 팀처럼 작동하는 바이브 코딩 협업 도구",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,4 +39,4 @@
39
39
  "tsx": "^4.19.0",
40
40
  "typescript": "^5.7.0"
41
41
  }
42
- }
42
+ }