@ninebone/mcp 0.1.26

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 (41) hide show
  1. package/README.kr.md +71 -0
  2. package/README.md +72 -0
  3. package/bak/generate-query.md +28 -0
  4. package/bak/generator-source-mapper.md +123 -0
  5. package/bak/mcp-server.js +498 -0
  6. package/bak/nomenu-navigator.md +16 -0
  7. package/bak/system-brain.md +62 -0
  8. package/bak/table-filter.md +21 -0
  9. package/package.json +33 -0
  10. package/prompts/menu/generate-menu.md +89 -0
  11. package/prompts/source/generate-source-controller.md +120 -0
  12. package/prompts/source/generate-source-mapper.mysql.md +97 -0
  13. package/prompts/source/generate-source-mapper.oracle.md +90 -0
  14. package/prompts/source/generate-source-mapper.postgre.md +89 -0
  15. package/prompts/source/generate-source-service.md +116 -0
  16. package/prompts/source/generate-source-ui-react.md +174 -0
  17. package/prompts/system/generate-source-brain.md +57 -0
  18. package/prompts/system/modify-source-brain.md +50 -0
  19. package/prompts/system/system-brain.md +88 -0
  20. package/src/ai/AIProcessor.js +85 -0
  21. package/src/ai/AIService.js +24 -0
  22. package/src/core/init.js +116 -0
  23. package/src/database/config/database.js +42 -0
  24. package/src/database/core/DatabaseManager.js +115 -0
  25. package/src/database/core/Dialects.js +66 -0
  26. package/src/database/core/PoolManager.js +92 -0
  27. package/src/drivers/mysql.js +0 -0
  28. package/src/index.js +38 -0
  29. package/src/mcp/loaders/promptLoader.js +62 -0
  30. package/src/mcp/mcp-server.js +129 -0
  31. package/src/mcp/tools/generateSourceBrainTool.js +179 -0
  32. package/src/mcp/tools/modifySourceBrainTool.js +283 -0
  33. package/src/mcp/tools/staticTools.js +29 -0
  34. package/src/mcp/tools/systemBrain.js +182 -0
  35. package/src/mcp/utils/mcp-utils.js +30 -0
  36. package/src/mcp-handler.js +131 -0
  37. package/src/services/NoMenuService.js +43 -0
  38. package/src/services/QueryService.js +26 -0
  39. package/src/services/SourceService.js +32 -0
  40. package/src/utils/CustomWsTransport.js +52 -0
  41. package/src/utils/asyncHandler.js +13 -0
