273 lines
8.4 KiB
JavaScript
273 lines
8.4 KiB
JavaScript
#!/usr/bin/env node
|
|
"use strict";
|
|
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
|
|
const ROOT = process.cwd();
|
|
const BASE_URL = (process.env.MNOTE_WEB_SMOKE_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, "");
|
|
const OUTPUT_DIR = path.join(ROOT, "tmp", "task787-weknora-default-lightrag-legacy-smoke");
|
|
const RESULT_PATH = path.join(OUTPUT_DIR, "result.json");
|
|
const FAILURE_PATH = path.join(OUTPUT_DIR, "failure.json");
|
|
const REQUEST_TIMEOUT_MS = Number(process.env.MNOTE_TASK787_STATUS_TIMEOUT_MS || 15_000);
|
|
|
|
const REQUIRED_STATIC_FILES = [
|
|
"rust/crates/mnote-web/browser/sidebar-page-settings-runtime.js",
|
|
"rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js",
|
|
"scripts/TESTING_REFERENCE.md",
|
|
];
|
|
|
|
const TASK_SMOKE_PATTERN = /^task(?:53|54|76|78)\d.*\.js$/;
|
|
const SELF_PATH = "scripts/task787-weknora-default-lightrag-legacy-smoke.js";
|
|
|
|
function writeJson(filePath, payload) {
|
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
}
|
|
|
|
function listTaskSmokeFiles() {
|
|
return fs.readdirSync(path.join(ROOT, "scripts"), { withFileTypes: true })
|
|
.filter((entry) => entry.isFile() && TASK_SMOKE_PATTERN.test(entry.name))
|
|
.map((entry) => `scripts/${entry.name}`)
|
|
.sort((left, right) => left.localeCompare(right));
|
|
}
|
|
|
|
function uniqueSorted(values) {
|
|
return Array.from(new Set(values)).sort((left, right) => left.localeCompare(right));
|
|
}
|
|
|
|
function extractWindow(line, start, length) {
|
|
const from = Math.max(0, start - 90);
|
|
const to = Math.min(line.length, start + length + 90);
|
|
return line.slice(from, to).trim();
|
|
}
|
|
|
|
function allowedLegacyContext(contextBlock, occurrenceWindow) {
|
|
const lowerBlock = contextBlock.toLowerCase();
|
|
const lowerWindow = occurrenceWindow.toLowerCase();
|
|
const legacyMarkers = [
|
|
"legacy",
|
|
"fallback",
|
|
"history",
|
|
"historical",
|
|
"debug",
|
|
"compat",
|
|
"retired",
|
|
"assertnotcontains",
|
|
"assertnotincludes",
|
|
"forbidden",
|
|
"notcontains",
|
|
"notincludes",
|
|
"not contain",
|
|
"does not include",
|
|
"no lightrag",
|
|
"nolightrag",
|
|
"nodefaultlightrag",
|
|
"without lightrag",
|
|
"bypass",
|
|
"bypasses",
|
|
"skip",
|
|
"旧",
|
|
"历史",
|
|
"兼容",
|
|
"调试",
|
|
"退役",
|
|
"软归档",
|
|
"不应包含",
|
|
"不作为",
|
|
"不再",
|
|
"不直接",
|
|
"不删除",
|
|
"不走",
|
|
"禁止",
|
|
"绕过",
|
|
"跳过",
|
|
"移除",
|
|
"缺少",
|
|
];
|
|
return legacyMarkers.some((marker) => lowerWindow.includes(marker) || lowerBlock.includes(marker));
|
|
}
|
|
|
|
function ignoredTechnicalField(line) {
|
|
return /\blightRag(?:DocId|FilePath|Status)\b/.test(line)
|
|
|| /lightrag-source-registry\.json/.test(line);
|
|
}
|
|
|
|
function ignoredLegacyAssetName(line) {
|
|
return /task\d+-lightrag-[\w-]+/.test(line);
|
|
}
|
|
|
|
function ignoredNegativeAssertionName(line) {
|
|
return /\b(?:uiNoDefault|ingestNo|deleteNo|sectionContextNo|noDefault)LightRag[A-Za-z0-9_]*\b/.test(line);
|
|
}
|
|
|
|
function classifyActiveLightRagContext(contextBlock, occurrenceWindow) {
|
|
const normalized = `${contextBlock}\n${occurrenceWindow}`.toLowerCase();
|
|
const reasons = [];
|
|
const activeDefaultPatterns = [
|
|
/默认\s*provider/i,
|
|
/唯一默认/i,
|
|
/主线\s*provider/i,
|
|
/default\s*provider/i,
|
|
/primary\s*provider/i,
|
|
/mainline\s*provider/i,
|
|
/only\s*default/i,
|
|
];
|
|
if (activeDefaultPatterns.some((pattern) => pattern.test(contextBlock) || pattern.test(occurrenceWindow))) {
|
|
reasons.push("forbidden_default_or_mainline_provider_copy");
|
|
}
|
|
const activeSurfacePatterns = [
|
|
/资料库/,
|
|
/知识库/,
|
|
/问答/,
|
|
/检索/,
|
|
/搜索/,
|
|
/引用/,
|
|
/入库/,
|
|
/dashboard/,
|
|
/health/,
|
|
/status/,
|
|
/source[-_ ]?registry/,
|
|
/search\s*provider/,
|
|
/references?/,
|
|
/retrieval/,
|
|
/smoke/,
|
|
];
|
|
if (activeSurfacePatterns.some((pattern) => pattern.test(normalized))) {
|
|
reasons.push("active_lightrag_surface_without_legacy_marker");
|
|
}
|
|
if (reasons.length === 0) {
|
|
reasons.push("unqualified_lightrag_reference_without_legacy_marker");
|
|
}
|
|
return reasons;
|
|
}
|
|
|
|
function scanStaticFiles(files) {
|
|
const violations = [];
|
|
const scannedFiles = [];
|
|
for (const relativePath of files) {
|
|
const absolutePath = path.join(ROOT, relativePath);
|
|
if (!fs.existsSync(absolutePath)) {
|
|
violations.push({
|
|
file: relativePath,
|
|
line: 0,
|
|
reason: "static_file_missing",
|
|
context: "required static/smoke baseline file is missing",
|
|
});
|
|
continue;
|
|
}
|
|
scannedFiles.push(relativePath);
|
|
const content = fs.readFileSync(absolutePath, "utf8");
|
|
const lines = content.split(/\r?\n/);
|
|
lines.forEach((line, index) => {
|
|
const matches = Array.from(line.matchAll(/lightrag/gi));
|
|
for (const match of matches) {
|
|
if (ignoredTechnicalField(line) || ignoredLegacyAssetName(line) || ignoredNegativeAssertionName(line)) {
|
|
continue;
|
|
}
|
|
const occurrenceWindow = extractWindow(line, match.index || 0, match[0].length);
|
|
const contextBlock = lines.slice(Math.max(0, index - 8), Math.min(lines.length, index + 5)).join("\n");
|
|
if (allowedLegacyContext(contextBlock, occurrenceWindow)) {
|
|
continue;
|
|
}
|
|
violations.push({
|
|
file: relativePath,
|
|
line: index + 1,
|
|
reason: classifyActiveLightRagContext(contextBlock, occurrenceWindow),
|
|
context: occurrenceWindow,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
return { scannedFiles, violations };
|
|
}
|
|
|
|
async function fetchStatusProviderConfig() {
|
|
const controller = new AbortController();
|
|
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
const url = `${BASE_URL}/api/knowledge-rag/status`;
|
|
try {
|
|
const response = await fetch(url, {
|
|
headers: { accept: "application/json" },
|
|
signal: controller.signal,
|
|
});
|
|
const text = await response.text();
|
|
let payload = null;
|
|
try {
|
|
payload = text ? JSON.parse(text) : null;
|
|
} catch (error) {
|
|
return {
|
|
ok: false,
|
|
layer: "status_api_invalid_json",
|
|
url,
|
|
status: response.status,
|
|
error: error.message,
|
|
rawText: text.slice(0, 1000),
|
|
providerConfig: null,
|
|
};
|
|
}
|
|
const providerConfig = payload && typeof payload.providerConfig === "object" ? payload.providerConfig : null;
|
|
const active = providerConfig && providerConfig.active;
|
|
const defaultProvider = providerConfig && providerConfig.default;
|
|
const provider = payload && payload.provider;
|
|
const ok = response.ok && providerConfig && active === "weknora" && defaultProvider === "weknora";
|
|
return {
|
|
ok,
|
|
layer: response.ok ? (ok ? "status_provider_config_ok" : "status_provider_config_mismatch") : "status_api_http_error",
|
|
url,
|
|
status: response.status,
|
|
provider,
|
|
providerConfig,
|
|
responseSample: ok ? undefined : payload,
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
ok: false,
|
|
layer: error && error.name === "AbortError" ? "status_api_timeout" : "status_api_unavailable",
|
|
url,
|
|
error: error && (error.stack || error.message) ? (error.stack || error.message) : String(error),
|
|
providerConfig: null,
|
|
};
|
|
} finally {
|
|
clearTimeout(timeout);
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
const staticFiles = uniqueSorted([...REQUIRED_STATIC_FILES, ...listTaskSmokeFiles()]);
|
|
const staticScan = scanStaticFiles(staticFiles.filter((relativePath) => relativePath !== SELF_PATH));
|
|
const statusCheck = await fetchStatusProviderConfig();
|
|
const ok = staticScan.violations.length === 0 && statusCheck.ok;
|
|
const payload = {
|
|
ok,
|
|
task: "task787-weknora-default-lightrag-legacy-smoke",
|
|
baseUrl: BASE_URL,
|
|
checkedAt: new Date().toISOString(),
|
|
scannedFiles: staticScan.scannedFiles,
|
|
violations: staticScan.violations,
|
|
statusProviderConfig: statusCheck,
|
|
};
|
|
if (ok) {
|
|
writeJson(RESULT_PATH, payload);
|
|
console.log(JSON.stringify(payload, null, 2));
|
|
return;
|
|
}
|
|
writeJson(FAILURE_PATH, payload);
|
|
console.error(JSON.stringify(payload, null, 2));
|
|
process.exit(1);
|
|
}
|
|
|
|
main().catch((error) => {
|
|
const payload = {
|
|
ok: false,
|
|
task: "task787-weknora-default-lightrag-legacy-smoke",
|
|
layer: "unexpected",
|
|
error: error && (error.stack || error.message) ? (error.stack || error.message) : String(error),
|
|
baseUrl: BASE_URL,
|
|
};
|
|
writeJson(FAILURE_PATH, payload);
|
|
console.error(JSON.stringify(payload, null, 2));
|
|
process.exit(1);
|
|
});
|