stellavault 0.3.0 → 0.4.1

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 (55) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +109 -119
  3. package/package.json +2 -2
  4. package/packages/cli/dist/commands/ask-cmd.d.ts +4 -0
  5. package/packages/cli/dist/commands/ask-cmd.js +35 -0
  6. package/packages/cli/dist/commands/autopilot-cmd.d.ts +4 -0
  7. package/packages/cli/dist/commands/autopilot-cmd.js +76 -0
  8. package/packages/cli/dist/commands/compile-cmd.d.ts +6 -0
  9. package/packages/cli/dist/commands/compile-cmd.js +30 -0
  10. package/packages/cli/dist/commands/digest-cmd.d.ts +1 -0
  11. package/packages/cli/dist/commands/digest-cmd.js +57 -0
  12. package/packages/cli/dist/commands/draft-cmd.d.ts +5 -0
  13. package/packages/cli/dist/commands/draft-cmd.js +99 -0
  14. package/packages/cli/dist/commands/fleeting-cmd.d.ts +4 -0
  15. package/packages/cli/dist/commands/fleeting-cmd.js +45 -0
  16. package/packages/cli/dist/commands/graph-cmd.js +13 -1
  17. package/packages/cli/dist/commands/ingest-cmd.d.ts +9 -0
  18. package/packages/cli/dist/commands/ingest-cmd.js +161 -0
  19. package/packages/cli/dist/commands/init-cmd.js +39 -1
  20. package/packages/cli/dist/commands/lint-cmd.d.ts +2 -0
  21. package/packages/cli/dist/commands/lint-cmd.js +61 -0
  22. package/packages/cli/dist/index.js +53 -1
  23. package/packages/cli/package.json +1 -1
  24. package/packages/core/dist/api/server.js +393 -0
  25. package/packages/core/dist/config.d.ts +8 -0
  26. package/packages/core/dist/config.js +9 -1
  27. package/packages/core/dist/i18n/note-strings.d.ts +5 -0
  28. package/packages/core/dist/i18n/note-strings.js +94 -0
  29. package/packages/core/dist/index.d.ts +11 -2
  30. package/packages/core/dist/index.js +6 -1
  31. package/packages/core/dist/intelligence/ask-engine.d.ts +23 -0
  32. package/packages/core/dist/intelligence/ask-engine.js +108 -0
  33. package/packages/core/dist/intelligence/draft-generator.d.ts +19 -0
  34. package/packages/core/dist/intelligence/draft-generator.js +161 -0
  35. package/packages/core/dist/intelligence/file-extractors.d.ts +18 -0
  36. package/packages/core/dist/intelligence/file-extractors.js +127 -0
  37. package/packages/core/dist/intelligence/ingest-pipeline.d.ts +32 -0
  38. package/packages/core/dist/intelligence/ingest-pipeline.js +209 -0
  39. package/packages/core/dist/intelligence/knowledge-lint.d.ts +27 -0
  40. package/packages/core/dist/intelligence/knowledge-lint.js +132 -0
  41. package/packages/core/dist/intelligence/wiki-compiler.d.ts +30 -0
  42. package/packages/core/dist/intelligence/wiki-compiler.js +222 -0
  43. package/packages/core/dist/intelligence/youtube-extractor.d.ts +29 -0
  44. package/packages/core/dist/intelligence/youtube-extractor.js +311 -0
  45. package/packages/core/dist/intelligence/zettelkasten.d.ts +59 -0
  46. package/packages/core/dist/intelligence/zettelkasten.js +234 -0
  47. package/packages/core/dist/mcp/server.d.ts +2 -0
  48. package/packages/core/dist/mcp/server.js +24 -1
  49. package/packages/core/dist/mcp/tools/agentic-graph.d.ts +6 -0
  50. package/packages/core/dist/mcp/tools/agentic-graph.js +35 -7
  51. package/packages/core/dist/mcp/tools/ask.d.ts +29 -0
  52. package/packages/core/dist/mcp/tools/ask.js +40 -0
  53. package/packages/core/dist/mcp/tools/generate-draft.d.ts +34 -0
  54. package/packages/core/dist/mcp/tools/generate-draft.js +120 -0
  55. package/packages/core/package.json +21 -2
@@ -41,6 +41,43 @@ export function createApiServer(options) {
41
41
  res.status(500).json({ error: 'Internal server error' });
42
42
  }
