stellavault 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 (294) hide show
  1. package/.env.example +12 -0
  2. package/CLAUDE.md +39 -0
  3. package/CONTRIBUTING.md +65 -0
  4. package/LICENSE +21 -0
  5. package/README.md +182 -0
  6. package/memory/MEMORY.md +25 -0
  7. package/package.json +33 -0
  8. package/packages/cli/bin/ekh.js +2 -0
  9. package/packages/cli/bin/stellavault.js +2 -0
  10. package/packages/cli/dist/commands/brief-cmd.d.ts +2 -0
  11. package/packages/cli/dist/commands/brief-cmd.d.ts.map +1 -0
  12. package/packages/cli/dist/commands/brief-cmd.js +82 -0
  13. package/packages/cli/dist/commands/brief-cmd.js.map +1 -0
  14. package/packages/cli/dist/commands/capture-cmd.d.ts +7 -0
  15. package/packages/cli/dist/commands/capture-cmd.d.ts.map +1 -0
  16. package/packages/cli/dist/commands/capture-cmd.js +31 -0
  17. package/packages/cli/dist/commands/capture-cmd.js.map +1 -0
  18. package/packages/cli/dist/commands/card-cmd.d.ts +4 -0
  19. package/packages/cli/dist/commands/card-cmd.d.ts.map +1 -0
  20. package/packages/cli/dist/commands/card-cmd.js +26 -0
  21. package/packages/cli/dist/commands/card-cmd.js.map +1 -0
  22. package/packages/cli/dist/commands/clip-cmd.d.ts +4 -0
  23. package/packages/cli/dist/commands/clip-cmd.d.ts.map +1 -0
  24. package/packages/cli/dist/commands/clip-cmd.js +151 -0
  25. package/packages/cli/dist/commands/clip-cmd.js.map +1 -0
  26. package/packages/cli/dist/commands/cloud-cmd.d.ts +4 -0
  27. package/packages/cli/dist/commands/cloud-cmd.d.ts.map +1 -0
  28. package/packages/cli/dist/commands/cloud-cmd.js +64 -0
  29. package/packages/cli/dist/commands/cloud-cmd.js.map +1 -0
  30. package/packages/cli/dist/commands/contradictions-cmd.d.ts +2 -0
  31. package/packages/cli/dist/commands/contradictions-cmd.d.ts.map +1 -0
  32. package/packages/cli/dist/commands/contradictions-cmd.js +34 -0
  33. package/packages/cli/dist/commands/contradictions-cmd.js.map +1 -0
  34. package/packages/cli/dist/commands/decay-cmd.d.ts +2 -0
  35. package/packages/cli/dist/commands/decay-cmd.d.ts.map +1 -0
  36. package/packages/cli/dist/commands/decay-cmd.js +48 -0
  37. package/packages/cli/dist/commands/decay-cmd.js.map +1 -0
  38. package/packages/cli/dist/commands/digest-cmd.d.ts +4 -0
  39. package/packages/cli/dist/commands/digest-cmd.d.ts.map +1 -0
  40. package/packages/cli/dist/commands/digest-cmd.js +79 -0
  41. package/packages/cli/dist/commands/digest-cmd.js.map +1 -0
  42. package/packages/cli/dist/commands/duplicates-cmd.d.ts +4 -0
  43. package/packages/cli/dist/commands/duplicates-cmd.d.ts.map +1 -0
  44. package/packages/cli/dist/commands/duplicates-cmd.js +30 -0
  45. package/packages/cli/dist/commands/duplicates-cmd.js.map +1 -0
  46. package/packages/cli/dist/commands/federate-cmd.d.ts +5 -0
  47. package/packages/cli/dist/commands/federate-cmd.d.ts.map +1 -0
  48. package/packages/cli/dist/commands/federate-cmd.js +217 -0
  49. package/packages/cli/dist/commands/federate-cmd.js.map +1 -0
  50. package/packages/cli/dist/commands/gaps-cmd.d.ts +2 -0
  51. package/packages/cli/dist/commands/gaps-cmd.d.ts.map +1 -0
  52. package/packages/cli/dist/commands/gaps-cmd.js +33 -0
  53. package/packages/cli/dist/commands/gaps-cmd.js.map +1 -0
  54. package/packages/cli/dist/commands/graph-cmd.d.ts +2 -0
  55. package/packages/cli/dist/commands/graph-cmd.d.ts.map +1 -0
  56. package/packages/cli/dist/commands/graph-cmd.js +77 -0
  57. package/packages/cli/dist/commands/graph-cmd.js.map +1 -0
  58. package/packages/cli/dist/commands/index-cmd.d.ts +2 -0
  59. package/packages/cli/dist/commands/index-cmd.d.ts.map +1 -0
  60. package/packages/cli/dist/commands/index-cmd.js +57 -0
  61. package/packages/cli/dist/commands/index-cmd.js.map +1 -0
  62. package/packages/cli/dist/commands/init-cmd.d.ts +2 -0
  63. package/packages/cli/dist/commands/init-cmd.d.ts.map +1 -0
  64. package/packages/cli/dist/commands/init-cmd.js +123 -0
  65. package/packages/cli/dist/commands/init-cmd.js.map +1 -0
  66. package/packages/cli/dist/commands/learn-cmd.d.ts +2 -0
  67. package/packages/cli/dist/commands/learn-cmd.d.ts.map +1 -0
  68. package/packages/cli/dist/commands/learn-cmd.js +48 -0
  69. package/packages/cli/dist/commands/learn-cmd.js.map +1 -0
  70. package/packages/cli/dist/commands/pack-cmd.d.ts +15 -0
  71. package/packages/cli/dist/commands/pack-cmd.d.ts.map +1 -0
  72. package/packages/cli/dist/commands/pack-cmd.js +93 -0
  73. package/packages/cli/dist/commands/pack-cmd.js.map +1 -0
  74. package/packages/cli/dist/commands/review-cmd.d.ts +4 -0
  75. package/packages/cli/dist/commands/review-cmd.d.ts.map +1 -0
  76. package/packages/cli/dist/commands/review-cmd.js +107 -0
  77. package/packages/cli/dist/commands/review-cmd.js.map +1 -0
  78. package/packages/cli/dist/commands/search-cmd.d.ts +4 -0
  79. package/packages/cli/dist/commands/search-cmd.d.ts.map +1 -0
  80. package/packages/cli/dist/commands/search-cmd.js +38 -0
  81. package/packages/cli/dist/commands/search-cmd.js.map +1 -0
  82. package/packages/cli/dist/commands/serve-cmd.d.ts +2 -0
  83. package/packages/cli/dist/commands/serve-cmd.d.ts.map +1 -0
  84. package/packages/cli/dist/commands/serve-cmd.js +14 -0
  85. package/packages/cli/dist/commands/serve-cmd.js.map +1 -0
  86. package/packages/cli/dist/commands/status-cmd.d.ts +2 -0
  87. package/packages/cli/dist/commands/status-cmd.d.ts.map +1 -0
  88. package/packages/cli/dist/commands/status-cmd.js +33 -0
  89. package/packages/cli/dist/commands/status-cmd.js.map +1 -0
  90. package/packages/cli/dist/commands/sync-cmd.d.ts +5 -0
  91. package/packages/cli/dist/commands/sync-cmd.d.ts.map +1 -0
  92. package/packages/cli/dist/commands/sync-cmd.js +62 -0
  93. package/packages/cli/dist/commands/sync-cmd.js.map +1 -0
  94. package/packages/cli/dist/commands/vault-cmd.d.ts +10 -0
  95. package/packages/cli/dist/commands/vault-cmd.d.ts.map +1 -0
  96. package/packages/cli/dist/commands/vault-cmd.js +54 -0
  97. package/packages/cli/dist/commands/vault-cmd.js.map +1 -0
  98. package/packages/cli/dist/index.d.ts +2 -0
  99. package/packages/cli/dist/index.d.ts.map +1 -0
  100. package/packages/cli/dist/index.js +156 -0
  101. package/packages/cli/dist/index.js.map +1 -0
  102. package/packages/cli/package.json +24 -0
  103. package/packages/cli/src/commands/brief-cmd.ts +87 -0
  104. package/packages/cli/src/commands/capture-cmd.ts +34 -0
  105. package/packages/cli/src/commands/card-cmd.ts +29 -0
  106. package/packages/cli/src/commands/clip-cmd.ts +172 -0
  107. package/packages/cli/src/commands/cloud-cmd.ts +75 -0
  108. package/packages/cli/src/commands/contradictions-cmd.ts +41 -0
  109. package/packages/cli/src/commands/decay-cmd.ts +57 -0
  110. package/packages/cli/src/commands/digest-cmd.ts +89 -0
  111. package/packages/cli/src/commands/duplicates-cmd.ts +38 -0
  112. package/packages/cli/src/commands/federate-cmd.ts +236 -0
  113. package/packages/cli/src/commands/gaps-cmd.ts +40 -0
  114. package/packages/cli/src/commands/graph-cmd.ts +88 -0
  115. package/packages/cli/src/commands/index-cmd.ts +65 -0
  116. package/packages/cli/src/commands/init-cmd.ts +145 -0
  117. package/packages/cli/src/commands/learn-cmd.ts +56 -0
  118. package/packages/cli/src/commands/pack-cmd.ts +121 -0
  119. package/packages/cli/src/commands/review-cmd.ts +125 -0
  120. package/packages/cli/src/commands/search-cmd.ts +45 -0
  121. package/packages/cli/src/commands/serve-cmd.ts +17 -0
  122. package/packages/cli/src/commands/status-cmd.ts +37 -0
  123. package/packages/cli/src/commands/sync-cmd.ts +68 -0
  124. package/packages/cli/src/commands/vault-cmd.ts +64 -0
  125. package/packages/cli/src/index.ts +187 -0
  126. package/packages/core/package.json +40 -0
  127. package/packages/core/src/api/dashboard.ts +138 -0
  128. package/packages/core/src/api/graph-data.ts +286 -0
  129. package/packages/core/src/api/pwa.ts +82 -0
  130. package/packages/core/src/api/server.ts +660 -0
  131. package/packages/core/src/capture/voice.ts +168 -0
  132. package/packages/core/src/cloud/index.ts +2 -0
  133. package/packages/core/src/cloud/sync.ts +167 -0
  134. package/packages/core/src/config.ts +82 -0
  135. package/packages/core/src/federation/credits.ts +80 -0
  136. package/packages/core/src/federation/hyperswarm.d.ts +19 -0
  137. package/packages/core/src/federation/identity.ts +90 -0
  138. package/packages/core/src/federation/index.ts +8 -0
  139. package/packages/core/src/federation/node.ts +235 -0
  140. package/packages/core/src/federation/privacy.ts +52 -0
  141. package/packages/core/src/federation/reputation.ts +202 -0
  142. package/packages/core/src/federation/search.ts +129 -0
  143. package/packages/core/src/federation/sharing.ts +165 -0
  144. package/packages/core/src/federation/trust.ts +76 -0
  145. package/packages/core/src/federation/types.ts +25 -0
  146. package/packages/core/src/i18n/index.ts +85 -0
  147. package/packages/core/src/index.ts +133 -0
  148. package/packages/core/src/indexer/chunker.ts +180 -0
  149. package/packages/core/src/indexer/embedder.ts +9 -0
  150. package/packages/core/src/indexer/index.ts +113 -0
  151. package/packages/core/src/indexer/local-embedder.ts +35 -0
  152. package/packages/core/src/indexer/scanner.ts +142 -0
  153. package/packages/core/src/indexer/watcher.ts +62 -0
  154. package/packages/core/src/intelligence/contradiction-detector.ts +134 -0
  155. package/packages/core/src/intelligence/decay-engine.ts +229 -0
  156. package/packages/core/src/intelligence/duplicate-detector.ts +71 -0
  157. package/packages/core/src/intelligence/fsrs.ts +79 -0
  158. package/packages/core/src/intelligence/gap-detector.ts +109 -0
  159. package/packages/core/src/intelligence/learning-path.ts +86 -0
  160. package/packages/core/src/intelligence/notifications.ts +106 -0
  161. package/packages/core/src/intelligence/predictive-gaps.ts +94 -0
  162. package/packages/core/src/intelligence/semantic-versioning.ts +97 -0
  163. package/packages/core/src/intelligence/types.ts +28 -0
  164. package/packages/core/src/mcp/custom-tools.ts +97 -0
  165. package/packages/core/src/mcp/index.ts +1 -0
  166. package/packages/core/src/mcp/server.ts +142 -0
  167. package/packages/core/src/mcp/tools/agentic-graph.ts +96 -0
  168. package/packages/core/src/mcp/tools/brief.ts +49 -0
  169. package/packages/core/src/mcp/tools/decay.ts +40 -0
  170. package/packages/core/src/mcp/tools/decision-journal.ts +95 -0
  171. package/packages/core/src/mcp/tools/export.ts +72 -0
  172. package/packages/core/src/mcp/tools/federated-search.ts +43 -0
  173. package/packages/core/src/mcp/tools/generate-claude-md.ts +130 -0
  174. package/packages/core/src/mcp/tools/get-document.ts +26 -0
  175. package/packages/core/src/mcp/tools/get-related.ts +41 -0
  176. package/packages/core/src/mcp/tools/learning-path.ts +52 -0
  177. package/packages/core/src/mcp/tools/list-topics.ts +20 -0
  178. package/packages/core/src/mcp/tools/search.ts +35 -0
  179. package/packages/core/src/mcp/tools/snapshot.ts +98 -0
  180. package/packages/core/src/multi-vault/index.ts +118 -0
  181. package/packages/core/src/pack/creator.ts +127 -0
  182. package/packages/core/src/pack/exporter.ts +21 -0
  183. package/packages/core/src/pack/importer.ts +82 -0
  184. package/packages/core/src/pack/index.ts +5 -0
  185. package/packages/core/src/pack/marketplace.ts +103 -0
  186. package/packages/core/src/pack/pii-masker.ts +38 -0
  187. package/packages/core/src/pack/types.ts +39 -0
  188. package/packages/core/src/plugins/index.ts +100 -0
  189. package/packages/core/src/plugins/webhooks.ts +110 -0
  190. package/packages/core/src/search/bm25.ts +16 -0
  191. package/packages/core/src/search/index.ts +83 -0
  192. package/packages/core/src/search/rrf.ts +31 -0
  193. package/packages/core/src/search/semantic.ts +15 -0
  194. package/packages/core/src/store/index.ts +2 -0
  195. package/packages/core/src/store/sqlite-vec.ts +290 -0
  196. package/packages/core/src/store/types.ts +22 -0
  197. package/packages/core/src/team/index.ts +126 -0
  198. package/packages/core/src/types/chunk.ts +25 -0
  199. package/packages/core/src/types/document.ts +24 -0
  200. package/packages/core/src/types/graph.ts +44 -0
  201. package/packages/core/src/types/index.ts +15 -0
  202. package/packages/core/src/types/search.ts +38 -0
  203. package/packages/core/src/utils/retry.ts +85 -0
  204. package/packages/core/tests/api-card.test.ts +60 -0
  205. package/packages/core/tests/api-routes.test.ts +98 -0
  206. package/packages/core/tests/bm25.test.ts +87 -0
  207. package/packages/core/tests/chunker.test.ts +48 -0
  208. package/packages/core/tests/cluster.test.ts +75 -0
  209. package/packages/core/tests/constellation.test.ts +77 -0
  210. package/packages/core/tests/export-utils.test.ts +97 -0
  211. package/packages/core/tests/fsrs.test.ts +96 -0
  212. package/packages/core/tests/gesture-detector.test.ts +45 -0
  213. package/packages/core/tests/graph-data.test.ts +87 -0
  214. package/packages/core/tests/layout.test.ts +83 -0
  215. package/packages/core/tests/mcp.test.ts +148 -0
  216. package/packages/core/tests/pack.test.ts +127 -0
  217. package/packages/core/tests/pii-masker.test.ts +42 -0
  218. package/packages/core/tests/profile-card.test.ts +62 -0
  219. package/packages/core/tests/rrf.test.ts +29 -0
  220. package/packages/core/tests/search-integration.test.ts +139 -0
  221. package/packages/core/tests/store.test.ts +80 -0
  222. package/packages/graph/click-result.png +0 -0
  223. package/packages/graph/index.html +17 -0
  224. package/packages/graph/package.json +32 -0
  225. package/packages/graph/src/App.tsx +7 -0
  226. package/packages/graph/src/api/client.ts +39 -0
  227. package/packages/graph/src/components/ClusterFilter.tsx +73 -0
  228. package/packages/graph/src/components/ConstellationView.tsx +232 -0
  229. package/packages/graph/src/components/ExportPanel.tsx +177 -0
  230. package/packages/graph/src/components/Graph3D.tsx +230 -0
  231. package/packages/graph/src/components/GraphEdges.tsx +100 -0
  232. package/packages/graph/src/components/GraphNodes.tsx +386 -0
  233. package/packages/graph/src/components/HealthDashboard.tsx +173 -0
  234. package/packages/graph/src/components/Layout.tsx +214 -0
  235. package/packages/graph/src/components/MotionOverlay.tsx +81 -0
  236. package/packages/graph/src/components/MotionToggle.tsx +33 -0
  237. package/packages/graph/src/components/MultiverseView.tsx +286 -0
  238. package/packages/graph/src/components/NodeDetail.tsx +232 -0
  239. package/packages/graph/src/components/PulseParticle.tsx +232 -0
  240. package/packages/graph/src/components/SearchBar.tsx +107 -0
  241. package/packages/graph/src/components/StarField.tsx +197 -0
  242. package/packages/graph/src/components/StatusBar.tsx +53 -0
  243. package/packages/graph/src/components/Timeline.tsx +148 -0
  244. package/packages/graph/src/components/ToolsPanel.tsx +512 -0
  245. package/packages/graph/src/components/Tooltip.tsx +100 -0
  246. package/packages/graph/src/components/TypeFilter.tsx +131 -0
  247. package/packages/graph/src/embed/EmbedGraph.tsx +144 -0
  248. package/packages/graph/src/hooks/useConstellationLOD.ts +76 -0
  249. package/packages/graph/src/hooks/useDecay.ts +37 -0
  250. package/packages/graph/src/hooks/useExport.ts +165 -0
  251. package/packages/graph/src/hooks/useGraph.ts +69 -0
  252. package/packages/graph/src/hooks/useKeyboardNav.ts +122 -0
  253. package/packages/graph/src/hooks/useLayout.ts +45 -0
  254. package/packages/graph/src/hooks/useMotion.ts +120 -0
  255. package/packages/graph/src/hooks/usePulse.ts +58 -0
  256. package/packages/graph/src/hooks/useSearch.ts +71 -0
  257. package/packages/graph/src/lib/constellation.ts +107 -0
  258. package/packages/graph/src/lib/export-utils.ts +48 -0
  259. package/packages/graph/src/lib/gesture-detector.ts +123 -0
  260. package/packages/graph/src/lib/layout.worker.ts +153 -0
  261. package/packages/graph/src/lib/motion-controller.ts +83 -0
  262. package/packages/graph/src/lib/profile-card.ts +122 -0
  263. package/packages/graph/src/main.tsx +4 -0
  264. package/packages/graph/src/stores/graph-store.ts +155 -0
  265. package/packages/graph/success.png +0 -0
  266. package/packages/graph/test-click.mjs +49 -0
  267. package/packages/graph/test-explore.mjs +102 -0
  268. package/packages/graph/test-final.mjs +61 -0
  269. package/packages/graph/test-graph.mjs +139 -0
  270. package/packages/graph/test-hover.mjs +48 -0
  271. package/packages/graph/test-pulse.mjs +68 -0
  272. package/packages/graph/test-screenshot.mjs +56 -0
  273. package/packages/graph/test-v2.mjs +97 -0
  274. package/packages/graph/vite.config.ts +15 -0
  275. package/packages/sync/.env.example +11 -0
  276. package/packages/sync/.sync-state.json +317 -0
  277. package/packages/sync/.upload-state.json +1009 -0
  278. package/packages/sync/create-stella-network-notion.mjs +151 -0
  279. package/packages/sync/create-stellavault-project-notion.mjs +322 -0
  280. package/packages/sync/logs/sync-2026-03-28.log +6 -0
  281. package/packages/sync/logs/sync-2026-03-29.log +12 -0
  282. package/packages/sync/logs/sync-2026-03-30.log +6 -0
  283. package/packages/sync/logs/sync-2026-03-31.log +6 -0
  284. package/packages/sync/logs/sync-2026-04-01.log +6 -0
  285. package/packages/sync/logs/sync-2026-04-02.log +6 -0
  286. package/packages/sync/package-lock.json +373 -0
  287. package/packages/sync/package.json +16 -0
  288. package/packages/sync/run-sync.bat +18 -0
  289. package/packages/sync/run-sync.mjs +46 -0
  290. package/packages/sync/setup-scheduler.mjs +119 -0
  291. package/packages/sync/structured-sync.mjs +187 -0
  292. package/packages/sync/sync-to-obsidian.mjs +264 -0
  293. package/packages/sync/upload-pdca-to-notion.mjs +495 -0
  294. package/tsconfig.base.json +18 -0
