@chnxthr/knowledge-mcp 0.0.0-stage → 0.3.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 (187) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -2
  3. package/dist/bootstrap.d.ts +1 -0
  4. package/dist/bootstrap.js +18 -0
  5. package/dist/bootstrap.js.map +1 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +5 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/core/auth.d.ts +38 -0
  10. package/dist/core/auth.js +93 -0
  11. package/dist/core/auth.js.map +1 -0
  12. package/dist/core/config.d.ts +259 -0
  13. package/dist/core/config.js +172 -0
  14. package/dist/core/config.js.map +1 -0
  15. package/dist/core/context.d.ts +25 -0
  16. package/dist/core/context.js +37 -0
  17. package/dist/core/context.js.map +1 -0
  18. package/dist/core/env.d.ts +2 -0
  19. package/dist/core/env.js +5 -0
  20. package/dist/core/env.js.map +1 -0
  21. package/dist/core/log_mask.d.ts +10 -0
  22. package/dist/core/log_mask.js +88 -0
  23. package/dist/core/log_mask.js.map +1 -0
  24. package/dist/core/oauth.d.ts +46 -0
  25. package/dist/core/oauth.js +507 -0
  26. package/dist/core/oauth.js.map +1 -0
  27. package/dist/core/paths.d.ts +5 -0
  28. package/dist/core/paths.js +47 -0
  29. package/dist/core/paths.js.map +1 -0
  30. package/dist/core/scope.d.ts +28 -0
  31. package/dist/core/scope.js +54 -0
  32. package/dist/core/scope.js.map +1 -0
  33. package/dist/core/upstream.d.ts +30 -0
  34. package/dist/core/upstream.js +164 -0
  35. package/dist/core/upstream.js.map +1 -0
  36. package/dist/index.d.ts +23 -0
  37. package/dist/index.js +21 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/plugin/define.d.ts +144 -0
  40. package/dist/plugin/define.js +44 -0
  41. package/dist/plugin/define.js.map +1 -0
  42. package/dist/plugin/load.d.ts +2 -0
  43. package/dist/plugin/load.js +41 -0
  44. package/dist/plugin/load.js.map +1 -0
  45. package/dist/plugin/ping.d.ts +9 -0
  46. package/dist/plugin/ping.js +29 -0
  47. package/dist/plugin/ping.js.map +1 -0
  48. package/dist/plugin/registry.d.ts +62 -0
  49. package/dist/plugin/registry.js +248 -0
  50. package/dist/plugin/registry.js.map +1 -0
  51. package/dist/plugin/result.d.ts +20 -0
  52. package/dist/plugin/result.js +59 -0
  53. package/dist/plugin/result.js.map +1 -0
  54. package/dist/plugins/drive/chunker.d.ts +28 -0
  55. package/dist/plugins/drive/chunker.js +356 -0
  56. package/dist/plugins/drive/chunker.js.map +1 -0
  57. package/dist/plugins/drive/clean_text.d.ts +13 -0
  58. package/dist/plugins/drive/clean_text.js +31 -0
  59. package/dist/plugins/drive/clean_text.js.map +1 -0
  60. package/dist/plugins/drive/client.d.ts +25 -0
  61. package/dist/plugins/drive/client.js +62 -0
  62. package/dist/plugins/drive/client.js.map +1 -0
  63. package/dist/plugins/drive/embeddings.d.ts +15 -0
  64. package/dist/plugins/drive/embeddings.js +118 -0
  65. package/dist/plugins/drive/embeddings.js.map +1 -0
  66. package/dist/plugins/drive/exclude.d.ts +11 -0
  67. package/dist/plugins/drive/exclude.js +36 -0
  68. package/dist/plugins/drive/exclude.js.map +1 -0
  69. package/dist/plugins/drive/extract_text.d.ts +26 -0
  70. package/dist/plugins/drive/extract_text.js +108 -0
  71. package/dist/plugins/drive/extract_text.js.map +1 -0
  72. package/dist/plugins/drive/get_token.d.ts +5 -0
  73. package/dist/plugins/drive/get_token.js +113 -0
  74. package/dist/plugins/drive/get_token.js.map +1 -0
  75. package/dist/plugins/drive/index.d.ts +5 -0
  76. package/dist/plugins/drive/index.js +72 -0
  77. package/dist/plugins/drive/index.js.map +1 -0
  78. package/dist/plugins/drive/indexer.d.ts +69 -0
  79. package/dist/plugins/drive/indexer.js +204 -0
  80. package/dist/plugins/drive/indexer.js.map +1 -0
  81. package/dist/plugins/drive/mathpix.d.ts +4 -0
  82. package/dist/plugins/drive/mathpix.js +102 -0
  83. package/dist/plugins/drive/mathpix.js.map +1 -0
  84. package/dist/plugins/drive/path.d.ts +40 -0
  85. package/dist/plugins/drive/path.js +87 -0
  86. package/dist/plugins/drive/path.js.map +1 -0
  87. package/dist/plugins/drive/qdrant.d.ts +50 -0
  88. package/dist/plugins/drive/qdrant.js +61 -0
  89. package/dist/plugins/drive/qdrant.js.map +1 -0
  90. package/dist/plugins/drive/reindex.d.ts +2 -0
  91. package/dist/plugins/drive/reindex.js +263 -0
  92. package/dist/plugins/drive/reindex.js.map +1 -0
  93. package/dist/plugins/drive/settings.d.ts +11 -0
  94. package/dist/plugins/drive/settings.js +13 -0
  95. package/dist/plugins/drive/settings.js.map +1 -0
  96. package/dist/plugins/drive/sync.d.ts +16 -0
  97. package/dist/plugins/drive/sync.js +139 -0
  98. package/dist/plugins/drive/sync.js.map +1 -0
  99. package/dist/plugins/drive/sync_state.d.ts +3 -0
  100. package/dist/plugins/drive/sync_state.js +26 -0
  101. package/dist/plugins/drive/sync_state.js.map +1 -0
  102. package/dist/plugins/drive/tools/list_drive_folder.d.ts +104 -0
  103. package/dist/plugins/drive/tools/list_drive_folder.js +221 -0
  104. package/dist/plugins/drive/tools/list_drive_folder.js.map +1 -0
  105. package/dist/plugins/drive/tools/read_drive_file.d.ts +66 -0
  106. package/dist/plugins/drive/tools/read_drive_file.js +206 -0
  107. package/dist/plugins/drive/tools/read_drive_file.js.map +1 -0
  108. package/dist/plugins/drive/tools/search_drive.d.ts +52 -0
  109. package/dist/plugins/drive/tools/search_drive.js +78 -0
  110. package/dist/plugins/drive/tools/search_drive.js.map +1 -0
  111. package/dist/plugins/drive/tools/semantic_search_drive.d.ts +109 -0
  112. package/dist/plugins/drive/tools/semantic_search_drive.js +228 -0
  113. package/dist/plugins/drive/tools/semantic_search_drive.js.map +1 -0
  114. package/dist/plugins/github/index.d.ts +1 -0
  115. package/dist/plugins/github/index.js +27 -0
  116. package/dist/plugins/github/index.js.map +1 -0
  117. package/dist/plugins/github/repo_tree.d.ts +57 -0
  118. package/dist/plugins/github/repo_tree.js +123 -0
  119. package/dist/plugins/github/repo_tree.js.map +1 -0
  120. package/dist/plugins/index.d.ts +11 -0
  121. package/dist/plugins/index.js +7 -0
  122. package/dist/plugins/index.js.map +1 -0
  123. package/dist/plugins/railway/index.d.ts +1 -0
  124. package/dist/plugins/railway/index.js +21 -0
  125. package/dist/plugins/railway/index.js.map +1 -0
  126. package/dist/plugins/railway/tools.d.ts +114 -0
  127. package/dist/plugins/railway/tools.js +165 -0
  128. package/dist/plugins/railway/tools.js.map +1 -0
  129. package/dist/plugins/slack/api.d.ts +63 -0
  130. package/dist/plugins/slack/api.js +280 -0
  131. package/dist/plugins/slack/api.js.map +1 -0
  132. package/dist/plugins/slack/index.d.ts +1 -0
  133. package/dist/plugins/slack/index.js +80 -0
  134. package/dist/plugins/slack/index.js.map +1 -0
  135. package/dist/plugins/slack/tools/slack_files.d.ts +87 -0
  136. package/dist/plugins/slack/tools/slack_files.js +204 -0
  137. package/dist/plugins/slack/tools/slack_files.js.map +1 -0
  138. package/dist/plugins/slack/tools/slack_list_channels.d.ts +46 -0
  139. package/dist/plugins/slack/tools/slack_list_channels.js +197 -0
  140. package/dist/plugins/slack/tools/slack_list_channels.js.map +1 -0
  141. package/dist/plugins/slack/tools/slack_read_channel.d.ts +56 -0
  142. package/dist/plugins/slack/tools/slack_read_channel.js +150 -0
  143. package/dist/plugins/slack/tools/slack_read_channel.js.map +1 -0
  144. package/dist/plugins/slack/tools/slack_search.d.ts +53 -0
  145. package/dist/plugins/slack/tools/slack_search.js +132 -0
  146. package/dist/plugins/slack/tools/slack_search.js.map +1 -0
  147. package/dist/run.d.ts +8 -0
  148. package/dist/run.js +270 -0
  149. package/dist/run.js.map +1 -0
  150. package/dist/server/http.d.ts +5 -0
  151. package/dist/server/http.js +406 -0
  152. package/dist/server/http.js.map +1 -0
  153. package/dist/server/identity.d.ts +17 -0
  154. package/dist/server/identity.js +47 -0
  155. package/dist/server/identity.js.map +1 -0
  156. package/dist/server/mcp.d.ts +7 -0
  157. package/dist/server/mcp.js +55 -0
  158. package/dist/server/mcp.js.map +1 -0
  159. package/dist/server/stdio.d.ts +2 -0
  160. package/dist/server/stdio.js +40 -0
  161. package/dist/server/stdio.js.map +1 -0
  162. package/dist/store/index.d.ts +11 -0
  163. package/dist/store/index.js +31 -0
  164. package/dist/store/index.js.map +1 -0
  165. package/dist/store/keys.d.ts +9 -0
  166. package/dist/store/keys.js +32 -0
  167. package/dist/store/keys.js.map +1 -0
  168. package/dist/store/sqlite.d.ts +38 -0
  169. package/dist/store/sqlite.js +316 -0
  170. package/dist/store/sqlite.js.map +1 -0
  171. package/dist/store/supabase.d.ts +35 -0
  172. package/dist/store/supabase.js +269 -0
  173. package/dist/store/supabase.js.map +1 -0
  174. package/dist/store/supabase_client.d.ts +10 -0
  175. package/dist/store/supabase_client.js +31 -0
  176. package/dist/store/supabase_client.js.map +1 -0
  177. package/dist/store/tool_log.d.ts +12 -0
  178. package/dist/store/tool_log.js +8 -0
  179. package/dist/store/tool_log.js.map +1 -0
  180. package/dist/store/types.d.ts +96 -0
  181. package/dist/store/types.js +2 -0
  182. package/dist/store/types.js.map +1 -0
  183. package/dist/version.d.ts +1 -0
  184. package/dist/version.js +11 -0
  185. package/dist/version.js.map +1 -0
  186. package/package.json +84 -4
  187. package/sql/supabase.sql +185 -0
