Expand request review tooling and KB

This commit is contained in:
Andraxion 2026-06-27 01:12:35 -04:00
parent ab1dfbf029
commit cae21b61b7
16 changed files with 1258 additions and 241 deletions

View file

@ -11,6 +11,33 @@ const app = express();
const port = Number(process.env.PORT) || 5180;
const host = process.env.HOST || "0.0.0.0";
const launcherAdminPassword = String(process.env.LAUNCHER_ADMIN_PASSWORD || "").trim();
const DEFAULT_REQUEST_TAG_DEFINITIONS = [
{ id: "launcher", label: "Launcher", aliases: ["launcher home", "home page", "landing page", "main page"] },
{ id: "request-board", label: "Request Board", aliases: ["requests", "request queue", "moderation", "request intake"] },
{ id: "chunks", label: "Chunks", aliases: ["chunk", "chunk loading", "chunk storage", "chunk overview"] },
{ id: "layers", label: "Layers", aliases: ["layer", "height layers", "z layers"] },
{ id: "tiling", label: "Tiling", aliases: ["tile", "tile editing", "tile painting", "brush"] },
{ id: "graphics-painter", label: "Graphics Painter", aliases: ["graphic painter", "tile art", "sprite painter", "art editor"] },
{ id: "rendering", label: "Rendering", aliases: ["renderer", "viewport", "pixi", "draw"] },
{ id: "animation", label: "Animation", aliases: ["animated", "timeline", "frame playback"] },
{ id: "content", label: "Content", aliases: ["assets", "catalog", "tiles", "sprites", "images"] },
{ id: "world-overview", label: "World Overview", aliases: ["overview", "world map", "bookmarks", "poi"] },
{ id: "windows", label: "Windows", aliases: ["windowing", "popout", "tool windows", "floating window"] },
{ id: "persistence", label: "Persistence", aliases: ["saving", "save pipeline", "history", "undo", "redo"] },
{ id: "performance", label: "Performance", aliases: ["optimization", "slow", "lag", "streaming"] },
{ id: "ui-workflow", label: "UI / Workflow", aliases: ["workflow", "ux", "ui", "quality of life", "qol", "tools", "tooling"] },
{ id: "worlds", label: "Worlds", aliases: ["world", "world bootstrap", "startup", "world systems"] },
{ id: "website", label: "Website", aliases: ["site", "web", "homepage"] },
{ id: "repo", label: "Repo", aliases: ["repository", "forgejo", "git"] },
{ id: "wiki", label: "Wiki", aliases: ["kb", "knowledge base", "docs", "documentation"] },
{ id: "polish", label: "Polish", aliases: ["cleanup", "fit and finish", "presentation"] },
{ id: "general", label: "General", aliases: ["broad", "misc", "cross-system"] },
{ id: "other", label: "Other", aliases: ["unknown", "unclear", "unsorted"] },
];
function normalizeRequestTagLookupValue(value) {
return String(value || "").replace(/\s+/g, " ").trim().toLowerCase();
}
function isLauncherAdminProtectionEnabled() {
return Boolean(launcherAdminPassword);
@ -73,6 +100,7 @@ const dialogueNodeMetaPath = path.join(dataRoot, "dialogue_node_meta.json");
const editorSettingsPath = path.join(dataRoot, "editor_settings.json");
const launcherRequestsPath = path.join(dataRoot, "launcher_requests.json");
const requestAnalysisWorkerScriptPath = path.join(__dirname, "scripts", "request-analysis-worker.mjs");
const requestTagCatalogPath = path.join(__dirname, "docs", "kb", "tags.json");
const requestAnalysisRunState = {
child: null,
restartTimer: null,
@ -81,6 +109,17 @@ const imagesCatalogPath = path.join(contentRoot, "images.json");
const legacyTilesCatalogPath = path.join(contentRoot, "tiles.json");
const legacySpritesCatalogPath = path.join(contentRoot, "sprites.json");
const recentSaveEvents = [];
const REQUEST_TAG_DEFINITIONS = loadRequestTagCatalog();
const REQUEST_TAG_LABELS = REQUEST_TAG_DEFINITIONS.map((entry) => entry.label);
const REQUEST_TAG_LOOKUP = new Map(
REQUEST_TAG_DEFINITIONS.flatMap((entry) => {
const normalizedLabel = normalizeRequestTagLookupValue(entry.label);
return [
[normalizedLabel, entry.label],
...entry.aliases.map((alias) => [normalizeRequestTagLookupValue(alias), entry.label]),
];
}),
);
const DEFAULT_WORLDSHAPER_THEME_PRESET = "azure";
const WORLDSHAPER_THEME_PRESET_IDS = new Set(["azure", "verdant", "ember", "amethyst"]);
const LEGACY_LAUNCHER_REQUEST_MIGRATIONS = {
@ -417,8 +456,8 @@ function normalizeLauncherRequestStatus(value) {
function normalizeLauncherRequestTags(value) {
return normalizeUniqueStringList(value, {
normalizeValue: (entry) => String(entry || "").replace(/\s+/g, " ").trim(),
dedupeKey: (entry) => String(entry || "").replace(/\s+/g, " ").trim().toLowerCase(),
normalizeValue: (entry) => normalizeRequestTag(entry),
dedupeKey: (entry) => normalizeRequestTag(entry).toLowerCase(),
});
}
@ -445,6 +484,14 @@ function normalizeLauncherRequestAnalysisStringList(value) {
});
}
function normalizeLauncherRequestAnalysisTags(value, fallback = []) {
const normalized = normalizeLauncherRequestTags(Array.isArray(value) ? value : []);
if (normalized.length > 0) {
return normalized;
}
return normalizeLauncherRequestTags(Array.isArray(fallback) ? fallback : []);
}
function normalizeLauncherRequestAnalysisItem(item, index = 0) {
const source = item && typeof item === "object" && !Array.isArray(item)
? item
@ -455,9 +502,11 @@ function normalizeLauncherRequestAnalysisItem(item, index = 0) {
const fallbackTitle = `Analyzed request ${index + 1}`;
const title = String(source.title || "").trim() || fallbackTitle;
const primaryCategory = String(source.primaryCategory || source.category || "").trim() || "Unsorted";
const tags = normalizeLauncherRequestAnalysisStringList(source.tags);
const tags = normalizeLauncherRequestAnalysisTags(source.tags, [primaryCategory]);
const parsedInterpretation = String(source.parsedInterpretation || source.summary || "").trim();
const implementationApproach = String(source.implementationApproach || source.implementationNotes || "").trim();
const reviewRationale = String(source.reviewRationale || source.reviewReason || "").trim();
const reviewOptions = normalizeLauncherRequestAnalysisStringList(source.reviewOptions);
const statusRecommendationRaw = String(source.statusRecommendation || source.status || "").trim().toLowerCase();
const statusRecommendation = statusRecommendationRaw === "active"
|| statusRecommendationRaw === "duplicate"
@ -480,7 +529,7 @@ function normalizeLauncherRequestAnalysisItem(item, index = 0) {
return {
title,
primaryCategory,
tags,
tags: tags.length > 0 ? tags : ["General"],
statusRecommendation,
parsedInterpretation,
implementationApproach,
@ -489,6 +538,8 @@ function normalizeLauncherRequestAnalysisItem(item, index = 0) {
problemType,
rawExcerpt: String(source.rawExcerpt || "").trim(),
confidence: normalizeLauncherRequestAnalysisConfidence(source.confidence),
reviewRationale,
reviewOptions,
notes: String(source.notes || "").trim(),
};
}
@ -2166,6 +2217,37 @@ function normalizeUniqueStringList(value, options = {}) {
return normalized;
}
function loadRequestTagCatalog() {
try {
const payload = JSON.parse(fs.readFileSync(requestTagCatalogPath, "utf8"));
const tags = Array.isArray(payload?.tags)
? payload.tags
.map((entry) => ({
id: String(entry?.id || "").trim(),
label: String(entry?.label || "").trim(),
aliases: Array.isArray(entry?.aliases)
? entry.aliases.map((alias) => String(alias || "").trim()).filter(Boolean)
: [],
}))
.filter((entry) => entry.id && entry.label)
: [];
if (tags.length > 0) {
return tags;
}
} catch {
// Fall back to built-in definitions when the KB file is unavailable.
}
return DEFAULT_REQUEST_TAG_DEFINITIONS;
}
function normalizeRequestTag(value) {
const normalizedValue = normalizeRequestTagLookupValue(value);
if (!normalizedValue) {
return "";
}
return REQUEST_TAG_LOOKUP.get(normalizedValue) || "";
}
function normalizeTagList(value) {
if (!Array.isArray(value)) {
return [];
@ -2710,6 +2792,13 @@ app.get("/api/debug/recent-saves", (req, res) => {
});
});
app.get("/api/launcher-request-meta", (_req, res) => {
res.json({
ok: true,
allowedTags: REQUEST_TAG_LABELS,
});
});
app.get("/api/launcher-requests", (_req, res) => {
try {
res.json(readLauncherRequestsPayload());