tag-suggest.mjs
57 lines 2.0 KB
Raw
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor ⚠ breaking 10 days ago
1 /**
2 * Suggest tags from similar notes (Issue #1 Phase C10).
3 */
4
5 import { loadConfig } from './config.mjs';
6 import { readNote, normalizeTags, normalizeSlug } from './vault.mjs';
7 import { embed } from './embedding.mjs';
8 import { createVectorStore } from './vector-store.mjs';
9
10 /**
11 * @param {{ path?: string, body?: string }} input - exactly one of path or body
12 * @returns {Promise<{ suggested_tags: string[], existing_tags: string[] }>}
13 */
14 export async function runTagSuggest(input) {
15 const config = loadConfig();
16 let text;
17 let existing = [];
18 if (input.path) {
19 const note = readNote(config.vault_path, input.path);
20 text = `${note.frontmatter?.title ? String(note.frontmatter.title) + '\n' : ''}${note.body || ''}`;
21 existing = note.tags?.length ? note.tags : normalizeTags(note.frontmatter?.tags);
22 } else if (input.body) {
23 text = String(input.body);
24 } else {
25 throw new Error('tag_suggest requires path or body.');
26 }
27
28 const [vector] = await embed([text.slice(0, 12000)], config.embedding || {}, { voyageInputType: 'document' });
29 if (!vector?.length) throw new Error('Embedding failed for tag_suggest.');
30
31 const store = await createVectorStore(config);
32 const hits = await store.search(vector, { limit: 40 });
33 const tagCounts = new Map();
34 const existingSet = new Set(existing.map((t) => normalizeSlug(String(t))).filter(Boolean));
35
36 for (const h of hits) {
37 let tags = h.tags || [];
38 if (!tags.length) {
39 try {
40 const n = readNote(config.vault_path, h.path);
41 tags = n.tags?.length ? n.tags : normalizeTags(n.frontmatter?.tags);
42 } catch (_) {}
43 }
44 for (const t of tags) {
45 const slug = normalizeSlug(String(t));
46 if (!slug || existingSet.has(slug)) continue;
47 tagCounts.set(slug, (tagCounts.get(slug) || 0) + 1);
48 }
49 }
50
51 const suggested_tags = [...tagCounts.entries()]
52 .sort((a, b) => b[1] - a[1])
53 .map(([name]) => name)
54 .slice(0, 12);
55
56 return { suggested_tags, existing_tags: existing };
57 }
File History 4 commits
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor 10 days ago
sha256:d8c648b20a4d53b2673c5c082ee7edfa7b2fc9b11080832da1f38807b6bf940b fix(7C-L1b): route hosted delegation proposals through cani… Human minor 30 days ago
sha256:2827ba9e7632a4b141c50caf1e8f7d77abbc3515be20e7465f2bccb0ac4edf91 fix: repair endpoint now sets has_active_subscription when … Human minor 50 days ago