From b1376b46af8bfaa1a0eb77209a7764c3ba291652 Mon Sep 17 00:00:00 2001 From: mes123456 Date: Thu, 3 Sep 2026 15:12:21 +0800 Subject: [PATCH] perf(web): add batch-scoped source search entrypoint --- docs/EXECUTION_QUEUE.md | 2 + tools/web/search-wbv2-batch-source.mjs | 64 ++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 tools/web/search-wbv2-batch-source.mjs diff --git a/docs/EXECUTION_QUEUE.md b/docs/EXECUTION_QUEUE.md index e758c93f5..9fcf340ad 100644 --- a/docs/EXECUTION_QUEUE.md +++ b/docs/EXECUTION_QUEUE.md @@ -43,6 +43,8 @@ node tools/web/print-task-context.mjs - 每完成一个段落或模块,报告必须明确列出源码动态 inventory 的 unitCount、verifiedUnits、unverifiedUnits、sourceTreeSha256,以及本段绑定的 unit 增量;历史 `6900-entry` 只读保留,未绑定源码单元不得计为覆盖进度。 - 失败、环境缺失或 hash 漂移保持 `in_progress`/`blocked`,不得推进指针。 +源码检索强制使用 `node tools/web/search-wbv2-batch-source.mjs --pattern `(可重复 `--runtime`);禁止全量 `rg`/`sed`,完整 inventory 仅显式 `--full-inventory` 审计。 + ## 交接 ```text diff --git a/tools/web/search-wbv2-batch-source.mjs b/tools/web/search-wbv2-batch-source.mjs new file mode 100644 index 000000000..ee38a5947 --- /dev/null +++ b/tools/web/search-wbv2-batch-source.mjs @@ -0,0 +1,64 @@ +import fs from "node:fs"; +import path from "node:path"; +import { spawnSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); +const abs = (file) => path.join(root, file); +const read = (file) => fs.readFileSync(abs(file), "utf8"); +const json = (file) => JSON.parse(read(file)); +const arg = (name, fallback = null) => { + const index = process.argv.indexOf(name); + return index >= 0 ? process.argv[index + 1] : fallback; +}; +const task = arg("--task") ?? read("docs/EXECUTION_QUEUE.md").match(/\| 当前任务 \| `([^`]+)` \|/u)?.[1]; +const pattern = arg("--pattern"); +if (!task) throw new Error("unable to resolve current WBV2 task"); +if (!pattern && !process.argv.includes("--list")) throw new Error("usage: node tools/web/search-wbv2-batch-source.mjs [--task ] --pattern [--runtime ...] or --list"); + +const queueTaskManifest = `tests/golden/${task}/manifest.json`; +const index = json("tests/golden/WBV2/task-index.json"); +const plan = json(index.source.path); +const taskRecord = plan.tasks.find((candidate) => candidate.id === task); +const manifest = fs.existsSync(abs(queueTaskManifest)) ? json(queueTaskManifest) : null; +const sourcePaths = new Set(); +for (const artifact of Object.values(manifest?.artifacts ?? {})) { + if (artifact?.path?.startsWith("blender-5.2.0/source/blender/")) sourcePaths.add(artifact.path); +} +for (const source of taskRecord?.module?.sourcePaths ?? []) { + if (source.startsWith("blender-5.2.0/source/blender/")) sourcePaths.add(source); +} +const runtimeAllowlist = new Set([ + "tools/web/run-wbv2-module-task.mjs", + "tools/web/bind-wbv2-module-coverage.mjs", + "tools/web/finalize-wbv2-module-batch.mjs", + "tools/web/check-source-dynamic-inventory.mjs", + "tools/web/reuse-wbv2-source-dependencies.mjs", + "my-tools/wbv2_runtime.py", + "web/app/src/engine-client/WebEngineClient.ts", +]); +const runtimePaths = []; +for (let index = 0; index < process.argv.length; index += 1) { + if (process.argv[index] === "--runtime") { + const file = process.argv[index + 1]; + if (!runtimeAllowlist.has(file)) throw new Error(`runtime path is not allowlisted: ${file}`); + runtimePaths.push(file); + index += 1; + } +} +const allowedPaths = [...sourcePaths, ...runtimePaths].filter((file, index, all) => all.indexOf(file) === index && fs.existsSync(abs(file))); +if (!allowedPaths.length) throw new Error(`no batch-scoped source paths found for ${task}`); +if (process.argv.includes("--list")) { + process.stdout.write(`${JSON.stringify({ schemaVersion: 1, task, scope: "batch", sourcePaths: [...sourcePaths], runtimePaths, allowedPaths }, null, 2)}\n`); + process.exit(0); +} +const result = spawnSync("rg", ["--line-number", "--no-heading", "--color", "never", pattern, ...allowedPaths], { + cwd: root, + encoding: "utf8", + maxBuffer: 8 * 1024 * 1024, +}); +if (result.error) throw result.error; +if (result.status !== 0 && result.status !== 1) throw new Error(`batch search failed with exit ${result.status}`); +const output = result.stdout ?? ""; +const hits = output ? output.trimEnd().split("\n").filter(Boolean) : []; +process.stdout.write(`${JSON.stringify({ schemaVersion: 1, task, scope: "batch", pattern, sourcePathCount: sourcePaths.size, runtimePathCount: runtimePaths.length, hitCount: hits.length, hits }, null, 2)}\n`);