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.
- package/CHANGELOG.md +84 -0
- package/README.md +109 -119
- package/package.json +2 -2
- package/packages/cli/dist/commands/ask-cmd.d.ts +4 -0
- package/packages/cli/dist/commands/ask-cmd.js +35 -0
- package/packages/cli/dist/commands/autopilot-cmd.d.ts +4 -0
- package/packages/cli/dist/commands/autopilot-cmd.js +76 -0
- package/packages/cli/dist/commands/compile-cmd.d.ts +6 -0
- package/packages/cli/dist/commands/compile-cmd.js +30 -0
- package/packages/cli/dist/commands/digest-cmd.d.ts +1 -0
- package/packages/cli/dist/commands/digest-cmd.js +57 -0
- package/packages/cli/dist/commands/draft-cmd.d.ts +5 -0
- package/packages/cli/dist/commands/draft-cmd.js +99 -0
- package/packages/cli/dist/commands/fleeting-cmd.d.ts +4 -0
- package/packages/cli/dist/commands/fleeting-cmd.js +45 -0
- package/packages/cli/dist/commands/graph-cmd.js +13 -1
- package/packages/cli/dist/commands/ingest-cmd.d.ts +9 -0
- package/packages/cli/dist/commands/ingest-cmd.js +161 -0
- package/packages/cli/dist/commands/init-cmd.js +39 -1
- package/packages/cli/dist/commands/lint-cmd.d.ts +2 -0
- package/packages/cli/dist/commands/lint-cmd.js +61 -0
- package/packages/cli/dist/index.js +53 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/dist/api/server.js +393 -0
- package/packages/core/dist/config.d.ts +8 -0
- package/packages/core/dist/config.js +9 -1
- package/packages/core/dist/i18n/note-strings.d.ts +5 -0
- package/packages/core/dist/i18n/note-strings.js +94 -0
- package/packages/core/dist/index.d.ts +11 -2
- package/packages/core/dist/index.js +6 -1
- package/packages/core/dist/intelligence/ask-engine.d.ts +23 -0
- package/packages/core/dist/intelligence/ask-engine.js +108 -0
- package/packages/core/dist/intelligence/draft-generator.d.ts +19 -0
- package/packages/core/dist/intelligence/draft-generator.js +161 -0
- package/packages/core/dist/intelligence/file-extractors.d.ts +18 -0
- package/packages/core/dist/intelligence/file-extractors.js +127 -0
- package/packages/core/dist/intelligence/ingest-pipeline.d.ts +32 -0
- package/packages/core/dist/intelligence/ingest-pipeline.js +209 -0
- package/packages/core/dist/intelligence/knowledge-lint.d.ts +27 -0
- package/packages/core/dist/intelligence/knowledge-lint.js +132 -0
- package/packages/core/dist/intelligence/wiki-compiler.d.ts +30 -0
- package/packages/core/dist/intelligence/wiki-compiler.js +222 -0
- package/packages/core/dist/intelligence/youtube-extractor.d.ts +29 -0
- package/packages/core/dist/intelligence/youtube-extractor.js +311 -0
- package/packages/core/dist/intelligence/zettelkasten.d.ts +59 -0
- package/packages/core/dist/intelligence/zettelkasten.js +234 -0
- package/packages/core/dist/mcp/server.d.ts +2 -0
- package/packages/core/dist/mcp/server.js +24 -1
- package/packages/core/dist/mcp/tools/agentic-graph.d.ts +6 -0
- package/packages/core/dist/mcp/tools/agentic-graph.js +35 -7
- package/packages/core/dist/mcp/tools/ask.d.ts +29 -0
- package/packages/core/dist/mcp/tools/ask.js +40 -0
- package/packages/core/dist/mcp/tools/generate-draft.d.ts +34 -0
- package/packages/core/dist/mcp/tools/generate-draft.js +120 -0
- package/packages/core/package.json +21 -2
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { VectorStore } from '../store/types.js';
|
|
2
|
+
export interface LintResult {
|
|
3
|
+
score: number;
|
|
4
|
+
issues: LintIssue[];
|
|
5
|
+
suggestions: string[];
|
|
6
|
+
stats: {
|
|
7
|
+
totalDocs: number;
|
|
8
|
+
gaps: number;
|
|
9
|
+
duplicates: number;
|
|
10
|
+
contradictions: number;
|
|
11
|
+
isolatedNodes: number;
|
|
12
|
+
orphanTags: number;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export interface LintIssue {
|
|
16
|
+
type: 'gap' | 'duplicate' | 'contradiction' | 'isolated' | 'stale' | 'empty';
|
|
17
|
+
severity: 'critical' | 'warning' | 'info';
|
|
18
|
+
message: string;
|
|
19
|
+
filePath?: string;
|
|
20
|
+
suggestion?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* vault 전체를 스캔하여 지식 건강도를 평가.
|
|
24
|
+
* 기존 gap-detector, duplicate-detector, contradiction-detector를 통합.
|
|
25
|
+
*/
|
|
26
|
+
export declare function lintKnowledge(store: VectorStore): Promise<LintResult>;
|
|
27
|
+
//# sourceMappingURL=knowledge-lint.d.ts.map
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Feature: stellavault lint — 자동 지식 건강 검사
|
|
2
|
+
// 불일치, 누락, 연결 제안, 고립 노드 등을 종합 리포트
|
|
3
|
+
import { detectKnowledgeGaps } from './gap-detector.js';
|
|
4
|
+
import { detectDuplicates } from './duplicate-detector.js';
|
|
5
|
+
import { detectContradictions } from './contradiction-detector.js';
|
|
6
|
+
/**
|
|
7
|
+
* vault 전체를 스캔하여 지식 건강도를 평가.
|
|
8
|
+
* 기존 gap-detector, duplicate-detector, contradiction-detector를 통합.
|
|
9
|
+
*/
|
|
10
|
+
export async function lintKnowledge(store) {
|
|
11
|
+
const issues = [];
|
|
12
|
+
const suggestions = [];
|
|
13
|
+
const docs = await store.getAllDocuments();
|
|
14
|
+
const totalDocs = docs.length;
|
|
15
|
+
// 1. 갭 탐지
|
|
16
|
+
let gapCount = 0;
|
|
17
|
+
let isolatedCount = 0;
|
|
18
|
+
try {
|
|
19
|
+
const gapReport = await detectKnowledgeGaps(store);
|
|
20
|
+
gapCount = gapReport.totalGaps;
|
|
21
|
+
isolatedCount = gapReport.isolatedNodes.length;
|
|
22
|
+
for (const gap of gapReport.gaps.filter(g => g.severity !== 'low')) {
|
|
23
|
+
issues.push({
|
|
24
|
+
type: 'gap',
|
|
25
|
+
severity: gap.severity === 'high' ? 'critical' : 'warning',
|
|
26
|
+
message: `Knowledge gap: ${gap.clusterA} ↔ ${gap.clusterB} (${gap.bridgeCount} connections)`,
|
|
27
|
+
suggestion: gap.suggestedTopic,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
for (const node of gapReport.isolatedNodes.slice(0, 10)) {
|
|
31
|
+
issues.push({
|
|
32
|
+
type: 'isolated',
|
|
33
|
+
severity: 'warning',
|
|
34
|
+
message: `Isolated note: "${node.title}" (${node.connections} connections)`,
|
|
35
|
+
filePath: node.id,
|
|
36
|
+
suggestion: 'Add links to related topics or tags to integrate this note.',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error('[lint] Gap detection failed:', err instanceof Error ? err.message : err);
|
|
42
|
+
}
|
|
43
|
+
// 2. 중복 탐지
|
|
44
|
+
let dupCount = 0;
|
|
45
|
+
try {
|
|
46
|
+
const dups = await detectDuplicates(store, 0.88, 10);
|
|
47
|
+
dupCount = dups.length;
|
|
48
|
+
for (const dup of dups) {
|
|
49
|
+
issues.push({
|
|
50
|
+
type: 'duplicate',
|
|
51
|
+
severity: 'warning',
|
|
52
|
+
message: `Possible duplicate: "${dup.docA.title}" ≈ "${dup.docB.title}" (${Math.round(dup.similarity * 100)}%)`,
|
|
53
|
+
suggestion: 'Consider merging these notes.',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
console.error('[lint] Detection failed:', err instanceof Error ? err.message : err);
|
|
59
|
+
}
|
|
60
|
+
// 3. 모순 탐지
|
|
61
|
+
let contradictionCount = 0;
|
|
62
|
+
try {
|
|
63
|
+
const contradictions = await detectContradictions(store, 5);
|
|
64
|
+
contradictionCount = contradictions.length;
|
|
65
|
+
for (const c of contradictions) {
|
|
66
|
+
issues.push({
|
|
67
|
+
type: 'contradiction',
|
|
68
|
+
severity: 'warning',
|
|
69
|
+
message: `Potential contradiction: "${c.docA.title}" vs "${c.docB.title}"`,
|
|
70
|
+
suggestion: 'Review these documents for conflicting information.',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
console.error('[lint] Detection failed:', err instanceof Error ? err.message : err);
|
|
76
|
+
}
|
|
77
|
+
// 4. 빈 문서 / 매우 짧은 문서
|
|
78
|
+
for (const doc of docs) {
|
|
79
|
+
if (doc.content.trim().length < 50) {
|
|
80
|
+
issues.push({
|
|
81
|
+
type: 'empty',
|
|
82
|
+
severity: 'info',
|
|
83
|
+
message: `Very short note: "${doc.title}" (${doc.content.trim().length} chars)`,
|
|
84
|
+
filePath: doc.filePath,
|
|
85
|
+
suggestion: 'Consider expanding or removing this note.',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// 5. 오래된 문서 (180일 이상 미수정)
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
const staleDocs = docs.filter(d => {
|
|
92
|
+
if (!d.lastModified)
|
|
93
|
+
return false;
|
|
94
|
+
const age = (now - new Date(d.lastModified).getTime()) / 86400000;
|
|
95
|
+
return age > 180;
|
|
96
|
+
});
|
|
97
|
+
if (staleDocs.length > totalDocs * 0.3) {
|
|
98
|
+
suggestions.push(`${staleDocs.length} documents haven't been modified in 6+ months. Run \`stellavault decay\` to review.`);
|
|
99
|
+
}
|
|
100
|
+
// 6. 건강도 점수 계산
|
|
101
|
+
const criticalCount = issues.filter(i => i.severity === 'critical').length;
|
|
102
|
+
const warningCount = issues.filter(i => i.severity === 'warning').length;
|
|
103
|
+
const penalty = criticalCount * 10 + warningCount * 3;
|
|
104
|
+
const score = Math.max(0, Math.min(100, 100 - penalty));
|
|
105
|
+
// 7. 종합 제안
|
|
106
|
+
if (gapCount > 5) {
|
|
107
|
+
suggestions.push(`${gapCount} knowledge gaps found. Start filling the most critical ones.`);
|
|
108
|
+
}
|
|
109
|
+
if (dupCount > 3) {
|
|
110
|
+
suggestions.push(`${dupCount} duplicate documents detected. Run \`stellavault duplicates\` to merge.`);
|
|
111
|
+
}
|
|
112
|
+
if (isolatedCount > 10) {
|
|
113
|
+
suggestions.push(`${isolatedCount} isolated notes found. Add tags or links to connect them.`);
|
|
114
|
+
}
|
|
115
|
+
if (totalDocs > 0 && issues.length === 0) {
|
|
116
|
+
suggestions.push('Your knowledge base is healthy! Keep it up.');
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
score,
|
|
120
|
+
issues,
|
|
121
|
+
suggestions,
|
|
122
|
+
stats: {
|
|
123
|
+
totalDocs,
|
|
124
|
+
gaps: gapCount,
|
|
125
|
+
duplicates: dupCount,
|
|
126
|
+
contradictions: contradictionCount,
|
|
127
|
+
isolatedNodes: isolatedCount,
|
|
128
|
+
orphanTags: 0,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=knowledge-lint.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface CompileResult {
|
|
2
|
+
rawDocCount: number;
|
|
3
|
+
wikiArticles: string[];
|
|
4
|
+
indexFile: string;
|
|
5
|
+
concepts: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface RawDocument {
|
|
8
|
+
filePath: string;
|
|
9
|
+
title: string;
|
|
10
|
+
content: string;
|
|
11
|
+
tags: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* raw/ 폴더를 스캔하여 문서 목록 반환
|
|
15
|
+
*/
|
|
16
|
+
export declare function scanRawDirectory(rawPath: string): RawDocument[];
|
|
17
|
+
/**
|
|
18
|
+
* raw 문서들에서 핵심 개념(concepts) 추출
|
|
19
|
+
*/
|
|
20
|
+
export declare function extractConcepts(docs: RawDocument[]): Map<string, string[]>;
|
|
21
|
+
/**
|
|
22
|
+
* 컴파일: raw/ → wiki/
|
|
23
|
+
* - 개념별 문서 생성
|
|
24
|
+
* - 인덱스 파일 생성
|
|
25
|
+
* - 백링크 추가
|
|
26
|
+
*/
|
|
27
|
+
export declare function compileWiki(rawPath: string, wikiPath: string, options?: {
|
|
28
|
+
force?: boolean;
|
|
29
|
+
}): CompileResult;
|
|
30
|
+
//# sourceMappingURL=wiki-compiler.d.ts.map
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// Feature: stellavault compile — raw → wiki 컴파일
|
|
2
|
+
// raw/ 폴더의 문서를 분석하여 구조화된 wiki .md 파일 생성
|
|
3
|
+
import { readdirSync, readFileSync, writeFileSync, mkdirSync, existsSync, statSync } from 'node:fs';
|
|
4
|
+
import { join, resolve, basename, extname } from 'node:path';
|
|
5
|
+
/**
|
|
6
|
+
* raw/ 폴더를 스캔하여 문서 목록 반환
|
|
7
|
+
*/
|
|
8
|
+
export function scanRawDirectory(rawPath) {
|
|
9
|
+
if (!existsSync(rawPath))
|
|
10
|
+
return [];
|
|
11
|
+
const docs = [];
|
|
12
|
+
const files = readdirSync(rawPath, { recursive: true });
|
|
13
|
+
for (const file of files) {
|
|
14
|
+
const fullPath = join(rawPath, file);
|
|
15
|
+
if (!statSync(fullPath).isFile())
|
|
16
|
+
continue;
|
|
17
|
+
const ext = extname(file).toLowerCase();
|
|
18
|
+
if (!['.md', '.txt', '.html'].includes(ext))
|
|
19
|
+
continue;
|
|
20
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
21
|
+
const title = extractTitle(content, file);
|
|
22
|
+
const tags = extractTags(content);
|
|
23
|
+
docs.push({ filePath: file, title, content, tags });
|
|
24
|
+
}
|
|
25
|
+
return docs;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 문서에서 제목 추출
|
|
29
|
+
*/
|
|
30
|
+
function extractTitle(content, filename) {
|
|
31
|
+
// YAML frontmatter title
|
|
32
|
+
const fmMatch = content.match(/^---[\s\S]*?title:\s*["']?(.+?)["']?\s*$/m);
|
|
33
|
+
if (fmMatch)
|
|
34
|
+
return fmMatch[1];
|
|
35
|
+
// First heading
|
|
36
|
+
const headingMatch = content.match(/^#\s+(.+)$/m);
|
|
37
|
+
if (headingMatch)
|
|
38
|
+
return headingMatch[1];
|
|
39
|
+
// Filename
|
|
40
|
+
return basename(filename, extname(filename));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 문서에서 태그 추출
|
|
44
|
+
*/
|
|
45
|
+
function extractTags(content) {
|
|
46
|
+
const tags = new Set();
|
|
47
|
+
// YAML tags
|
|
48
|
+
const fmMatch = content.match(/^---[\s\S]*?tags:\s*\[([^\]]*)\]/m);
|
|
49
|
+
if (fmMatch) {
|
|
50
|
+
fmMatch[1].split(',').map(t => t.trim().replace(/["']/g, '')).filter(Boolean).forEach(t => tags.add(t));
|
|
51
|
+
}
|
|
52
|
+
// Inline #tags
|
|
53
|
+
const inlineTags = content.match(/#([a-zA-Z가-힣][a-zA-Z0-9가-힣_-]*)/g) ?? [];
|
|
54
|
+
inlineTags.forEach(t => tags.add(t.slice(1)));
|
|
55
|
+
return [...tags];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* raw 문서들에서 핵심 개념(concepts) 추출
|
|
59
|
+
*/
|
|
60
|
+
export function extractConcepts(docs) {
|
|
61
|
+
const conceptMap = new Map(); // concept → [docPaths]
|
|
62
|
+
for (const doc of docs) {
|
|
63
|
+
// 태그를 개념으로
|
|
64
|
+
for (const tag of doc.tags) {
|
|
65
|
+
const existing = conceptMap.get(tag) ?? [];
|
|
66
|
+
existing.push(doc.filePath);
|
|
67
|
+
conceptMap.set(tag, existing);
|
|
68
|
+
}
|
|
69
|
+
// 제목 키워드를 개념으로
|
|
70
|
+
const titleWords = doc.title
|
|
71
|
+
.split(/[\s\-_,]+/)
|
|
72
|
+
.filter(w => w.length > 3)
|
|
73
|
+
.map(w => w.toLowerCase());
|
|
74
|
+
for (const word of titleWords) {
|
|
75
|
+
const existing = conceptMap.get(word) ?? [];
|
|
76
|
+
existing.push(doc.filePath);
|
|
77
|
+
conceptMap.set(word, existing);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// 2개 이상 문서에서 등장하는 개념만
|
|
81
|
+
const filtered = new Map();
|
|
82
|
+
for (const [concept, docPaths] of conceptMap) {
|
|
83
|
+
if (docPaths.length >= 2) {
|
|
84
|
+
filtered.set(concept, [...new Set(docPaths)]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return filtered;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* 컴파일: raw/ → wiki/
|
|
91
|
+
* - 개념별 문서 생성
|
|
92
|
+
* - 인덱스 파일 생성
|
|
93
|
+
* - 백링크 추가
|
|
94
|
+
*/
|
|
95
|
+
export function compileWiki(rawPath, wikiPath, options = {}) {
|
|
96
|
+
const docs = scanRawDirectory(rawPath);
|
|
97
|
+
if (docs.length === 0) {
|
|
98
|
+
return { rawDocCount: 0, wikiArticles: [], indexFile: '', concepts: [] };
|
|
99
|
+
}
|
|
100
|
+
// wiki 디렉토리 생성
|
|
101
|
+
if (!existsSync(wikiPath)) {
|
|
102
|
+
mkdirSync(wikiPath, { recursive: true });
|
|
103
|
+
}
|
|
104
|
+
const concepts = extractConcepts(docs);
|
|
105
|
+
const wikiArticles = [];
|
|
106
|
+
// 1. 각 raw 문서의 요약 문서 생성
|
|
107
|
+
for (const doc of docs) {
|
|
108
|
+
const slug = doc.title.replace(/[^a-zA-Z0-9가-힣\s]/g, '').replace(/\s+/g, '-').toLowerCase();
|
|
109
|
+
const wikiFile = `${slug}.md`;
|
|
110
|
+
const wikiFullPath = resolve(wikiPath, wikiFile);
|
|
111
|
+
// path traversal 방지
|
|
112
|
+
if (!wikiFullPath.startsWith(resolve(wikiPath)))
|
|
113
|
+
continue;
|
|
114
|
+
const relatedDocs = findRelatedDocs(doc, docs);
|
|
115
|
+
const articleContent = generateArticle(doc, relatedDocs, concepts);
|
|
116
|
+
writeFileSync(wikiFullPath, articleContent, 'utf-8');
|
|
117
|
+
wikiArticles.push(wikiFile);
|
|
118
|
+
}
|
|
119
|
+
// 2. 개념별 허브 문서 생성
|
|
120
|
+
for (const [concept, docPaths] of concepts) {
|
|
121
|
+
const slug = concept.replace(/[^a-zA-Z0-9가-힣]/g, '-').toLowerCase();
|
|
122
|
+
const hubFile = `_concept-${slug}.md`;
|
|
123
|
+
const hubFullPath = resolve(wikiPath, hubFile);
|
|
124
|
+
if (!hubFullPath.startsWith(resolve(wikiPath)))
|
|
125
|
+
continue;
|
|
126
|
+
const relatedArticles = docPaths.map(p => {
|
|
127
|
+
const doc = docs.find(d => d.filePath === p);
|
|
128
|
+
return doc?.title ?? p;
|
|
129
|
+
});
|
|
130
|
+
const hubContent = [
|
|
131
|
+
'---',
|
|
132
|
+
`title: "Concept: ${concept}"`,
|
|
133
|
+
'type: concept',
|
|
134
|
+
`date: ${new Date().toISOString()}`,
|
|
135
|
+
'---',
|
|
136
|
+
'',
|
|
137
|
+
`# ${concept}`,
|
|
138
|
+
'',
|
|
139
|
+
`This concept appears in ${relatedArticles.length} documents.`,
|
|
140
|
+
'',
|
|
141
|
+
'## Related Documents',
|
|
142
|
+
...relatedArticles.map(a => `- [[${a}]]`),
|
|
143
|
+
'',
|
|
144
|
+
'---',
|
|
145
|
+
'*Auto-generated by `stellavault compile`*',
|
|
146
|
+
].join('\n');
|
|
147
|
+
writeFileSync(hubFullPath, hubContent, 'utf-8');
|
|
148
|
+
wikiArticles.push(hubFile);
|
|
149
|
+
}
|
|
150
|
+
// 3. 인덱스 파일 생성
|
|
151
|
+
const indexContent = generateIndex(docs, concepts, wikiArticles);
|
|
152
|
+
const indexPath = resolve(wikiPath, '_index.md');
|
|
153
|
+
writeFileSync(indexPath, indexContent, 'utf-8');
|
|
154
|
+
return {
|
|
155
|
+
rawDocCount: docs.length,
|
|
156
|
+
wikiArticles,
|
|
157
|
+
indexFile: '_index.md',
|
|
158
|
+
concepts: [...concepts.keys()],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function findRelatedDocs(doc, allDocs) {
|
|
162
|
+
return allDocs
|
|
163
|
+
.filter(d => d.filePath !== doc.filePath)
|
|
164
|
+
.filter(d => {
|
|
165
|
+
const sharedTags = doc.tags.filter(t => d.tags.includes(t));
|
|
166
|
+
return sharedTags.length > 0;
|
|
167
|
+
})
|
|
168
|
+
.slice(0, 5);
|
|
169
|
+
}
|
|
170
|
+
function generateArticle(doc, related, concepts) {
|
|
171
|
+
const docConcepts = [...concepts.keys()].filter(c => doc.content.toLowerCase().includes(c.toLowerCase()) || doc.tags.includes(c));
|
|
172
|
+
return [
|
|
173
|
+
'---',
|
|
174
|
+
`title: "${doc.title}"`,
|
|
175
|
+
`source: "${doc.filePath}"`,
|
|
176
|
+
`date: ${new Date().toISOString()}`,
|
|
177
|
+
`tags: [${doc.tags.map(t => `"${t}"`).join(', ')}]`,
|
|
178
|
+
'type: wiki-article',
|
|
179
|
+
'---',
|
|
180
|
+
'',
|
|
181
|
+
`# ${doc.title}`,
|
|
182
|
+
'',
|
|
183
|
+
'## Summary',
|
|
184
|
+
doc.content.substring(0, 500).replace(/^#.+$/gm, '').trim() + '...',
|
|
185
|
+
'',
|
|
186
|
+
docConcepts.length > 0 ? `## Related Concepts\n${docConcepts.map(c => `- [[_concept-${c}|${c}]]`).join('\n')}` : '',
|
|
187
|
+
'',
|
|
188
|
+
related.length > 0 ? `## 관련 문서\n${related.map(r => `- [[${r.title}]]`).join('\n')}` : '',
|
|
189
|
+
'',
|
|
190
|
+
`## Source`,
|
|
191
|
+
`- Source: \`${doc.filePath}\``,
|
|
192
|
+
'',
|
|
193
|
+
'---',
|
|
194
|
+
'*Compiled by `stellavault compile`*',
|
|
195
|
+
].filter(Boolean).join('\n');
|
|
196
|
+
}
|
|
197
|
+
function generateIndex(docs, concepts, articles) {
|
|
198
|
+
return [
|
|
199
|
+
'---',
|
|
200
|
+
'title: "Wiki Index"',
|
|
201
|
+
`date: ${new Date().toISOString()}`,
|
|
202
|
+
'type: wiki-index',
|
|
203
|
+
'---',
|
|
204
|
+
'',
|
|
205
|
+
'# Wiki Index',
|
|
206
|
+
'',
|
|
207
|
+
`Compiled ${articles.length} wiki articles from ${docs.length} source documents.`,
|
|
208
|
+
'',
|
|
209
|
+
'## Concepts',
|
|
210
|
+
...[...concepts.entries()]
|
|
211
|
+
.sort((a, b) => b[1].length - a[1].length)
|
|
212
|
+
.map(([c, paths]) => `- [[_concept-${c}|${c}]] (${paths.length})`),
|
|
213
|
+
'',
|
|
214
|
+
'## Articles',
|
|
215
|
+
...docs.map(d => `- [[${d.title}]] — ${d.tags.map(t => `#${t}`).join(' ')}`),
|
|
216
|
+
'',
|
|
217
|
+
'---',
|
|
218
|
+
`*Last compiled: ${new Date().toISOString()}*`,
|
|
219
|
+
'*Generated by `stellavault compile`*',
|
|
220
|
+
].join('\n');
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=wiki-compiler.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface YouTubeContent {
|
|
2
|
+
title: string;
|
|
3
|
+
channelName: string;
|
|
4
|
+
description: string;
|
|
5
|
+
transcript: TimedSegment[];
|
|
6
|
+
rawTranscript: string;
|
|
7
|
+
duration: string;
|
|
8
|
+
publishDate: string;
|
|
9
|
+
tags: string[];
|
|
10
|
+
url: string;
|
|
11
|
+
videoId: string;
|
|
12
|
+
viewCount: string;
|
|
13
|
+
thumbnail: string;
|
|
14
|
+
summary: string;
|
|
15
|
+
}
|
|
16
|
+
export interface TimedSegment {
|
|
17
|
+
startTime: number;
|
|
18
|
+
text: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* YouTube URL에서 모든 콘텐츠 추출.
|
|
22
|
+
* 반환값은 데이터만 — 노트 포맷팅은 별도.
|
|
23
|
+
*/
|
|
24
|
+
export declare function extractYouTubeContent(url: string): Promise<YouTubeContent>;
|
|
25
|
+
/**
|
|
26
|
+
* 추출된 콘텐츠 → Stellavault .md 노트 (frontmatter 포함하지 않음 — pipeline이 처리).
|
|
27
|
+
*/
|
|
28
|
+
export declare function formatYouTubeNote(content: YouTubeContent): string;
|
|
29
|
+
//# sourceMappingURL=youtube-extractor.d.ts.map
|