Files
llm-wiki/scripts/lib/source-signal-eligibility.js
T
2026-07-12 21:26:08 +08:00

159 lines
4.3 KiB
JavaScript

#!/usr/bin/env node
"use strict";
const SCAN_KINDS = [
{ subdir: "entities", pageType: "entity", applicable: true },
{ subdir: "topics", pageType: "topic", applicable: true },
{ subdir: "sources", pageType: "source", applicable: true },
{ subdir: "comparisons", pageType: "comparison", applicable: true },
{ subdir: "queries", pageType: "query", applicable: false },
{ subdir: "synthesis", pageType: "synthesis", applicable: false }
];
function sortedUnique(values) {
return Array.from(new Set(values)).sort();
}
function extractFrontmatter(text) {
if (!text.startsWith("---\n") && !text.startsWith("---\r\n")) {
return { hasFrontmatter: false, frontmatter: "", body: text };
}
const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)([\s\S]*)$/);
if (!match) {
return { hasFrontmatter: false, frontmatter: "", body: text };
}
return {
hasFrontmatter: true,
frontmatter: match[1],
body: match[2]
};
}
function normalizeSourceToken(token) {
const trimmed = String(token || "").trim();
if (!trimmed) return null;
let value = trimmed;
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
value = value.slice(1, -1).trim();
}
return value || null;
}
function parseInlineSources(raw) {
const trimmed = raw.trim();
if (trimmed === "[]") return { ok: true, values: [] };
if (!(trimmed.startsWith("[") && trimmed.endsWith("]"))) {
return { ok: false, values: [] };
}
const inner = trimmed.slice(1, -1).trim();
if (!inner) return { ok: true, values: [] };
const values = inner
.split(",")
.map(normalizeSourceToken)
.filter(Boolean);
return { ok: true, values };
}
function parseSourcesFrontmatter(frontmatter) {
if (!frontmatter) {
return { hasField: false, parsed: false, sources: [], signalAvailable: false };
}
const lines = frontmatter.split(/\r?\n/);
for (let index = 0; index < lines.length; index += 1) {
const match = lines[index].match(/^sources:\s*(.*)$/);
if (!match) continue;
const rest = match[1].trim();
if (rest) {
if (!rest.startsWith("[")) {
const single = normalizeSourceToken(rest);
return {
hasField: true,
parsed: Boolean(single),
sources: single ? [single] : [],
signalAvailable: Boolean(single)
};
}
const parsedInline = parseInlineSources(rest);
return {
hasField: true,
parsed: parsedInline.ok,
sources: parsedInline.ok ? sortedUnique(parsedInline.values) : [],
signalAvailable: parsedInline.ok && parsedInline.values.length > 0
};
}
const collected = [];
let parsed = true;
let consumed = 0;
for (let cursor = index + 1; cursor < lines.length; cursor += 1) {
const line = lines[cursor];
if (!line.trim()) {
consumed += 1;
continue;
}
if (/^[^\s-]/.test(line)) break;
const itemMatch = line.match(/^\s*-\s*(.+)$/);
if (!itemMatch) {
parsed = false;
consumed += 1;
continue;
}
const token = normalizeSourceToken(itemMatch[1]);
if (token) collected.push(token);
consumed += 1;
}
index += consumed;
return {
hasField: true,
parsed,
sources: parsed ? sortedUnique(collected) : [],
signalAvailable: parsed && collected.length > 0
};
}
return { hasField: false, parsed: false, sources: [], signalAvailable: false };
}
function evaluateSourceSignalEligibility({ pageType, frontmatter }) {
const kind = SCAN_KINDS.find((k) => k.pageType === pageType);
if (!kind || !kind.applicable) {
return { eligible: false, reason: "not_applicable", sources: [] };
}
const parsed = parseSourcesFrontmatter(frontmatter);
if (!parsed.hasField) {
return { eligible: false, reason: "missing_sources", sources: [] };
}
if (!parsed.parsed) {
return { eligible: false, reason: "invalid_sources", sources: [] };
}
if (parsed.sources.length === 0) {
return { eligible: false, reason: "empty_sources", sources: [] };
}
return { eligible: true, reason: "ok", sources: parsed.sources };
}
module.exports = {
SCAN_KINDS,
extractFrontmatter,
evaluateSourceSignalEligibility,
normalizeSourceToken,
parseSourcesFrontmatter,
sortedUnique
};