43
43
  });
44
+ // POST /api/reindex — 웹에서 인덱싱 트리거
45
+ let isReindexing = false;
46
+ app.post('/api/reindex', async (_req, res) => {
47
+ if (isReindexing) {
48
+ res.json({ success: false, error: '인덱싱이 이미 진행 중입니다' });
49
+ return;
50
+ }
51
+ isReindexing = true;
52
+ try {
53
+ const indexer = await import('../indexer/index.js');
54
+ const embedder = indexer.createLocalEmbedder('all-MiniLM-L6-v2');
55
+ await embedder.initialize();
56
+ const result = await indexer.indexVault(vaultPath, {
57
+ store,
58
+ embedder,
59
+ onProgress: (current, total) => {
60
+ if (current % 50 === 0)
61
+ console.error(`[reindex] ${current}/${total}`);
62
+ },
63
+ });
64
+ // 그래프 캐시 리셋
65
+ graphCaches.clear();
66
+ res.json({
67
+ success: true,
68
+ indexed: result.indexed,
69
+ skipped: result.skipped,
70
+ chunks: result.totalChunks,
71
+ });
72
+ }
73
+ catch (err) {
74
+ console.error('[reindex]', err);
75
+ res.status(500).json({ error: `Reindex failed: ${err?.message ?? String(err)}` });
76
+ }
77
+ finally {
78
+ isReindexing = false;
79
+ }
80
+ });
44
81
  // GET /api/search?q=&limit=
