vibe-collab 0.1.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.
Files changed (60) hide show
  1. package/README.md +177 -0
  2. package/dist/ai/client.d.ts +15 -0
  3. package/dist/ai/client.js +89 -0
  4. package/dist/charter/generator.d.ts +10 -0
  5. package/dist/charter/generator.js +41 -0
  6. package/dist/charter/updater.d.ts +10 -0
  7. package/dist/charter/updater.js +41 -0
  8. package/dist/cli/commands/auth.d.ts +12 -0
  9. package/dist/cli/commands/auth.js +180 -0
  10. package/dist/cli/commands/connect.d.ts +1 -0
  11. package/dist/cli/commands/connect.js +171 -0
  12. package/dist/cli/commands/init.d.ts +1 -0
  13. package/dist/cli/commands/init.js +149 -0
  14. package/dist/cli/commands/serve.d.ts +2 -0
  15. package/dist/cli/commands/serve.js +26 -0
  16. package/dist/cli/commands/start.d.ts +4 -0
  17. package/dist/cli/commands/start.js +307 -0
  18. package/dist/cli/commands/status.d.ts +1 -0
  19. package/dist/cli/commands/status.js +89 -0
  20. package/dist/cli/index.d.ts +2 -0
  21. package/dist/cli/index.js +55 -0
  22. package/dist/github/auth.d.ts +22 -0
  23. package/dist/github/auth.js +77 -0
  24. package/dist/github/branches.d.ts +5 -0
  25. package/dist/github/branches.js +70 -0
  26. package/dist/github/client.d.ts +4 -0
  27. package/dist/github/client.js +38 -0
  28. package/dist/github/files.d.ts +6 -0
  29. package/dist/github/files.js +52 -0
  30. package/dist/github/issues.d.ts +7 -0
  31. package/dist/github/issues.js +51 -0
  32. package/dist/github/merges.d.ts +1 -0
  33. package/dist/github/merges.js +30 -0
  34. package/dist/github/pulls.d.ts +10 -0
  35. package/dist/github/pulls.js +27 -0
  36. package/dist/mcp/server.d.ts +1 -0
  37. package/dist/mcp/server.js +264 -0
  38. package/dist/mcp/tools/analyzeRequest.d.ts +5 -0
  39. package/dist/mcp/tools/analyzeRequest.js +60 -0
  40. package/dist/mcp/tools/createPR.d.ts +5 -0
  41. package/dist/mcp/tools/createPR.js +71 -0
  42. package/dist/mcp/tools/executeMerge.d.ts +6 -0
  43. package/dist/mcp/tools/executeMerge.js +61 -0
  44. package/dist/mcp/tools/recordCheckpoint.d.ts +15 -0
  45. package/dist/mcp/tools/recordCheckpoint.js +76 -0
  46. package/dist/mcp/tools/requestMergeReview.d.ts +5 -0
  47. package/dist/mcp/tools/requestMergeReview.js +85 -0
  48. package/dist/mcp/tools/requestQA.d.ts +6 -0
  49. package/dist/mcp/tools/requestQA.js +147 -0
  50. package/dist/mcp/tools/startSession.d.ts +5 -0
  51. package/dist/mcp/tools/startSession.js +97 -0
  52. package/dist/mcp/tools/startWork.d.ts +7 -0
  53. package/dist/mcp/tools/startWork.js +97 -0
  54. package/dist/state/reader.d.ts +5 -0
  55. package/dist/state/reader.js +50 -0
  56. package/dist/state/types.d.ts +83 -0
  57. package/dist/state/types.js +2 -0
  58. package/dist/state/writer.d.ts +10 -0
  59. package/dist/state/writer.js +82 -0
  60. package/package.json +42 -0
