From 4b8d272e836816d8d99012c60624c29ebcb81b48 Mon Sep 17 00:00:00 2001 From: Jay Date: Mon, 13 Apr 2026 12:36:59 +0900 Subject: [PATCH] feat: Second Brain Github Sync (v1.0.13) --- package.json | 7 ++++- src/extension.ts | 76 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 78 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f720351..0ecb021 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "connect-ai-lab", "displayName": "Connect AI", "description": "100% 로컬 AI 코딩 에이전트 — 파일 생성, 코드 편집, 터미널 실행을 오프라인으로. Ollama + Gemma/Llama/DeepSeek 지원.", - "version": "1.0.12", + "version": "1.0.13", "publisher": "connectailab", "license": "MIT", "icon": "assets/icon.png", @@ -107,6 +107,11 @@ "type": "number", "default": 300, "description": "AI 응답 대기 시간 (초, 기본값: 300초)" + }, + "connectAiLab.secondBrainRepo": { + "type": "string", + "default": "", + "description": "🧠 Second Brain — 지식 저장소 GitHub URL (예: https://github.com/user/my-knowledge). 여기에 입력한 깃허브의 마크다운(.md) 파일들이 AI의 지식 기반이 됩니다." } } } diff --git a/src/extension.ts b/src/extension.ts index 7d58d32..82e0df4 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,6 +2,11 @@ import * as vscode from 'vscode'; import axios from 'axios'; import * as fs from 'fs'; import * as path from 'path'; +import * as os from 'os'; +import { exec } from 'child_process'; +import { promisify } from 'util'; + +const execAsync = promisify(exec); // ============================================================ // Connect AI — Full Agentic Local AI for VS Code @@ -16,6 +21,7 @@ function getConfig() { defaultModel: cfg.get('defaultModel', 'gemma4:e2b'), maxTreeFiles: cfg.get('maxContextFiles', 200), timeout: cfg.get('requestTimeout', 300) * 1000, + secondBrainRepo: cfg.get('secondBrainRepo', ''), }; } @@ -238,6 +244,9 @@ class SidebarChatProvider implements vscode.WebviewViewProvider { await this._sendModels(); } break; + case 'syncBrain': + await this._syncSecondBrain(); + break; } }); @@ -276,6 +285,61 @@ class SidebarChatProvider implements vscode.WebviewViewProvider { } } + // -------------------------------------------------------- + // Second Brain (Github Repo Knowledge Sync) + // -------------------------------------------------------- + private async _syncSecondBrain() { + if (!this._view) { return; } + const { secondBrainRepo } = getConfig(); + + if (!secondBrainRepo) { + vscode.window.showErrorMessage('설정에서 Second Brain Github 주소를 먼저 입력해주세요!'); + return; + } + + const brainDir = path.join(os.homedir(), '.connect-ai-brain'); + try { + this._view.webview.postMessage({ type: 'response', value: '🧠 **Second Brain 동기화 시작 중... 깃허브에서 지식을 복제합니다.**' }); + + if (fs.existsSync(brainDir)) { + // 깔끔한 최신화를 위해 기존 폴더 삭제 후 다시 클론 + fs.rmSync(brainDir, { recursive: true, force: true }); + } + + await execAsync(`git clone ${secondBrainRepo} "${brainDir}"`); + vscode.window.showInformationMessage('🧠 Second Brain 지식 연동이 완료되었습니다!'); + this._view.webview.postMessage({ type: 'response', value: '✅ **Second Brain 업데이트 완료! 이제 회원님의 뇌(문서)를 바탕으로 특화된 코딩을 진행합니다.**' }); + } catch (error: any) { + vscode.window.showErrorMessage(`Second Brain 동기화 실패: ${error.message}`); + this._view.webview.postMessage({ type: 'error', value: `⚠️ 동기화 실패: ${error.message}` }); + } + } + + private _getSecondBrainContext(): string { + const brainDir = path.join(os.homedir(), '.connect-ai-brain'); + if (!fs.existsSync(brainDir)) return ''; + + let combined = ''; + try { + const files = fs.readdirSync(brainDir); + for (const file of files) { + // 마크다운(.md)과 텍스트 파일만 수집 + if (file.endsWith('.md') || file.endsWith('.txt')) { + const content = fs.readFileSync(path.join(brainDir, file), 'utf-8'); + // 컨텍스트 크기 제한 방지 (각 파일당 최대 5000자 반영) + combined += `\n--- [User Knowledge Base: ${file}] ---\n${content.slice(0, 5000)}\n`; + } + } + } catch (e) { + console.error('Brain read error', e); + } + + if (combined.trim().length > 0) { + return `\n\n[CRITICAL INSTRUCTION: USER'S SECOND BRAIN - KNOWLEDGE BASE]\nYou MUST strictly read and follow the styling, rules, and knowledges defined in these provided files authored by the user when answering or coding:\n${combined}\n\n`; + } + return ''; + } + /** 저장된 대화 메시지를 웹뷰에 다시 전송 (복원) */ private _restoreDisplayMessages() { if (!this._view || this._displayMessages.length === 0) { return; } @@ -379,6 +443,9 @@ class SidebarChatProvider implements vscode.WebviewViewProvider { // 2. Context: workspace file tree + key file contents const workspaceCtx = this._getWorkspaceContext(); + + // 2.5 Inject Second Brain Knowledge + const brainCtx = this._getSecondBrainContext(); // 3. Push user message this._chatHistory.push({ @@ -398,7 +465,7 @@ class SidebarChatProvider implements vscode.WebviewViewProvider { if (reqMessages.length > 0 && reqMessages[0].role === 'system') { reqMessages[0] = { role: 'system', - content: `${SYSTEM_PROMPT}\n\n[BACKGROUND CONTEXT - DO NOT EXPLAIN THIS TO THE USER UNLESS ASKED]\n${contextBlock}\n${workspaceCtx}` + content: `${SYSTEM_PROMPT}\n\n[BACKGROUND CONTEXT - DO NOT EXPLAIN THIS TO THE USER UNLESS ASKED]\n${contextBlock}\n${workspaceCtx}\n${brainCtx}` }; } @@ -646,9 +713,9 @@ textarea::placeholder{color:var(--text-dim)} .stop-btn.visible{display:flex} @keyframes msgIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}} @keyframes shimmer{0%{left:-40px}100%{left:120px}} -@keyframes pulse{0%,100%{opacity:.5}50%{opacity:1}} +@keyframes pulse{0%,100%{opacity:.5}50%{opacity:1}.pulse-btn{animation:pulse 2s infinite} -
Connect AI
+
Connect AI
@@ -663,7 +730,7 @@ textarea::placeholder{color:var(--text-dim)} try { const vscode=acquireVsCodeApi(),chat=document.getElementById('chat'),input=document.getElementById('input'), sendBtn=document.getElementById('sendBtn'),stopBtn=document.getElementById('stopBtn'), -modelSel=document.getElementById('modelSel'),newChatBtn=document.getElementById('newChatBtn'),settingsBtn=document.getElementById('settingsBtn'); +modelSel=document.getElementById('modelSel'),newChatBtn=document.getElementById('newChatBtn'),settingsBtn=document.getElementById('settingsBtn'),brainBtn=document.getElementById('brainBtn'); let loader=null,sending=false; vscode.postMessage({type:'getModels'}); setTimeout(()=>vscode.postMessage({type:'ready'}),300); @@ -702,6 +769,7 @@ sendBtn.addEventListener('click',send); input.addEventListener('keydown',e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();send()}}); newChatBtn.addEventListener('click',()=>vscode.postMessage({type:'newChat'})); settingsBtn.addEventListener('click',()=>vscode.postMessage({type:'openSettings'})); +brainBtn.addEventListener('click',()=>vscode.postMessage({type:'syncBrain'})); window.addEventListener('message',e=>{const msg=e.data;switch(msg.type){ case 'response':hideLoader();setSending(false);addMsg(msg.value,'ai');break; case 'error':hideLoader();setSending(false);addMsg(msg.value,'error');break;