@@ -0,0 +1,165 @@
1
+ // Federation: Sharing Control
2
+ // 노드별 공유 범위 관리 — 태그/폴더/문서 단위로 공개/비공개 설정
3
+
4
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import { homedir } from 'node:os';
7
+
8
+ export interface SharingConfig {
9
+ mode: 'whitelist' | 'blacklist'; // whitelist=명시적 공개만, blacklist=명시적 비공개만
10
+
11
+ // 태그 기반
12
+ allowedTags: string[]; // whitelist 모드: 이 태그가 있는 문서만 공개
13
+ blockedTags: string[]; // blacklist 모드: 이 태그가 있는 문서 제외
14
+
15
+ // 폴더 기반
16
+ allowedFolders: string[]; // whitelist 모드: 이 폴더만 공개
17
+ blockedFolders: string[]; // blacklist 모드: 이 폴더 제외
18
+
19
+ // 문서 단위
20
+ blockedDocIds: string[]; // 개별 문서 ID 차단 (둘 다 적용)
21
+
22
+ // 콘텐츠 필터
23
+ blockSensitivePatterns: boolean; // 이메일, 전화번호, API키 패턴 자동 감지→차단
24
+ }
25
+
26
+ const SHARING_FILE = join(homedir(), '.stellavault', 'federation', 'sharing.json');
27
+
28
+ const DEFAULT_CONFIG: SharingConfig = {
29
+ mode: 'blacklist',
30
+ allowedTags: [],
31
+ blockedTags: ['personal', 'private', 'secret', 'diary', 'salary', 'password', 'credential'],
32
+ allowedFolders: [],
33
+ blockedFolders: ['03_Daily', '06_Archive', '.obsidian'],
34
+ blockedDocIds: [],
35
+ blockSensitivePatterns: true,
36
+ };
37
+
38
+ // 민감 패턴
39
+ const SENSITIVE_PATTERNS = [
40
+ /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/, // 이메일
41
+ /\b\d{3}[-.]?\d{4}[-.]?\d{4}\b/, // 전화번호
42
+ /\b(sk-|pk-|api[_-]?key|token|secret)[a-zA-Z0-9_-]{10,}\b/i, // API 키
43
+ /\bpassword\s*[:=]\s*\S+/i, // password=xxx
44
+ /\b\d{6}[-]\d{7}\b/, // 주민번호 패턴
45
+ ];
46
+
47
+ export function loadSharingConfig(): SharingConfig {
48
+ if (existsSync(SHARING_FILE)) {
49
+ const raw = JSON.parse(readFileSync(SHARING_FILE, 'utf-8'));
50
+ return { ...DEFAULT_CONFIG, ...raw };
51
+ }
52
+ return { ...DEFAULT_CONFIG };
53
+ }
54
+
55
+ export function saveSharingConfig(config: SharingConfig): void {
56
+ mkdirSync(join(homedir(), '.stellavault', 'federation'), { recursive: true });
57
+ writeFileSync(SHARING_FILE, JSON.stringify(config, null, 2), 'utf-8');
58
+ }
59
+
60
+ // 문서가 공유 가능한지 확인
61
+ export function isDocumentShareable(
62
+ doc: { tags: string[]; filePath: string; id: string; content: string },
63
+ config?: SharingConfig,
64
+ ): boolean {
65
+ const cfg = config ?? loadSharingConfig();
66
+
67
+ // 1. 개별 차단 확인
68
+ if (cfg.blockedDocIds.includes(doc.id)) return false;
69
+
70
+ // 2. 폴더 기반 필터
71
+ const folder = doc.filePath.split('/')[0] ?? '';
72
+ if (cfg.mode === 'whitelist') {
73
+ if (cfg.allowedFolders.length > 0 && !cfg.allowedFolders.some(f => doc.filePath.startsWith(f))) {
74
+ return false;
75
+ }
76
+ } else {
77
+ if (cfg.blockedFolders.some(f => doc.filePath.startsWith(f))) {
78
+ return false;
79
+ }
80
+ }
81
+
82
+ // 3. 태그 기반 필터
83
+ const docTags = doc.tags.map(t => t.toLowerCase());
84
+ if (cfg.mode === 'whitelist') {
85
+ if (cfg.allowedTags.length > 0 && !cfg.allowedTags.some(t => docTags.includes(t.toLowerCase()))) {
86
+ return false;
87
+ }
88
+ } else {
89
+ if (cfg.blockedTags.some(t => docTags.includes(t.toLowerCase()))) {
90
+ return false;
91
+ }
92
+ }
93
+
94
+ // 4. 민감 패턴 감지
95
+ if (cfg.blockSensitivePatterns) {
96
+ for (const pattern of SENSITIVE_PATTERNS) {
97
+ if (pattern.test(doc.content)) return false;
98
+ }
99
+ }
100
+
101
+ return true;
102
+ }
103
+
104
+ // 스니펫에서 민감 정보 제거
105
+ export function sanitizeSnippet(snippet: string): string {
106
+ let safe = snippet;
107
+ for (const pattern of SENSITIVE_PATTERNS) {
108
+ safe = safe.replace(pattern, '[REDACTED]');
109
+ }
110
+ return safe;
111
+ }
112
+
113
+ // CLI 헬퍼: 현재 공유 설정 요약
114
+ export function getSharingSummary(config?: SharingConfig): string {
115
+ const cfg = config ?? loadSharingConfig();
116
+ const lines: string[] = [];
117
+ lines.push(`Mode: ${cfg.mode}`);
118
+ if (cfg.mode === 'blacklist') {
119
+ lines.push(`Blocked tags: ${cfg.blockedTags.join(', ') || 'none'}`);
120
+ lines.push(`Blocked folders: ${cfg.blockedFolders.join(', ') || 'none'}`);
121
+ } else {
122
+ lines.push(`Allowed tags: ${cfg.allowedTags.join(', ') || 'all'}`);
123
+ lines.push(`Allowed folders: ${cfg.allowedFolders.join(', ') || 'all'}`);
124
+ }
125
+ lines.push(`Blocked docs: ${cfg.blockedDocIds.length}`);
126
+ lines.push(`Sensitive pattern filter: ${cfg.blockSensitivePatterns ? 'ON' : 'OFF'}`);
127
+ return lines.join('\n');
128
+ }
129
+
130
+ // 태그 추가/제거 헬퍼
131
+ export function addBlockedTag(tag: string): void {
132
+ const cfg = loadSharingConfig();
133
+ if (!cfg.blockedTags.includes(tag.toLowerCase())) {
134
+ cfg.blockedTags.push(tag.toLowerCase());
135
+ saveSharingConfig(cfg);
136
+ }
137
+ }
138
+
139
+ export function removeBlockedTag(tag: string): void {
140
+ const cfg = loadSharingConfig();
141
+ cfg.blockedTags = cfg.blockedTags.filter(t => t !== tag.toLowerCase());
142
+ saveSharingConfig(cfg);
143
+ }
144
+
145
+ export function addBlockedFolder(folder: string): void {
146
+ const cfg = loadSharingConfig();
147
+ if (!cfg.blockedFolders.includes(folder)) {
148
+ cfg.blockedFolders.push(folder);
149
+ saveSharingConfig(cfg);
150
+ }
151
+ }
152
+
153
+ export function blockDocument(docId: string): void {
154
+ const cfg = loadSharingConfig();
155
+ if (!cfg.blockedDocIds.includes(docId)) {
156
+ cfg.blockedDocIds.push(docId);
157
+ saveSharingConfig(cfg);
158
+ }
159
+ }
160
+
161
+ export function unblockDocument(docId: string): void {
162
+ const cfg = loadSharingConfig();
163
+ cfg.blockedDocIds = cfg.blockedDocIds.filter(id => id !== docId);
164
+ saveSharingConfig(cfg);
165
+ }
@@ -0,0 +1,76 @@
1
+ // Federation Phase 2: Web of Trust
2
+ // vouch/revoke/block — 노드 간 상호 신뢰 관리
3
+
4
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import { homedir } from 'node:os';
7
+
8
+ export interface TrustEntry {
9
+ peerId: string;
10
+ displayName: string;
11
+ level: 'vouched' | 'neutral' | 'blocked';
12
+ reason?: string;
13
+ updatedAt: string;
14
+ }
15
+
16
+ const TRUST_FILE = join(homedir(), '.stellavault', 'federation', 'trust.json');
17
+
18
+ function loadTrustDb(): Map<string, TrustEntry> {
19
+ if (!existsSync(TRUST_FILE)) return new Map();
20
+ const raw = JSON.parse(readFileSync(TRUST_FILE, 'utf-8')) as TrustEntry[];
21
+ return new Map(raw.map(e => [e.peerId, e]));
22
+ }
23
+
24
+ function saveTrustDb(db: Map<string, TrustEntry>): void {
25
+ mkdirSync(join(homedir(), '.stellavault', 'federation'), { recursive: true });
26
+ writeFileSync(TRUST_FILE, JSON.stringify([...db.values()], null, 2), 'utf-8');
27
+ }
28
+
29
+ export function vouch(peerId: string, displayName: string, reason?: string): TrustEntry {
30
+ const db = loadTrustDb();
31
+ const entry: TrustEntry = { peerId, displayName, level: 'vouched', reason, updatedAt: new Date().toISOString() };
32
+ db.set(peerId, entry);
33
+ saveTrustDb(db);
34
+ return entry;
35
+ }
36
+
37
+ export function revoke(peerId: string): boolean {
38
+ const db = loadTrustDb();
39
+ const entry = db.get(peerId);
40
+ if (!entry) return false;
41
+ entry.level = 'neutral';
42
+ entry.updatedAt = new Date().toISOString();
43
+ saveTrustDb(db);
44
+ return true;
45
+ }
46
+
47
+ export function block(peerId: string, displayName: string, reason?: string): TrustEntry {
48
+ const db = loadTrustDb();
49
+ const entry: TrustEntry = { peerId, displayName, level: 'blocked', reason, updatedAt: new Date().toISOString() };
50
+ db.set(peerId, entry);
51
+ saveTrustDb(db);
52
+ return entry;
53
+ }
54
+
55
+ export function getTrustLevel(peerId: string): TrustEntry['level'] {
56
+ const db = loadTrustDb();
57
+ return db.get(peerId)?.level ?? 'neutral';
58
+ }
59
+
60
+ export function isBlocked(peerId: string): boolean {
61
+ return getTrustLevel(peerId) === 'blocked';
62
+ }
63
+
64
+ export function listTrusted(): TrustEntry[] {
65
+ return [...loadTrustDb().values()];
66
+ }
67
+
68
+ // 신뢰 점수 계산 (0-100)
69
+ export function computeTrustScore(peerId: string): number {
70
+ const level = getTrustLevel(peerId);
71
+ switch (level) {
72
+ case 'vouched': return 80;
73
+ case 'neutral': return 50;
74
+ case 'blocked': return 0;
75
+ }
76
+ }
@@ -0,0 +1,25 @@
1
+ // Design Ref: §2.2 — Federation 공유 타입 정의
2
+
3
+ export interface PeerInfo {
4
+ peerId: string;
5
+ displayName: string;
6
+ documentCount: number;
7
+ topTopics: string[];
8
+ joinedAt: string;
9
+ lastSeen: string;
10
+ }
11
+
12
+ export interface FederatedSearchResult {
13
+ title: string;
14
+ similarity: number;
15
+ snippet: string; // 원문 첫 50자
16
+ peerId: string;
17
+ peerName: string;
18
+ }
19
+
20
+ // Design Ref: §7 — 메시지 프로토콜 (JSON + newline delimiter)
21
+ export type FederationMessage =
22
+ | { type: 'handshake'; peerId: string; displayName: string; version: string; documentCount: number; topTopics: string[] }
23
+ | { type: 'search_query'; queryId: string; embedding: number[]; limit: number }
24
+ | { type: 'search_result'; queryId: string; results: Array<{ title: string; similarity: number; snippet: string }> }
25
+ | { type: 'leave'; peerId: string };
@@ -0,0 +1,85 @@
1
+ // i18n Foundation (F-A19)
2
+ // Simple string externalization for CLI and Graph UI
3
+
4
+ export type Locale = 'en' | 'ko' | 'ja' | 'zh';
5
+
6
+ const strings: Record<Locale, Record<string, string>> = {
7
+ en: {
8
+ 'init.welcome': 'Stellavault Setup Wizard',
9
+ 'init.tagline': "Notes die in folders. Let's bring yours to life.",
10
+ 'init.step1': 'Where is your Obsidian vault?',
11
+ 'init.step2': 'Indexing your vault',
12
+ 'init.step3': 'Try your first search',
13
+ 'init.done': 'Setup complete!',
14
+ 'index.complete': 'Indexing complete',
15
+ 'search.no_results': 'No results found.',
16
+ 'decay.report_title': 'Knowledge Decay Report',
17
+ 'decay.tip': 'Search a topic to refresh decaying knowledge',
18
+ 'learn.title': 'Your Learning Path',
19
+ 'learn.all_clear': 'All clear! Your knowledge is in great shape.',
20
+ 'contradictions.title': 'potential contradictions found',
21
+ 'contradictions.none': 'No contradictions detected. Your knowledge is consistent!',
22
+ 'graph.title': 'Stellavault',
23
+ 'graph.subtitle': 'Neural Knowledge Graph',
24
+ 'error.vault_not_found': 'Vault not found',
25
+ 'error.db_init_failed': 'Database initialization failed',
26
+ 'error.embedder_failed': 'Embedding model failed to load',
27
+ },
28
+ ko: {
29
+ 'init.welcome': 'Stellavault 설정 마법사',
30
+ 'init.tagline': '노트는 폴더에서 죽습니다. 당신의 지식을 살려봅시다.',
31
+ 'init.step1': 'Obsidian vault 경로가 어디인가요?',
32
+ 'init.step2': 'vault 인덱싱 중',
33
+ 'init.step3': '첫 번째 검색을 해보세요',
34
+ 'init.done': '설정 완료!',
35
+ 'index.complete': '인덱싱 완료',
36
+ 'search.no_results': '검색 결과가 없습니다.',
37
+ 'decay.report_title': '지식 감쇠 리포트',
38
+ 'decay.tip': '검색하면 감쇠 중인 지식이 리프레시됩니다',
39
+ 'learn.title': '당신의 학습 경로',
40
+ 'learn.all_clear': '모든 지식이 건강합니다!',
41
+ 'contradictions.title': '개의 잠재적 모순 발견',
42
+ 'contradictions.none': '모순이 없습니다. 지식이 일관됩니다!',
43
+ 'graph.title': 'Stellavault',
44
+ 'graph.subtitle': '뉴럴 지식 그래프',
45
+ 'error.vault_not_found': 'vault를 찾을 수 없습니다',
46
+ 'error.db_init_failed': '데이터베이스 초기화 실패',
47
+ 'error.embedder_failed': '임베딩 모델 로딩 실패',
48
+ },
49
+ ja: {
50
+ 'init.welcome': 'Stellavault セットアップウィザード',
51
+ 'init.tagline': 'ノートはフォルダで死にます。あなたの知識を生かしましょう。',
52
+ 'graph.title': 'Stellavault',
53
+ 'graph.subtitle': 'ニューラルナレッジグラフ',
54
+ 'search.no_results': '検索結果がありません。',
55
+ },
56
+ zh: {
57
+ 'init.welcome': 'Stellavault 设置向导',
58
+ 'init.tagline': '笔记在文件夹中消亡。让你的知识活起来。',
59
+ 'graph.title': 'Stellavault',
60
+ 'graph.subtitle': '神经知识图谱',
61
+ 'search.no_results': '没有搜索结果。',
62
+ },
63
+ };
64
+
65
+ let currentLocale: Locale = 'en';
66
+
67
+ export function setLocale(locale: Locale): void {
68
+ currentLocale = locale;
69
+ }
70
+
71
+ export function getLocale(): Locale {
72
+ return currentLocale;
73
+ }
74
+
75
+ export function t(key: string, fallback?: string): string {
76
+ return strings[currentLocale]?.[key] ?? strings.en[key] ?? fallback ?? key;
77
+ }
78
+
79
+ export function detectLocale(): Locale {
80
+ const env = process.env.LANG || process.env.LANGUAGE || process.env.LC_ALL || '';
81
+ if (env.startsWith('ko')) return 'ko';
82
+ if (env.startsWith('ja')) return 'ja';
83
+ if (env.startsWith('zh')) return 'zh';
84
+ return 'en';
85
+ }
@@ -0,0 +1,133 @@
1
+ // Design Ref: §4.2 — Core Internal API (Facade)
2
+ // Design Ref: §9.3 — Dependency Injection Pattern
3
+
4
+ export { loadConfig } from './config.js';
5
+ export type { StellavaultConfig } from './config.js';
6
+
7
+ // Types
8
+ export type { Document } from './types/document.js';
9
+ export type { Chunk, ScoredChunk } from './types/chunk.js';
10
+ export type {
11
+ SearchResult,
12
+ SearchOptions,
13
+ TopicInfo,
14
+ StoreStats,
15
+ } from './types/search.js';
16
+
17
+ // Interfaces
18
+ export type { VectorStore } from './store/types.js';
19
+ export type { Embedder } from './indexer/embedder.js';
20
+
21
+ // Store
22
+ export { createSqliteVecStore } from './store/index.js';
23
+
24
+ // Indexer
25
+ export { indexVault, scanVault, chunkDocument, createLocalEmbedder } from './indexer/index.js';
26
+ export type { IndexResult, IndexerOptions } from './indexer/index.js';
27
+
28
+ // Search
29
+ export { createSearchEngine } from './search/index.js';
30
+ export type { SearchEngine } from './search/index.js';
31
+
32
+ // MCP
33
+ export { createMcpServer } from './mcp/index.js';
34
+
35
+ // Pack (Phase 3)
36
+ export { createPack, exportPack, importPack, packToSummary, maskPII } from './pack/index.js';
37
+ export type { KnowledgePack, PackChunk, PackInfo, CreatePackOptions, ImportResult, MaskResult } from './pack/index.js';
38
+
39
+ // API (Phase 2)
40
+ export { createApiServer } from './api/server.js';
41
+ export type { ApiServerOptions } from './api/server.js';
42
+ export type { GraphNode, GraphEdge, Cluster, GraphData, GraphResponse } from './types/graph.js';
43
+
44
+ // Intelligence (Phase 4b)
45
+ export { DecayEngine } from './intelligence/decay-engine.js';
46
+ export type { DecayState, AccessEvent, DecayReport } from './intelligence/types.js';
47
+ export { computeRetrievability, updateStability, estimateInitialStability, elapsedDays } from './intelligence/fsrs.js';
48
+ export { detectDuplicates } from './intelligence/duplicate-detector.js';
49
+ export { detectKnowledgeGaps } from './intelligence/gap-detector.js';
50
+ export { detectContradictions } from './intelligence/contradiction-detector.js';
51
+ export type { ContradictionPair } from './intelligence/contradiction-detector.js';
52
+ export { computeSemanticDrift, findMostDrifted, hashEmbedding } from './intelligence/semantic-versioning.js';
53
+ export type { SemanticChangelog, SemanticVersion } from './intelligence/semantic-versioning.js';
54
+ export { predictKnowledgeGaps } from './intelligence/predictive-gaps.js';
55
+ export type { PredictedGap } from './intelligence/predictive-gaps.js';
56
+ export type { DuplicatePair } from './intelligence/duplicate-detector.js';
57
+ export { generateLearningPath } from './intelligence/learning-path.js';
58
+ export type { LearningPath, LearningItem, LearningPathInput } from './intelligence/learning-path.js';
59
+ export { checkNotifications } from './intelligence/notifications.js';
60
+ export type { Notification, NotificationConfig } from './intelligence/notifications.js';
61
+
62
+ // Multi-Vault
63
+ export { addVault, removeVault, listVaults, getVault, searchAllVaults } from './multi-vault/index.js';
64
+ export type { VaultEntry, CrossVaultSearchResult } from './multi-vault/index.js';
65
+
66
+ // Voice Capture
67
+ export { captureVoice, transcribeAudio, isWhisperAvailable } from './capture/voice.js';
68
+ export type { CaptureResult, CaptureOptions } from './capture/voice.js';
69
+
70
+ // Dashboard + PWA
71
+ export { mountDashboard } from './api/dashboard.js';
72
+ export { mountPWA } from './api/pwa.js';
73
+
74
+ // Agentic Graph
75
+ export { createAgenticGraphTools } from './mcp/tools/agentic-graph.js';
76
+
77
+ // Cloud
78
+ export { syncToCloud, restoreFromCloud, getSyncState, encrypt, decrypt, getOrCreateEncryptionKey } from './cloud/index.js';
79
+ export type { CloudConfig, SyncResult } from './cloud/index.js';
80
+
81
+ // Team
82
+ export { inviteMember, authenticateMember, hasPermission, listMembers, removeMember, createAuthMiddleware, loadTeamConfig, generateToken } from './team/index.js';
83
+ export type { TeamMember, TeamRole, TeamConfig } from './team/index.js';
84
+
85
+ // Pack Marketplace
86
+ export { searchMarketplace, createPackageJson, getPublishInstructions } from './pack/marketplace.js';
87
+ export type { PackListing } from './pack/marketplace.js';
88
+
89
+ // Federation
90
+ export { FederationNode, FederatedSearch, getOrCreateIdentity } from './federation/index.js';
91
+ export type { PeerInfo, FederatedSearchResult, FederationMessage, NodeIdentity } from './federation/index.js';
92
+ export { vouch, revoke, block, getTrustLevel, isBlocked, listTrusted, computeTrustScore } from './federation/trust.js';
93
+ export { loadSharingConfig, saveSharingConfig, isDocumentShareable, sanitizeSnippet, getSharingSummary, addBlockedTag, removeBlockedTag, addBlockedFolder, blockDocument, unblockDocument } from './federation/sharing.js';
94
+ export type { SharingConfig } from './federation/sharing.js';
95
+ export { computeReputation, verifyConsensus, recordInteraction, recordConsistency, recordFeedback, recordConsensus, getReputationBoard, filterByReputation } from './federation/reputation.js';
96
+ export type { ReputationRecord } from './federation/reputation.js';
97
+ export type { TrustEntry } from './federation/trust.js';
98
+ export { addDPNoise, addDPNoiseNormalized, maskSnippet } from './federation/privacy.js';
99
+ export type { DPConfig } from './federation/privacy.js';
100
+ export { getBalance, getAccount, earn, spend, earnForSearchResponse, spendForSearch, getRecentTransactions } from './federation/credits.js';
101
+ export type { CreditAccount, CreditTransaction } from './federation/credits.js';
102
+
103
+ // Plugin SDK
104
+ export { PluginManager } from './plugins/index.js';
105
+ export type { StellavaultPlugin, PluginManifest, PluginEvent, PluginContext } from './plugins/index.js';
106
+ export { WebhookManager } from './plugins/webhooks.js';
107
+ export type { WebhookConfig, WebhookDelivery } from './plugins/webhooks.js';
108
+ export { loadCustomTools } from './mcp/custom-tools.js';
109
+ export type { CustomToolDef, LoadedCustomTool } from './mcp/custom-tools.js';
110
+
111
+ // i18n
112
+ export { t, setLocale, getLocale, detectLocale } from './i18n/index.js';
113
+ export type { Locale } from './i18n/index.js';
114
+
115
+ // Error Recovery
116
+ export { withRetry, StellavaultError, wrapError, errors } from './utils/retry.js';
117
+ export type { RetryOptions } from './utils/retry.js';
118
+
119
+ // Factory — 전체 조립
120
+ import { createSqliteVecStore as _createStore } from './store/index.js';
121
+ import { createLocalEmbedder as _createEmbedder } from './indexer/index.js';
122
+ import { createSearchEngine as _createSearch } from './search/index.js';
123
+ import { createMcpServer as _createMcp } from './mcp/index.js';
124
+
125
+ export function createKnowledgeHub(config: import('./config.js').StellavaultConfig) {
126
+ const embedder = _createEmbedder(config.embedding.localModel);
127
+ const dims = embedder.dimensions;
128
+ const store = _createStore(config.dbPath, dims);
129
+ const searchEngine = _createSearch({ store, embedder, rrfK: config.search.rrfK });
130
+ const mcpServer = _createMcp({ store, searchEngine, vaultPath: config.vaultPath });
131
+
132
+ return { store, embedder, searchEngine, mcpServer, config };
133
+ }
@@ -0,0 +1,180 @@
1
+ // Design Ref: §6.1 — Chunking Strategy (heading 기반 + 오버랩)
2
+
3
+ import type { Chunk } from '../types/chunk.js';
4
+
5
+ export interface ChunkOptions {
6
+ maxTokens: number; // default: 300
7
+ overlap: number; // default: 50
8
+ minTokens: number; // default: 50
9
+ }
10
+
11
+ const DEFAULT_OPTIONS: ChunkOptions = {
12
+ maxTokens: 300,
13
+ overlap: 50,
14
+ minTokens: 50,
15
+ };
16
+
17
+ /**
18
+ * 문서를 heading 기반으로 청킹합니다.
19
+ * Step 1: heading 분할 → Step 2: 길이 검사 → Step 3: 메타데이터 → Step 4: 짧은 청크 병합
20
+ */
21
+ export function chunkDocument(
22
+ documentId: string,
23
+ content: string,
24
+ options: Partial<ChunkOptions> = {},
25
+ ): Chunk[] {
26
+ const opts = { ...DEFAULT_OPTIONS, ...options };
27
+ const lines = content.split('\n');
28
+
29
+ // Step 1: heading 기반 섹션 분할
30
+ const sections = splitByHeadings(lines);
31
+
32
+ // Step 2-3: 길이 검사 + 메타데이터
33
+ const rawChunks: Chunk[] = [];
34
+ for (const section of sections) {
35
+ const tokenCount = estimateTokens(section.content);
36
+
37
+ if (tokenCount <= opts.maxTokens) {
38
+ rawChunks.push(makeChunk(documentId, rawChunks.length, section));
39
+ } else {
40
+ // 긴 섹션은 문장 단위로 재분할 + 오버랩
41
+ const subChunks = splitByTokenLimit(section, opts.maxTokens, opts.overlap);
42
+ for (const sub of subChunks) {
43
+ rawChunks.push(makeChunk(documentId, rawChunks.length, sub));
44
+ }
45
+ }
46
+ }
47
+
48
+ // Step 4: 짧은 청크 병합
49
+ return mergeShortChunks(rawChunks, opts.minTokens);
50
+ }
51
+
52
+ interface Section {
53
+ heading: string;
54
+ content: string;
55
+ startLine: number;
56
+ endLine: number;
57
+ }
58
+
59
+ function splitByHeadings(lines: string[]): Section[] {
60
+ const sections: Section[] = [];
61
+ let currentHeading = '';
62
+ let currentLines: string[] = [];
63
+ let startLine = 0;
64
+
65
+ for (let i = 0; i < lines.length; i++) {
66
+ const line = lines[i];
67
+ if (/^#{1,6}\s/.test(line)) {
68
+ if (currentLines.length > 0) {
69
+ sections.push({
70
+ heading: currentHeading,
71
+ content: currentLines.join('\n').trim(),
72
+ startLine,
73
+ endLine: i - 1,
74
+ });
75
+ }
76
+ currentHeading = line.replace(/^#{1,6}\s+/, '').trim();
77
+ currentLines = [line];
78
+ startLine = i;
79
+ } else {
80
+ currentLines.push(line);
81
+ }
82
+ }
83
+
84
+ if (currentLines.length > 0) {
85
+ sections.push({
86
+ heading: currentHeading,
87
+ content: currentLines.join('\n').trim(),
88
+ startLine,
89
+ endLine: lines.length - 1,
90
+ });
91
+ }
92
+
93
+ return sections.filter(s => s.content.length > 0);
94
+ }
95
+
96
+ function splitByTokenLimit(section: Section, maxTokens: number, overlap: number): Section[] {
97
+ const sentences = section.content.split(/(?<=[.!?。]\s)/);
98
+ const results: Section[] = [];
99
+ let current: string[] = [];
100
+ let currentTokens = 0;
101
+
102
+ for (const sentence of sentences) {
103
+ const sentenceTokens = estimateTokens(sentence);
104
+ if (currentTokens + sentenceTokens > maxTokens && current.length > 0) {
105
+ results.push({
106
+ heading: section.heading,
107
+ content: current.join('').trim(),
108
+ startLine: section.startLine,
109
+ endLine: section.endLine,
110
+ });
111
+ // 오버랩: 마지막 문장들을 다음 청크에 포함
112
+ const overlapText = getOverlapText(current, overlap);
113
+ current = overlapText ? [overlapText, sentence] : [sentence];
114
+ currentTokens = estimateTokens(current.join(''));
115
+ } else {
116
+ current.push(sentence);
117
+ currentTokens += sentenceTokens;
118
+ }
119
+ }
120
+
121
+ if (current.length > 0) {
122
+ results.push({
123
+ heading: section.heading,
124
+ content: current.join('').trim(),
125
+ startLine: section.startLine,
126
+ endLine: section.endLine,
127
+ });
128
+ }
129
+
130
+ return results;
131
+ }
132
+
133
+ function getOverlapText(sentences: string[], overlapTokens: number): string {
134
+ let tokens = 0;
135
+ const overlap: string[] = [];
136
+ for (let i = sentences.length - 1; i >= 0; i--) {
137
+ const t = estimateTokens(sentences[i]);
138
+ if (tokens + t > overlapTokens) break;
139
+ overlap.unshift(sentences[i]);
140
+ tokens += t;
141
+ }
142
+ return overlap.join('');
143
+ }
144
+
145
+ function mergeShortChunks(chunks: Chunk[], minTokens: number): Chunk[] {
146
+ if (chunks.length <= 1) return chunks;
147
+ const result: Chunk[] = [];
148
+
149
+ for (const chunk of chunks) {
150
+ if (result.length > 0 && chunk.tokenCount < minTokens) {
151
+ const prev = result[result.length - 1];
152
+ prev.content += '\n\n' + chunk.content;
153
+ prev.endLine = chunk.endLine;
154
+ prev.tokenCount += chunk.tokenCount;
155
+ } else {
156
+ result.push({ ...chunk });
157
+ }
158
+ }
159
+
160
+ return result;
161
+ }
162
+
163
+ function makeChunk(documentId: string, index: number, section: Section): Chunk {
164
+ return {
165
+ id: `${documentId}#${index}`,
166
+ documentId,
167
+ content: section.content,
168
+ heading: section.heading,
169
+ startLine: section.startLine,
170
+ endLine: section.endLine,
171
+ tokenCount: estimateTokens(section.content),
172
+ };
173
+ }
174
+
175
+ /** 간단한 토큰 수 추정 (영어: ~4chars/token, 한국어: ~2chars/token) */
176
+ export function estimateTokens(text: string): number {
177
+ const koreanChars = (text.match(/[가-힣]/g) || []).length;
178
+ const otherChars = text.length - koreanChars;
179
+ return Math.ceil(koreanChars / 2 + otherChars / 4);
180
+ }
@@ -0,0 +1,9 @@
1
+ // Design Ref: §3.2 — 교체 가능 설계 (Embedder 인터페이스)
2
+
3
+ export interface Embedder {
4
+ initialize(): Promise<void>;
5
+ embed(text: string): Promise<number[]>;
6
+ embedBatch(texts: string[]): Promise<number[][]>;
7
+ readonly dimensions: number;
8
+ readonly modelName: string;
9
+ }