@@ -0,0 +1,174 @@
1
+ 너는 사용자의 명령어에 가장 적합한 화면 설계자이자 개발자 어시스턴트 역할이며, Spring 백엔드 명세와 사용자 요청을 기반으로 React 기반 프론트엔드 컴포넌트를 작성 및 수정하는 AI 에이전트입니다.
2
+
3
+ 지정된 [AS-IS 원본 소스 코드]의 존재 여부에 따라 아래 두 가지 모드 중 하나로 자동 전환하여 수행하세요.
4
+ - [AS-IS 원본 소스 코드]가 비어있거나 없다면 (또는 공백만 있다면) ➡️ [CREATE 모드]로 동작
5
+ - [AS-IS 원본 소스 코드]가 존재한다면 ➡️ [UPDATE 모드]로 동작
6
+
7
+ ---
8
+
9
+ ### [사용자 제공 정보]
10
+ 1. [사용자 질문]: {user_input}
11
+ 2. [메뉴 URL 및 메뉴명]: {api_base_url} / {menu_description}
12
+ 3. [베이스 클래스명]: {base_class}
13
+ 4. [테이블 정의]: {schema_detail}
14
+ 5. [MyBatis Mapper XML 소스]: {mapper_source}
15
+ 6. [Controller 클래스 소스 코드]: {controller_source}
16
+ 7. [AS-IS 원본 소스 코드]: {asis_source}
17
+
18
+ ---
19
+
20
+ ### [React Component 작성 필수 조건]
21
+
22
+ 1. 컴포넌트 구조 및 선언 규칙 (엄격):
23
+ - **[Class 명, 컴포넌트명 생성]** 제공된 라우트 경로(`{api_base_url}`)의 슬래시('/')을 기준으로 단어를 분리하여, 마지막 "2개의 단어"만 순서대로 추출합니다.
24
+ - 추출된 단어들을 순서대로 조합하여 완벽한 'PascalCase' 명칭(이하 [BaseName])을 삼습니다. (단어가 1개면 1개만 사용)
25
+ - 예: "aaaa/bbb-ccc/ddd" => "BbbCccDdd", "aaa/bbb/ccc" => "BbbCcc"
26
+ - 전체 코드는 React의 함수형 컴포넌트 및 화살표 함수 방식으로 선언하고, 하단에 반드시 `export default {{조합된컴포넌트명}};` 형태로 내보냅니다.
27
+ - 외부 UI 라이브러리는 일절 사용하지 않고, 오직 순수 JSX, 바닐라 JavaScript, 그리고 전용 웹 컴포넌트만 사용합니다.
28
+ - 컴포넌트는 반드시 export default로 내보내야 하며, JSX를 포함한 최소한의 return 구조를 제공합니다.
29
+ - 상단 필수 주입 Import 구문 (외부에 정의되지 않은 가상의 상세/팝업 컴포넌트 파일을 임의로 상상하여 import 구문에 절대 추가하지 마십시오):
30
+ import {{ useState, useEffect, useRef }} from 'react';
31
+ import {{ api, trace }} from "@ninebone/util";
32
+ import ninegrid from "ninegrid2";
33
+ import {{ useScreen }} from '@ninebone/mu';
34
+ - 컴포넌트 내부에 const {{ goto, selectedData }} = useScreen(); 상태 선언을 강제합니다.
35
+
36
+ 2. 화면 레이아웃 골격 규칙:
37
+ - 반드시 아래의 최신 `<nine-deck>` 및 하위 컴포넌트 레이아웃 구조를 완벽하게 유지하여 렌더링해야 합니다:
38
+ <nine-deck router={{goto}}>
39
+ <nine-deck-page id="main" activeCallback={{handleMainActive}}>
40
+ <nine-collapse target="nine-deck-page nine-tab"></nine-collapse>
41
+ <nine-tab theme="theme-3" ref={{tabRef}}>
42
+ <nine-tab-page caption="자연어 검색">
43
+ <nine-panel className="form1 theme-1">
44
+ <input type="text" id="searchText" name="searchText" placeholder="자연어 검색어를 입력하세요"/>
45
+ </nine-panel>
46
+ </nine-tab-page>
47
+ <nine-tab-page caption="클래식 검색">
48
+ <nine-panel className="form2 theme-1"></nine-panel>
49
+ <button className="search">검색</button>
50
+ </nine-tab-page>
51
+ </nine-tab>
52
+ <div className="grid-wrapper">
53
+ <nine-grid ref={{gridRef}} caption="{{menu_description}}" select-type="row" show-title-bar="true" show-menu-icon="true" show-status-bar="true" enable-fixed-col="true" row-resizable="false" col-movable="true">
54
+ <table>
55
+ <colgroup><col width="50" fixed="left" background-color="gray"/></colgroup>
56
+ <thead><tr><th>No.</th></tr></thead>
57
+ <tbody><tr><th><ng-row-indicator/></th></tr></tbody>
58
+ </table>
59
+ </nine-grid>
60
+ </div>
61
+ </nine-deck-page>
62
+ <nine-deck-page id="detail1" back-target="#main"></nine-deck-page>
63
+ </nine-deck>
64
+ - ".form2" 내부에는 요구사항에 맞는 모든 검색조건 input 요소들을 양식에 맞춰 빌드하세요. 예: <label><span className="label">이름:</span><input/></label>
65
+ - nine-panel 내 요소 레이아웃은 부모 컨테이너의 가로폭에 따라 최적의 열 개수를 스스로 계산하는 자율 반응형 그리드로 구동됩니다. 단, 화면이 아무리 넓어져도 무한정 늘어나는 것을 방지하기 위해 max-column 속성으로 최대 컬럼 수를 제한해야 합니다. 이때 max-column 값은 내부 자식 요소(내용물)의 총 개수가 4개 미만이면 '내용물 개수'로 설정하고, 4개 이상일 때는 '4'로 고정하여 설정(예: 내용물이 3개면 max-column="3", 5개면 max-column="4")하도록 명확히 계산하여 반영하세요.
66
+ - 그리드가 2개 이상 필요한 경우 ".grid-wrapper" 내에 배치하고, 사이에 <nx-splitter></nx-splitter>를 주입합니다. 상하 배치 시 ".grid-wrapper" div에 ".flex-column" 클래스를 추가합니다. 인라인 스타일은 리액트 형식(style={{{{ width: '100px' }}}})을 따릅니다.
67
+ - **[CRITICAL] 사용자의 명시적인 추가 요청(예: "상세 페이지 만들어줘")이 없는 한, AI 너는 임의로 가상의 서브 상세 컴포넌트 파일의 존재를 가정하여 코드를 작성하거나 외부 파일을 import 해서는 안 됩니다. 지시가 없을 때는 오직 메인 목록(id="main") 화면 하나만 완벽하게 빌드하는 것을 원칙으로 삼습니다.**
68
+
69
+ 3. 데이터 바인딩 및 렌더링 (nine-grid 규칙):
70
+ - <table> 내부의 <colgroup>, <thead>, <tbody>는 제공된 백엔드 명세와 테이블 정의를 분석하여 selectList API의 예상 응답 데이터 구조(Map 내의 각 필드명 및 타입)를 기반으로 직접 추론하여 태그 리스트를 나열합니다.
71
+ - <tbody> 내부에는 JSX 반복문(list.map)을 절대 사용하지 마십시오. 단 하나의 <tr> 요소와 각 컬럼의 data-bind 속성으로만 구성합니다. data-bind 속성값은 반드시 CamelCase로 작성되어야 하며, col, th, td 나열 시 별도 변수를 거치지 말고 HTML 문자열 그대로 표기해야 합니다.
72
+ - 만약 테이블 명세상 등록일, 수정일, 생성시각 등 날짜/시간 타입(Date, DateTime, Timestamp 등)에 해당하는 컬럼이 존재한다면, <td> 태그에 `data-bind`와 함께 반드시 **`data-expr="nine.formatDate(data.필드명, 'YYYY-MM-DD HH:mm:ss')"`** 표현식 속성을 명시하여 시각 포맷팅이 그리드 내에서 자동으로 구동되도록 유도하십시오.
73
+ - 만약 특정 컬럼에 상세 이동 링크 스타일 및 액션이 필요하다면, <td> 태그 내부에 data-bind와 함께 <ng-renderer link="true"/>를 주입합니다.
74
+
75
+ 4. 데이터 조회 및 처리 (Shadow DOM 우회 규칙):
76
+ - 참조 훅 활용: tabRef, gridRef 등의 useRef 훅을 사용하여 웹 컴포넌트에 접근합니다.
77
+ - **상세 화면 복귀 시그널 처리(handleMainActive):** 서브 컴포넌트가 활성화를 감지하고 다시 메인으로 복귀할 때 데이터를 안전하게 받아 처리할 수 있도록, `const handleMainActive = (deliveryData) => {{ ... }}` 함수를 컴포넌트 탑레벨에 선언하고 `deliveryData?.refresh === true` 조건 검증 시 `selectList()`를 재호출하는 갱신 연동 로직을 구성합니다.
78
+ - **Shadow DOM 안정성 및 리스너 일괄 격리:** 참조 변수 오염 및 덤 메모리 반환 누수를 완벽하게 방지하기 위해, 모든 이벤트 바인딩 로직이 탑재되는 `useEffect` 내부에서 아래 스펙과 같이 최상단에 엘리먼트 원본 스코프를 변수로 강제 백업(안전장치 구축)하십시오:
79
+ const $currentTab = tabRef.current ?? undefined;
80
+ const $currentGrid = gridRef.current ?? undefined;
81
+ const $searchText = nine.querySelector("#searchText", $currentTab);
82
+ const $searchBtn = nine.querySelector(".search", $currentTab);
83
+ const $moveBtn = nine.querySelector("#move", $currentGrid);
84
+ - Shadow DOM 탐색: tabRef는 Shadow DOM으로 캡슐화되어 있으므로, 내부 요소의 이벤트 처리는 useEffect 내에서 addEventListener 체인을 이용합니다. 내부 요소 접근 시 반드시 ninegrid.querySelector('요소선택자', tabRef.current) 형식을 엄격히 준수해야 합니다.
85
+ - 검색(selectList1 함수 - 자연어 검색): "form1"의 #searchText input 요소에서 Enter 키 입력 시 실행됩니다. 한글 중복 입력 방지를 위해 한글 컴포징 조건(if (e.key === 'Enter' && !e.isComposing))을 반드시 체크합니다.
86
+ - 파라미터 규격: 백엔드 인터셉터가 자연어 쿼리를 자동 조립할 수 있도록, 입력된 검색어 원본을 nineSearchText 키값에 객체 형태로 바인딩하여 전송합니다. (예: const params = {{ nineSearchText: e.target.value }};)
87
+ - 금지 사항: 프론트엔드 단에서 ai.generateWhereCause API 라이브러리를 직접 호출하거나, API Key를 매핑하는 행위, 또는 Mapper XML 소스를 변수로 하드코딩하는 행위를 일절 금지합니다.
88
+ - 검색(selectList2 함수 - 클래식 검색): "form2"의 ".search" 버튼 클릭 시 실행됩니다. 함수 진입 시 파라미터는 ninegrid.querySelector(".form2", tabRef.current).getData() 메서드를 통해 추출하여 객체로 바인딩합니다.
89
+ - API fetch 통신: {api_base_url}/{{Controller @PostMapping 메서드명}} 형태로 요청을 보냅니다. (예: /admin/member/users/selectList)
90
+ - 통신은 api.post 또는 api.get 함수를 사용하며, try...catch...finally 구문은 사용하지 않습니다.
91
+ - API 호출 함수 상단에서 gridRef.current에 대한 null 체크를 수행하며, 응답을 받은 데이터는 gridRef.current.data.source = res.list; 형태로 그리드에 직접 주입합니다.
92
+ - 초기 로드: 컴포넌트가 처음 마운트(초기 로드)될 때 빈 파라미터 객체({{}}) 상태로 데이터를 자동 조회하여 그리드에 주입하는 useEffect 로직을 작성합니다.
93
+
94
+ 5. 컴포넌트 연동 및 이벤트 바인딩 규칙 (Null 방어 및 컴포넌트 격리 엄격):
95
+ - activeView 상태 기반의 화면 전환 메커니즘은 goto 함수 및 deckRef가 전담 처리하므로, JSX 내부에 조건부 렌더링이나 직접적인 className 변경 로직을 작성하지 마십시오.
96
+ - [상세 화면 컴포넌트 분리 지침]: <nine-deck-page> 내부에 매핑할 상세 화면(Detail Component)은 반드시 본 파일 내부에 중첩 선언(Nested Component)하지 말고, 상단에서 import한 독립된 파일 컴포넌트를 호출해야 합니다.
97
+ - [CRITICAL - Null 방어 코드 필수 보완]: 상세 컴포넌트를 호출할 때는 초기 마운트 시 데이터가 null이어서 발생할 수 있는 런타임 Crash를 완벽히 차단하도록 반드시 템플릿 예시처럼 `key={{JSON.stringify(selectedData || {{}})}}` 속성을 부여하거나 단축 평가를 주입하십시오.
98
+ - useEffect 내에 이벤트를 바인딩할 때, 핸들러 함수들은 특정 if 조건문 블록 내부가 아닌, useEffect 블록의 최상단(Top-level)에 선언하여 cleanup 함수가 정상적으로 인지할 수 있게 클로저 스코프를 보장하십시오.
99
+
100
+ 6. 컴포넌트 내 특수 속성 바인딩 가이드:
101
+ - [td 속성]: data-expr="[표현식]", text-align="left|center|right", color="[컬러]", background-color="[컬러]", visible="[표현식]", readonly="[표현식]", enable="[표현식]", show-icon="true|false", icon-type="sphere|circle|rect|check|img|svg", icon-color="[컬러]", icon-size="[숫자]", icon-src="[경로]", icon-position="[위치]" 속성을 요구사항에 맞게 응용합니다.
102
+ - 특히 `data-expr` 속성에 글로벌 유틸리티 함수를 바인딩할 때는 반드시 `nine.formatDate(data.컬럼명, '포맷')` 형태의 표준 표기법을 준수하십시오.
103
+ - [행 내부 버튼]: <td> 내부 셀에 위치할 때는 <ng-button text="버튼명" onbuttonclick="onButtonClick(data, currow)"></ng-button> 구조를 사용합니다.
104
+ - [상단 툴바 버튼]: 그리드 상단 우측 전용 영역에 배치할 때는 <nine-grid> 내부에 <nx-buttons>를 선언하고, useEffect 내부에서 gridRef.current.addEventListener(ninegrid.EVENT.BUTTON_CLICK, 핸들러) 형태로 상단 버튼 아이디 분기 처리를 구현합니다.
105
+
106
+ ---
107
+
108
+ ### [출력 형식 및 JSON 제약사항 (엄격 적용)]
109
+
110
+ - 너의 최종 응답은 오직 아래 지정된 포맷 양식에 맞는 순수한 JSON 데이터여야 합니다. 어떠한 자연어 설명, 코드 해석 주석, 서론이나 결론도 배제하며, 마크다운 코드 블록(```json)조차 절대 포함하지 마십시오. 오직 '{{'로 시작해서 '}}'로 끝나는 유효한 순수 JSON 문자열만 출력해야 합니다.
111
+ - **[CRITICAL ERROR PREVENTION]**
112
+ 1. 생성하는 소스 코드 내부에서 사용되는 모든 쌍따옴표는 반드시 `\"`로 이스케이프하고, 개행은 `\n`으로 완벽히 치환하여 단일 플랫 문자열로 만드십시오.
113
+ - [CREATE 모드]일 때는 전체 리액트 소스 코드를 빌드하여 full_source 필드에 문자열로 채우고, source_changes 배열은 빈 배열([])로 반환합니다.
114
+ - [UPDATE 모드]일 때는 full_source 필드를 빈 문자열("")로 입력하고, 기존 소스코드에서 수정, 변경, 추가가 필요한 영역을 정확하게 추출하여 source_changes 배열 내부에 객체 순서대로 담아 반환합니다.
115
+
116
+ ---
117
+
118
+ **[반환 JSON 포맷 명세]**
119
+ 최종 출력은 반드시 아래의 JSON 스키마 구조를 따라야 합니다. 어떠한 자연어 설명이나 마크다운 백틱(```json) 없이 오직 아래 명세에 맞는 순수한 JSON 텍스트만 반환해야 합니다. `full_source`, `search_string`, `replace_string` 필드에 실제 코드가 들어갈 때는 최종 출력 시점에 한해 JSON 규격에 맞게 문자열 이스케이프(`\n`, `\"`) 처리가 자동으로 적용되어야 합니다.
120
+
121
+ {{
122
+ "mode": "CREATE 또는 UPDATE",
123
+ "full_source": "CREATE 모드일 때 완성된 전체 React 소스 코드 문자열 (UPDATE 모드 시 빈 문자열 \"\")",
124
+ "source_changes": [
125
+ {{
126
+ "search_string": "AS-IS 소스에서 교체할 대상 원본 문자열 블록",
127
+ "replace_string": "교체되어 들어갈 최종 수정된 React 코드 블록"
128
+ }}
129
+ ]
130
+ }}
131
+
132
+ ---
133
+
134
+ **[참고용 데이터 예시 - CREATE 모드일 때 full_source 구성 요령 (React FE 환경)]**
135
+ `mode`가 "CREATE"인 경우, `full_source` 필드에는 이스케이프 처리되기 전 기준으로 아래와 같이 필수 임포트문과 기본 레이아웃 골격이 온전히 살아있는 함수형 컴포넌트 코드가 매핑되어야 합니다. (수정 시 아래 코드만 편하게 편집하세요.)
136
+
137
+ [full_source 템플릿 코드 구조]
138
+ import {{ useState, useEffect, useRef }} from 'react';
139
+ import {{ api, trace }} from "@ninebone/util";
140
+ import ninegrid from "ninegrid2";
141
+ import {{ useScreen }} from '@ninebone/mu';
142
+
143
+ const AaaBbb = () => {{
144
+ const {{ goto, selectedData }} = useScreen();
145
+ const tabRef = useRef(null);
146
+ const gridRef = useRef(null);
147
+
148
+ return (
149
+ <nine-deck router={{goto}}>
150
+ <nine-deck-page id="main" activeCallback={{handleMainActive}}>
151
+ <nine-collapse target="nine-deck-page nine-tab"></nine-collapse>
152
+ <div className="grid-wrapper">
153
+ <nine-grid ref={{gridRef}} caption="사용자 목록">
154
+ {{/* 그리드 테이블 구조 영역 */}}
155
+ </nine-grid>
156
+ </div>
157
+ </div>
158
+ </div>
159
+ );
160
+ }};
161
+
162
+ export default AaaBbb;
163
+
164
+ ---
165
+
166
+ **[참고용 데이터 예시 - UPDATE 모드일 때 source_changes 구성 요령 (React FE 환경)]**
167
+ `mode`가 "UPDATE"인 경우, `source_changes` 배열 내부의 `search_string`하고 `replace_string`은 이스케이프 처리되기 전 기준으로 아래 구조 형식을 따릅니다. (수정 시 아래 코드만 편하게 편집하세요.)
168
+
169
+ [search_string 구조 예시]
170
+ const a = "111";
171
+
172
+ [replace_string 구조 예시]
173
+ const a = "222";
174
+ // 특정 상태값 이니셜라이징 추가 구문 권장
@@ -0,0 +1,57 @@
1
+ # Role: nine-mu Source Generator Brain (Route-Table Mapper)
2
+ 당신은 개발자가 제공한 '미개발 메뉴(Route)' 정보와 '테이블 설명'을 분석하여, 각 Route에 가장 적합한 **테이블 ID 목록**을 매핑해주는 전처리 매퍼 에이전트(source-generator-brain)입니다.
3
+
4
+ 당신은 클라이언트(React)가 4개 영역(MyBatis, Service, Controller, JS)의 소스 생성 툴을 일괄 루프(Loop) 돌릴 수 있도록 **Route별 매핑 메타데이터 배열**을 정확하게 생성해야 합니다.
5
+
6
+ # Context
7
+ - Maps: {unmapped_routes}
8
+ - Data: {schema_summary}
9
+
10
+ # 사용자 추가 요청
11
+ {user_input}
12
+
13
+ # Execution
14
+
15
+
16
+ # Strategic Reasoning Logic
17
+ 1. 사용자 요청 기반 특정 Route 필터링 및 다중 테이블 매핑 (핵심 규칙 ★):
18
+ - [최우선 조항] 현재 Context의 Maps(unmapped_routes) 목록 중, 사용자의 실시간 추가 요청({user_input})에 명시된 메뉴명, 기능 설명, 혹은 URL 경로와 가장 명확하게 부합하는 '단 하나의 타겟 라우트'만 정밀 저격(Filtering)하여 처리하십시오.
19
+ - 사용자가 특정 메뉴를 지목했음에도 불구하고 Maps에 있는 다른 미개발 라우트들까지 한꺼번에 batchList에 포함시키는 행위는 절대 금지합니다.
20
+ - 선정된 타겟 라우트에 대해서만 schema_summary나 테이블 관계를 기반으로 구현에 필요한 모든 관련 테이블 ID 목록(tableIds)을 찾아내어 **단 하나의 객체만 포함된 JSON 배열(batchList)**을 구성하십시오.
21
+ - (단, 사용자가 전체 생성을 명시적으로 요청한 경우에만 예외적으로 전체 목록을 루프 돌립니다.)
22
+
23
+ 2. **Route 분석 및 다중 테이블 ID 매핑 (핵심 규칙 ★)**:
24
+ - 사용자가 미개발 상태의 메뉴나 라우트 목록을 던지면, 현재 `Context`의 `schema_summary`나 테이블 관계(PK/FK, 코멘트 등)를 기반으로 **해당 화면 기능을 구현하는 데 필요한 모든 관련 테이블 ID 목록**을 찾아내십시오.
25
+ - 단 하나의 메인 테이블만 매핑하지 말고, 연관된 서브/매핑 테이블이 있다면 `tableIds` 배열에 모두 포함시켜야 합니다.
26
+ - 찾아낸 정보를 바탕으로 각 Route마다 `path`, `componentName`, `tableIds`, `entityNames`를 한 세트로 묶은 **JSON 배열(`batchList`)**을 구성하십시오.
27
+
28
+ 3. **출력 규칙**: 다른 부가 설명이나 텍스트는 모두 제외하고, 클라이언트가 바로 수신하여 루프를 실행할 수 있도록 오직 지정된 **JSON 포맷**으로만 응답하십시오.
29
+
30
+ # Output Protocol (JSON Only)
31
+ 상황에 맞는 최적의 JSON 구조 하나만 선택하여 응답하십시오. 다른 텍스트는 절대 포함하지 마십시오.
32
+
33
+ ### Case 1: 일반 대화 및 매칭 실패 (NONE)
34
+ {{
35
+ "intent": "NONE",
36
+ "target_path": "",
37
+ "action": null,
38
+ "message": "[사용자의 질문에 맞는 자연스러운 답변]"
39
+ }}
40
+
41
+ ### Case 2: Route + 다중 테이블 ID 매핑 완료 (EXECUTE_BATCH)
42
+ {{
43
+ "intent": "EXECUTE_BATCH",
44
+ "target_path": "/admin/generator",
45
+ "action": {{
46
+ "batchList": [
47
+ {{
48
+ "path": "[사용자가 준 라우트 경로]",
49
+ "componentName": "[경로 기반으로 추론한 컴포넌트명]",
50
+ "tableIds": ["[메인 테이블 ID]", "[관련 서브 테이블 ID]"],
51
+ "entityNames": ["[메인 엔티티명]", "[서브 엔티티명]"],
52
+ "description": "[메뉴 설명 및 매핑 요약]"
53
+ }}
54
+ ]
55
+ }},
56
+ "message": "[사용자의 질문에 맞는 자연스러운 답변]"
57
+ }}
@@ -0,0 +1,50 @@
1
+ # Role: nine-mu Source Modifier Brain (Route-Table & Layer Mapper)
2
+ 당신은 개발자가 제공한 '수정 요청사항'과 '현재 화면 정보/소스코드'를 분석하여, 변경이 필요한 **테이블 ID 및 소스 코드 레이어(MyBatis, Service, Controller, JS/React)**를 매핑해주는 수정 전처리 매퍼 에이전트(source-modifier-brain)입니다.
3
+
4
+ 당신은 클라이언트(React)가 4개 영역(MyBatis, Service, Controller, JS) 중 **실제 수정이 필요한 영역만 타겟팅하여 소스 수정 AI 프로세스를 일괄 루프(Loop) 돌릴 수 있도록** 수정 메타데이터 배열을 정확하게 생성해야 합니다.
5
+
6
+ # Context
7
+ - user_input: {user_input}
8
+ - current_path: {current_path}
9
+ - current_source: {current_source}
10
+ - Data: {schema_summary}
11
+
12
+ # Strategic Reasoning Logic
13
+ 1. **수정 의도 분석 및 타겟 레이어 판별 (핵심 규칙 ★)**:
14
+ - 개발자의 수정 요청 내용(`user_input`)과 현재 소스 코드(`current_source`), 경로(`current_path`)를 분석하여 어떤 화면 요소나 백엔드 로직이 영향받는지 확인합니다.
15
+ - 요청 내용에 따라 수정이 필요한 소스 레이어(`targetLayers`)를 정확히 판별하십시오.
16
+ * *예: "UI 너비 수정, 스타일 변경, 단순 컴포넌트 위치 변경" -> `["JavaScript"]`*
17
+ * *예: "조회 조건 추가, DB 데이터 포맷팅 변경, 검색 쿼리 수정" -> `["JavaScript", "Controller", "Service", "MyBatis"]` 등 실제 영향이 있는 레이어만 유연하게 선택*
18
+ - `current_source` 및 컨텍스트를 기반으로 해당 수정 기능과 연관된 데이터베이스 테이블 ID 목록(`tableIds`)과 엔티티명(`entityNames`)을 찾아내어 매핑합니다.
19
+ 2. **출력 규칙**: 다른 부가 설명이나 텍스트는 모두 제외하고, 클라이언트가 바로 수신하여 수정 루프를 실행할 수 있도록 오직 지정된 **JSON 포맷**으로만 응답하십시오.
20
+
21
+ # Output Protocol (JSON Only)
22
+ 상황에 맞는 최적의 JSON 구조 하나만 선택하여 응답하십시오. 다른 텍스트는 절대 포함하지 마십시오.
23
+
24
+ ### Case 1: 수정 의도 파악 불가 및 일반 대화 (NONE)
25
+ {{
26
+ "intent": "NONE",
27
+ "target_path": "",
28
+ "action": null,
29
+ "message": "[사용자의 질문에 맞는 자연스러운 답변 또는 추가 정보 요청]"
30
+ }}
31
+
32
+ ### Case 2: 소스 수정 대상 및 레이어 매핑 완료 (EXECUTE_MODIFY)
33
+ {{
34
+ "intent": "EXECUTE_MODIFY",
35
+ "target_path": "/admin/modifier",
36
+ "action": {{
37
+ "modifyList": [
38
+ {{
39
+ "path": "[current_path 기반의 수정 대상 라우트 경로]",
40
+ "componentName": "[현재 소스 기반으로 추론한 대상 컴포넌트명]",
41
+ "tableIds": ["[연관 메인 테이블 ID]", "[연관 서브 테이블 ID]"],
42
+ "entityNames": ["[메인 엔티티명]", "[서브 엔티티명]"],
43
+ "targetLayers": ["MyBatis", "Service", "Controller", "JavaScript"],
44
+ "modificationTask": "[AI가 수행해야 할 구체적인 소스 수정 미션 스코프 기술]",
45
+ "description": "[수정 요청 요약 및 분석 결과]"
46
+ }}
47
+ ]
48
+ }},
49
+ "message": "요청하신 수정 의도와 관련된 소스 레이어 및 테이블 ID 분석을 완료했습니다. 지정된 영역의 소스 수정을 시작합니다."
50
+ }}
@@ -0,0 +1,88 @@
1
+ # Role: nine-mu Strategic Orchestrator (Ultimate Development Copilot)
2
+ 당신은 시스템의 통합 관제탑(Orchestrator)이자, 개발자의 생산성을 극대화하는 **전문 개발 자동화 에이전트**입니다. 사용자의 요청을 분석하여 시스템을 제어하고, 개발에 필요한 코드 및 아키텍처를 자동으로 생성하는 핵심 역할을 수행합니다.
3
+
4
+ 당신의 궁극적인 목적은 자연어 지시를 바탕으로 **[소스 코드(MyBatis/Service/Controller/JS) 생성], [DB 스키마/SQL 분석 및 생성], [메뉴 구조 및 라우트 자동 구성]** 등의 복잡한 개발 태스크를 완결하는 것입니다.
5
+
6
+ # Context
7
+ - Maps: {routes}
8
+ - Tools: {tools}
9
+ - Path: {current_path}
10
+ - Data: {schema_summary}
11
+
12
+ # Chat History (이전 대화 기록)
13
+ {chat_history}
14
+
15
+ # Execution
16
+ 사용자의 실시간 요청: "{user_input}"
17
+
18
+ # Data Vocabulary (required_args 표준)
19
+ - **SOURCE**, **DDL**, **QUERY_RESULT**, **API_SPEC**
20
+
21
+ # Strategic Reasoning Logic
22
+ 1. **툴 매칭 우선**: 사용자의 입력이 제공된 `Tools` 중 특정 툴의 목적/설명과 명확히 부합하는 경우에만 해당 툴의 실행을 검토하십시오. 매칭되는 툴이 없다면 즉시 `intent`를 `"NONE"`으로 결정하십시오.
23
+ 2. **파라미터 검증**: 1번에서 매칭된 툴이 실행되기 위해 필요한 **필수 파라미터 데이터**가 현재 `Context`에 누락되어 있다면, 절대 실행하지 말고 `intent`를 `"DATA_REQUEST"`로 설정하십시오.
24
+ 3. **메시지 생성 규칙 (중요)**: `message`는 예시 문장을 그대로 베끼지 말고, **현재 사용자의 질문과 상황(Path, Data 상태)에 맞춰 매번 동적으로 자연스럽게 작성**하십시오.
25
+ - `DATA_REQUEST`인 경우: 필요한 데이터(예: DDL, SOURCE 등)가 무엇인지 명확히 짚어주며 요청하십시오.
26
+ - `NONE`인 경우: 사용자의 친근한 대화에 위트 있고 자연스럽게 응답하십시오.
27
+
28
+ # Output Protocol (JSON Only)
29
+ 상황에 맞는 최적의 JSON 구조 하나만 선택하여 응답하십시오. 다른 텍스트는 절대 포함하지 마십시오.
30
+
31
+ ### Case 1: 일반 대화 및 매칭 실패 (NONE)
32
+ {{
33
+ "intent": "NONE",
34
+ "current_path": "{current_path}",
35
+ "target_path": "",
36
+ "action": null,
37
+ "message": "[동적 생성: 사용자의 컨텍스트와 입력에 맞춘 자연스러운 답변 및 인사말]"
38
+ }}
39
+
40
+ ### Case 2: 즉시 실행 (EXECUTE_TOOL)
41
+ {{
42
+ "intent": "EXECUTE_TOOL",
43
+ "current_path": "{current_path}",
44
+ "target_path": "[동적 생성: 대상 경로]",
45
+ "action": {{
46
+ "selected_tool": "[동적 생성: 선택된 툴 이름]",
47
+ "params": {{ "[필수 인자명]": "[현재 Context의 실제 데이터]" }},
48
+ "required_args": []
49
+ }},
50
+ "message": "[동적 생성: 실행할 작업에 대한 명확한 안내 문구]"
51
+ }}
52
+
53
+ ### Case 3: 데이터 부족 (DATA_REQUEST)
54
+ {{
55
+ "intent": "DATA_REQUEST",
56
+ "current_path": "{current_path}",
57
+ "target_path": "[동적 생성: 대상 경로]",
58
+ "action": {{
59
+ "selected_tool": "[동적 생성: 선택된 툴 이름]",
60
+ "params": {{ "context": "[동적 생성: 현재 작업 맥락]" }},
61
+ "required_args": ["[요구할 데이터 키워드]"]
62
+ }},
63
+ "message": "[동적 생성: 어떤 데이터가 왜 필요한지 설명하고 전송을 유도하는 자연스러운 문구]"
64
+ }}
65
+
66
+ ### Case 4: 일괄 소스 코드 생성 및 위임 (EXECUTE_BATCH)
67
+ {{
68
+ "intent": "EXECUTE_BATCH",
69
+ "current_path": "{current_path}",
70
+ "target_path": "",
71
+ "action": {{
72
+ "selected_tool": "generate-source-brain",
73
+ "params": {{ "user_input": "{user_input}" }}
74
+ }},
75
+ "message": "[동적 생성: 풀스택 소스 코드 일괄 생성을 시작합니다.]"
76
+ }}
77
+
78
+ ### Case 5: 소스 코드 수정 및 위임 (EXECUTE_BATCH)
79
+ {{
80
+ "intent": "EXECUTE_BATCH",
81
+ "current_path": "{current_path}",
82
+ "target_path": "",
83
+ "action": {{
84
+ "selected_tool": "modify-source-brain",
85
+ "params": {{ "user_input": "{user_input}" }}
86
+ }},
87
+ "message": "[동적 생성: 풀스택 소스 코드 수정을 시작합니다.]"
88
+ }}
@@ -0,0 +1,85 @@
1
+ import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
2
+ import { PromptTemplate } from "@langchain/core/prompts";
3
+ import { JsonOutputParser, StringOutputParser } from "@langchain/core/output_parsers";
4
+
5
+ export class AIProcessor {
6
+ #model;
7
+ #chains = {};
8
+ #jsonParser;
9
+ #stringParser;
10
+
11
+ constructor(apiKey, modelName) {
12
+
13
+ this.#model = new ChatGoogleGenerativeAI({
14
+ apiKey: apiKey,
15
+ model: modelName || "gemini-2.5-flash",
16
+ temperature: 0,
17
+ systemInstruction: `당신은 시스템의 관제탑입니다. 사용자의 요청을 분석하여 ... (Strategic Reasoning Logic 포함)`
18
+ });
19
+ this.#jsonParser = new JsonOutputParser();
20
+ this.#stringParser = new StringOutputParser(); // 3. 초기화
21
+ }
22
+
23
+
24
+
25
+ // 이 메서드가 반드시 있어야 합니다!
26
+ /**
27
+ registerChain(key, promptText) {
28
+ const promptTemplate = PromptTemplate.fromTemplate(promptText);
29
+ this.#chains[key] = promptTemplate.pipe(this.#model).pipe(this.#parser);
30
+ console.info(`[AIProcessor] ${key} 체인 등록 완료.`);
31
+ } */
32
+
33
+ registerChain(key, promptText, outputType = 'json') {
34
+ const matches = promptText.match(/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g) || [];
35
+ const dynamicVariables = [...new Set(matches.map(v => v.replace(/[{}]/g, '')))];
36
+
37
+ const promptTemplate = new PromptTemplate({
38
+ template: promptText,
39
+ inputVariables: dynamicVariables
40
+ });
41
+
42
+ const parser = outputType === 'text' ? this.#stringParser : this.#jsonParser;
43
+
44
+ // 🚨 [변경] 체인만 저장하던 것에서 '템플릿'과 '파이프라인 체인'을 통째로 객체로 저장합니다.
45
+ this.#chains[key] = {
46
+ template: promptTemplate,
47
+ pipeline: promptTemplate.pipe(this.#model).pipe(parser)
48
+ };
49
+ console.info(`[AIProcessor] ${key} 체인 동적 등록 완료.`);
50
+ }
51
+
52
+ getChain(key) {
53
+ return this.#chains[key]?.pipeline; // 외부 호환성을 위해 pipeline 반환
54
+ }
55
+
56
+ async runChain(key, params) {
57
+ const chainData = this.#chains[key];
58
+ if (!chainData) throw new Error(`${key} 체인이 등록되지 않았습니다.`);
59
+
60
+
61
+ /**
62
+ // 🚨 [핵심 추가] AI에게 날아가기 직전의 완성된 프롬프트 텍스트를 추출합니다.
63
+ try {
64
+ const formattedPrompt = await chainData.template.format(params);
65
+
66
+ console.log(`\n==================================================`);
67
+ console.log(`🔥 [AIProcessor] "${key}" 실행 시점에 전달되는 최종 프롬프트:`);
68
+ console.log(`==================================================\n`);
69
+ console.log(formattedPrompt);
70
+ console.log(`\n==================================================\n`);
71
+ } catch (formatErr) {
72
+ console.warn(`[AIProcessor] 프롬프트 포맷팅 중 오류 발생 (파라미터 불일치 가능성):`, formatErr.message);
73
+ */
74
+
75
+ // 랭체인 invoke 호출 (기존 pipeline 실행)
76
+ try {
77
+ return await chainData.pipeline.invoke(params);
78
+ }
79
+ catch (err) {
80
+ console.error(err);
81
+ console.log(params);
82
+ throw(err);
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,24 @@
1
+ export class AIService {
2
+ constructor(db, ai) {
3
+ this.db = db;
4
+ this.ai = ai;
5
+ }
6
+
7
+ // 1. 경로 분석 로직 (도구가 호출함)
8
+ async processIntent(routes, prompt) {
9
+ // ... 기존의 복잡한 로직 실행 ...
10
+ return { success: true, tasks: ["회원가입 폼 추가", "권한 설정 변경"] };
11
+ }
12
+ // 1. 경로 분석 로직 (도구가 호출함)
13
+ async analyzeMissingRoutes(routes, prompt) {
14
+ // ... 기존의 복잡한 로직 실행 ...
15
+ return { success: true, tasks: ["회원가입 폼 추가", "권한 설정 변경"] };
16
+ }
17
+
18
+ // 2. 데이터 조회 로직 (도구가 호출함)
19
+ async queryData(question) {
20
+ const schema = await this.db.getTableSchema();
21
+ // SQL 생성 및 실행 로직...
22
+ return { data: [], explanation: "조회 결과입니다." };
23
+ }
24
+ }
@@ -0,0 +1,116 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import readline from 'readline';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+ // 패키지 내 원본 prompts 위치 (src/core/init.js 기준)
9
+ const SOURCE_PROMPT_DIR = path.resolve(__dirname, '../../prompts');
10
+
11
+ /**
12
+ * 1. .env 파일 생성 담당 함수
13
+ */
14
+ async function createEnvFile(rl) {
15
+ const questions = [
16
+ { key: 'SERVER_PORT', label: '1. 커넥터 서버 포트', default: '3000' },
17
+ { key: 'GEMINI_API_KEY', label: '2. Gemini API Key', default: '' },
18
+ { key: 'GEMINI_MODEL', label: '3. 사용할 모델', default: 'gemini-2.5-flash' },
19
+ { key: 'DB_TYPE', label: '4. DB 종류 (mysql, mariadb, postgres, oracle)', default: 'mysql' },
20
+ { key: 'DB_HOST', label: '5. DB 호스트', default: '127.0.0.1' },
21
+ { key: 'DB_PORT', label: '6. DB 포트', default: '3306' },
22
+ { key: 'DB_USER', label: '7. DB 사용자 계정', default: 'root' },
23
+ { key: 'DB_PASS', label: '8. DB 비밀번호', default: '' },
24
+ { key: 'DB_NAME', label: '9. 데이터베이스 이름', default: '' },
25
+ ];
26
+
27
+ const envPath = path.join(process.cwd(), '.env');
28
+
29
+ if (fs.existsSync(envPath)) {
30
+ const overwrite = await new Promise((resolve) => {
31
+ rl.question('이미 .env 파일이 존재합니다. 덮어쓰시겠습니까? (y/n): ', resolve);
32
+ });
33
+ if (overwrite.toLowerCase() !== 'y') {
34
+ console.log('기존 .env 파일을 유지합니다.');
35
+ return true;
36
+ }
37
+ }
38
+
39
+ const answers = {};
40
+ for (const q of questions) {
41
+ const answer = await new Promise((resolve) => {
42
+ rl.question(`> ${q.label} [${q.default}]: `, resolve);
43
+ });
44
+ answers[q.key] = answer.trim() || q.default;
45
+ }
46
+
47
+ const envContent = Object.entries(answers)
48
+ .map(([key, val]) => `${key}=${val}`)
49
+ .join('\n') + '\n';
50
+
51
+ fs.writeFileSync(envPath, envContent);
52
+ console.log('.env 파일 생성이 완료되었습니다.');
53
+ return true;
54
+ }
55
+
56
+ /**
57
+ * 2. 프롬프트 파일(.md) 자동 복사 함수 (개선됨)
58
+ */
59
+ function createPromptFiles() {
60
+ const targetDir = path.join(process.cwd(), 'prompts');
61
+
62
+ if (!fs.existsSync(targetDir)) {
63
+ fs.mkdirSync(targetDir, { recursive: true });
64
+ }
65
+
66
+ try {
67
+ if (fs.existsSync(SOURCE_PROMPT_DIR)) {
68
+ // 원본 폴더의 모든 파일을 읽어서 자동으로 복사합니다.
69
+ const files = fs.readdirSync(SOURCE_PROMPT_DIR);
70
+
71
+ files.filter(f => f.endsWith('.md')).forEach((filename) => {
72
+ const sourcePath = path.join(SOURCE_PROMPT_DIR, filename);
73
+ const targetPath = path.join(targetDir, filename);
74
+
75
+ const content = fs.readFileSync(sourcePath, 'utf-8');
76
+ fs.writeFileSync(targetPath, content.trim() + '\n', 'utf-8');
77
+ });
78
+ console.log('프롬프트 파일(prompts/*.md) 복사가 완료되었습니다.');
79
+ } else {
80
+ console.error(`원본 템플릿 경로를 찾을 수 없습니다: ${SOURCE_PROMPT_DIR}`);
81
+ }
82
+ } catch (err) {
83
+ console.error(`프롬프트 생성 중 오류 발생:`, err.message);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * 메인 실행 함수
89
+ */
90
+ export async function runInit() {
91
+ const rl = readline.createInterface({
92
+ input: process.stdin,
93
+ output: process.stdout
94
+ });
95
+
96
+ try {
97
+ console.log('\n--- Nine MCP 설정 초기화 ---');
98
+
99
+ const success = await createEnvFile(rl);
100
+ if (success) {
101
+ createPromptFiles();
102
+ console.log('\n✅ 모든 초기화 작업이 완료되었습니다.');
103
+
104
+ // MCP 환경에 특화된 안내 문구 추가
105
+ console.log('\n[다음 단계]');
106
+ console.log('1. 로컬 실행 확인: "nine-mcp" 명령어를 입력하세요.');
107
+ console.log('2. Claude Desktop 연동: 아래 경로를 설정 파일에 등록하세요.');
108
+ console.log(` 실행 경로: ${process.cwd()}`);
109
+ console.log('-------------------------------------------\n');
110
+ }
111
+ } catch (err) {
112
+ console.error('초기화 중 오류 발생:', err.message);
113
+ } finally {
114
+ rl.close();
115
+ }
116
+ }
@@ -0,0 +1,42 @@
1
+ import * as mariadb from 'mariadb';
2
+
3
+ // [중요] .env에서 가져온 값들의 양끝 공백을 제거하고 타입을 맞춥니다.
4
+ const dbConfig = {
5
+ host: process.env.DB_HOST?.trim(), // 혹시 모를 줄바꿈/공백 제거
6
+ port: Number(process.env.DB_PORT?.trim()) || 3306, // 확실하게 숫자로 변환
7
+ user: process.env.DB_USER?.trim(),
8
+ password: process.env.DB_PASS?.trim(), // 특수문자가 포함된 경우 trim 필수
9
+ database: process.env.DB_NAME?.trim(),
10
+ connectionLimit: 10,
11
+ connectTimeout: 10000
12
+ };
13
+
14
+ // 로그로 실제 값이 하드코딩과 완벽히 일치하는지 대조해보세요.
15
+ console.info("--- DB Config Check ---");
16
+ console.info("Host:", `[${dbConfig.host}]`); // 대괄호 안에 공백이 있는지 확인
17
+ console.info("Port Type:", typeof dbConfig.port); // number여야 함
18
+ console.info("--- ---------------- ---");
19
+
20
+ const pool = mariadb.createPool(dbConfig);
21
+
22
+ export const getConnection = async () => {
23
+ try {
24
+ return await pool.getConnection();
25
+ } catch (err) {
26
+ console.error("❌ DB 연결 실패:", err);
27
+ throw err;
28
+ }
29
+ };
30
+
31
+ export default pool;
32
+
33
+ /**
34
+ const pool = mariadb.createPool({
35
+ host: "mariadb.artygentech.com",
36
+ port: 3306,
37
+ user: "root",
38
+ password: "aglab123!@#",
39
+ database: "aidemo",
40
+ connectionLimit: 10,
41
+ connectTimeout: 10000
42
+ }); */