220 lines
7.2 KiB
JavaScript
220 lines
7.2 KiB
JavaScript
#!/usr/bin/env node
|
|
"use strict";
|
|
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
const vm = require("node:vm");
|
|
const { TextDecoder } = require("node:util");
|
|
|
|
const repoRoot = path.resolve(__dirname, "..");
|
|
const openHubRoot = process.env.OPENHUB_RESEARCH_ROOT || "/tmp/mnote-openhub-research/OpenHub";
|
|
const embedPath = path.join(openHubRoot, "smart-query-frontend/src/mnoteEmbed.js");
|
|
const smartQueryPath = path.join(openHubRoot, "smart-query-frontend/src/pages/SmartQueryPage.jsx");
|
|
const diffViewerPath = path.join(openHubRoot, "smart-query-frontend/src/components/DiffViewer.jsx");
|
|
const designPath = path.join(
|
|
repoRoot,
|
|
"design/07-ai/process/7-68-openhub-weknora-mnote-deep-fusion-checklist-v1.md"
|
|
);
|
|
|
|
const forbiddenFulltextKeys = [
|
|
"message",
|
|
"messages",
|
|
"messageContent",
|
|
"conversation",
|
|
"conversationMessages",
|
|
"assistantMessage",
|
|
"userMessage",
|
|
"content",
|
|
"text",
|
|
"transcript",
|
|
];
|
|
|
|
function read(filePath) {
|
|
return fs.readFileSync(filePath, "utf8");
|
|
}
|
|
|
|
function assertCheck(failures, name, passed, details = undefined) {
|
|
if (!passed) failures.push({ name, details });
|
|
}
|
|
|
|
function base64UrlJson(value) {
|
|
return Buffer.from(JSON.stringify(value), "utf8")
|
|
.toString("base64")
|
|
.replace(/\+/g, "-")
|
|
.replace(/\//g, "_")
|
|
.replace(/=+$/g, "");
|
|
}
|
|
|
|
function hasForbiddenKey(value) {
|
|
if (!value || typeof value !== "object") return false;
|
|
if (Array.isArray(value)) return value.some(hasForbiddenKey);
|
|
return Object.entries(value).some(([key, entry]) => (
|
|
forbiddenFulltextKeys.includes(key) || hasForbiddenKey(entry)
|
|
));
|
|
}
|
|
|
|
async function probeEmbedRuntime(embedSource) {
|
|
const mnoteScope = {
|
|
workspaceScope: {
|
|
rootUri: "file:///tmp/mnote-artifact-root",
|
|
workspaceId: "ws-task780",
|
|
pageResourceId: "page-task780",
|
|
},
|
|
};
|
|
const calls = [];
|
|
const sandbox = {
|
|
console,
|
|
TextDecoder,
|
|
URLSearchParams,
|
|
Uint8Array,
|
|
window: {
|
|
location: {
|
|
pathname: "/page-ai/openhub/ai",
|
|
search: `?scope=session-task780&mnoteScope=${base64UrlJson(mnoteScope)}`,
|
|
origin: "http://127.0.0.1:3000",
|
|
},
|
|
parent: {},
|
|
atob: (value) => Buffer.from(value, "base64").toString("binary"),
|
|
},
|
|
fetch: async (url, options = {}) => {
|
|
calls.push({ url, options });
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ ok: true }),
|
|
};
|
|
},
|
|
module: { exports: {} },
|
|
exports: {},
|
|
};
|
|
const transformed = embedSource
|
|
.replace(/import\.meta\.env\.VITE_API_BASE_URL/g, "undefined")
|
|
.replace(/\bexport const /g, "const ");
|
|
vm.runInNewContext(
|
|
`${transformed}\nmodule.exports = { getMNoteArtifactIndexContext, postMNoteArtifactIndex };`,
|
|
sandbox,
|
|
{ filename: embedPath }
|
|
);
|
|
const result = await sandbox.module.exports.postMNoteArtifactIndex({
|
|
openhubSessionId: "ses-task780",
|
|
kind: "changed_file",
|
|
providerId: "opencode_tool_event",
|
|
path: "/tmp/mnote-artifact-root/page.md",
|
|
citationPayload: {
|
|
schema: "openhub.changed_file_locator.v1",
|
|
rootRelativePath: "page.md",
|
|
diffAvailable: false,
|
|
},
|
|
});
|
|
const body = calls[0] ? JSON.parse(calls[0].options.body) : null;
|
|
return { result, calls, body };
|
|
}
|
|
|
|
async function main() {
|
|
const failures = [];
|
|
const embed = read(embedPath);
|
|
const smartQuery = read(smartQueryPath);
|
|
const diffViewer = read(diffViewerPath);
|
|
const design = read(designPath);
|
|
|
|
assertCheck(
|
|
failures,
|
|
"mnoteEmbed posts artifact index to MNote root API",
|
|
embed.includes("postMNoteArtifactIndex") &&
|
|
embed.includes("getMNoteArtifactIndexContext") &&
|
|
embed.includes("fetch('/api/page-ai/openhub/artifact-index'") &&
|
|
embed.includes("credentials: 'include'")
|
|
);
|
|
assertCheck(
|
|
failures,
|
|
"artifact index payload is limited to locator fields",
|
|
["openhubSessionId", "kind", "providerId", "path", "citationPayload", "rootUri", "workspaceId", "pageResourceId"]
|
|
.every((field) => embed.includes(field))
|
|
);
|
|
assertCheck(
|
|
failures,
|
|
"SmartQueryPage indexes changed files after diff metadata is loaded",
|
|
smartQuery.includes("indexChangedFiles(sessionId, files)") &&
|
|
smartQuery.includes("kind: 'changed_file'") &&
|
|
smartQuery.includes("schema: 'openhub.changed_file_locator.v1'") &&
|
|
smartQuery.includes("providerId: file?.source || 'opencode_tool_event'")
|
|
);
|
|
assertCheck(
|
|
failures,
|
|
"SmartQueryPage indexes WeKnora citation locator payloads",
|
|
smartQuery.includes("indexCitationArtifacts") &&
|
|
smartQuery.includes("kind: 'citation'") &&
|
|
smartQuery.includes("schema: 'openhub.weknora_citation_locator.v1'") &&
|
|
smartQuery.includes("citation?.sourceRootRelativePath")
|
|
);
|
|
assertCheck(
|
|
failures,
|
|
"citation payload sanitizer strips fulltext-like fields recursively",
|
|
forbiddenFulltextKeys.every((key) => smartQuery.includes(`'${key}'`)) &&
|
|
smartQuery.includes("sanitizeCitationPayload(entry)") &&
|
|
smartQuery.includes(".filter(([key]) => !forbiddenKeys.has(key))")
|
|
);
|
|
assertCheck(
|
|
failures,
|
|
"changed file bridge remains connected to MNote open-file event",
|
|
diffViewer.includes("postMNoteOpenFile") &&
|
|
smartQuery.includes("data-mnote-openhub-changed-file") &&
|
|
smartQuery.includes("handleOpenChangedFile")
|
|
);
|
|
assertCheck(
|
|
failures,
|
|
"design checklist records task780 runtime artifact index bridge",
|
|
design.includes("task780-openhub-artifact-index-runtime-smoke.js") &&
|
|
design.includes("changed_file / citation 轻量 artifact index")
|
|
);
|
|
|
|
let runtimeProbe = null;
|
|
try {
|
|
runtimeProbe = await probeEmbedRuntime(embed);
|
|
assertCheck(
|
|
failures,
|
|
"runtime request uses MNote artifact-index endpoint",
|
|
runtimeProbe.calls.length === 1 &&
|
|
runtimeProbe.calls[0].url === "/api/page-ai/openhub/artifact-index" &&
|
|
runtimeProbe.calls[0].options.method === "POST",
|
|
runtimeProbe
|
|
);
|
|
assertCheck(
|
|
failures,
|
|
"runtime request body contains required locator fields",
|
|
runtimeProbe.body &&
|
|
runtimeProbe.body.openhubSessionId === "ses-task780" &&
|
|
runtimeProbe.body.kind === "changed_file" &&
|
|
runtimeProbe.body.providerId === "opencode_tool_event" &&
|
|
runtimeProbe.body.path === "/tmp/mnote-artifact-root/page.md" &&
|
|
runtimeProbe.body.rootUri === "file:///tmp/mnote-artifact-root" &&
|
|
runtimeProbe.body.workspaceId === "ws-task780" &&
|
|
runtimeProbe.body.pageResourceId === "page-task780",
|
|
runtimeProbe.body
|
|
);
|
|
assertCheck(
|
|
failures,
|
|
"runtime request body does not contain OpenHub message fulltext fields",
|
|
runtimeProbe.body && !hasForbiddenKey(runtimeProbe.body),
|
|
runtimeProbe.body
|
|
);
|
|
} catch (error) {
|
|
runtimeProbe = { error: error instanceof Error ? error.stack || error.message : String(error) };
|
|
assertCheck(failures, "runtime request shape probe executes", false, runtimeProbe);
|
|
}
|
|
|
|
const result = {
|
|
ok: failures.length === 0,
|
|
task: "task780-openhub-artifact-index-runtime-smoke",
|
|
runtimeProbe,
|
|
failures,
|
|
};
|
|
console.log(JSON.stringify(result, null, 2));
|
|
if (!result.ok) process.exit(1);
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error instanceof Error ? error.stack || error.message : String(error));
|
|
process.exit(1);
|
|
});
|