45
82
  app.get('/api/search', async (req, res) => {
46
83
  try {
@@ -62,7 +99,10 @@ export function createApiServer(options) {
62
99
  results: results.map(r => ({
63
100
  documentId: r.document.id,
64
101
  title: r.document.title,
102
+ filePath: r.document.filePath,
65
103
  score: Math.round(r.score * 1000) / 1000,
104
+ snippet: r.chunk?.content?.substring(0, 200) ?? '',
105
+ tags: r.document.tags ?? [],
66
106
  highlights: r.highlights,
67
107
  })),
68
108
  query,
@@ -213,6 +253,359 @@ export function createApiServer(options) {
213
253
  res.status(500).json({ error: 'Internal server error' });
214
254
  }
215
255
  });
256
+ // PUT /api/document/:id — 노트 편집 (vault 파일 직접 수정)
257
+ app.put('/api/document/:id', async (req, res) => {
258
+ try {
259
+ const { id } = req.params;
260
+ const { title, content, tags } = req.body;
261
+ const doc = await store.getDocument(id);
262
+ if (!doc) {
263
+ res.status(404).json({ error: 'Document not found' });
264
+ return;
265
+ }
266
+ const { resolve, join } = await import('node:path');
267
+ const { writeFileSync, readFileSync } = await import('node:fs');
268
+ const fullPath = resolve(vaultPath, doc.filePath);
269
+ // path traversal 방지
270
+ if (!fullPath.startsWith(resolve(vaultPath))) {
271
+ res.status(403).json({ error: 'Access denied' });
272
+ return;
273
+ }
274
+ // 기존 파일 읽기
275
+ const existing = readFileSync(fullPath, 'utf-8');
276
+ // frontmatter 업데이트
277
+ let updated = existing;
278
+ if (title && title !== doc.title) {
279
+ updated = updated.replace(/^title:\s*.+$/m, `title: "${title.replace(/"/g, "''")}"`);
280
+ }
281
+ if (tags) {
282
+ const tagStr = `tags: [${tags.map((t) => `"${t}"`).join(', ')}]`;
283
+ if (updated.match(/^tags:\s*.+$/m)) {
284
+ updated = updated.replace(/^tags:\s*.+$/m, tagStr);
285
+ }
286
+ }
287
+ if (content !== undefined) {
288
+ // frontmatter 이후 본문 교체
289
+ const fmEnd = updated.indexOf('---', 4);
290
+ if (fmEnd > 0) {
291
+ const fm = updated.substring(0, fmEnd + 3);
292
+ updated = fm + '\n\n' + content;
293
+ }
294
+ else {
295
+ updated = content;
296
+ }
297
+ }
298
+ writeFileSync(fullPath, updated, 'utf-8');
299
+ // DB 업데이트
300
+ await store.upsertDocument({
301
+ ...doc,
302
+ title: title ?? doc.title,
303
+ content: content ?? doc.content,
304
+ tags: tags ?? doc.tags,
305
+ lastModified: new Date().toISOString(),
306
+ });
307
+ res.json({ success: true, id, title: title ?? doc.title });
308
+ }
309
+ catch (err) {
310
+ console.error('[edit]', err);
311
+ res.status(500).json({ error: err?.message ?? 'Edit failed' });
312
+ }
313
+ });
314
+ // DELETE /api/document/:id — 노트 삭제 (vault 파일 + DB)
315
+ app.delete('/api/document/:id', async (req, res) => {
316
+ try {
317
+ const { id } = req.params;
318
+ const doc = await store.getDocument(id);
319
+ if (!doc) {
320
+ res.status(404).json({ error: 'Document not found' });
321
+ return;
322
+ }
323
+ const { resolve } = await import('node:path');
324
+ const { unlinkSync, existsSync } = await import('node:fs');
325
+ const fullPath = resolve(vaultPath, doc.filePath);
326
+ // path traversal 방지
327
+ if (!fullPath.startsWith(resolve(vaultPath))) {
328
+ res.status(403).json({ error: 'Access denied' });
329
+ return;
330
+ }
331
+ // 파일 삭제
332
+ if (existsSync(fullPath)) {
333
+ unlinkSync(fullPath);
334
+ }
335
+ // DB에서 삭제
336
+ await store.deleteByDocumentId(id);
337
+ res.json({ success: true, id, deleted: doc.filePath });
338
+ }
339
+ catch (err) {
340
+ console.error('[delete]', err);
341
+ res.status(500).json({ error: err?.message ?? 'Delete failed' });
342
+ }
343
+ });
344
+ // GET /api/ask — 웹 UI Q&A
345
+ app.get('/api/ask', async (req, res) => {
346
+ try {
347
+ const question = String(req.query.q || '');
348
+ if (!question) {
349
+ res.json({ question: '', answer: '', sources: [] });
350
+ return;
351
+ }
352
+ const { askVault } = await import('../intelligence/ask-engine.js');
353
+ const result = await askVault(searchEngine, question, {
354
+ limit: 10,
355
+ save: req.query.save === 'true',
356
+ vaultPath,
357
+ });
358
+ res.json(result);
359
+ }
360
+ catch (err) {
361
+ console.error(err);
362
+ res.status(500).json({ error: 'Ask failed' });
363
+ }
364
+ });
365
+ // POST /api/ingest — 웹 UI에서 URL/텍스트 인제스트
366
+ app.post('/api/ingest', async (req, res) => {
367
+ try {
368
+ const { input, type, tags, title, stage, locale } = req.body;
369
+ // 노트 언어 설정
370
+ if (locale) {
371
+ const { setNoteLocale } = await import('../i18n/note-strings.js');
372
+ setNoteLocale(locale);
373
+ }
374
+ if (!input || typeof input !== 'string') {
375
+ res.status(400).json({ error: 'input is required' });
376
+ return;
377
+ }
378
+ // URL인 경우 제목+내용 가져오기
379
+ let content = input;
380
+ let autoTitle = title;
381
+ let autoTags = tags ?? [];
382
+ let autoStage = stage ?? 'fleeting';
383
+ const isYouTube = /youtube\.com\/watch|youtu\.be\//.test(input);
384
+ if (isYouTube) {
385
+ // YouTube 전용: 자막 + 메타데이터 추출
386
+ try {
387
+ const { extractYouTubeContent, formatYouTubeNote } = await import('../intelligence/youtube-extractor.js');
388
+ const ytContent = await extractYouTubeContent(input);
389
+ autoTitle = ytContent.title;
390
+ autoTags = [...new Set(['youtube', ...ytContent.tags, ...(tags ?? [])])];
391
+ autoStage = 'literature';
392
+ // formatYouTubeNote는 frontmatter 없이 본문만 생성 → pipeline이 frontmatter 담당
393
+ content = formatYouTubeNote(ytContent);
394
+ }
395
+ catch (ytErr) {
396
+ console.error('[ingest] YouTube extraction failed, falling back to basic:', ytErr instanceof Error ? ytErr.message : ytErr);
397
+ // 폴백: 기본 HTML 추출
398
+ try {
399
+ const resp = await fetch(input, { signal: AbortSignal.timeout(8000) });
400
+ const html = await resp.text();
401
+ const titleMatch = html.match(/<title[^>]*>([^<]+)<\/title>/i);
402
+ if (titleMatch && !autoTitle)
403
+ autoTitle = titleMatch[1].trim();
404
+ content = input + '\n\n' + html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 5000);
405
+ }
406
+ catch { /* URL만 저장 */ }
407
+ }
408
+ }
409
+ else if (input.startsWith('http')) {
410
+ // 일반 웹페이지
411
+ try {
412
+ const resp = await fetch(input, { signal: AbortSignal.timeout(8000) });
413
+ const html = await resp.text();
414
+ const titleMatch = html.match(/<title[^>]*>([^<]+)<\/title>/i);
415
+ if (titleMatch && !autoTitle)
416
+ autoTitle = titleMatch[1].trim();
417
+ const text = html
418
+ .replace(/<script[\s\S]*?<\/script>/gi, '')
419
+ .replace(/<style[\s\S]*?<\/style>/gi, '')
420
+ .replace(/<[^>]+>/g, ' ')
421
+ .replace(/&nbsp;/g, ' ')
422
+ .replace(/&amp;/g, '&')
423
+ .replace(/&lt;/g, '<')
424
+ .replace(/&gt;/g, '>')
425
+ .replace(/\s+/g, ' ')
426
+ .trim()
427
+ .slice(0, 5000);
428
+ content = input + '\n\n' + text;
429
+ }
430
+ catch { /* URL만 저장 */ }
431
+ }
432
+ const { ingest } = await import('../intelligence/ingest-pipeline.js');
433
+ const result = ingest(vaultPath, {
434
+ type: type ?? (isYouTube ? 'youtube' : input.startsWith('http') ? 'url' : 'text'),
435
+ content,
436
+ tags: autoTags,
437
+ title: autoTitle,
438
+ stage: autoStage,
439
+ source: input.startsWith('http') ? input : undefined,
440
+ });
441
+ // 저장 후 자동 인덱싱 (그래프에 바로 반영)
442
+ try {
443
+ const fullPath = require('node:path').resolve(vaultPath, result.savedTo);
444
+ const { chunkDocument } = await import('../indexer/index.js');
445
+ const doc = {
446
+ id: require('node:crypto').createHash('sha256').update(result.savedTo).digest('hex').slice(0, 16),
447
+ filePath: result.savedTo,
448
+ title: result.title,
449
+ content: content,
450
+ frontmatter: {},
451
+ tags: result.tags,
452
+ lastModified: new Date().toISOString(),
453
+ contentHash: '',
454
+ source: 'ingest',
455
+ type: result.stage,
456
+ };
457
+ await store.upsertDocument(doc);
458
+ }
459
+ catch (indexErr) {
460
+ console.error('[ingest] Auto-index failed:', indexErr instanceof Error ? indexErr.message : indexErr);
461
+ }
462
+ res.json({
463
+ success: true,
464
+ savedTo: result.savedTo,
465
+ stage: result.stage,
466
+ title: result.title,
467
+ indexCode: result.indexCode,
468
+ tags: result.tags,
469
+ wordCount: result.wordCount,
470
+ });
471
+ }
472
+ catch (err) {
473
+ console.error(err);
474
+ res.status(500).json({ error: 'Ingest failed' });
475
+ }
476
+ });
477
+ // POST /api/ingest/file — 웹 UI에서 파일 드래그앤드롭 인제스트
478
+ app.post('/api/ingest/file', async (req, res) => {
479
+ try {
480
+ const multer = (await import('multer')).default;
481
+ const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 50 * 1024 * 1024 } });
482
+ upload.single('file')(req, res, async (uploadErr) => {
483
+ if (uploadErr) {
484
+ res.status(400).json({ error: uploadErr.message || 'Upload failed' });
485
+ return;
486
+ }
487
+ const file = req.file;
488
+ if (!file) {
489
+ res.status(400).json({ error: 'No file provided' });
490
+ return;
491
+ }
492
+ // MIME 화이트리스트
493
+ const allowedMimes = new Set([
494
+ 'application/pdf',
495
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
496
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
497
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
498
+ 'application/vnd.ms-excel',
499
+ 'text/plain', 'text/markdown', 'text/csv',
500
+ ]);
501
+ if (!allowedMimes.has(file.mimetype)) {
502
+ res.status(400).json({ error: `Unsupported file type: ${file.mimetype}` });
503
+ return;
504
+ }
505
+ try {
506
+ const { writeFileSync, unlinkSync } = await import('node:fs');
507
+ const { join } = await import('node:path');
508
+ const { tmpdir } = await import('node:os');
509
+ const tmpPath = join(tmpdir(), `sv-upload-${Date.now()}-${file.originalname}`);
510
+ // 임시 파일 저장 → 파서가 파일 경로 필요
511
+ writeFileSync(tmpPath, file.buffer);
512
+ const { extractFileContent, isBinaryFormat } = await import('../intelligence/file-extractors.js');
513
+ const ext = file.originalname.split('.').pop()?.toLowerCase() ?? '';
514
+ const binaryExts = new Set(['pdf', 'docx', 'pptx', 'xlsx', 'xls']);
515
+ let content;
516
+ let extractedTitle;
517
+ let formatTag = ext;
518
+ if (binaryExts.has(ext)) {
519
+ const extracted = await extractFileContent(tmpPath);
520
+ content = extracted.text;
521
+ extractedTitle = extracted.metadata.title;
522
+ formatTag = extracted.sourceFormat;
523
+ }
524
+ else {
525
+ content = file.buffer.toString('utf-8');
526
+ }
527
+ // 임시 파일 삭제
528
+ try {
529
+ unlinkSync(tmpPath);
530
+ }
531
+ catch { /* ok */ }
532
+ // locale 설정
533
+ const locale = req.body?.locale;
534
+ if (locale) {
535
+ const { setNoteLocale } = await import('../i18n/note-strings.js');
536
+ setNoteLocale(locale);
537
+ }
538
+ const tags = req.body?.tags ? (Array.isArray(req.body.tags) ? req.body.tags : req.body.tags.split(',').map((t) => t.trim())) : [];
539
+ const { ingest } = await import('../intelligence/ingest-pipeline.js');
540
+ const result = ingest(vaultPath, {
541
+ type: formatTag,
542
+ content,
543
+ tags: [...tags, formatTag],
544
+ title: req.body?.title ?? extractedTitle,
545
+ stage: 'fleeting',
546
+ source: file.originalname,
547
+ });
548
+ // 자동 인덱싱
549
+ try {
550
+ const doc = {
551
+ id: require('node:crypto').createHash('sha256').update(result.savedTo).digest('hex').slice(0, 16),
552
+ filePath: result.savedTo,
553
+ title: result.title,
554
+ content,
555
+ frontmatter: {},
556
+ tags: result.tags,
557
+ lastModified: new Date().toISOString(),
558
+ contentHash: '',
559
+ source: 'upload',
560
+ type: result.stage,
561
+ };
562
+ await store.upsertDocument(doc);
563
+ }
564
+ catch (indexErr) {
565
+ console.error('[ingest/file] Auto-index failed:', indexErr instanceof Error ? indexErr.message : indexErr);
566
+ }
567
+ res.json({
568
+ success: true,
569
+ savedTo: result.savedTo,
570
+ stage: result.stage,
571
+ title: result.title,
572
+ indexCode: result.indexCode,
573
+ tags: result.tags,
574
+ wordCount: result.wordCount,
575
+ });
576
+ }
577
+ catch (err) {
578
+ res.status(500).json({ error: err instanceof Error ? err.message : 'Processing failed' });
579
+ }
580
+ });
581
+ }
582
+ catch (err) {
583
+ res.status(500).json({ error: 'File upload initialization failed' });
584
+ }
585
+ });
586
+ // GET /api/recent — 최근 저장된 노트 목록
587
+ app.get('/api/recent', async (_req, res) => {
588
+ try {
589
+ const docs = await store.getAllDocuments();
590
+ const recent = docs
591
+ .filter(d => d.lastModified)
592
+ .sort((a, b) => new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime())
593
+ .slice(0, 20)
594
+ .map(d => ({
595
+ id: d.id,
596
+ title: d.title,
597
+ filePath: d.filePath,
598
+ lastModified: d.lastModified,
599
+ tags: d.tags.slice(0, 5),
600
+ type: d.type ?? 'note',
601
+ }));
602
+ res.json({ recent });
603
+ }
604
+ catch (err) {
605
+ console.error(err);
606
+ res.status(500).json({ error: 'Failed' });
607
+ }
608
+ });
216
609
  // GET /api/heatmap — Design Ref: §2.2 — 지식 히트맵 활동 점수