@@ -0,0 +1,6 @@
1
+ import type { Actor } from '../../state/types.js';
2
+ export declare function requestQA(repoPath: string, input: {
3
+ issueNumber: number;
4
+ actor: Actor;
5
+ changedFiles: string[];
6
+ }): Promise<string>;
@@ -0,0 +1,147 @@
1
+ import { callAI, getAIProvider } from '../../ai/client.js';
2
+ import { readState, readCharter, readConfig } from '../../state/reader.js';
3
+ import { getFileContentsByPaths } from '../../github/files.js';
4
+ import { extractForbiddenPatterns } from '../../charter/updater.js';
5
+ const STATIC_RULES = [
6
+ {
7
+ rule: 'no-any-type',
8
+ pattern: /:\s*any[\s;,)>]/g,
9
+ severity: 'error',
10
+ description: '타입이 지정되지 않은 코드가 있습니다',
11
+ suggestion: '구체적인 타입을 지정해주세요',
12
+ },
13
+ {
14
+ rule: 'no-hardcoded-secrets',
15
+ pattern: /(api_key|apikey|secret|password|token)\s*[=:]\s*["'][^"'$\s]{8,}["']/gi,
16
+ severity: 'error',
17
+ description: '비밀 코드가 직접 작성돼 있습니다 (보안 위험)',
18
+ suggestion: '환경변수를 사용해주세요',
19
+ },
20
+ {
21
+ rule: 'no-console-log',
22
+ pattern: /console\.log\(/g,
23
+ severity: 'warning',
24
+ description: '기록용 코드가 남아있습니다',
25
+ suggestion: 'logger를 사용하거나 제거해주세요',
26
+ },
27
+ {
28
+ rule: 'no-sync-io',
29
+ pattern: /(?:fs\.)?readFileSync|writeFileSync/g,
30
+ severity: 'warning',
31
+ description: '처리 속도를 저하시키는 방식이 사용됐습니다',
32
+ suggestion: '비동기 방식을 사용해주세요',
33
+ },
34
+ ];
35
+ export async function requestQA(repoPath, input) {
36
+ const { issueNumber, changedFiles } = input;
37
+ const [state, charter, config] = await Promise.all([
38
+ readState(repoPath),
39
+ readCharter(repoPath),
40
+ readConfig(repoPath),
41
+ ]);
42
+ const issue = state.issues.find((i) => i.number === issueNumber);
43
+ const branchName = issue?.branch;
44
+ if (!config || !branchName) {
45
+ return JSON.stringify({
46
+ result: {
47
+ passed: false,
48
+ violations: [],
49
+ summary: 'config 또는 브랜치 정보가 없습니다.',
50
+ },
51
+ });
52
+ }
53
+ const { owner, repo } = config;
54
+ // 파일 내용 가져오기
55
+ const fileContents = await getFileContentsByPaths(owner, repo, changedFiles, branchName);
56
+ const layer1Violations = [];
57
+ const filesWithErrors = new Set();
58
+ // 계층 1 — 정적 분석
59
+ for (const { path: filePath, content } of fileContents) {
60
+ for (const rule of STATIC_RULES) {
61
+ if (rule.pattern.test(content)) {
62
+ layer1Violations.push({
63
+ severity: rule.severity,
64
+ layer: 'static',
65
+ rule: rule.rule,
66
+ file: filePath,
67
+ description: rule.description,
68
+ suggestion: rule.suggestion,
69
+ });
70
+ if (rule.severity === 'error') {
71
+ filesWithErrors.add(filePath);
72
+ }
73
+ }
74
+ rule.pattern.lastIndex = 0;
75
+ }
76
+ // CHARTER 금지 패턴 동적 검사
77
+ const forbiddenPatterns = extractForbiddenPatterns(charter);
78
+ for (const fp of forbiddenPatterns) {
79
+ if (fp.patternString) {
80
+ try {
81
+ const regex = new RegExp(fp.patternString, 'gi');
82
+ if (regex.test(content)) {
83
+ layer1Violations.push({
84
+ severity: 'error',
85
+ layer: 'static',
86
+ rule: 'charter-violation',
87
+ file: filePath,
88
+ description: `CHARTER 위반: ${fp.description}`,
89
+ suggestion: '해당 패턴을 제거해주세요',
90
+ });
91
+ filesWithErrors.add(filePath);
92
+ }
93
+ }
94
+ catch {
95
+ // 유효하지 않은 정규식 무시
96
+ }
97
+ }
98
+ }
99
+ }
100
+ // 계층 2 — LLM 의미 검사 (layer1에서 error 없는 파일만, AI 있을 때만)
101
+ const layer2Violations = [];
102
+ const cleanFiles = fileContents.filter((f) => !filesWithErrors.has(f.path));
103
+ if (cleanFiles.length > 0 && charter && getAIProvider()) {
104
+ const forbiddenSection = charter.match(/##\s*절대\s*하면\s*안\s*되는\s*것[\s\S]*?(?=\n##\s|\n#\s|$)/)?.[0] ?? '';
105
+ if (forbiddenSection) {
106
+ try {
107
+ const codeContent = cleanFiles
108
+ .map((f) => `### ${f.path}\n${f.content}`)
109
+ .join('\n\n');
110
+ const raw = await callAI({
111
+ system: '다음 코드가 CHARTER 규칙을 위반하는지 검사하라. 반드시 순수 JSON 배열만 반환 (마크다운 없이). 위반 없으면 []. 각 항목: { "rule": string, "file": string, "description": string, "suggestion": string }',
112
+ user: `CHARTER 규칙:\n${forbiddenSection}\n\n코드:\n${codeContent}`,
113
+ tier: 'fast',
114
+ maxTokens: 1024,
115
+ });
116
+ const clean = raw.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
117
+ const llmViolations = JSON.parse(clean);
118
+ for (const v of llmViolations) {
119
+ layer2Violations.push({
120
+ severity: 'warning',
121
+ layer: 'llm',
122
+ rule: v.rule,
123
+ file: v.file,
124
+ description: v.description,
125
+ suggestion: v.suggestion,
126
+ });
127
+ }
128
+ }
129
+ catch {
130
+ // LLM 검사 실패 시 빈 배열
131
+ }
132
+ }
133
+ }
134
+ const allViolations = [...layer1Violations, ...layer2Violations];
135
+ const passed = !allViolations.some((v) => v.severity === 'error');
136
+ const errorCount = allViolations.filter((v) => v.severity === 'error').length;
137
+ const warningCount = allViolations.filter((v) => v.severity === 'warning').length;
138
+ const result = {
139
+ passed,
140
+ violations: allViolations,
141
+ summary: passed
142
+ ? `검토 통과 (경고 ${warningCount}개)`
143
+ : `검토 실패 (오류 ${errorCount}개, 경고 ${warningCount}개)`,
144
+ };
145
+ return JSON.stringify({ result });
146
+ }
147
+ //# sourceMappingURL=requestQA.js.map
@@ -0,0 +1,5 @@
1
+ export declare function startSession(repoPath: string, input: {
2
+ userName: string;
3
+ githubId: string;
4
+ agentName: string;
5
+ }): Promise<string>;
@@ -0,0 +1,97 @@
1
+ import { readCharter, readState } from '../../state/reader.js';
2
+ import { registerCollaborator, appendWorkLog } from '../../state/writer.js';
3
+ function stageToKorean(stage) {
4
+ const map = {
5
+ not_started: '시작 전',
6
+ context_loaded: '맥락 로드됨',
7
+ work_started: '작업 중',
8
+ code_complete: '코드 완료',
9
+ qa_passed: '검토 통과, 팀 공유 대기',
10
+ qa_failed: '검토 실패, 재작업 중',
11
+ pr_created: '팀 공유됨, 최종 반영 대기',
12
+ merge_approved: '최종 반영 승인됨',
13
+ merge_rejected: '최종 반영 거절됨',
14
+ merge_completed: '완료',
15
+ };
16
+ return map[stage] ?? stage;
17
+ }
18
+ export async function startSession(repoPath, input) {
19
+ const { userName, githubId, agentName } = input;
20
+ // 1. CHARTER + state 읽기
21
+ const [charter, state] = await Promise.all([
22
+ readCharter(repoPath),
23
+ readState(repoPath),
24
+ ]);
25
+ // 2. collaborator 등록/갱신
26
+ await registerCollaborator(repoPath, githubId, {
27
+ name: userName,
28
+ githubId,
29
+ lastActive: new Date().toISOString(),
30
+ });
31
+ // 3. 이슈 분류
32
+ const inProgressIssues = state.issues.filter((i) => i.status === 'in_progress' || i.status === 'pr_created');
33
+ const availableIssues = state.issues.filter((i) => i.status === 'open' && !i.assignee);
34
+ const recentMerged = state.issues
35
+ .filter((i) => i.status === 'merged')
36
+ .slice(-3);
37
+ // 4. 일시중단 작업 확인
38
+ const pausedWork = state.activeWork?.paused && state.activeWork.actor === githubId
39
+ ? state.activeWork
40
+ : null;
41
+ const pausedIssue = pausedWork
42
+ ? state.issues.find((i) => i.number === pausedWork.issueNumber)
43
+ : null;
44
+ // 5. workLog에 context_loaded 추가
45
+ await appendWorkLog(repoPath, {
46
+ actor: { name: userName, githubId, agent: agentName },
47
+ stage: 'context_loaded',
48
+ action: 'start_session',
49
+ message: `세션 시작: ${userName} (${githubId}) - ${agentName}`,
50
+ });
51
+ const inProgressText = inProgressIssues.length > 0
52
+ ? inProgressIssues
53
+ .map((i) => ` #${i.number} "${i.title}" — ${i.assignee ?? '미배정'} (${stageToKorean(i.stage)})`)
54
+ .join('\n')
55
+ : ' 없음';
56
+ const availableText = availableIssues.length > 0
57
+ ? availableIssues.map((i) => ` #${i.number} "${i.title}"`).join('\n')
58
+ : ' 없음';
59
+ const mergedText = recentMerged.length > 0
60
+ ? recentMerged.map((i) => ` #${i.number} "${i.title}" — ${i.assignee ?? '알 수 없음'}`).join('\n')
61
+ : ' 없음';
62
+ const pausedText = pausedIssue
63
+ ? `⏸ 일시중단된 작업: #${pausedIssue.number} — "${pausedIssue.title}" (${stageToKorean(pausedWork.stage)})`
64
+ : '일시중단된 작업 없음';
65
+ return `=== VIBE ORCHESTRATOR 컨텍스트 로드 완료 ===
66
+
67
+ [프로젝트]
68
+ 이름: ${state.project || '(미설정)'}
69
+ 최종 업데이트: ${state.lastUpdated || '(없음)'}
70
+
71
+ [레포 헌법 — CHARTER]
72
+ ${charter || '(CHARTER 없음 — vibe init을 먼저 실행해주세요)'}
73
+
74
+ [팀 현황]
75
+ 진행 중:
76
+ ${inProgressText}
77
+
78
+ 시작 가능한 작업:
79
+ ${availableText}
80
+
81
+ 최근 완료:
82
+ ${mergedText}
83
+
84
+ [내 상태]
85
+ 사용자: ${userName} (${githubId})
86
+ 에이전트: ${agentName}
87
+ ${pausedText}
88
+
89
+ [지시사항]
90
+ - 모든 코드는 CHARTER 규칙을 반드시 준수할 것
91
+ - 작업 시작 전 반드시 vibe_analyze_request를 호출할 것
92
+ - 각 단계 완료 시 vibe_record_checkpoint를 호출할 것
93
+ - 사용자에게 체크포인트 메시지를 그대로 전달하고 확인을 받을 것
94
+ - Issue, PR, Merge, Branch 같은 기술 용어 절대 사용 금지
95
+ ==========================================`;
96
+ }
97
+ //# sourceMappingURL=startSession.js.map
@@ -0,0 +1,7 @@
1
+ import type { Actor } from '../../state/types.js';
2
+ export declare function startWork(repoPath: string, input: {
3
+ issueNumber: number;
4
+ actor: Actor;
5
+ createNew: boolean;
6
+ newIssueTitle?: string;
7
+ }): Promise<string>;
@@ -0,0 +1,97 @@
1
+ import { readState, readIntentLog, readConfig } from '../../state/reader.js';
2
+ import { writeState, setActiveWork, updateIssueStage, appendWorkLog, } from '../../state/writer.js';
3
+ import { branchExists, createBranch, getRecentCommits, getChangedFiles } from '../../github/branches.js';
4
+ import { createIssue } from '../../github/issues.js';
5
+ function slugify(title) {
6
+ return (title
7
+ .toLowerCase()
8
+ .replace(/[가-힣]/g, '')
9
+ .replace(/[^a-z0-9]+/g, '-')
10
+ .replace(/^-|-$/g, '')
11
+ .substring(0, 30) || 'task');
12
+ }
13
+ export async function startWork(repoPath, input) {
14
+ const { actor, createNew, newIssueTitle } = input;
15
+ let { issueNumber } = input;
16
+ const config = await readConfig(repoPath);
17
+ if (!config) {
18
+ return JSON.stringify({ error: 'config.json이 없습니다. vibe init을 먼저 실행해주세요.' });
19
+ }
20
+ const { owner, repo, defaultBranch } = config;
21
+ // 1. 새 이슈 생성
22
+ let issueTitle = '';
23
+ if (createNew && newIssueTitle) {
24
+ issueNumber = await createIssue(owner, repo, newIssueTitle, '');
25
+ issueTitle = newIssueTitle;
26
+ // state에 추가
27
+ const st = await readState(repoPath);
28
+ st.issues.push({
29
+ number: issueNumber,
30
+ title: issueTitle,
31
+ status: 'open',
32
+ branch: null,
33
+ assignee: null,
34
+ stage: 'not_started',
35
+ prNumber: null,
36
+ intentLogPath: null,
37
+ });
38
+ await writeState(repoPath, st);
39
+ }
40
+ else {
41
+ const st = await readState(repoPath);
42
+ const issue = st.issues.find((i) => i.number === issueNumber);
43
+ issueTitle = issue?.title ?? `이슈 #${issueNumber}`;
44
+ }
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);
51
+ }
52
+ // 4. 기존 커밋/변경 파일
53
+ const [commits, changedFiles] = await Promise.all([
54
+ getRecentCommits(owner, repo, branchName, 5),
55
+ getChangedFiles(owner, repo, branchName, defaultBranch),
56
+ ]);
57
+ const existingChanges = commits.length > 0
58
+ ? `이미 ${commits.length}개 저장이 있습니다:\n${commits.map((c) => ` · ${c}`).join('\n')}`
59
+ : '이전 저장 없음';
60
+ // 5. intent 로그
61
+ const intentLog = await readIntentLog(repoPath, issueNumber);
62
+ const intentText = intentLog
63
+ ? `결정 사항: ${intentLog.decided.join(', ')}\n거절된 방안: ${intentLog.rejected.join(', ')}`
64
+ : '없음 (새 작업)';
65
+ // 6. state 업데이트
66
+ await setActiveWork(repoPath, {
67
+ issueNumber,
68
+ actor: actor.githubId,
69
+ stage: 'work_started',
70
+ startedAt: new Date().toISOString(),
71
+ paused: false,
72
+ pausedAt: null,
73
+ retryCount: 0,
74
+ });
75
+ await updateIssueStage(repoPath, issueNumber, {
76
+ status: 'in_progress',
77
+ branch: branchName,
78
+ assignee: actor.githubId,
79
+ stage: 'work_started',
80
+ });
81
+ // 7. workLog
82
+ await appendWorkLog(repoPath, {
83
+ actor,
84
+ stage: 'work_started',
85
+ issueNumber,
86
+ action: 'start_work',
87
+ message: `작업 시작: #${issueNumber} "${issueTitle}"`,
88
+ filesChanged: changedFiles,
89
+ });
90
+ return JSON.stringify({
91
+ branchName,
92
+ existingChanges,
93
+ intentLog: intentText,
94
+ readyMessage: `작업 공간이 준비됐습니다.\n브랜치: ${branchName}\n\n코드 작성을 시작하세요.`,
95
+ });
96
+ }
97
+ //# sourceMappingURL=startWork.js.map
@@ -0,0 +1,5 @@
1
+ import type { VibeState, IntentLog, VibeConfig } from './types.js';
2
+ export declare function readState(repoPath: string): Promise<VibeState>;
3
+ export declare function readIntentLog(repoPath: string, issueNumber: number): Promise<IntentLog | null>;
4
+ export declare function readCharter(repoPath: string): Promise<string>;
5
+ export declare function readConfig(repoPath: string): Promise<VibeConfig | null>;
@@ -0,0 +1,50 @@
1
+ import { readFile } from 'fs/promises';
2
+ import path from 'path';
3
+ const DEFAULT_STATE = {
4
+ project: '',
5
+ lastUpdated: '',
6
+ collaborators: {},
7
+ activeWork: null,
8
+ issues: [],
9
+ workLog: [],
10
+ };
11
+ export async function readState(repoPath) {
12
+ const statePath = path.join(repoPath, '.vibe', 'state.json');
13
+ try {
14
+ const raw = await readFile(statePath, 'utf-8');
15
+ return JSON.parse(raw);
16
+ }
17
+ catch {
18
+ return { ...DEFAULT_STATE };
19
+ }
20
+ }
21
+ export async function readIntentLog(repoPath, issueNumber) {
22
+ const logPath = path.join(repoPath, '.vibe', 'intents', `${issueNumber}.json`);
23
+ try {
24
+ const raw = await readFile(logPath, 'utf-8');
25
+ return JSON.parse(raw);
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
31
+ export async function readCharter(repoPath) {
32
+ const charterPath = path.join(repoPath, 'CHARTER.md');
33
+ try {
34
+ return await readFile(charterPath, 'utf-8');
35
+ }
36
+ catch {
37
+ return '';
38
+ }
39
+ }
40
+ export async function readConfig(repoPath) {
41
+ const configPath = path.join(repoPath, '.vibe', 'config.json');
42
+ try {
43
+ const raw = await readFile(configPath, 'utf-8');
44
+ return JSON.parse(raw);
45
+ }
46
+ catch {
47
+ return null;
48
+ }
49
+ }
50
+ //# sourceMappingURL=reader.js.map
@@ -0,0 +1,83 @@
1
+ export type Stage = 'not_started' | 'context_loaded' | 'work_started' | 'code_complete' | 'qa_passed' | 'qa_failed' | 'pr_created' | 'merge_approved' | 'merge_rejected' | 'merge_completed';
2
+ export interface Actor {
3
+ name: string;
4
+ githubId: string;
5
+ agent: string;
6
+ }
7
+ export interface WorkLogEntry {
8
+ id: string;
9
+ timestamp: string;
10
+ actor: Actor;
11
+ stage: Stage;
12
+ issueNumber?: number;
13
+ action: string;
14
+ message: string;
15
+ filesChanged?: string[];
16
+ retryCount?: number;
17
+ }
18
+ export interface IssueState {
19
+ number: number;
20
+ title: string;
21
+ status: 'open' | 'in_progress' | 'pr_created' | 'merged' | 'closed';
22
+ branch: string | null;
23
+ assignee: string | null;
24
+ stage: Stage;
25
+ prNumber: number | null;
26
+ intentLogPath: string | null;
27
+ }
28
+ export interface ActiveWork {
29
+ issueNumber: number;
30
+ actor: string;
31
+ stage: Stage;
32
+ startedAt: string;
33
+ paused: boolean;
34
+ pausedAt: string | null;
35
+ retryCount: number;
36
+ }
37
+ export interface Collaborator {
38
+ name: string;
39
+ githubId: string;
40
+ lastActive: string;
41
+ }
42
+ export interface VibeState {
43
+ project: string;
44
+ lastUpdated: string;
45
+ collaborators: Record<string, Collaborator>;
46
+ activeWork: ActiveWork | null;
47
+ issues: IssueState[];
48
+ workLog: WorkLogEntry[];
49
+ }
50
+ export interface QAViolation {
51
+ severity: 'error' | 'warning';
52
+ layer: 'static' | 'llm';
53
+ rule: string;
54
+ file: string;
55
+ description: string;
56
+ suggestion?: string;
57
+ }
58
+ export interface QAResult {
59
+ passed: boolean;
60
+ violations: QAViolation[];
61
+ summary: string;
62
+ }
63
+ export interface MergeReviewResult {
64
+ approved: boolean;
65
+ reason: string;
66
+ conflictingIssues: number[];
67
+ riskyFiles: string[];
68
+ }
69
+ export interface IntentLog {
70
+ issueNumber: number;
71
+ author: Actor;
72
+ timestamp: string;
73
+ decided: string[];
74
+ rejected: string[];
75
+ warnings: string[];
76
+ nextSteps: string[];
77
+ }
78
+ export interface VibeConfig {
79
+ owner: string;
80
+ repo: string;
81
+ defaultBranch: string;
82
+ protectedPaths: string[];
83
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,10 @@
1
+ import type { VibeState, WorkLogEntry, IssueState, ActiveWork, Collaborator, IntentLog, VibeConfig } from './types.js';
2
+ export declare function ensureVibeDir(repoPath: string): Promise<void>;
3
+ export declare function writeState(repoPath: string, state: VibeState): Promise<void>;
4
+ export declare function appendWorkLog(repoPath: string, entry: Omit<WorkLogEntry, 'id' | 'timestamp'>): Promise<void>;
5
+ export declare function updateIssueStage(repoPath: string, issueNumber: number, updates: Partial<IssueState>): Promise<void>;
6
+ export declare function setActiveWork(repoPath: string, work: ActiveWork | null): Promise<void>;
7
+ export declare function registerCollaborator(repoPath: string, key: string, collaborator: Collaborator): Promise<void>;
8
+ export declare function saveIntentLog(repoPath: string, log: IntentLog): Promise<void>;
9
+ export declare function writeConfig(repoPath: string, config: VibeConfig): Promise<void>;
10
+ export declare function writeCharter(repoPath: string, content: string): Promise<void>;
@@ -0,0 +1,82 @@
1
+ import { mkdir, writeFile } from 'fs/promises';
2
+ import path from 'path';
3
+ import { readState } from './reader.js';
4
+ async function withRetry(fn, retries = 3) {
5
+ for (let i = 0; i < retries; i++) {
6
+ try {
7
+ return await fn();
8
+ }
9
+ catch (error) {
10
+ if (i === retries - 1)
11
+ throw error;
12
+ await new Promise((resolve) => setTimeout(resolve, 100));
13
+ }
14
+ }
15
+ throw new Error('재시도 횟수 초과');
16
+ }
17
+ export async function ensureVibeDir(repoPath) {
18
+ await mkdir(path.join(repoPath, '.vibe', 'intents'), { recursive: true });
19
+ }
20
+ export async function writeState(repoPath, state) {
21
+ await ensureVibeDir(repoPath);
22
+ const updated = { ...state, lastUpdated: new Date().toISOString() };
23
+ const statePath = path.join(repoPath, '.vibe', 'state.json');
24
+ await withRetry(() => writeFile(statePath, JSON.stringify(updated, null, 2), 'utf-8'));
25
+ }
26
+ export async function appendWorkLog(repoPath, entry) {
27
+ const state = await readState(repoPath);
28
+ const newEntry = {
29
+ ...entry,
30
+ id: `log-${Date.now()}`,
31
+ timestamp: new Date().toISOString(),
32
+ };
33
+ state.workLog.push(newEntry);
34
+ await writeState(repoPath, state);
35
+ }
36
+ export async function updateIssueStage(repoPath, issueNumber, updates) {
37
+ const state = await readState(repoPath);
38
+ const idx = state.issues.findIndex((i) => i.number === issueNumber);
39
+ if (idx !== -1) {
40
+ state.issues[idx] = { ...state.issues[idx], ...updates };
41
+ }
42
+ else {
43
+ // 이슈가 없으면 새로 추가
44
+ state.issues.push({
45
+ number: issueNumber,
46
+ title: '',
47
+ status: 'open',
48
+ branch: null,
49
+ assignee: null,
50
+ stage: 'not_started',
51
+ prNumber: null,
52
+ intentLogPath: null,
53
+ ...updates,
54
+ });
55
+ }
56
+ await writeState(repoPath, state);
57
+ }
58
+ export async function setActiveWork(repoPath, work) {
59
+ const state = await readState(repoPath);
60
+ state.activeWork = work;
61
+ await writeState(repoPath, state);
62
+ }
63
+ export async function registerCollaborator(repoPath, key, collaborator) {
64
+ const state = await readState(repoPath);
65
+ state.collaborators[key] = collaborator;
66
+ await writeState(repoPath, state);
67
+ }
68
+ export async function saveIntentLog(repoPath, log) {
69
+ await ensureVibeDir(repoPath);
70
+ const logPath = path.join(repoPath, '.vibe', 'intents', `${log.issueNumber}.json`);
71
+ await writeFile(logPath, JSON.stringify(log, null, 2), 'utf-8');
72
+ }
73
+ export async function writeConfig(repoPath, config) {
74
+ await ensureVibeDir(repoPath);
75
+ const configPath = path.join(repoPath, '.vibe', 'config.json');
76
+ await writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
77
+ }
78
+ export async function writeCharter(repoPath, content) {
79
+ const charterPath = path.join(repoPath, 'CHARTER.md');
80
+ await writeFile(charterPath, content, 'utf-8');
81
+ }
82
+ //# sourceMappingURL=writer.js.map
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "vibe-collab",
3
+ "version": "0.1.0",
4
+ "description": "누가 어떤 AI를 써도, 항상 한 팀처럼 작동하는 바이브 코딩 협업 도구",
5
+ "type": "module",
6
+ "bin": {
7
+ "vibe": "./dist/cli/index.js",
8
+ "vibe-orchestrator": "./dist/cli/index.js"
9
+ },
10
+ "files": [
11
+ "dist/**",
12
+ "!dist/**/*.map",
13
+ "README.md"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "dev": "tsx src/cli/index.ts",
21
+ "typecheck": "tsc --noEmit",
22
+ "start": "node dist/cli/index.js"
23
+ },
24
+ "dependencies": {
25
+ "@anthropic-ai/sdk": "^0.39.0",
26
+ "@modelcontextprotocol/sdk": "^1.0.0",
27
+ "@octokit/rest": "^21.0.0",
28
+ "chalk": "^5.3.0",
29
+ "commander": "^12.1.0",
30
+ "dotenv": "^16.4.0",
31
+ "inquirer": "^10.2.0",
32
+ "openai": "^4.77.0",
33
+ "ora": "^8.1.0",
34
+ "uipro-cli": "^2.2.3"
35
+ },
36
+ "devDependencies": {
37
+ "@types/inquirer": "^9.0.7",
38
+ "@types/node": "^22.0.0",
39
+ "tsx": "^4.19.0",
40
+ "typescript": "^5.7.0"
41
+ }
42
+ }