chore: checkpoint web Blender migration source, tools and docs
Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer, web workflow engine and protocol, tooling, docs and task records. Large generated evidence, user-testing archives, build snapshots and imported test assets remain untracked. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,7 @@ const arg = (name, fallback = null) => {
|
||||
return index >= 0 ? process.argv[index + 1] : fallback;
|
||||
};
|
||||
if (process.argv.includes("--help")) {
|
||||
process.stdout.write("usage: node tools/web/search-wbv2-batch-source.mjs [--task <task-id>] --pattern <text> [--unit <source-unit-id> ...] [--runtime <path> ...] [--max-hits <count>] or --list\n");
|
||||
process.stdout.write("usage: node tools/web/search-wbv2-batch-source.mjs [--task <task-id>] --pattern <text> [--unit <source-unit-id> ...] [--source <exact-blender-file> ...] [--runtime <exact-source-file> ...] [--max-hits <count=6>] [--max-output-bytes <1024..4096=1024>] or --list; matched lines use UTF-8 byte windows around the first match; narrow the pattern for omitted text\n");
|
||||
process.exit(0);
|
||||
}
|
||||
const task = arg("--task") ?? read("docs/EXECUTION_QUEUE.md").match(/\| 当前任务 \| `([^`]+)` \|/u)?.[1];
|
||||
@@ -26,6 +26,23 @@ 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();
|
||||
const explicitSources = process.argv.filter((value) => value === "--source").length;
|
||||
if (explicitSources > 8) throw new Error("source context is limited to 8 exact dependency files");
|
||||
for (let i = 0; i < process.argv.length; i += 1) {
|
||||
if (process.argv[i] !== "--source") continue;
|
||||
const file = process.argv[++i];
|
||||
if (!file || path.isAbsolute(file) || file.split("/").includes("..") ||
|
||||
!/^blender-5\.2\.0\/(source|intern|build_files|scripts|tests)\//u.test(file) ||
|
||||
!/(?:\.(?:c|cc|cpp|h|hh|hpp|py|cmake)|\/CMakeLists\.txt)$/u.test(file)) {
|
||||
throw new Error("--source requires an exact local Blender source file");
|
||||
}
|
||||
const resolved = fs.realpathSync(abs(file));
|
||||
const relative = path.relative(fs.realpathSync(abs("blender-5.2.0")), resolved);
|
||||
if (relative.startsWith("..") || path.isAbsolute(relative) || !fs.statSync(resolved).isFile()) {
|
||||
throw new Error("--source must stay within the local Blender source tree");
|
||||
}
|
||||
sourcePaths.add(file);
|
||||
}
|
||||
for (const artifact of Object.values(manifest?.artifacts ?? {})) {
|
||||
if (artifact?.path?.startsWith("blender-5.2.0/source/blender/")) sourcePaths.add(artifact.path);
|
||||
}
|
||||
@@ -48,6 +65,140 @@ if (selectedUnits.length) {
|
||||
}
|
||||
}
|
||||
const runtimeAllowlist = new Set([
|
||||
"web/app/src/app/WasmEditorContext.tsx",
|
||||
"web/app/src/app/WasmGPUCanvas.tsx",
|
||||
"blender-5.2.0/build_files/cmake/platform/web.cmake",
|
||||
"blender-5.2.0/build_files/cmake/platform/dependency_targets.cmake",
|
||||
"blender-5.2.0/source/blender/python/generic/CMakeLists.txt",
|
||||
"blender-5.2.0/source/blender/python/bmesh/CMakeLists.txt",
|
||||
"blender-5.2.0/source/blender/CMakeLists.txt",
|
||||
"blender-5.2.0/source/blender/python/CMakeLists.txt",
|
||||
"blender-5.2.0/source/blender/python/gpu/CMakeLists.txt",
|
||||
"tools/web/build-webengine-python.py",
|
||||
"blender-5.2.0/source/blender/web_engine/web_engine_python.cpp",
|
||||
"blender-5.2.0/source/blender/web_engine/web_engine_python.hh",
|
||||
"blender-5.2.0/source/blender/web_engine/web_engine_python_bootstrap.py",
|
||||
"blender-5.2.0/source/blender/python/mathutils/mathutils.cc",
|
||||
"blender-5.2.0/source/blender/python/mathutils/CMakeLists.txt",
|
||||
"blender-5.2.0/source/blender/python/generic/py_capi_utils.cc",
|
||||
"blender-5.2.0/source/blender/web_engine/web_engine_viewport.cpp",
|
||||
"blender-5.2.0/source/blender/web_engine/web_engine_viewport.hh",
|
||||
"web/app/src/app/WasmViewportBinding.tsx",
|
||||
"web/protocol/viewport-camera.ts",
|
||||
"blender-5.2.0/source/blender/blenkernel/BKE_camera.h",
|
||||
"blender-5.2.0/source/blender/blenkernel/intern/camera.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_view3d/view3d_view.cc",
|
||||
"web/app/src/app/WasmWorkspace.tsx",
|
||||
"web/app/src/app/WasmPythonConsole.tsx",
|
||||
"web/app/src/engine-client/PythonSessionClient.ts",
|
||||
"web/app/src/python/web_blender.py",
|
||||
"blender-5.2.0/source/blender/editors/space_image/image_edit.cc",
|
||||
"blender-5.2.0/source/blender/web_engine/extract_editor_storage.py",
|
||||
"blender-5.2.0/source/blender/makesrna/intern/rna_space.cc",
|
||||
"blender-5.2.0/source/blender/python/intern/bpy.cc",
|
||||
"blender-5.2.0/source/blender/python/intern/CMakeLists.txt",
|
||||
"blender-5.2.0/source/blender/blenkernel/intern/idtype.cc",
|
||||
"blender-5.2.0/source/blender/windowmanager/intern/wm.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_graph/space_graph.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_nla/space_nla.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_sequencer/space_sequencer.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_clip/space_clip.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_file/space_file.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_spreadsheet/space_spreadsheet.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_userpref/space_userpref.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_topbar/space_topbar.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_statusbar/space_statusbar.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_action/space_action.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_outliner/space_outliner.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_buttons/space_buttons.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_node/space_node.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_image/space_image.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_text/space_text.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_console/space_console.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_info/space_info.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_view3d/space_view3d.cc",
|
||||
"blender-5.2.0/source/blender/blenkernel/intern/screen.cc",
|
||||
"blender-5.2.0/source/blender/blenkernel/BKE_screen.hh",
|
||||
"blender-5.2.0/source/blender/makesdna/DNA_space_types.h",
|
||||
"blender-5.2.0/source/blender/makesdna/DNA_workspace_types.h",
|
||||
"blender-5.2.0/source/blender/makesdna/DNA_screen_types.h",
|
||||
"blender-5.2.0/source/blender/makesdna/DNA_windowmanager_types.h",
|
||||
"blender-5.2.0/source/blender/makesdna/DNA_space_enums.h",
|
||||
"blender-5.2.0/intern/ghost/intern/GHOST_NDOFHID.hh",
|
||||
"blender-5.2.0/source/blender/editors/space_view3d/view3d_ndof_math.hh",
|
||||
"blender-5.2.0/source/blender/web_engine/web_engine_ndof.cpp",
|
||||
"web/app/src/app/WasmShaderEditor.tsx",
|
||||
"web/app/src/app/WasmTextEditor.tsx",
|
||||
"web/app/src/app/WasmUVView.tsx",
|
||||
"web/app/src/app/WasmImageView.tsx",
|
||||
"web/app/src/app/NDOFControl.tsx",
|
||||
"web/protocol/ndof.ts",
|
||||
"web/protocol/shader-compiler.ts",
|
||||
"blender-5.2.0/source/blender/windowmanager/intern/wm_event_query.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_view3d/view3d_navigate_view_ndof.cc",
|
||||
"blender-5.2.0/intern/ghost/intern/GHOST_NDOFManager.cc",
|
||||
"blender-5.2.0/intern/ghost/intern/GHOST_NDOFManager.hh",
|
||||
"blender-5.2.0/intern/ghost/intern/GHOST_SystemWin32.cc",
|
||||
"blender-5.2.0/source/blender/blenkernel/intern/workspace.cc",
|
||||
"blender-5.2.0/source/blender/blenkernel/BKE_workspace.hh",
|
||||
"blender-5.2.0/source/blender/web_engine/CMakeLists.txt",
|
||||
"web/protocol/editor-workflow.ts",
|
||||
"web/app/src/three-adapter/viewport-navigation.ts",
|
||||
"blender-5.2.0/source/blender/windowmanager/wm_web_input.hh",
|
||||
"tools/web/native-extension-proxy.mjs",
|
||||
"tools/web/native-desktop-host.py",
|
||||
"tools/web/prepare-native-desktop.py",
|
||||
"tools/web/build-native-desktop-adapters.py",
|
||||
"web/app/src/app/DesktopWorkspace.tsx",
|
||||
"web/protocol/native-workflow.ts",
|
||||
"blender-5.2.0/source/blender/makesrna/intern/makesrna.cc",
|
||||
"blender-5.2.0/intern/ghost/GHOST_Types.hh",
|
||||
"blender-5.2.0/source/blender/windowmanager/WM_api.hh",
|
||||
"blender-5.2.0/source/blender/windowmanager/wm_event_system.hh",
|
||||
"blender-5.2.0/source/blender/windowmanager/wm.hh",
|
||||
"blender-5.2.0/source/blender/makesrna/intern/rna_wm_api.cc",
|
||||
"blender-5.2.0/intern/ghost/intern/GHOST_SystemX11.cc",
|
||||
"blender-5.2.0/scripts/addons_core/bl_pkg/cli/blender_ext.py",
|
||||
"blender-5.2.0/scripts/addons_core/bl_pkg/bl_extension_utils.py",
|
||||
"blender-5.2.0/scripts/startup/bl_operators/wm.py",
|
||||
"blender-5.2.0/source/blender/blenkernel/intern/appdir.cc",
|
||||
"blender-5.2.0/source/creator/creator_args.cc",
|
||||
"blender-5.2.0/scripts/startup/bl_ui/space_filebrowser.py",
|
||||
"blender-5.2.0/source/blender/editors/space_file/file_ops.cc",
|
||||
"blender-5.2.0/source/blender/makesrna/intern/rna_screen.cc",
|
||||
"blender-5.2.0/source/blender/windowmanager/intern/wm_window.cc",
|
||||
"tools/web/native-session-server.mjs",
|
||||
"tools/web/native-session-blender.py",
|
||||
"tools/web/native_desktop_io.py",
|
||||
"web/app/src/app/NativeSessionPanel.tsx",
|
||||
"web/app/src/engine-client/NativeSessionClient.ts",
|
||||
"blender-5.2.0/scripts/startup/bl_ui/space_userpref.py",
|
||||
"blender-5.2.0/source/blender/editors/io/io_obj.cc",
|
||||
"blender-5.2.0/source/blender/editors/io/io_ply_ops.cc",
|
||||
"blender-5.2.0/source/blender/editors/io/io_stl_ops.cc",
|
||||
"blender-5.2.0/source/blender/editors/io/io_usd.cc",
|
||||
"blender-5.2.0/source/blender/editors/io/io_alembic.cc",
|
||||
"blender-5.2.0/source/blender/windowmanager/intern/wm_files.cc",
|
||||
"blender-5.2.0/scripts/addons_core/io_scene_fbx/__init__.py",
|
||||
"blender-5.2.0/scripts/addons_core/io_scene_gltf2/__init__.py",
|
||||
"blender-5.2.0/source/blender/blenkernel/intern/node.cc",
|
||||
"blender-5.2.0/scripts/startup/bl_ui/space_topbar.py",
|
||||
"blender-5.2.0/scripts/startup/bl_ui/space_view3d.py",
|
||||
"blender-5.2.0/scripts/presets/keyconfig/keymap_data/blender_default.py",
|
||||
"blender-5.2.0/source/blender/editors/space_view3d/view3d_navigate_view_move.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_view3d/view3d_navigate_view_zoom.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_view3d/view3d_navigate_view_all.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_view3d/view3d_navigate_view_axis.cc",
|
||||
"blender-5.2.0/scripts/addons_core/io_scene_gltf2/blender/com/conversion.py",
|
||||
"blender-5.2.0/scripts/addons_core/io_scene_gltf2/blender/exp/nodes.py",
|
||||
"web/protocol/glb-export.ts",
|
||||
"blender-5.2.0/scripts/modules/_console_python.py",
|
||||
"blender-5.2.0/source/blender/makesrna/intern/rna_object.cc",
|
||||
"blender-5.2.0/source/blender/blenkernel/intern/object.cc",
|
||||
"blender-5.2.0/source/blender/blenkernel/intern/bpath.cc",
|
||||
"blender-5.2.0/source/blender/blenkernel/intern/packedFile.cc",
|
||||
"tools/web/native-resources.mjs",
|
||||
"tools/web/native_resources.py",
|
||||
"tools/web/run-wbv2-module-task.mjs",
|
||||
"tools/web/run-wbv2-continuous.mjs",
|
||||
"tools/web/bind-wbv2-module-coverage.mjs",
|
||||
@@ -91,8 +242,20 @@ const runtimeAllowlist = new Set([
|
||||
"blender-5.2.0/source/blender/web_engine/web_engine_api.cpp",
|
||||
"blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp",
|
||||
"blender-5.2.0/source/blender/web_engine/web_engine_main_state.h",
|
||||
"blender-5.2.0/source/blender/web_engine/web_engine_blend_reader.cpp",
|
||||
"blender-5.2.0/source/blender/editors/object/object_modifier.cc",
|
||||
"blender-5.2.0/source/blender/editors/physics/particle_object.cc",
|
||||
"blender-5.2.0/source/blender/editors/render/render_internal.cc",
|
||||
"blender-5.2.0/source/blender/editors/space_text/text_ops.cc",
|
||||
"blender-5.2.0/source/blender/python/intern/bpy_app_timers.cc",
|
||||
"blender-5.2.0/source/blender/windowmanager/intern/wm_event_system.cc",
|
||||
"blender-5.2.0/source/blender/blenkernel/intern/packedFile.cc",
|
||||
"blender-5.2.0/source/blender/makesrna/intern/rna_wm.cc",
|
||||
"blender-5.2.0/source/blender/blenkernel/BKE_particle.h",
|
||||
"blender-5.2.0/source/blender/blenkernel/intern/particle.cc",
|
||||
]);
|
||||
const runtimePaths = [];
|
||||
runtimeAllowlist.add("web/app/src/app/ModifierParameterField.tsx");
|
||||
// Product workflows are bounded subtasks of the current authoritative task.
|
||||
const backlogPath = abs("tests/golden/WBV2/web-product-backlog.json");
|
||||
if (fs.existsSync(backlogPath)) {
|
||||
@@ -103,12 +266,15 @@ if (fs.existsSync(backlogPath)) {
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const file of ["tools/web/deployment-http-server.mjs", "my-tools/wbv2_autopilot.py", "my-tools/run_web_product_migration.py", "tools/web/run-wbv2-scheduler.mjs", "tools/web/run-wbv2-guardian.mjs", "tools/web/check-wbv2-module-execution.mjs"]) runtimeAllowlist.add(file);
|
||||
for (const file of ["tools/web/deployment-http-server.mjs", "my-tools/wbv2_autopilot.py", "my-tools/wbv2_implementation_loop.py", "my-tools/run_web_product_migration.py", "tools/web/run-wbv2-scheduler.mjs", "tools/web/run-wbv2-guardian.mjs", "tools/web/check-wbv2-module-execution.mjs"]) runtimeAllowlist.add(file);
|
||||
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);
|
||||
if (runtimePaths.length + explicitSources >= 8) throw new Error("source context is limited to 8 exact dependency files");
|
||||
const canonical = fs.realpathSync(abs(file)), relative = path.relative(fs.realpathSync(root), canonical).split(path.sep).join("/");
|
||||
if (relative.startsWith("../") || path.isAbsolute(relative) || !fs.statSync(canonical).isFile()) throw new Error(`runtime path must be a file inside the repository: ${file}`);
|
||||
if (!runtimeAllowlist.has(file) && !/^(?:web\/app\/src|web\/protocol)\/.*\.(?:[cm]?[jt]sx?|css)$/u.test(relative)) throw new Error(`runtime path is not an approved source file: ${file}`);
|
||||
runtimePaths.push(relative);
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
@@ -118,7 +284,35 @@ 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], {
|
||||
function formatSourceHit(match) {
|
||||
const raw = typeof match.lines.text === "string" ? Buffer.from(match.lines.text, "utf8") : Buffer.from(match.lines.bytes ?? "", "base64");
|
||||
const length = raw.length - (raw.at(-1) === 10 ? (raw.at(-2) === 13 ? 2 : 1) : 0);
|
||||
const bytes = raw.subarray(0, length), line = bytes.toString("utf8"), matchStart = match.submatches[0]?.start ?? 0;
|
||||
const file = match.path.text ?? Buffer.from(match.path.bytes ?? "", "base64").toString("utf8");
|
||||
const prefix = `${allowedPaths.length > 1 ? `${file}:` : ""}${match.line_number}:`;
|
||||
let start = bytes.length > 384 ? Math.max(0, matchStart - 96) : 0;
|
||||
while (start > 0 && (bytes[start] & 0xc0) === 0x80) start -= 1;
|
||||
let end = Math.min(bytes.length, start + 384);
|
||||
while (end < bytes.length && end > start && (bytes[end] & 0xc0) === 0x80) end -= 1;
|
||||
const windowed = start > 0 || end < bytes.length;
|
||||
return { text: windowed ? `${prefix}[bytes ${start + 1}-${end}, match ${matchStart + 1}] ${start ? "…" : ""}${bytes.subarray(start, end).toString("utf8")}${end < bytes.length ? "…" : ""}` : prefix + line, windowed };
|
||||
}
|
||||
function renderSourceHits(matches, maxHits, maxOutputBytes) {
|
||||
const response = { schemaVersion: 1, task, scope: "batch", pattern: pattern.slice(0, 128), selectedUnits: [...selectedUnits], selectedUnitCount: selectedUnits.length, sourcePathCount: sourcePaths.size, runtimePathCount: runtimePaths.length, hitCount: matches.length, truncated: matches.length > maxHits || pattern.length > 128, windowed: false, outputByteLimit: maxOutputBytes, hitWindowByteLimit: 384, hits: [] };
|
||||
const serialize = () => JSON.stringify(response, null, 2) + "\n";
|
||||
while (Buffer.byteLength(serialize()) > maxOutputBytes && response.selectedUnits.length) { response.selectedUnits.pop(); response.truncated = true; }
|
||||
if (Buffer.byteLength(serialize()) > maxOutputBytes) { response.pattern = ""; response.truncated = true; }
|
||||
if (Buffer.byteLength(serialize()) > maxOutputBytes) throw new Error("search metadata exceeds output budget; narrow the task and source selection");
|
||||
for (const match of matches.slice(0, maxHits)) {
|
||||
const hit = formatSourceHit(match), previousWindowed = response.windowed;
|
||||
response.hits.push(hit.text); response.windowed ||= hit.windowed; response.truncated ||= hit.windowed;
|
||||
if (Buffer.byteLength(serialize()) > maxOutputBytes) { response.hits.pop(); response.windowed = previousWindowed; response.truncated = true; break; }
|
||||
}
|
||||
return serialize();
|
||||
}
|
||||
const maxOutputBytes = Number(arg("--max-output-bytes", "1024"));
|
||||
if (!Number.isSafeInteger(maxOutputBytes) || maxOutputBytes < 1024 || maxOutputBytes > 4096) throw new Error("--max-output-bytes must be between 1024 and 4096");
|
||||
const result = spawnSync("rg", ["--json", "--color", "never", "--", pattern, ...allowedPaths], {
|
||||
cwd: root,
|
||||
encoding: "utf8",
|
||||
maxBuffer: 8 * 1024 * 1024,
|
||||
@@ -126,7 +320,7 @@ const result = spawnSync("rg", ["--line-number", "--no-heading", "--color", "nev
|
||||
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) : [];
|
||||
const maxHits = Number(arg("--max-hits", "60"));
|
||||
const hits = output ? output.trimEnd().split("\n").filter(Boolean).map(line => JSON.parse(line)).filter(event => event.type === "match").map(event => event.data) : [];
|
||||
const maxHits = Number(arg("--max-hits", "6"));
|
||||
if (!Number.isSafeInteger(maxHits) || maxHits < 1 || maxHits > 200) throw new Error("--max-hits must be between 1 and 200");
|
||||
process.stdout.write(`${JSON.stringify({ schemaVersion: 1, task, scope: "batch", pattern, selectedUnits, sourcePathCount: sourcePaths.size, runtimePathCount: runtimePaths.length, hitCount: hits.length, truncated: hits.length > maxHits, hits: hits.slice(0, maxHits) }, null, 2)}\n`);
|
||||
process.stdout.write(renderSourceHits(hits, maxHits, maxOutputBytes));
|
||||
|
||||
Reference in New Issue
Block a user