Docs/Skills and code/src/

src/analysis/vocabulary-store.js

Code59 lines2.2 KBsrc/

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.