Docs/Skills and code/src/
src/analysis/vocabulary-store.js
import { getDb } from "../db.js";
export function normalizeLabel(value) {
return String(value ?? "")
.normalize("NFKC")
.toLocaleLowerCase("en")
.replace(/[’‘]/g, "'")
.replace(/[‐‑‒–—]/g, "-")
.replace(/[^\p{L}\p{N}]+/gu, " ")
.trim()
.replace(/\s+/g, " ");
}
export function cleanLabel(value) {
if (typeof value === "string") return value.trim();
if (value && typeof value === "object") return String(value.name ?? "").trim();
return "";
}
export function upsertVocabularyTerm({ ecosystem, type, label }) {
const db = getDb();
const normalized = normalizeLabel(label);
if (!normalized) return null;
const now = new Date().toISOString();
db.prepare(`
INSERT INTO vocabulary_terms (
ecosystem, term_type, label, normalized_label, status, first_seen_at, updated_at
) VALUES (?, ?, ?, ?, 'candidate', ?, ?)
ON CONFLICT(ecosystem, term_type, normalized_label) DO UPDATE SET
updated_at = excluded.updated_at
`).run(ecosystem, type, label.trim(), normalized, now, now);
return db.prepare(`
SELECT term_id, label, status FROM vocabulary_terms
WHERE ecosystem=? AND term_type=? AND normalized_label=?
`).get(ecosystem, type, normalized);
}
export function addVocabularyEvidence({ termId, objectType, objectId, source, rawLabel, evidence = {} }) {
const db = getDb();
const result = db.prepare(`
INSERT OR IGNORE INTO vocabulary_evidence (
term_id, object_type, object_id, source, raw_label, evidence_json, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?)
`).run(termId, objectType, objectId, source, rawLabel,
JSON.stringify(evidence), new Date().toISOString());
return result.changes;
}
export function resolveCanonicalTerm(termId) {
const db = getDb();
let current = db.prepare(`SELECT term_id, status, canonical_term_id FROM vocabulary_terms WHERE term_id=?`).get(termId);
const visited = new Set();
while (current?.status === "alias" && current.canonical_term_id && !visited.has(current.term_id)) {
visited.add(current.term_id);
current = db.prepare(`SELECT term_id, status, canonical_term_id FROM vocabulary_terms WHERE term_id=?`).get(current.canonical_term_id);
}
return current?.status === "canonical" ? current.term_id : null;
}
Shown as it is in the repository at build time (2026-11-03); the home directory appears as <home>. If this page and the file differ, the file is right. Back to the catalogue.