@@ -0,0 +1,204 @@
1
+ import { createHash } from 'crypto';
2
+ import { chunkMarkdown } from './chunker.js';
3
+ import { embedTexts } from './embeddings.js';
4
+ import { extractText } from './extract_text.js';
5
+ import { getQdrantClient, DRIVE_COLLECTION } from './qdrant.js';
6
+ import { DRIVE_ROOTS, MAX_FILE_BYTES } from './settings.js';
7
+ export { DRIVE_ROOTS, MAX_FILE_BYTES };
8
+ /** 추출 지원 mimeType (extract_text.ts 와 일치). 스캔 PDF/이미지는 Mathpix OCR. 풀 reindex·증분 sync 공용 SSOT. */
9
+ export const SUPPORTED_MIMES = new Set([
10
+ 'text/markdown',
11
+ 'text/plain',
12
+ 'application/vnd.google-apps.document',
13
+ 'application/vnd.google-apps.spreadsheet',
14
+ 'application/vnd.google-apps.presentation',
15
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // .docx
16
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation', // .pptx
17
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
18
+ 'application/pdf',
19
+ 'image/png',
20
+ 'image/jpeg',
21
+ 'image/jpg',
22
+ 'image/webp',
23
+ ]);
24
+ /** 점진적 chunk ID — file_id + index 면 항상 같은 ID (idempotent upsert). 색인 전 경로 공용. */
25
+ export function makeChunkId(fileId, chunkIndex) {
26
+ const hash = createHash('md5').update(`${fileId}:${chunkIndex}`).digest();
27
+ return Number(hash.readBigUInt64BE(0) & 0x7fffffffffffffffn);
28
+ }
29
+ /** path + source -> file_type / cluster. cluster = top-level folder (generic). */
30
+ export function classifyFile(path, source) {
31
+ const m = path.match(/^([^/]+)\//);
32
+ const topFolder = m ? m[1] : null;
33
+ return { type: source === 'biz' ? 'biz' : 'other', cluster: topFolder };
34
+ }
35
+ /**
36
+ * 청크의 임베딩 입력 텍스트 — heading breadcrumb(상위>하위 섹션 맥락)를 본문 앞에 붙인다.
37
+ * (contextual chunk header) 부모 heading 은 청크 본문에 없어(다른 섹션) 벡터가 섹션 맥락을 잃는데,
38
+ * 이를 임베딩에 포함하면 recall 이 오른다. 저장·표시용 content(payload)는 그대로 두고 임베딩 입력에만 적용.
39
+ * '문서 시작'(첫 heading 이전 placeholder)은 의미 없어 skip. ★증분(indexFile)·전체(reindex.ts) 공용 SSOT —
40
+ * 두 경로가 반드시 같은 입력을 임베딩해야 같은 청크가 같은 벡터를 가진다(벡터 공간 정합).
41
+ */
42
+ export function embedInputForChunk(chunk) {
43
+ const h = chunk.heading?.trim();
44
+ if (!h || h === '문서 시작')
45
+ return chunk.content;
46
+ return `${h}\n\n${chunk.content}`;
47
+ }
48
+ /** 추출 텍스트 → sha256 (무변경 재색인 skip 판정용). */
49
+ export function contentHash(text) {
50
+ return createHash('sha256').update(text).digest('hex');
51
+ }
52
+ // 폴더/파일명 앞 번호 prefix("01_", "06_", "0316_") 제거용.
53
+ const NN_PREFIX = /^\d{1,4}[_-]/;
54
+ /**
55
+ * biz 소스(PDF/docx — frontmatter 없음)의 경로/파일명에서 메타 파생.
56
+ * EXTRACTED — 사람이 폴더로 분류한 사실(LLM 추론 아님). dev 소스는 frontmatter 가 SSOT 라 빈 값.
57
+ * doc_category : 최상위 폴더(NN_ 제거) 예) "01_고객사/…" → "고객사"
58
+ * doc_customer : 01_고객사/{name} 예) "01_고객사/Acme/…" → "Acme"
59
+ * path_tags : 폴더 세그먼트(NN_ 제거) + 파일명 토큰 (검색 태그)
60
+ */
61
+ export function deriveBizMeta(path, source) {
62
+ if (source !== 'biz')
63
+ return { doc_category: null, doc_customer: null, path_tags: [] };
64
+ const segs = path.split('/').filter(Boolean);
65
+ const folders = segs.slice(0, -1); // 마지막 = 파일명
66
+ const fileName = segs[segs.length - 1] ?? '';
67
+ const strip = (s) => s.replace(NN_PREFIX, '');
68
+ const category = folders[0] ? strip(folders[0]) : null;
69
+ const customer = folders[0] && folders[0].includes('고객사') && folders[1] ? folders[1] : null;
70
+ const folderTags = folders.map(strip);
71
+ const nameTokens = fileName
72
+ .replace(/\.[a-z0-9]+$/i, '') // 확장자 제거
73
+ .split(/[_\-\s]+/)
74
+ .map((t) => t.trim())
75
+ .filter((t) => t.length >= 2);
76
+ const path_tags = [...new Set([...folderTags, ...nameTokens])].filter(Boolean);
77
+ return { doc_category: category, doc_customer: customer, path_tags };
78
+ }
79
+ /**
80
+ * chunk + 파일/분류/경로 메타 → ChunkPayload. reindex(reindex.ts)·증분(indexFile) 공유 SSOT.
81
+ * dev 메타는 chunk(frontmatter 파싱)에, biz 메타는 bizMeta(경로 파싱)에 담겨 들어온다.
82
+ */
83
+ export function buildChunkPayload(chunk, file, idx, chunkCount, classification, bizMeta, hash) {
84
+ const tags = [...new Set([...chunk.doc_tags, ...bizMeta.path_tags])];
85
+ return {
86
+ chunk_id: `${file.id}_${idx}`,
87
+ file_id: file.id,
88
+ file_name: file.name,
89
+ file_path: file.path,
90
+ heading: chunk.heading,
91
+ chunk_index: idx,
92
+ chunk_count: chunkCount,
93
+ content: chunk.content,
94
+ modified_time: file.modifiedTime,
95
+ content_hash: hash,
96
+ file_type: classification.type,
97
+ cluster: classification.cluster,
98
+ source: file.source,
99
+ image_urls: chunk.image_urls,
100
+ has_image: chunk.image_urls.length > 0,
101
+ table_count: chunk.table_count,
102
+ has_code_block: chunk.has_code_block,
103
+ doc_status: chunk.doc_status,
104
+ doc_tier: chunk.doc_tier,
105
+ doc_date: chunk.doc_date,
106
+ doc_authors: chunk.doc_authors,
107
+ doc_related: chunk.doc_related,
108
+ doc_source_refs: chunk.doc_source_refs,
109
+ doc_affects: chunk.doc_affects,
110
+ doc_products: chunk.doc_products,
111
+ doc_type: chunk.doc_type,
112
+ doc_confidence: chunk.doc_confidence,
113
+ doc_tags: tags,
114
+ doc_category: bizMeta.doc_category,
115
+ doc_customer: bizMeta.doc_customer,
116
+ };
117
+ }
118
+ /**
119
+ * 파일의 기존 색인 상태(청크0 payload) 조회 — content_hash·청크 수.
120
+ * 못 찾으면 null (미색인). 조회 실패 시 null 취급(안전 — 재색인 진행).
121
+ */
122
+ async function getIndexedState(fileId) {
123
+ try {
124
+ const got = await (await getQdrantClient()).retrieve(DRIVE_COLLECTION, {
125
+ ids: [makeChunkId(fileId, 0)],
126
+ with_payload: true,
127
+ with_vector: false,
128
+ });
129
+ if (!got || got.length === 0)
130
+ return null;
131
+ const p = got[0].payload;
132
+ return {
133
+ content_hash: p?.content_hash ?? null,
134
+ chunk_count: typeof p?.chunk_count === 'number' ? p.chunk_count : 0,
135
+ file_path: String(p?.file_path ?? ''),
136
+ file_name: String(p?.file_name ?? ''),
137
+ source: String(p?.source ?? ''),
138
+ };
139
+ }
140
+ catch {
141
+ return null; // 조회 실패 = 미확인 → 재색인 진행(안전)
142
+ }
143
+ }
144
+ /**
145
+ * 파일 1개 형식별 추출 → chunk → embed → Qdrant upsert. 색인된 chunk 수 반환 (skip 시 0).
146
+ *
147
+ * - EFF-1: 추출 텍스트 sha256 이 기존 색인과 동일하면 embed/upsert 생략(무변경 skip).
148
+ * - B-1: 재색인 시 청크 수가 줄면 옛 청크(chunk_index >= 새 수)를 삭제해 유령 청크(폐기된 사실) 잔류 방지.
149
+ * - B-9: 증분 경로는 wait:true 로 색인 직후 검색 가시성 + 실패 인지.
150
+ */
151
+ export async function indexFile(drive, file) {
152
+ if (file.size && file.size > MAX_FILE_BYTES) {
153
+ console.error(`[index] skip (too large: ${file.size}B > ${MAX_FILE_BYTES}B): ${file.path}`);
154
+ return 0;
155
+ }
156
+ const { text, needsOcr } = await extractText(drive, file.id, file.mimeType);
157
+ // needsOcr=true 는 이제 미지원 형식만 (PDF 스캔본·이미지는 extract_text 가 Mathpix 로 처리).
158
+ // 미지원 또는 추출 결과 빈 파일 → 색인 skip.
159
+ if (needsOcr || text.trim().length === 0)
160
+ return 0;
161
+ const hash = contentHash(text);
162
+ // EFF-1: 본문·경로·이름이 모두 그대로면 embed/upsert 생략 (sharing 등 메타 change 비용 차단).
163
+ // 옮기거나 이름만 바꾼 파일은 다시 색인한다 — payload 의 file_path 가 직원별 폴더 scope 판정 기준이라
164
+ // 옛 경로가 남으면 옮긴 뒤에도 옛 폴더 권한자에게 검색된다. 경로 태그(doc_tags)도 청크마다 다시 만든다.
165
+ const prev = await getIndexedState(file.id);
166
+ if (prev &&
167
+ prev.content_hash === hash &&
168
+ prev.file_path === file.path &&
169
+ prev.file_name === file.name &&
170
+ prev.source === file.source) {
171
+ return 0;
172
+ }
173
+ const chunks = chunkMarkdown(text);
174
+ if (chunks.length === 0)
175
+ return 0;
176
+ const classification = classifyFile(file.path, file.source);
177
+ const bizMeta = deriveBizMeta(file.path, file.source);
178
+ // heading breadcrumb 포함 임베딩(embedInputForChunk) — 저장 content 는 그대로, 임베딩 입력만 섹션 맥락 부여.
179
+ const vectors = await embedTexts(chunks.map(embedInputForChunk), 'RETRIEVAL_DOCUMENT');
180
+ const points = chunks.map((c, idx) => ({
181
+ id: makeChunkId(file.id, idx),
182
+ vector: vectors[idx],
183
+ payload: buildChunkPayload(c, file, idx, chunks.length, classification, bizMeta, hash),
184
+ }));
185
+ const client = await getQdrantClient();
186
+ // 새 청크 0..M-1 upsert (wait:true — 단일 파일이라 비용 작고, 직후 검색 가시성·실패 인지 확보)
187
+ await client.upsert(DRIVE_COLLECTION, { wait: true, points });
188
+ // B-1: 줄어든 경우 옛 청크(index >= M)를 결정적 chunk ID 로 직접 삭제.
189
+ // (Qdrant range 필터는 비인덱싱 payload 에서 400 Bad Request → ID 삭제로 우회. upsert 와 disjoint 집합)
190
+ if (prev && prev.chunk_count > chunks.length) {
191
+ const orphanIds = [];
192
+ for (let i = chunks.length; i < prev.chunk_count; i++)
193
+ orphanIds.push(makeChunkId(file.id, i));
194
+ await client.delete(DRIVE_COLLECTION, { wait: true, points: orphanIds });
195
+ }
196
+ return chunks.length;
197
+ }
198
+ /** 특정 파일의 모든 chunk 를 Qdrant 에서 삭제 (파일 삭제·trashed 시). */
199
+ export async function deleteFileVectors(fileId) {
200
+ await (await getQdrantClient()).delete(DRIVE_COLLECTION, {
201
+ filter: { must: [{ key: 'file_id', match: { value: fileId } }] },
202
+ });
203
+ }
204
+ //# sourceMappingURL=indexer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexer.js","sourceRoot":"","sources":["../../../src/plugins/drive/indexer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ /** PDF (스캔본) → markdown. async upload + poll + .md 다운로드. */
2
+ export declare function mathpixPdfToMarkdown(buffer: Buffer, maxWaitSec?: number): Promise<string>;
3
+ /** 단일 이미지 → markdown. v3/text (즉시). */
4
+ export declare function mathpixImageToMarkdown(buffer: Buffer, mimeType: string): Promise<string>;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Mathpix OCR — 스캔 PDF / 이미지 → markdown.
3
+ *
4
+ * 키: MATHPIX_APP_ID / MATHPIX_APP_KEY.
5
+ *
6
+ * - PDF (스캔본): v3/pdf (async) — upload → poll → /pdf/{id}.md
7
+ * - 이미지: v3/text (즉시)
8
+ *
9
+ * 수식·표 구조 보존 (Mathpix Markdown) → chunk 시 표 안 깨짐 → 검색 품질 ↑.
10
+ */
11
+ const MATHPIX_API = 'https://api.mathpix.com/v3';
12
+ function mathpixHeaders() {
13
+ const appId = process.env.MATHPIX_APP_ID;
14
+ const appKey = process.env.MATHPIX_APP_KEY;
15
+ if (!appId || !appKey)
16
+ throw new Error('[mathpix] MATHPIX_APP_ID / MATHPIX_APP_KEY 미설정');
17
+ return { app_id: appId, app_key: appKey };
18
+ }
19
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
20
+ /**
21
+ * timeout·재시도 감싼 fetch. 예전엔 어떤 fetch 도 timeout·재시도가 없어, status 폴링이 네트워크
22
+ * 정체 시 deadline 까지 매달리고 일시 5xx 에 전체 OCR 이 실패했다. AbortSignal.timeout + 5xx/네트워크
23
+ * 짧은 재시도로 안정화(OCR 은 유저 대기 경로).
24
+ */
25
+ async function mpFetch(url, init, timeoutMs = 30_000, retries = 2) {
26
+ for (let attempt = 0;; attempt++) {
27
+ try {
28
+ const resp = await fetch(url, { ...init, signal: AbortSignal.timeout(timeoutMs) });
29
+ if (resp.status >= 500 && resp.status <= 599 && attempt < retries) {
30
+ await sleep(1000 * (attempt + 1));
31
+ continue;
32
+ }
33
+ return resp;
34
+ }
35
+ catch (e) {
36
+ // timeout(TimeoutError)·네트워크 오류 — 재시도, 소진 시 throw
37
+ if (attempt < retries) {
38
+ await sleep(1000 * (attempt + 1));
39
+ continue;
40
+ }
41
+ throw new Error(`[mathpix] fetch 실패(${url.split('/').slice(-1)[0]}): ${e instanceof Error ? e.message : String(e)}`);
42
+ }
43
+ }
44
+ }
45
+ /** PDF (스캔본) → markdown. async upload + poll + .md 다운로드. */
46
+ export async function mathpixPdfToMarkdown(buffer, maxWaitSec = 180) {
47
+ const headers = mathpixHeaders();
48
+ // 1. Upload
49
+ const form = new FormData();
50
+ form.append('file', new Blob([buffer], { type: 'application/pdf' }), 'doc.pdf');
51
+ form.append('options_json', JSON.stringify({
52
+ conversion_formats: { md: true },
53
+ math_inline_delimiters: ['$', '$'],
54
+ rm_spaces: false,
55
+ enable_tables_fallback: true,
56
+ include_diagram_text: true, // 다이어그램 내부 텍스트도 OCR (figure URL 은 .md 에 기본 포함)
57
+ }));
58
+ const upResp = await mpFetch(`${MATHPIX_API}/pdf`, { method: 'POST', headers, body: form }, 60_000);
59
+ if (!upResp.ok)
60
+ throw new Error(`[mathpix] upload 실패 HTTP ${upResp.status}: ${(await upResp.text()).slice(0, 200)}`);
61
+ const { pdf_id } = (await upResp.json());
62
+ if (!pdf_id)
63
+ throw new Error('[mathpix] pdf_id 없음');
64
+ // 2. Poll (2s 간격)
65
+ const deadline = Date.now() + maxWaitSec * 1000;
66
+ while (Date.now() < deadline) {
67
+ await sleep(2000);
68
+ const stResp = await mpFetch(`${MATHPIX_API}/pdf/${pdf_id}`, { headers });
69
+ const stData = (await stResp.json());
70
+ if (stData.status === 'completed') {
71
+ // 3. .md 다운로드
72
+ const mdResp = await mpFetch(`${MATHPIX_API}/pdf/${pdf_id}.md`, { headers });
73
+ const md = await mdResp.text();
74
+ if (!md.trim())
75
+ throw new Error('[mathpix] completed but empty markdown');
76
+ return md;
77
+ }
78
+ if (stData.status === 'error')
79
+ throw new Error('[mathpix] 처리 error');
80
+ }
81
+ throw new Error(`[mathpix] timeout ${maxWaitSec}s (pdf_id=${pdf_id})`);
82
+ }
83
+ /** 단일 이미지 → markdown. v3/text (즉시). */
84
+ export async function mathpixImageToMarkdown(buffer, mimeType) {
85
+ const headers = { ...mathpixHeaders(), 'Content-Type': 'application/json' };
86
+ const b64 = buffer.toString('base64');
87
+ const resp = await mpFetch(`${MATHPIX_API}/text`, {
88
+ method: 'POST',
89
+ headers,
90
+ body: JSON.stringify({
91
+ src: `data:${mimeType};base64,${b64}`,
92
+ formats: ['text', 'data'],
93
+ math_inline_delimiters: ['$', '$'],
94
+ rm_spaces: false,
95
+ }),
96
+ });
97
+ if (!resp.ok)
98
+ throw new Error(`[mathpix] image 실패 HTTP ${resp.status}`);
99
+ const data = (await resp.json());
100
+ return data.text ?? '';
101
+ }
102
+ //# sourceMappingURL=mathpix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mathpix.js","sourceRoot":"","sources":["../../../src/plugins/drive/mathpix.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Drive 파일의 부모 체인 분류 — search_drive 가 민감/제외 폴더를 응답에서 거르는 데 사용.
3
+ *
4
+ * 왜: semantic 색인은 `06_증빙_PII`·`02_계약_법무`·`07_채용_HR`·deprecated 를 제외(exclude.ts)하지만,
5
+ * search_drive 는 Drive API 를 직접 때려 service account 가 공유받은 전체를 노출했다 → 계약·PII·HR 유출 위험.
6
+ * 이 모듈로 검색 결과를 "dev/biz root 하위 + 제외 폴더 미경유"로 한정한다.
7
+ *
8
+ * 성능: 폴더 메타(id→name,parent)는 거의 변하지 않으므로 프로세스 캐시 → 부모 walk 가 warmup 후 캐시 hit.
9
+ */
10
+ import { type drive_v3 } from 'googleapis';
11
+ export declare function getFolderMeta(drive: drive_v3.Drive, id: string): Promise<{
12
+ name: string;
13
+ parent: string | undefined;
14
+ }>;
15
+ export type SearchClass = 'ok' | 'excluded' | 'outside';
16
+ /**
17
+ * 파일의 immediate parents 에서 root 까지 올라가며 분류:
18
+ * - 'excluded' : 제외 폴더(PII/계약/HR/deprecated/node_modules 등)를 경유 → 응답에서 제거
19
+ * - 'ok' : dev/biz root 하위 + 제외 미경유 → 노출
20
+ * - 'outside' : root 에 닿지 못함(색인 대상 밖) → 보수적으로 제거
21
+ * resolvePathAndSource(drive_sync) 와 동일하게 parents[0] 만 따라간다(최대 12 hop).
22
+ */
23
+ export declare function classifyForSearch(drive: drive_v3.Drive, parents: string[] | undefined): Promise<SearchClass>;
24
+ /**
25
+ * classifyForSearch + 폴더 경로(root 하위) 반환 — 직원별 Drive 폴더 scope 필터용.
26
+ * path 는 폴더만(파일명 제외), root 바로 아래부터, 예: "01_고객사/Acme". cls!=='ok' 면 path 무의미.
27
+ */
28
+ export declare function classifyAndPath(drive: drive_v3.Drive, parents: string[] | undefined): Promise<{
29
+ cls: SearchClass;
30
+ path: string;
31
+ }>;
32
+ /**
33
+ * 폴더 자신을 분류 — list_drive_folder 의 시작 폴더 검사용.
34
+ * root 폴더면 'ok'(path ''), 그 밖에는 자기 이름이 제외 대상인지 본 뒤 부모 체인을 classifyAndPath 로 분류한다.
35
+ * path 는 root 바로 아래부터 이 폴더까지(자기 이름 포함). 예: root/01_고객사/Acme → "01_고객사/Acme".
36
+ */
37
+ export declare function classifyFolder(drive: drive_v3.Drive, folderId: string): Promise<{
38
+ cls: SearchClass;
39
+ path: string;
40
+ }>;
@@ -0,0 +1,87 @@
1
+ import { DRIVE_ROOTS } from './settings.js';
2
+ import { isExcludedName } from './exclude.js';
3
+ const ROOT_IDS = new Set(DRIVE_ROOTS.map((r) => r.id));
4
+ // 폴더 메타 캐시 (id → {name, parent}). TTL 부여 — 영구 캐시면 폴더를 민감 분류(`06_증빙_PII` 등)로
5
+ // rename 하거나 제외 폴더로 move 해도 옛 이름으로 분류돼 새로 민감해진 폴더가 재배포 전까지
6
+ // 노출(under-exclude)된다. 짧은 TTL(기본 10분)로 분류 변경을 반영. rename/move 는 드물어 캐시 효과는 유지.
7
+ const FOLDER_META_TTL_MS = Number(process.env.FOLDER_META_TTL_MS ?? 600_000);
8
+ const _folderMeta = new Map();
9
+ export async function getFolderMeta(drive, id) {
10
+ const now = Date.now();
11
+ const cached = _folderMeta.get(id);
12
+ if (cached && cached.expiresAt > now)
13
+ return { name: cached.name, parent: cached.parent };
14
+ try {
15
+ const d = (await drive.files.get({ fileId: id, fields: 'name,parents', supportsAllDrives: true }))
16
+ .data;
17
+ const meta = { name: d.name ?? '', parent: d.parents?.[0], expiresAt: now + FOLDER_META_TTL_MS };
18
+ _folderMeta.set(id, meta);
19
+ return { name: meta.name, parent: meta.parent };
20
+ }
21
+ catch {
22
+ return { name: '', parent: undefined };
23
+ }
24
+ }
25
+ /**
26
+ * 파일의 immediate parents 에서 root 까지 올라가며 분류:
27
+ * - 'excluded' : 제외 폴더(PII/계약/HR/deprecated/node_modules 등)를 경유 → 응답에서 제거
28
+ * - 'ok' : dev/biz root 하위 + 제외 미경유 → 노출
29
+ * - 'outside' : root 에 닿지 못함(색인 대상 밖) → 보수적으로 제거
30
+ * resolvePathAndSource(drive_sync) 와 동일하게 parents[0] 만 따라간다(최대 12 hop).
31
+ */
32
+ export async function classifyForSearch(drive, parents) {
33
+ let chain = parents;
34
+ for (let hop = 0; hop < 12 && chain && chain.length > 0; hop++) {
35
+ const pid = chain[0];
36
+ if (ROOT_IDS.has(pid))
37
+ return 'ok';
38
+ const meta = await getFolderMeta(drive, pid);
39
+ if (isExcludedName(meta.name))
40
+ return 'excluded';
41
+ chain = meta.parent ? [meta.parent] : undefined;
42
+ }
43
+ return 'outside';
44
+ }
45
+ /**
46
+ * classifyForSearch + 폴더 경로(root 하위) 반환 — 직원별 Drive 폴더 scope 필터용.
47
+ * path 는 폴더만(파일명 제외), root 바로 아래부터, 예: "01_고객사/Acme". cls!=='ok' 면 path 무의미.
48
+ */
49
+ export async function classifyAndPath(drive, parents) {
50
+ const names = [];
51
+ let chain = parents;
52
+ for (let hop = 0; hop < 12 && chain && chain.length > 0; hop++) {
53
+ const pid = chain[0];
54
+ if (ROOT_IDS.has(pid))
55
+ return { cls: 'ok', path: names.reverse().join('/') };
56
+ const meta = await getFolderMeta(drive, pid);
57
+ if (isExcludedName(meta.name))
58
+ return { cls: 'excluded', path: '' };
59
+ if (meta.name)
60
+ names.push(meta.name);
61
+ chain = meta.parent ? [meta.parent] : undefined;
62
+ }
63
+ return { cls: 'outside', path: '' };
64
+ }
65
+ /**
66
+ * 폴더 자신을 분류 — list_drive_folder 의 시작 폴더 검사용.
67
+ * root 폴더면 'ok'(path ''), 그 밖에는 자기 이름이 제외 대상인지 본 뒤 부모 체인을 classifyAndPath 로 분류한다.
68
+ * path 는 root 바로 아래부터 이 폴더까지(자기 이름 포함). 예: root/01_고객사/Acme → "01_고객사/Acme".
69
+ */
70
+ export async function classifyFolder(drive, folderId) {
71
+ if (ROOT_IDS.has(folderId))
72
+ return { cls: 'ok', path: '' };
73
+ let d;
74
+ try {
75
+ d = (await drive.files.get({ fileId: folderId, fields: 'name,parents', supportsAllDrives: true })).data;
76
+ }
77
+ catch {
78
+ return { cls: 'outside', path: '' };
79
+ }
80
+ if (isExcludedName(d.name))
81
+ return { cls: 'excluded', path: '' };
82
+ const parent = await classifyAndPath(drive, d.parents ?? undefined);
83
+ if (parent.cls !== 'ok')
84
+ return { cls: parent.cls, path: '' };
85
+ return { cls: 'ok', path: parent.path ? `${parent.path}/${d.name ?? ''}` : d.name ?? '' };
86
+ }
87
+ //# sourceMappingURL=path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.js","sourceRoot":"","sources":["../../../src/plugins/drive/path.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Qdrant client singleton + collection 관리.
3
+ *
4
+ * Collection: config.qdrant.collection (default knowledge_v1), or env QDRANT_COLLECTION.
5
+ * Vector size: 1536 (text-embedding-3-small)
6
+ * Distance: Cosine
7
+ */
8
+ import type { QdrantClient } from '@qdrant/js-client-rest';
9
+ import { EMBEDDING_DIM } from './embeddings.js';
10
+ export declare const DRIVE_COLLECTION: string;
11
+ export { EMBEDDING_DIM };
12
+ /** Qdrant 클라이언트. 라이브러리는 처음 쓸 때 불러온다(서버와 CLI 시작 시간 단축 — client.ts 와 같은 이유). */
13
+ export declare function getQdrantClient(): Promise<QdrantClient>;
14
+ /**
15
+ * Collection 이 없으면 생성. Idempotent.
16
+ * Payload field 중 자주 filter 하는 것은 indexed payload 로 등록.
17
+ */
18
+ export declare function ensureCollection(): Promise<void>;
19
+ export type ChunkPayload = {
20
+ chunk_id: string;
21
+ file_id: string;
22
+ file_name: string;
23
+ file_path: string;
24
+ heading: string;
25
+ chunk_index: number;
26
+ chunk_count: number;
27
+ content: string;
28
+ modified_time: string;
29
+ content_hash: string;
30
+ file_type: 'dev_log' | 'decision' | 'rule' | 'template' | 'biz' | 'other';
31
+ cluster: string | null;
32
+ source: 'dev' | 'biz';
33
+ image_urls: string[];
34
+ has_image: boolean;
35
+ table_count: number;
36
+ has_code_block: boolean;
37
+ doc_status: string | null;
38
+ doc_tier: string | null;
39
+ doc_date: string | null;
40
+ doc_authors: string[];
41
+ doc_related: string[];
42
+ doc_tags: string[];
43
+ doc_source_refs: string[];
44
+ doc_affects: string[];
45
+ doc_products: string[];
46
+ doc_type: string | null;
47
+ doc_confidence: string | null;
48
+ doc_category: string | null;
49
+ doc_customer: string | null;
50
+ };
@@ -0,0 +1,61 @@
1
+ import { EMBEDDING_DIM } from './embeddings.js';
2
+ import { getConfig } from '../../core/config.js';
3
+ let _client = null;
4
+ // Zero-downtime reindex: env QDRANT_COLLECTION overrides (index into a new collection, then swap the
5
+ // env var -> 0 downtime; remove it to roll back). Falls back to config.qdrant.collection.
6
+ export const DRIVE_COLLECTION = process.env.QDRANT_COLLECTION || getConfig().qdrant.collection;
7
+ // 차원 SSOT = embeddings.ts (모델 출력 차원). collection size 가 임베딩 차원과 항상 일치하도록 재노출.
8
+ export { EMBEDDING_DIM };
9
+ /** Qdrant 클라이언트. 라이브러리는 처음 쓸 때 불러온다(서버와 CLI 시작 시간 단축 — client.ts 와 같은 이유). */
10
+ export async function getQdrantClient() {
11
+ if (_client)
12
+ return _client;
13
+ const { QdrantClient } = await import('@qdrant/js-client-rest');
14
+ const url = process.env.QDRANT_URL;
15
+ const apiKey = process.env.QDRANT_API_KEY;
16
+ if (!url) {
17
+ throw new Error('[qdrant] QDRANT_URL env missing');
18
+ }
19
+ _client = new QdrantClient({
20
+ url,
21
+ apiKey: apiKey || undefined,
22
+ // production: 짧은 timeout 으로 cold-start hang 회피
23
+ timeout: 10_000,
24
+ });
25
+ return _client;
26
+ }
27
+ /**
28
+ * Collection 이 없으면 생성. Idempotent.
29
+ * Payload field 중 자주 filter 하는 것은 indexed payload 로 등록.
30
+ */
31
+ export async function ensureCollection() {
32
+ const client = await getQdrantClient();
33
+ const collections = await client.getCollections();
34
+ const exists = collections.collections.some((c) => c.name === DRIVE_COLLECTION);
35
+ if (exists)
36
+ return;
37
+ await client.createCollection(DRIVE_COLLECTION, {
38
+ vectors: {
39
+ size: EMBEDDING_DIM,
40
+ distance: 'Cosine',
41
+ },
42
+ // HNSW 기본 (m=16, ef_construct=100) — 1만 vec 까지 충분
43
+ optimizers_config: {
44
+ default_segment_number: 2,
45
+ },
46
+ });
47
+ // 자주 filter 하는 payload field 에 index (검색 빠름)
48
+ for (const fieldName of [
49
+ 'file_type', 'cluster', 'file_id', 'source',
50
+ // 이미지 유무 / frontmatter 메타 filter
51
+ 'has_image', 'doc_status', 'doc_tier',
52
+ // 신규 — 태그·모드·경로 파생 filter (배열 doc_tags 는 원소 단위 keyword 매칭)
53
+ 'doc_tags', 'doc_type', 'doc_category', 'doc_customer',
54
+ ]) {
55
+ await client.createPayloadIndex(DRIVE_COLLECTION, {
56
+ field_name: fieldName,
57
+ field_schema: fieldName === 'has_image' ? 'bool' : 'keyword',
58
+ });
59
+ }
60
+ }
61
+ //# sourceMappingURL=qdrant.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qdrant.js","sourceRoot":"","sources":["../../../src/plugins/drive/qdrant.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEjD,IAAI,OAAO,GAAwB,IAAI,CAAC;AAExC,qGAAqG;AACrG,0FAA0F;AAC1F,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,SAAS,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC;AAC/F,8EAA8E;AAC9E,OAAO,EAAE,aAAa,EAAE,CAAC;AAEzB,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAC5B,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;IAEhE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;IACnC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAE1C,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,GAAG,IAAI,YAAY,CAAC;QACzB,GAAG;QACH,MAAM,EAAE,MAAM,IAAI,SAAS;QAC3B,+CAA+C;QAC/C,OAAO,EAAE,MAAM;KAChB,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,MAAM,GAAG,MAAM,eAAe,EAAE,CAAC;IACvC,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,cAAc,EAAE,CAAC;IAClD,MAAM,MAAM,GAAG,WAAW,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,gBAAgB,CAAC,CAAC;IAEhF,IAAI,MAAM;QAAE,OAAO;IAEnB,MAAM,MAAM,CAAC,gBAAgB,CAAC,gBAAgB,EAAE;QAC9C,OAAO,EAAE;YACP,IAAI,EAAE,aAAa;YACnB,QAAQ,EAAE,QAAQ;SACnB;QACD,kDAAkD;QAClD,iBAAiB,EAAE;YACjB,sBAAsB,EAAE,CAAC;SAC1B;KACF,CAAC,CAAC;IAEH,6CAA6C;IAC7C,KAAK,MAAM,SAAS,IAAI;QACtB,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ;QAC3C,iCAAiC;QACjC,WAAW,EAAE,YAAY,EAAE,UAAU;QACrC,2DAA2D;QAC3D,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,cAAc;KAC9C,EAAE,CAAC;QACX,MAAM,MAAM,CAAC,kBAAkB,CAAC,gBAAgB,EAAE;YAChD,UAAU,EAAE,SAAS;YACrB,YAAY,EAAE,SAAS,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;SAC7D,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ /** `knowledge-mcp drive reindex [--root ID | --dev-only | --biz-only] [--dry-run] [--recreate]` */
2
+ export declare function runReindex(argv: string[]): Promise<void>;