tag-suggest.mjs
57 lines 2.0 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 1 day 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 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 1 day ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 1 day ago