217
610
  app.get('/api/heatmap', async (_req, res) => {
218
611
  try {
@@ -1,6 +1,13 @@
1
+ export interface FolderNames {
2
+ fleeting: string;
3
+ literature: string;
4
+ permanent: string;
5
+ wiki: string;
6
+ }
1
7
  export interface StellavaultConfig {
2
8
  vaultPath: string;
3
9
  dbPath: string;
10
+ folders: FolderNames;
4
11
  embedding: {
5
12
  model: 'local' | 'openai';
6
13
  localModel: string;
@@ -19,6 +26,7 @@ export interface StellavaultConfig {
19
26
  port: number;
20
27
  };
21
28
  }
29
+ export declare const DEFAULT_FOLDERS: FolderNames;
22
30
  /**
23
31
  * .stellavault.json 파일을 찾아 로드합니다.
24
32
  * 탐색 순서: cwd → home directory → defaults
@@ -2,12 +2,19 @@
2
2
  import { readFileSync, existsSync } from 'node:fs';
3
3
  import { resolve, join } from 'node:path';
4
4
  import { homedir } from 'node:os';
5
+ export const DEFAULT_FOLDERS = {
6
+ fleeting: 'raw',
7
+ literature: '_literature',
8
+ permanent: '_permanent',
9
+ wiki: '_wiki',
10
+ };
5
11
  const DEFAULT_CONFIG = {
6
12
  vaultPath: '',
7
13
  dbPath: join(homedir(), '.stellavault', 'index.db'),
14
+ folders: { ...DEFAULT_FOLDERS },
8
15
  embedding: {
9
16
  model: 'local',
10
- localModel: 'all-MiniLM-L6-v2',
17
+ localModel: 'paraphrase-multilingual-MiniLM-L12-v2',
11
18
  },
12
19
  chunking: {
13
20
  maxTokens: 300,
@@ -46,6 +53,7 @@ function mergeConfig(defaults, overrides) {
46
53
  return {
47
54
  vaultPath: overrides.vaultPath ?? defaults.vaultPath,
48
55
  dbPath: overrides.dbPath ?? defaults.dbPath,
56
+ folders: { ...defaults.folders, ...overrides.folders },
49
57
  embedding: { ...defaults.embedding, ...overrides.embedding },
50
58
  chunking: { ...defaults.chunking, ...overrides.chunking },
51
59
  search: { ...defaults.search, ...overrides.search },
@@ -0,0 +1,5 @@
1
+ export type NoteLocale = 'en' | 'ko' | 'ja' | 'zh';
2
+ export declare function setNoteLocale(locale: NoteLocale): void;
3
+ export declare function getNoteLocale(): NoteLocale;
4
+ export declare function nt(key: string): string;
5
+ //# sourceMappingURL=note-strings.d.ts.map
@@ -0,0 +1,94 @@
1
+ // 저장 노트용 다국어 문자열 — 사용자 언어에 따라 노트 섹션 제목 결정
2
+ const strings = {
3
+ en: {
4
+ 'summary': 'Summary',
5
+ 'description': 'Description',
6
+ 'transcript': 'Transcript',
7
+ 'views': 'Views',
8
+ 'relatedDocs': 'Related Documents',
9
+ 'relatedConcepts': 'Related Concepts',
10
+ 'relatedTags': 'Related Tags',
11
+ 'source': 'Source',
12
+ 'articles': 'Articles',
13
+ 'concepts': 'Concepts',
14
+ 'exploreFurther': 'Explore Further',
15
+ 'noResults': 'No results found',
16
+ 'tryDifferent': 'Try different keywords or create a note on this topic.',
17
+ 'digDeeper': 'Dig deeper',
18
+ 'findGaps': 'Find knowledge gaps',
19
+ 'thisConceptAppears': 'This concept appears in',
20
+ 'documents': 'documents',
21
+ 'compiledFrom': 'Compiled',
22
+ 'wikiArticlesFrom': 'wiki articles from',
23
+ 'sourceDocuments': 'source documents',
24
+ 'staleWarning': "documents haven't been modified in 6+ months. Run `stellavault decay` to review.",
25
+ 'gapsFound': 'knowledge gaps found. Start filling the most critical ones.',
26
+ 'duplicatesFound': 'duplicate documents detected. Run `stellavault duplicates` to merge.',
27
+ 'isolatedFound': 'isolated notes found. Add tags or links to connect them.',
28
+ 'healthy': 'Your knowledge base is healthy! Keep it up.',
29
+ 'youtubeVideo': 'YouTube video',
30
+ },
31
+ ko: {
32
+ 'summary': '핵심 요약',
33
+ 'description': '설명',
34
+ 'transcript': '영상 내용',
35
+ 'views': '조회수',
36
+ 'relatedDocs': '관련 문서',
37
+ 'relatedConcepts': '관련 개념',
38
+ 'relatedTags': '관련 태그',
39
+ 'source': '원본',
40
+ 'articles': '문서 목록',
41
+ 'concepts': '개념',
42
+ 'exploreFurther': '더 알아보기',
43
+ 'noResults': '검색 결과 없음',
44
+ 'tryDifferent': '다른 키워드로 검색하거나, 이 주제에 대한 노트를 작성해보세요.',
45
+ 'digDeeper': '더 깊이 알아보기',
46
+ 'findGaps': '빠진 지식 찾기',
47
+ 'thisConceptAppears': '이 개념이 등장하는 문서',
48
+ 'documents': '개',
49
+ 'compiledFrom': '컴파일됨',
50
+ 'wikiArticlesFrom': '개 위키 문서 생성 (원본',
51
+ 'sourceDocuments': '개)',
52
+ 'staleWarning': '개 문서가 6개월 이상 수정되지 않았어요. `stellavault decay`로 확인하세요.',
53
+ 'gapsFound': '개 지식 빈틈이 발견됐어요. 중요한 것부터 채워보세요.',
54
+ 'duplicatesFound': '개 겹치는 문서가 있어요. `stellavault duplicates`로 합치세요.',
55
+ 'isolatedFound': '개 떨어진 노트가 있어요. 태그나 링크로 연결하세요.',
56
+ 'healthy': '지식이 건강해요! 계속 유지하세요.',
57
+ 'youtubeVideo': 'YouTube 영상',
58
+ },
59
+ ja: {
60
+ 'summary': '要約',
61
+ 'description': '説明',
62
+ 'transcript': '内容',
63
+ 'views': '再生回数',
64
+ 'relatedDocs': '関連ドキュメント',
65
+ 'relatedConcepts': '関連コンセプト',
66
+ 'source': 'ソース',
67
+ 'noResults': '検索結果なし',
68
+ 'healthy': 'ナレッジベースは健全です!',
69
+ 'youtubeVideo': 'YouTube動画',
70
+ },
71
+ zh: {
72
+ 'summary': '摘要',
73
+ 'description': '描述',
74
+ 'transcript': '内容',
75
+ 'views': '观看次数',
76
+ 'relatedDocs': '相关文档',
77
+ 'relatedConcepts': '相关概念',
78
+ 'source': '来源',
79
+ 'noResults': '未找到结果',
80
+ 'healthy': '知识库很健康!',
81
+ 'youtubeVideo': 'YouTube视频',
82
+ },
83
+ };
84
+ let currentNoteLocale = 'en';
85
+ export function setNoteLocale(locale) {
86
+ currentNoteLocale = locale;
87
+ }
88
+ export function getNoteLocale() {
89
+ return currentNoteLocale;
90
+ }
91
+ export function nt(key) {
92
+ return strings[currentNoteLocale]?.[key] ?? strings.en[key] ?? key;
93
+ }
94
+ //# sourceMappingURL=note-strings.js.map
@@ -1,5 +1,5 @@
1
- export { loadConfig } from './config.js';
2
- export type { StellavaultConfig } from './config.js';
1
+ export { loadConfig, DEFAULT_FOLDERS } from './config.js';
2
+ export type { StellavaultConfig, FolderNames } from './config.js';
3
3
  export type { Document } from './types/document.js';
4
4
  export type { Chunk, ScoredChunk } from './types/chunk.js';
5
5
  export type { SearchResult, SearchOptions, TopicInfo, StoreStats, } from './types/search.js';
@@ -29,6 +29,15 @@ export { predictKnowledgeGaps } from './intelligence/predictive-gaps.js';
29
29
  export type { PredictedGap } from './intelligence/predictive-gaps.js';
30
30
  export type { DuplicatePair } from './intelligence/duplicate-detector.js';
31
31
  export { generateLearningPath } from './intelligence/learning-path.js';
32
+ export { askVault } from './intelligence/ask-engine.js';
33
+ export { compileWiki, scanRawDirectory, extractConcepts } from './intelligence/wiki-compiler.js';
34
+ export { lintKnowledge } from './intelligence/knowledge-lint.js';
35
+ export { scanFrontmatter, generateNextIndexCode, assignIndexCodes, getInboxItems, archiveFile, checkAtomicity, detectOrphansAndBrokenLinks, } from './intelligence/zettelkasten.js';
36
+ export type { FrontmatterEntry } from './intelligence/zettelkasten.js';
37
+ export { ingest, ingestBatch, promoteNote } from './intelligence/ingest-pipeline.js';
38
+ export type { IngestInput, IngestResult, NoteStage } from './intelligence/ingest-pipeline.js';
39
+ export type { LintResult, LintIssue } from './intelligence/knowledge-lint.js';
40
+ export type { AskResult } from './intelligence/ask-engine.js';
32
41
  export type { LearningPath, LearningItem, LearningPathInput } from './intelligence/learning-path.js';
33
42
  export { checkNotifications } from './intelligence/notifications.js';
34
43
  export type { Notification, NotificationConfig } from './intelligence/notifications.js';
@@ -1,6 +1,6 @@
1
1
  // Design Ref: §4.2 — Core Internal API (Facade)
2
2
  // Design Ref: §9.3 — Dependency Injection Pattern
3
- export { loadConfig } from './config.js';
3
+ export { loadConfig, DEFAULT_FOLDERS } from './config.js';
4
4
  // Store
5
5
  export { createSqliteVecStore } from './store/index.js';
6
6
  // Indexer
@@ -22,6 +22,11 @@ export { detectContradictions } from './intelligence/contradiction-detector.js';
22
22
  export { computeSemanticDrift, findMostDrifted, hashEmbedding } from './intelligence/semantic-versioning.js';
23
23
  export { predictKnowledgeGaps } from './intelligence/predictive-gaps.js';
24
24
  export { generateLearningPath } from './intelligence/learning-path.js';
25
+ export { askVault } from './intelligence/ask-engine.js';
26
+ export { compileWiki, scanRawDirectory, extractConcepts } from './intelligence/wiki-compiler.js';
27
+ export { lintKnowledge } from './intelligence/knowledge-lint.js';
28
+ export { scanFrontmatter, generateNextIndexCode, assignIndexCodes, getInboxItems, archiveFile, checkAtomicity, detectOrphansAndBrokenLinks, } from './intelligence/zettelkasten.js';
29
+ export { ingest, ingestBatch, promoteNote } from './intelligence/ingest-pipeline.js';
25
30
  export { checkNotifications } from './intelligence/notifications.js';
26
31
  // Multi-Vault
27
32
  export { addVault, removeVault, listVaults, getVault, searchAllVaults } from './multi-vault/index.js';
@@ -0,0 +1,23 @@
1
+ import type { SearchEngine } from '../search/index.js';
2
+ export interface AskResult {
3
+ question: string;
4
+ answer: string;
5
+ sources: Array<{
6
+ title: string;
7
+ filePath: string;
8
+ score: number;
9
+ snippet: string;
10
+ }>;
11
+ savedTo: string | null;
12
+ }
13
+ /**
14
+ * 질문에 대해 vault를 검색하고 구조화된 답변을 생성.
15
+ * LLM 없이 검색 결과를 구조화하는 버전 (LLM 연동은 MCP ask tool에서 처리).
16
+ */
17
+ export declare function askVault(searchEngine: SearchEngine, question: string, options?: {
18
+ limit?: number;
19
+ save?: boolean;
20
+ vaultPath?: string;
21
+ outputDir?: string;
22
+ }): Promise<AskResult>;
23
+ //# sourceMappingURL=ask-engine.d.ts.map