From 727eaad463c4edd0db4d279643e41e937b587a37 Mon Sep 17 00:00:00 2001 From: Sagi Date: Sat, 4 Jul 2026 22:38:14 +0800 Subject: [PATCH 1/5] feat(ubml): complete EntityGen MoF v0 milestone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deliver explicit entity/page metamodels, typed BE edit operations, model right panel with Mermaid ER, Dev Lab JSON output, and archive OpenSpec changes E1–E4 with living specs and acceptance checklist. Co-authored-by: Cursor --- bun.lock | 234 ++++++ docs/MOF-ENTITYGEN-V0-ACCEPTANCE-CHECKLIST.md | 211 +++++ docs/UBML-ENTITY-VERIFY.md | 12 +- docs/UBML-EXTERNAL-REFERENCE.md | 69 ++ docs/product-design/10-mof-entity-modeling.md | 724 ++++++++++++++++++ docs/product-design/README.md | 3 +- .../.openspec.yaml | 2 + .../design.md | 67 ++ .../proposal.md | 27 + .../specs/ubml-entity-metamodel/spec.md | 67 ++ .../2026-07-04-ubml-entity-metamodel/tasks.md | 21 + .../.openspec.yaml | 2 + .../design.md | 43 ++ .../proposal.md | 29 + .../specs/ubml-entity-agent/spec.md | 18 + .../specs/ubml-entity-modeling-ops/spec.md | 48 ++ .../tasks.md | 32 + .../.openspec.yaml | 2 + .../design.md | 17 + .../proposal.md | 26 + .../specs/ubml-model-right-panel/spec.md | 28 + .../tasks.md | 20 + .../.openspec.yaml | 2 + .../proposal.md | 25 + .../specs/ubml-model-tab-pages/spec.md | 16 + .../2026-07-04-ubml-model-tab-pages/tasks.md | 19 + .../.openspec.yaml | 2 + .../2026-07-04-ubml-page-metamodel/design.md | 66 ++ .../proposal.md | 28 + .../specs/ubml-page-metamodel/spec.md | 69 ++ .../2026-07-04-ubml-page-metamodel/tasks.md | 24 + openspec/specs/ubml-entity-agent/spec.md | 13 + openspec/specs/ubml-entity-metamodel/spec.md | 71 ++ .../specs/ubml-entity-modeling-ops/spec.md | 52 ++ openspec/specs/ubml-model-right-panel/spec.md | 32 + openspec/specs/ubml-model-tab-pages/spec.md | 20 + openspec/specs/ubml-page-metamodel/spec.md | 73 ++ .../assets/ubml-entity/metamodel/README.md | 49 ++ .../metamodel/ubml-entity.metamodel.yaml | 220 ++++++ .../assets/ubml-entity/prompts/entitygen.md | 62 +- .../extension/assets/ubml/metamodel/README.md | 29 + .../ubml/metamodel/ubml-page.metamodel.yaml | 119 +++ .../extension/assets/ubml/prompts/pagegen.md | 27 + packages/extension/package.json | 1 + .../src/components/dev-lab-compile-output.tsx | 72 ++ .../src/components/entity-preview-content.tsx | 30 +- .../src/components/mermaid-diagram.tsx | 80 ++ .../src/components/page-preview-content.tsx | 72 ++ .../src/components/ubml-artifact-card.tsx | 5 +- .../components/ubml-assistant-text-part.tsx | 7 +- packages/extension/src/index.ts | 1 + .../extension/src/lib/entity-preview-model.ts | 47 +- .../src/lib/model-preview-model.test.ts | 89 +++ .../extension/src/lib/model-preview-model.ts | 78 ++ .../extension/src/lib/page-preview-model.ts | 89 +++ .../src/lib/right-panel-preview-utils.ts | 4 +- .../src/lib/ubml-artifact-actions.ts | 91 ++- .../src/lib/ubml-dev-lab-bridge.test.ts | 37 + .../extension/src/lib/ubml-dev-lab-bridge.ts | 38 +- .../src/lib/ubml-entity-artifact-actions.ts | 21 +- .../extension/src/lib/ubml-model-bridge.ts | 83 ++ .../extension/src/lib/ubml-model-tab-store.ts | 83 ++ .../pages/layout/project-workspace-shell.tsx | 5 + .../layout/right-panel-entity-preview.tsx | 13 +- .../src/pages/layout/right-panel-labels.ts | 1 + .../src/pages/layout/right-panel-model.tsx | 5 + .../src/pages/layout/right-panel.tsx | 6 +- .../src/pages/workbench/ubml-app-editor.tsx | 125 ++- .../pages/workbench/ubml-entity-editor.tsx | 51 +- .../src/pages/workbench/ubml-main-view.tsx | 10 +- .../src/pages/workbench/ubml-model-panel.tsx | 136 ++++ .../src/pages/workbench/ubml-page-editor.tsx | 47 +- .../eval/entity-generation-tasks.json | 56 ++ .../eval/eval-entity-generation-core.ts | 59 ++ .../knowledge/script/be-modeling-ops-smoke.ts | 95 +++ packages/knowledge/src/mcp/server.ts | 20 +- packages/knowledge/src/service.ts | 6 +- .../src/entity/edit/entity-dsl.ts | 23 +- .../src/entity/edit/operations.ts | 139 ++++ .../ubml-codegen/src/entity/edit/typed-ops.ts | 309 ++++++++ .../ubml-codegen/src/entity/edit/types.ts | 33 + .../ubml-codegen/src/entity/metamodel/load.ts | 70 ++ .../src/entity/metamodel/prompt.ts | 93 +++ .../src/entity/metamodel/types.ts | 73 ++ packages/ubml-codegen/src/index.ts | 43 ++ .../ubml-codegen/src/page/metamodel/load.ts | 71 ++ .../ubml-codegen/src/page/metamodel/prompt.ts | 85 ++ .../ubml-codegen/src/page/metamodel/types.ts | 10 + packages/ubml-codegen/src/runtime.ts | 5 + .../test/entity-edit-operations.test.ts | 208 +++++ .../test/entity-metamodel-consistency.test.ts | 107 +++ .../test/page-metamodel-consistency.test.ts | 115 +++ 92 files changed, 5537 insertions(+), 130 deletions(-) create mode 100644 docs/MOF-ENTITYGEN-V0-ACCEPTANCE-CHECKLIST.md create mode 100644 docs/UBML-EXTERNAL-REFERENCE.md create mode 100644 docs/product-design/10-mof-entity-modeling.md create mode 100644 openspec/changes/archive/2026-07-04-ubml-entity-metamodel/.openspec.yaml create mode 100644 openspec/changes/archive/2026-07-04-ubml-entity-metamodel/design.md create mode 100644 openspec/changes/archive/2026-07-04-ubml-entity-metamodel/proposal.md create mode 100644 openspec/changes/archive/2026-07-04-ubml-entity-metamodel/specs/ubml-entity-metamodel/spec.md create mode 100644 openspec/changes/archive/2026-07-04-ubml-entity-metamodel/tasks.md create mode 100644 openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/.openspec.yaml create mode 100644 openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/design.md create mode 100644 openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/proposal.md create mode 100644 openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/specs/ubml-entity-agent/spec.md create mode 100644 openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/specs/ubml-entity-modeling-ops/spec.md create mode 100644 openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/tasks.md create mode 100644 openspec/changes/archive/2026-07-04-ubml-model-right-panel/.openspec.yaml create mode 100644 openspec/changes/archive/2026-07-04-ubml-model-right-panel/design.md create mode 100644 openspec/changes/archive/2026-07-04-ubml-model-right-panel/proposal.md create mode 100644 openspec/changes/archive/2026-07-04-ubml-model-right-panel/specs/ubml-model-right-panel/spec.md create mode 100644 openspec/changes/archive/2026-07-04-ubml-model-right-panel/tasks.md create mode 100644 openspec/changes/archive/2026-07-04-ubml-model-tab-pages/.openspec.yaml create mode 100644 openspec/changes/archive/2026-07-04-ubml-model-tab-pages/proposal.md create mode 100644 openspec/changes/archive/2026-07-04-ubml-model-tab-pages/specs/ubml-model-tab-pages/spec.md create mode 100644 openspec/changes/archive/2026-07-04-ubml-model-tab-pages/tasks.md create mode 100644 openspec/changes/archive/2026-07-04-ubml-page-metamodel/.openspec.yaml create mode 100644 openspec/changes/archive/2026-07-04-ubml-page-metamodel/design.md create mode 100644 openspec/changes/archive/2026-07-04-ubml-page-metamodel/proposal.md create mode 100644 openspec/changes/archive/2026-07-04-ubml-page-metamodel/specs/ubml-page-metamodel/spec.md create mode 100644 openspec/changes/archive/2026-07-04-ubml-page-metamodel/tasks.md create mode 100644 openspec/specs/ubml-entity-metamodel/spec.md create mode 100644 openspec/specs/ubml-entity-modeling-ops/spec.md create mode 100644 openspec/specs/ubml-model-right-panel/spec.md create mode 100644 openspec/specs/ubml-model-tab-pages/spec.md create mode 100644 openspec/specs/ubml-page-metamodel/spec.md create mode 100644 packages/extension/assets/ubml-entity/metamodel/README.md create mode 100644 packages/extension/assets/ubml-entity/metamodel/ubml-entity.metamodel.yaml create mode 100644 packages/extension/assets/ubml/metamodel/README.md create mode 100644 packages/extension/assets/ubml/metamodel/ubml-page.metamodel.yaml create mode 100644 packages/extension/src/components/dev-lab-compile-output.tsx create mode 100644 packages/extension/src/components/mermaid-diagram.tsx create mode 100644 packages/extension/src/components/page-preview-content.tsx create mode 100644 packages/extension/src/lib/model-preview-model.test.ts create mode 100644 packages/extension/src/lib/model-preview-model.ts create mode 100644 packages/extension/src/lib/page-preview-model.ts create mode 100644 packages/extension/src/lib/ubml-dev-lab-bridge.test.ts create mode 100644 packages/extension/src/lib/ubml-model-bridge.ts create mode 100644 packages/extension/src/lib/ubml-model-tab-store.ts create mode 100644 packages/extension/src/pages/layout/right-panel-model.tsx create mode 100644 packages/extension/src/pages/workbench/ubml-model-panel.tsx create mode 100644 packages/knowledge/eval/entity-generation-tasks.json create mode 100644 packages/knowledge/eval/eval-entity-generation-core.ts create mode 100644 packages/knowledge/script/be-modeling-ops-smoke.ts create mode 100644 packages/ubml-codegen/src/entity/edit/operations.ts create mode 100644 packages/ubml-codegen/src/entity/edit/typed-ops.ts create mode 100644 packages/ubml-codegen/src/entity/edit/types.ts create mode 100644 packages/ubml-codegen/src/entity/metamodel/load.ts create mode 100644 packages/ubml-codegen/src/entity/metamodel/prompt.ts create mode 100644 packages/ubml-codegen/src/entity/metamodel/types.ts create mode 100644 packages/ubml-codegen/src/page/metamodel/load.ts create mode 100644 packages/ubml-codegen/src/page/metamodel/prompt.ts create mode 100644 packages/ubml-codegen/src/page/metamodel/types.ts create mode 100644 packages/ubml-codegen/test/entity-edit-operations.test.ts create mode 100644 packages/ubml-codegen/test/entity-metamodel-consistency.test.ts create mode 100644 packages/ubml-codegen/test/page-metamodel-consistency.test.ts diff --git a/bun.lock b/bun.lock index fdf1e5b0..7d0f1349 100644 --- a/bun.lock +++ b/bun.lock @@ -244,6 +244,7 @@ "@thisbeyond/solid-dnd": "0.7.5", "effect": "catalog:", "luxon": "catalog:", + "mermaid": "11.12.0", "solid-js": "catalog:", "virtua": "catalog:", }, @@ -567,6 +568,7 @@ "ubml-component-compile": "./src/component/cli/component-compile.ts", "ubml-javagen": "./src/javagen/cli/javagen.ts", "ubml-app-generate": "./src/app/cli/app-generate.ts", + "ubml-app-decompile": "./src/app/cli/app-decompile.ts", }, "dependencies": { "js-yaml": "^4.1.0", @@ -818,6 +820,8 @@ "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], + "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "https://registry.npmmirror.com/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], + "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.71.2", "https://registry.npmmirror.com/@anthropic-ai/sdk/-/sdk-0.71.2.tgz", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-TGNDEUuEstk/DKu0/TflXAEt+p+p/WhTlFzEnoosvbaDU2LTjm42igSdlL0VijrKpWejtOKxX0b8A7uc+XiSAQ=="], "@anycable/core": ["@anycable/core@0.9.2", "https://registry.npmmirror.com/@anycable/core/-/core-0.9.2.tgz", { "dependencies": { "nanoevents": "^7.0.1" } }, "sha512-x5ZXDcW/N4cxWl93CnbHs/u7qq4793jS2kNPWm+duPrXlrva+ml2ZGT7X9tuOBKzyIHf60zWCdIK7TUgMPAwXA=="], @@ -930,6 +934,18 @@ "@babel/types": ["@babel/types@8.0.0", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.0" } }, "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw=="], + "@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "https://registry.npmmirror.com/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="], + + "@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@11.0.3", "https://registry.npmmirror.com/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", { "dependencies": { "@chevrotain/gast": "11.0.3", "@chevrotain/types": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ=="], + + "@chevrotain/gast": ["@chevrotain/gast@11.0.3", "https://registry.npmmirror.com/@chevrotain/gast/-/gast-11.0.3.tgz", { "dependencies": { "@chevrotain/types": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q=="], + + "@chevrotain/regexp-to-ast": ["@chevrotain/regexp-to-ast@11.0.3", "https://registry.npmmirror.com/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", {}, "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA=="], + + "@chevrotain/types": ["@chevrotain/types@11.0.3", "https://registry.npmmirror.com/@chevrotain/types/-/types-11.0.3.tgz", {}, "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ=="], + + "@chevrotain/utils": ["@chevrotain/utils@11.0.3", "https://registry.npmmirror.com/@chevrotain/utils/-/utils-11.0.3.tgz", {}, "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ=="], + "@clack/core": ["@clack/core@1.0.0-alpha.1", "https://registry.npmmirror.com/@clack/core/-/core-1.0.0-alpha.1.tgz", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-rFbCU83JnN7l3W1nfgCqqme4ZZvTTgsiKQ6FM0l+r0P+o2eJpExcocBUWUIwnDzL76Aca9VhUdWmB2MbUv+Qyg=="], "@clack/prompts": ["@clack/prompts@1.0.0-alpha.1", "https://registry.npmmirror.com/@clack/prompts/-/prompts-1.0.0-alpha.1.tgz", { "dependencies": { "@clack/core": "1.0.0-alpha.1", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-07MNT0OsxjKOcyVfX8KhXBhJiyUbDP1vuIAcHc+nx5v93MJO23pX3X/k3bWz6T3rpM9dgWPq90i4Jq7gZAyMbw=="], @@ -1092,6 +1108,10 @@ "@hono/node-server": ["@hono/node-server@1.19.14", "https://registry.npmmirror.com/@hono/node-server/-/node-server-1.19.14.tgz", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="], + "@iconify/types": ["@iconify/types@2.0.0", "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], + + "@iconify/utils": ["@iconify/utils@3.1.3", "https://registry.npmmirror.com/@iconify/utils/-/utils-3.1.3.tgz", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "import-meta-resolve": "^4.2.0" } }, "sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw=="], + "@internationalized/date": ["@internationalized/date@3.12.2", "https://registry.npmmirror.com/@internationalized/date/-/date-3.12.2.tgz", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-FY1Y+H64NDs+HAF6omlnWxm3mEpfgaCSWtL5l551ZZfImA+kGjPFgrnJrGjH6lfmLL0g8Z/mBu1R3kufeCp6Jw=="], "@internationalized/number": ["@internationalized/number@3.6.7", "https://registry.npmmirror.com/@internationalized/number/-/number-3.6.7.tgz", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg=="], @@ -1156,6 +1176,8 @@ "@malept/flatpak-bundler": ["@malept/flatpak-bundler@0.4.0", "https://registry.npmmirror.com/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", { "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.0", "lodash": "^4.17.15", "tmp-promise": "^3.0.2" } }, "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q=="], + "@mermaid-js/parser": ["@mermaid-js/parser@0.6.3", "https://registry.npmmirror.com/@mermaid-js/parser/-/parser-0.6.3.tgz", { "dependencies": { "langium": "3.3.1" } }, "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA=="], + "@microsoft/api-extractor": ["@microsoft/api-extractor@7.58.9", "https://registry.npmmirror.com/@microsoft/api-extractor/-/api-extractor-7.58.9.tgz", { "dependencies": { "@microsoft/api-extractor-model": "7.33.8", "@microsoft/tsdoc": "~0.16.0", "@microsoft/tsdoc-config": "~0.18.1", "@rushstack/node-core-library": "5.23.1", "@rushstack/rig-package": "0.7.3", "@rushstack/terminal": "0.24.0", "@rushstack/ts-command-line": "5.3.10", "diff": "~8.0.2", "minimatch": "10.2.3", "resolve": "~1.22.1", "semver": "~7.7.4", "source-map": "~0.6.1", "typescript": "5.9.3" }, "bin": { "api-extractor": "bin/api-extractor" } }, "sha512-S2UF4yza5GoxCmf7hJQNxJNZN9ltOVuOQv8Dy+Z21aol5ERoBNMdWcQHm4MJMPPItW4H/4rZD906iaf4mUojJA=="], "@microsoft/api-extractor-model": ["@microsoft/api-extractor-model@7.33.8", "https://registry.npmmirror.com/@microsoft/api-extractor-model/-/api-extractor-model-7.33.8.tgz", { "dependencies": { "@microsoft/tsdoc": "~0.16.0", "@microsoft/tsdoc-config": "~0.18.1", "@rushstack/node-core-library": "5.23.1" } }, "sha512-aIcoQggPyer3B6Ze3usz0YWC/oBwUHfRH5ETUsr+oT2BRA6SfTJl7IKPcPZkX4UR+PohowzW4uMxsvjrn8vm+w=="], @@ -1674,6 +1696,68 @@ "@types/cross-spawn": ["@types/cross-spawn@6.0.6", "https://registry.npmmirror.com/@types/cross-spawn/-/cross-spawn-6.0.6.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA=="], + "@types/d3": ["@types/d3@7.4.3", "https://registry.npmmirror.com/@types/d3/-/d3-7.4.3.tgz", { "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", "@types/d3-brush": "*", "@types/d3-chord": "*", "@types/d3-color": "*", "@types/d3-contour": "*", "@types/d3-delaunay": "*", "@types/d3-dispatch": "*", "@types/d3-drag": "*", "@types/d3-dsv": "*", "@types/d3-ease": "*", "@types/d3-fetch": "*", "@types/d3-force": "*", "@types/d3-format": "*", "@types/d3-geo": "*", "@types/d3-hierarchy": "*", "@types/d3-interpolate": "*", "@types/d3-path": "*", "@types/d3-polygon": "*", "@types/d3-quadtree": "*", "@types/d3-random": "*", "@types/d3-scale": "*", "@types/d3-scale-chromatic": "*", "@types/d3-selection": "*", "@types/d3-shape": "*", "@types/d3-time": "*", "@types/d3-time-format": "*", "@types/d3-timer": "*", "@types/d3-transition": "*", "@types/d3-zoom": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="], + + "@types/d3-array": ["@types/d3-array@3.2.2", "https://registry.npmmirror.com/@types/d3-array/-/d3-array-3.2.2.tgz", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], + + "@types/d3-axis": ["@types/d3-axis@3.0.6", "https://registry.npmmirror.com/@types/d3-axis/-/d3-axis-3.0.6.tgz", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw=="], + + "@types/d3-brush": ["@types/d3-brush@3.0.6", "https://registry.npmmirror.com/@types/d3-brush/-/d3-brush-3.0.6.tgz", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A=="], + + "@types/d3-chord": ["@types/d3-chord@3.0.6", "https://registry.npmmirror.com/@types/d3-chord/-/d3-chord-3.0.6.tgz", {}, "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg=="], + + "@types/d3-color": ["@types/d3-color@3.1.3", "https://registry.npmmirror.com/@types/d3-color/-/d3-color-3.1.3.tgz", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], + + "@types/d3-contour": ["@types/d3-contour@3.0.6", "https://registry.npmmirror.com/@types/d3-contour/-/d3-contour-3.0.6.tgz", { "dependencies": { "@types/d3-array": "*", "@types/geojson": "*" } }, "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg=="], + + "@types/d3-delaunay": ["@types/d3-delaunay@6.0.4", "https://registry.npmmirror.com/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", {}, "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw=="], + + "@types/d3-dispatch": ["@types/d3-dispatch@3.0.7", "https://registry.npmmirror.com/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", {}, "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA=="], + + "@types/d3-drag": ["@types/d3-drag@3.0.7", "https://registry.npmmirror.com/@types/d3-drag/-/d3-drag-3.0.7.tgz", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ=="], + + "@types/d3-dsv": ["@types/d3-dsv@3.0.7", "https://registry.npmmirror.com/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", {}, "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g=="], + + "@types/d3-ease": ["@types/d3-ease@3.0.2", "https://registry.npmmirror.com/@types/d3-ease/-/d3-ease-3.0.2.tgz", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="], + + "@types/d3-fetch": ["@types/d3-fetch@3.0.7", "https://registry.npmmirror.com/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", { "dependencies": { "@types/d3-dsv": "*" } }, "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA=="], + + "@types/d3-force": ["@types/d3-force@3.0.10", "https://registry.npmmirror.com/@types/d3-force/-/d3-force-3.0.10.tgz", {}, "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw=="], + + "@types/d3-format": ["@types/d3-format@3.0.4", "https://registry.npmmirror.com/@types/d3-format/-/d3-format-3.0.4.tgz", {}, "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g=="], + + "@types/d3-geo": ["@types/d3-geo@3.1.0", "https://registry.npmmirror.com/@types/d3-geo/-/d3-geo-3.1.0.tgz", { "dependencies": { "@types/geojson": "*" } }, "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ=="], + + "@types/d3-hierarchy": ["@types/d3-hierarchy@3.1.7", "https://registry.npmmirror.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", {}, "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg=="], + + "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "https://registry.npmmirror.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="], + + "@types/d3-path": ["@types/d3-path@3.1.1", "https://registry.npmmirror.com/@types/d3-path/-/d3-path-3.1.1.tgz", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], + + "@types/d3-polygon": ["@types/d3-polygon@3.0.2", "https://registry.npmmirror.com/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", {}, "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA=="], + + "@types/d3-quadtree": ["@types/d3-quadtree@3.0.6", "https://registry.npmmirror.com/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", {}, "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg=="], + + "@types/d3-random": ["@types/d3-random@3.0.4", "https://registry.npmmirror.com/@types/d3-random/-/d3-random-3.0.4.tgz", {}, "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA=="], + + "@types/d3-scale": ["@types/d3-scale@4.0.9", "https://registry.npmmirror.com/@types/d3-scale/-/d3-scale-4.0.9.tgz", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="], + + "@types/d3-scale-chromatic": ["@types/d3-scale-chromatic@3.1.0", "https://registry.npmmirror.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", {}, "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ=="], + + "@types/d3-selection": ["@types/d3-selection@3.0.11", "https://registry.npmmirror.com/@types/d3-selection/-/d3-selection-3.0.11.tgz", {}, "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w=="], + + "@types/d3-shape": ["@types/d3-shape@3.1.8", "https://registry.npmmirror.com/@types/d3-shape/-/d3-shape-3.1.8.tgz", { "dependencies": { "@types/d3-path": "*" } }, "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w=="], + + "@types/d3-time": ["@types/d3-time@3.0.4", "https://registry.npmmirror.com/@types/d3-time/-/d3-time-3.0.4.tgz", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="], + + "@types/d3-time-format": ["@types/d3-time-format@4.0.3", "https://registry.npmmirror.com/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", {}, "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg=="], + + "@types/d3-timer": ["@types/d3-timer@3.0.2", "https://registry.npmmirror.com/@types/d3-timer/-/d3-timer-3.0.2.tgz", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="], + + "@types/d3-transition": ["@types/d3-transition@3.0.9", "https://registry.npmmirror.com/@types/d3-transition/-/d3-transition-3.0.9.tgz", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg=="], + + "@types/d3-zoom": ["@types/d3-zoom@3.0.8", "https://registry.npmmirror.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw=="], + "@types/debug": ["@types/debug@4.1.13", "https://registry.npmmirror.com/@types/debug/-/debug-4.1.13.tgz", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], "@types/estree": ["@types/estree@1.0.9", "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], @@ -1682,6 +1766,8 @@ "@types/gensync": ["@types/gensync@1.0.5", "", {}, "sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg=="], + "@types/geojson": ["@types/geojson@7946.0.16", "https://registry.npmmirror.com/@types/geojson/-/geojson-7946.0.16.tgz", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="], + "@types/hast": ["@types/hast@3.0.4", "https://registry.npmmirror.com/@types/hast/-/hast-3.0.4.tgz", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], "@types/http-cache-semantics": ["@types/http-cache-semantics@4.2.0", "https://registry.npmmirror.com/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", {}, "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q=="], @@ -1956,6 +2042,10 @@ "character-entities-legacy": ["character-entities-legacy@3.0.0", "https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + "chevrotain": ["chevrotain@11.0.3", "https://registry.npmmirror.com/chevrotain/-/chevrotain-11.0.3.tgz", { "dependencies": { "@chevrotain/cst-dts-gen": "11.0.3", "@chevrotain/gast": "11.0.3", "@chevrotain/regexp-to-ast": "11.0.3", "@chevrotain/types": "11.0.3", "@chevrotain/utils": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw=="], + + "chevrotain-allstar": ["chevrotain-allstar@0.3.1", "https://registry.npmmirror.com/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", { "dependencies": { "lodash-es": "^4.17.21" }, "peerDependencies": { "chevrotain": "^11.0.0" } }, "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw=="], + "chokidar": ["chokidar@4.0.3", "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "chownr": ["chownr@3.0.0", "https://registry.npmmirror.com/chownr/-/chownr-3.0.0.tgz", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], @@ -2022,6 +2112,8 @@ "cors": ["cors@2.8.6", "https://registry.npmmirror.com/cors/-/cors-2.8.6.tgz", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], + "cose-base": ["cose-base@1.0.3", "https://registry.npmmirror.com/cose-base/-/cose-base-1.0.3.tgz", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="], + "cross-dirname": ["cross-dirname@0.1.0", "https://registry.npmmirror.com/cross-dirname/-/cross-dirname-0.1.0.tgz", {}, "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q=="], "cross-fetch": ["cross-fetch@3.2.0", "https://registry.npmmirror.com/cross-fetch/-/cross-fetch-3.2.0.tgz", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q=="], @@ -2036,10 +2128,84 @@ "csstype": ["csstype@3.2.3", "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + "cytoscape": ["cytoscape@3.34.0", "https://registry.npmmirror.com/cytoscape/-/cytoscape-3.34.0.tgz", {}, "sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg=="], + + "cytoscape-cose-bilkent": ["cytoscape-cose-bilkent@4.1.0", "https://registry.npmmirror.com/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", { "dependencies": { "cose-base": "^1.0.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ=="], + + "cytoscape-fcose": ["cytoscape-fcose@2.2.0", "https://registry.npmmirror.com/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", { "dependencies": { "cose-base": "^2.2.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ=="], + + "d3": ["d3@7.9.0", "https://registry.npmmirror.com/d3/-/d3-7.9.0.tgz", { "dependencies": { "d3-array": "3", "d3-axis": "3", "d3-brush": "3", "d3-chord": "3", "d3-color": "3", "d3-contour": "4", "d3-delaunay": "6", "d3-dispatch": "3", "d3-drag": "3", "d3-dsv": "3", "d3-ease": "3", "d3-fetch": "3", "d3-force": "3", "d3-format": "3", "d3-geo": "3", "d3-hierarchy": "3", "d3-interpolate": "3", "d3-path": "3", "d3-polygon": "3", "d3-quadtree": "3", "d3-random": "3", "d3-scale": "4", "d3-scale-chromatic": "3", "d3-selection": "3", "d3-shape": "3", "d3-time": "3", "d3-time-format": "4", "d3-timer": "3", "d3-transition": "3", "d3-zoom": "3" } }, "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA=="], + + "d3-array": ["d3-array@3.2.4", "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], + + "d3-axis": ["d3-axis@3.0.0", "https://registry.npmmirror.com/d3-axis/-/d3-axis-3.0.0.tgz", {}, "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw=="], + + "d3-brush": ["d3-brush@3.0.0", "https://registry.npmmirror.com/d3-brush/-/d3-brush-3.0.0.tgz", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "3", "d3-transition": "3" } }, "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ=="], + + "d3-chord": ["d3-chord@3.0.1", "https://registry.npmmirror.com/d3-chord/-/d3-chord-3.0.1.tgz", { "dependencies": { "d3-path": "1 - 3" } }, "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g=="], + + "d3-color": ["d3-color@3.1.0", "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], + + "d3-contour": ["d3-contour@4.0.2", "https://registry.npmmirror.com/d3-contour/-/d3-contour-4.0.2.tgz", { "dependencies": { "d3-array": "^3.2.0" } }, "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA=="], + + "d3-delaunay": ["d3-delaunay@6.0.4", "https://registry.npmmirror.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="], + + "d3-dispatch": ["d3-dispatch@3.0.1", "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="], + + "d3-drag": ["d3-drag@3.0.0", "https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz", { "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" } }, "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg=="], + + "d3-dsv": ["d3-dsv@3.0.1", "https://registry.npmmirror.com/d3-dsv/-/d3-dsv-3.0.1.tgz", { "dependencies": { "commander": "7", "iconv-lite": "0.6", "rw": "1" }, "bin": { "csv2json": "bin/dsv2json.js", "csv2tsv": "bin/dsv2dsv.js", "dsv2dsv": "bin/dsv2dsv.js", "dsv2json": "bin/dsv2json.js", "json2csv": "bin/json2dsv.js", "json2dsv": "bin/json2dsv.js", "json2tsv": "bin/json2dsv.js", "tsv2csv": "bin/dsv2dsv.js", "tsv2json": "bin/dsv2json.js" } }, "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q=="], + + "d3-ease": ["d3-ease@3.0.1", "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], + + "d3-fetch": ["d3-fetch@3.0.1", "https://registry.npmmirror.com/d3-fetch/-/d3-fetch-3.0.1.tgz", { "dependencies": { "d3-dsv": "1 - 3" } }, "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw=="], + + "d3-force": ["d3-force@3.0.0", "https://registry.npmmirror.com/d3-force/-/d3-force-3.0.0.tgz", { "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", "d3-timer": "1 - 3" } }, "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg=="], + + "d3-format": ["d3-format@3.1.2", "https://registry.npmmirror.com/d3-format/-/d3-format-3.1.2.tgz", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="], + + "d3-geo": ["d3-geo@3.1.1", "https://registry.npmmirror.com/d3-geo/-/d3-geo-3.1.1.tgz", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="], + + "d3-hierarchy": ["d3-hierarchy@3.1.2", "https://registry.npmmirror.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="], + + "d3-interpolate": ["d3-interpolate@3.0.1", "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], + + "d3-path": ["d3-path@3.1.0", "https://registry.npmmirror.com/d3-path/-/d3-path-3.1.0.tgz", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], + + "d3-polygon": ["d3-polygon@3.0.1", "https://registry.npmmirror.com/d3-polygon/-/d3-polygon-3.0.1.tgz", {}, "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg=="], + + "d3-quadtree": ["d3-quadtree@3.0.1", "https://registry.npmmirror.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz", {}, "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw=="], + + "d3-random": ["d3-random@3.0.1", "https://registry.npmmirror.com/d3-random/-/d3-random-3.0.1.tgz", {}, "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="], + + "d3-sankey": ["d3-sankey@0.12.3", "https://registry.npmmirror.com/d3-sankey/-/d3-sankey-0.12.3.tgz", { "dependencies": { "d3-array": "1 - 2", "d3-shape": "^1.2.0" } }, "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ=="], + + "d3-scale": ["d3-scale@4.0.2", "https://registry.npmmirror.com/d3-scale/-/d3-scale-4.0.2.tgz", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], + + "d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "https://registry.npmmirror.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="], + + "d3-selection": ["d3-selection@3.0.0", "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz", {}, "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="], + + "d3-shape": ["d3-shape@3.2.0", "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], + + "d3-time": ["d3-time@3.1.0", "https://registry.npmmirror.com/d3-time/-/d3-time-3.1.0.tgz", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], + + "d3-time-format": ["d3-time-format@4.1.0", "https://registry.npmmirror.com/d3-time-format/-/d3-time-format-4.1.0.tgz", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="], + + "d3-timer": ["d3-timer@3.0.1", "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], + + "d3-transition": ["d3-transition@3.0.1", "https://registry.npmmirror.com/d3-transition/-/d3-transition-3.0.1.tgz", { "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "peerDependencies": { "d3-selection": "2 - 3" } }, "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w=="], + + "d3-zoom": ["d3-zoom@3.0.0", "https://registry.npmmirror.com/d3-zoom/-/d3-zoom-3.0.0.tgz", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="], + + "dagre-d3-es": ["dagre-d3-es@7.0.11", "https://registry.npmmirror.com/dagre-d3-es/-/dagre-d3-es-7.0.11.tgz", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-tvlJLyQf834SylNKax8Wkzco/1ias1OPw8DcUMDE7oUIoSEW25riQVuiu/0OWEFqT0cxHT3Pa9/D82Jr47IONw=="], + "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], "date-fns": ["date-fns@3.6.0", "https://registry.npmmirror.com/date-fns/-/date-fns-3.6.0.tgz", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="], + "dayjs": ["dayjs@1.11.21", "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.21.tgz", {}, "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA=="], + "debounce-fn": ["debounce-fn@6.0.0", "https://registry.npmmirror.com/debounce-fn/-/debounce-fn-6.0.0.tgz", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ=="], "debug": ["debug@4.4.3", "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], @@ -2062,6 +2228,8 @@ "defu": ["defu@6.1.7", "https://registry.npmmirror.com/defu/-/defu-6.1.7.tgz", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="], + "delaunator": ["delaunator@5.1.0", "https://registry.npmmirror.com/delaunator/-/delaunator-5.1.0.tgz", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ=="], + "delayed-stream": ["delayed-stream@1.0.0", "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], "denque": ["denque@2.1.0", "https://registry.npmmirror.com/denque/-/denque-2.1.0.tgz", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], @@ -2322,6 +2490,8 @@ "gtoken": ["gtoken@8.0.0", "https://registry.npmmirror.com/gtoken/-/gtoken-8.0.0.tgz", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="], + "hachure-fill": ["hachure-fill@0.5.2", "https://registry.npmmirror.com/hachure-fill/-/hachure-fill-0.5.2.tgz", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="], + "happy-dom": ["happy-dom@20.10.6", "https://registry.npmmirror.com/happy-dom/-/happy-dom-20.10.6.tgz", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-6QD0ilzDDt93tX44y8tbmZdAcdTRYDhUP+Asgi6pC8Pp5IA3cvaZGyoVN/EGtlq9ziT65iPuBBn3ASLr6hCgVw=="], "has-flag": ["has-flag@4.0.0", "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], @@ -2380,6 +2550,8 @@ "ini": ["ini@7.0.0", "https://registry.npmmirror.com/ini/-/ini-7.0.0.tgz", {}, "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w=="], + "internmap": ["internmap@1.0.1", "https://registry.npmmirror.com/internmap/-/internmap-1.0.1.tgz", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], + "ioredis": ["ioredis@5.11.1", "https://registry.npmmirror.com/ioredis/-/ioredis-5.11.1.tgz", { "dependencies": { "@ioredis/commands": "1.10.0", "cluster-key-slot": "1.1.1", "debug": "4.4.3", "denque": "2.1.0", "redis-errors": "1.2.0", "redis-parser": "3.0.0", "standard-as-callback": "2.1.0" } }, "sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A=="], "ip-address": ["ip-address@10.2.0", "https://registry.npmmirror.com/ip-address/-/ip-address-10.2.0.tgz", {}, "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA=="], @@ -2486,12 +2658,18 @@ "keyv": ["keyv@4.5.4", "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + "khroma": ["khroma@2.1.0", "https://registry.npmmirror.com/khroma/-/khroma-2.1.0.tgz", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="], + "kind-of": ["kind-of@6.0.3", "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], "kolorist": ["kolorist@1.8.0", "https://registry.npmmirror.com/kolorist/-/kolorist-1.8.0.tgz", {}, "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="], "kubernetes-types": ["kubernetes-types@1.30.0", "https://registry.npmmirror.com/kubernetes-types/-/kubernetes-types-1.30.0.tgz", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="], + "langium": ["langium@3.3.1", "https://registry.npmmirror.com/langium/-/langium-3.3.1.tgz", { "dependencies": { "chevrotain": "~11.0.3", "chevrotain-allstar": "~0.3.0", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.11", "vscode-uri": "~3.0.8" } }, "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w=="], + + "layout-base": ["layout-base@1.0.2", "https://registry.npmmirror.com/layout-base/-/layout-base-1.0.2.tgz", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="], + "lazy-val": ["lazy-val@1.0.5", "https://registry.npmmirror.com/lazy-val/-/lazy-val-1.0.5.tgz", {}, "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q=="], "light-my-request": ["light-my-request@6.6.0", "https://registry.npmmirror.com/light-my-request/-/light-my-request-6.6.0.tgz", { "dependencies": { "cookie": "^1.0.1", "process-warning": "^4.0.0", "set-cookie-parser": "^2.6.0" } }, "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A=="], @@ -2564,6 +2742,8 @@ "merge2": ["merge2@1.4.1", "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + "mermaid": ["mermaid@11.12.0", "https://registry.npmmirror.com/mermaid/-/mermaid-11.12.0.tgz", { "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.1", "@mermaid-js/parser": "^0.6.2", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.11", "dayjs": "^1.11.18", "dompurify": "^3.2.5", "katex": "^0.16.22", "khroma": "^2.1.0", "lodash-es": "^4.17.21", "marked": "^16.2.1", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0" } }, "sha512-ZudVx73BwrMJfCFmSSJT84y6u5brEoV8DOItdHomNLz32uBjNrelm7mg95X7g+C6UoQH/W6mBLGDEDv73JdxBg=="], + "micromark-util-character": ["micromark-util-character@2.1.1", "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], "micromark-util-encode": ["micromark-util-encode@2.0.1", "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], @@ -2728,6 +2908,8 @@ "package-json-from-dist": ["package-json-from-dist@1.0.1", "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "package-manager-detector": ["package-manager-detector@1.7.0", "https://registry.npmmirror.com/package-manager-detector/-/package-manager-detector-1.7.0.tgz", {}, "sha512-xg1eHpwYL/D/HEdWw2goFZP6vV0FH7W+PZ5rFkGjdIDLtxq7EkzBUeT3m+lndYCt8wKbmofUu1MUdMCXkCk9ZQ=="], + "pacote": ["pacote@21.5.1", "https://registry.npmmirror.com/pacote/-/pacote-21.5.1.tgz", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/git": "^7.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "@npmcli/run-script": "^10.0.0", "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", "npm-package-arg": "^13.0.0", "npm-packlist": "^10.0.1", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "proc-log": "^6.0.0", "sigstore": "^4.0.0", "ssri": "^13.0.0", "tar": "^7.4.3" }, "bin": { "pacote": "bin/index.js" } }, "sha512-KvcJ9iy3crysCsgqc4+PknH/w6jkrp8JN36mpZBPwNaDRwTfMZD37YzRazNstiZUOhuF5pno9f78n9mEJBavwg=="], "parse-conflict-json": ["parse-conflict-json@5.0.1", "https://registry.npmmirror.com/parse-conflict-json/-/parse-conflict-json-5.0.1.tgz", { "dependencies": { "json-parse-even-better-errors": "^5.0.0", "just-diff": "^6.0.0", "just-diff-apply": "^5.2.0" } }, "sha512-ZHEmNKMq1wyJXNwLxyHnluPfRAFSIliBvbK/UiOceROt4Xh9Pz0fq49NytIaeaCUf5VR86hwQ/34FCcNU5/LKQ=="], @@ -2740,6 +2922,8 @@ "path-browserify": ["path-browserify@1.0.1", "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], + "path-data-parser": ["path-data-parser@0.1.0", "https://registry.npmmirror.com/path-data-parser/-/path-data-parser-0.1.0.tgz", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], + "path-exists": ["path-exists@4.0.0", "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], "path-expression-matcher": ["path-expression-matcher@1.6.0", "https://registry.npmmirror.com/path-expression-matcher/-/path-expression-matcher-1.6.0.tgz", {}, "sha512-e5y7RCLHKjemsgQ4eqGJtPyr10ILz25HO7flzxhTV8bgvd5yHx98DGtCAtbVW9f2TqnYI/gEVZd+vz7snrdPTw=="], @@ -2788,6 +2972,10 @@ "poe-oauth": ["poe-oauth@0.0.10", "https://registry.npmmirror.com/poe-oauth/-/poe-oauth-0.0.10.tgz", {}, "sha512-XohR2aTnXT6U3KAly540b7IcJT7yxa4d8PKibeqShpeLN1KLRHb5FBJ9WY42IhDQxOrF/GNndc+nnsliJtPlSA=="], + "points-on-curve": ["points-on-curve@0.2.0", "https://registry.npmmirror.com/points-on-curve/-/points-on-curve-0.2.0.tgz", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="], + + "points-on-path": ["points-on-path@0.2.1", "https://registry.npmmirror.com/points-on-path/-/points-on-path-0.2.1.tgz", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="], + "postcss": ["postcss@8.5.15", "https://registry.npmmirror.com/postcss/-/postcss-8.5.15.tgz", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], "postcss-selector-parser": ["postcss-selector-parser@7.1.4", "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg=="], @@ -2904,14 +3092,20 @@ "roarr": ["roarr@2.15.4", "https://registry.npmmirror.com/roarr/-/roarr-2.15.4.tgz", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], + "robust-predicates": ["robust-predicates@3.0.3", "https://registry.npmmirror.com/robust-predicates/-/robust-predicates-3.0.3.tgz", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="], + "rollup": ["rollup@4.62.2", "https://registry.npmmirror.com/rollup/-/rollup-4.62.2.tgz", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.62.2", "@rollup/rollup-android-arm64": "4.62.2", "@rollup/rollup-darwin-arm64": "4.62.2", "@rollup/rollup-darwin-x64": "4.62.2", "@rollup/rollup-freebsd-arm64": "4.62.2", "@rollup/rollup-freebsd-x64": "4.62.2", "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", "@rollup/rollup-linux-arm-musleabihf": "4.62.2", "@rollup/rollup-linux-arm64-gnu": "4.62.2", "@rollup/rollup-linux-arm64-musl": "4.62.2", "@rollup/rollup-linux-loong64-gnu": "4.62.2", "@rollup/rollup-linux-loong64-musl": "4.62.2", "@rollup/rollup-linux-ppc64-gnu": "4.62.2", "@rollup/rollup-linux-ppc64-musl": "4.62.2", "@rollup/rollup-linux-riscv64-gnu": "4.62.2", "@rollup/rollup-linux-riscv64-musl": "4.62.2", "@rollup/rollup-linux-s390x-gnu": "4.62.2", "@rollup/rollup-linux-x64-gnu": "4.62.2", "@rollup/rollup-linux-x64-musl": "4.62.2", "@rollup/rollup-openbsd-x64": "4.62.2", "@rollup/rollup-openharmony-arm64": "4.62.2", "@rollup/rollup-win32-arm64-msvc": "4.62.2", "@rollup/rollup-win32-ia32-msvc": "4.62.2", "@rollup/rollup-win32-x64-gnu": "4.62.2", "@rollup/rollup-win32-x64-msvc": "4.62.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA=="], + "roughjs": ["roughjs@4.6.6", "https://registry.npmmirror.com/roughjs/-/roughjs-4.6.6.tgz", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="], + "router": ["router@2.2.0", "https://registry.npmmirror.com/router/-/router-2.2.0.tgz", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], "run-applescript": ["run-applescript@7.1.0", "https://registry.npmmirror.com/run-applescript/-/run-applescript-7.1.0.tgz", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], "run-parallel": ["run-parallel@1.2.0", "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + "rw": ["rw@1.3.3", "https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="], + "rxjs": ["rxjs@7.8.2", "https://registry.npmmirror.com/rxjs/-/rxjs-7.8.2.tgz", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], "s-js": ["s-js@0.4.9", "https://registry.npmmirror.com/s-js/-/s-js-0.4.9.tgz", {}, "sha512-RtpOm+cM6O0sHg6IA70wH+UC3FZcND+rccBZpBAHzlUgNO2Bm5BN+FnM8+OBxzXdwpKWFwX11JGF0MFRkhSoIQ=="], @@ -3050,6 +3244,8 @@ "stubborn-utils": ["stubborn-utils@1.0.2", "https://registry.npmmirror.com/stubborn-utils/-/stubborn-utils-1.0.2.tgz", {}, "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg=="], + "stylis": ["stylis@4.4.0", "https://registry.npmmirror.com/stylis/-/stylis-4.4.0.tgz", {}, "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA=="], + "sumchecker": ["sumchecker@3.0.1", "https://registry.npmmirror.com/sumchecker/-/sumchecker-3.0.1.tgz", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="], "supports-color": ["supports-color@7.2.0", "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -3106,6 +3302,8 @@ "ts-algebra": ["ts-algebra@2.0.0", "https://registry.npmmirror.com/ts-algebra/-/ts-algebra-2.0.0.tgz", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="], + "ts-dedent": ["ts-dedent@2.3.0", "https://registry.npmmirror.com/ts-dedent/-/ts-dedent-2.3.0.tgz", {}, "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg=="], + "ts-morph": ["ts-morph@18.0.0", "https://registry.npmmirror.com/ts-morph/-/ts-morph-18.0.0.tgz", { "dependencies": { "@ts-morph/common": "~0.19.0", "code-block-writer": "^12.0.0" } }, "sha512-Kg5u0mk19PIIe4islUI/HWRvm9bC1lHejK4S0oh1zaZ77TMZAEmQC0sHQYiu2RgCQFZKXz1fMVi/7nOOeirznA=="], "tslib": ["tslib@2.8.1", "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -3198,8 +3396,16 @@ "vscode-jsonrpc": ["vscode-jsonrpc@8.2.1", "https://registry.npmmirror.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", {}, "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ=="], + "vscode-languageserver": ["vscode-languageserver@9.0.1", "https://registry.npmmirror.com/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", { "dependencies": { "vscode-languageserver-protocol": "3.17.5" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g=="], + + "vscode-languageserver-protocol": ["vscode-languageserver-protocol@3.17.5", "https://registry.npmmirror.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", { "dependencies": { "vscode-jsonrpc": "8.2.0", "vscode-languageserver-types": "3.17.5" } }, "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg=="], + + "vscode-languageserver-textdocument": ["vscode-languageserver-textdocument@1.0.12", "https://registry.npmmirror.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", {}, "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA=="], + "vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "https://registry.npmmirror.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="], + "vscode-uri": ["vscode-uri@3.0.8", "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.0.8.tgz", {}, "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw=="], + "vue": ["vue@3.5.24", "https://registry.npmmirror.com/vue/-/vue-3.5.24.tgz", { "dependencies": { "@vue/compiler-dom": "3.5.24", "@vue/compiler-sfc": "3.5.24", "@vue/runtime-dom": "3.5.24", "@vue/server-renderer": "3.5.24", "@vue/shared": "3.5.24" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg=="], "vue-demi": ["vue-demi@0.14.10", "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", { "peerDependencies": { "@vue/composition-api": "^1.0.0-rc.1", "vue": "^3.0.0-0 || ^2.6.0" }, "optionalPeers": ["@vue/composition-api"], "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" } }, "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="], @@ -3352,6 +3558,8 @@ "@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.21.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + "@antfu/install-pkg/tinyexec": ["tinyexec@1.2.4", "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.2.4.tgz", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="], + "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "https://registry.npmmirror.com/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "https://registry.npmmirror.com/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], @@ -3396,6 +3604,10 @@ "@babel/traverse/@babel/parser": ["@babel/parser@8.0.0", "", { "dependencies": { "@babel/types": "^8.0.0" }, "bin": "./bin/babel-parser.js" }, "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ=="], + "@chevrotain/cst-dts-gen/lodash-es": ["lodash-es@4.17.21", "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], + + "@chevrotain/gast/lodash-es": ["lodash-es@4.17.21", "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], + "@electron/asar/commander": ["commander@5.1.0", "https://registry.npmmirror.com/commander/-/commander-5.1.0.tgz", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="], "@electron/asar/glob": ["glob@7.2.3", "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], @@ -3604,6 +3816,8 @@ "cacache/lru-cache": ["lru-cache@11.5.1", "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.5.1.tgz", {}, "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A=="], + "chevrotain/lodash-es": ["lodash-es@4.17.21", "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], + "clone-response/mimic-response": ["mimic-response@1.0.1", "https://registry.npmmirror.com/mimic-response/-/mimic-response-1.0.1.tgz", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="], "code-agent/typescript": ["typescript@5.9.3", "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -3612,6 +3826,16 @@ "cross-spawn/which": ["which@2.0.2", "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "cytoscape-fcose/cose-base": ["cose-base@2.2.0", "https://registry.npmmirror.com/cose-base/-/cose-base-2.2.0.tgz", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="], + + "d3-dsv/commander": ["commander@7.2.0", "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], + + "d3-dsv/iconv-lite": ["iconv-lite@0.6.3", "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "d3-sankey/d3-array": ["d3-array@2.12.1", "https://registry.npmmirror.com/d3-array/-/d3-array-2.12.1.tgz", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], + + "d3-sankey/d3-shape": ["d3-shape@1.3.7", "https://registry.npmmirror.com/d3-shape/-/d3-shape-1.3.7.tgz", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], + "dir-compare/minimatch": ["minimatch@3.1.5", "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "dom-serializer/entities": ["entities@4.5.0", "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], @@ -3660,6 +3884,10 @@ "lightningcss/detect-libc": ["detect-libc@2.1.2", "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "mermaid/marked": ["marked@16.4.2", "https://registry.npmmirror.com/marked/-/marked-16.4.2.tgz", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="], + + "mermaid/uuid": ["uuid@11.1.1", "https://registry.npmmirror.com/uuid/-/uuid-11.1.1.tgz", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="], + "micromatch/picomatch": ["picomatch@2.3.2", "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "minipass-flush/minipass": ["minipass@3.3.6", "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], @@ -3750,6 +3978,8 @@ "vite-plugin-solid/@babel/core": ["@babel/core@7.28.4", "https://registry.npmmirror.com/@babel/core/-/core-7.28.4.tgz", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA=="], + "vscode-languageserver-protocol/vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "https://registry.npmmirror.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="], + "vue/@vue/shared": ["@vue/shared@3.5.24", "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.24.tgz", {}, "sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A=="], "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], @@ -4070,6 +4300,10 @@ "cross-spawn/which/isexe": ["isexe@2.0.0", "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "https://registry.npmmirror.com/layout-base/-/layout-base-2.0.1.tgz", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="], + + "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "https://registry.npmmirror.com/d3-path/-/d3-path-1.0.9.tgz", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], + "dir-compare/minimatch/brace-expansion": ["brace-expansion@1.1.15", "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.15.tgz", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], "electron-builder/yargs/cliui": ["cliui@8.0.1", "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], diff --git a/docs/MOF-ENTITYGEN-V0-ACCEPTANCE-CHECKLIST.md b/docs/MOF-ENTITYGEN-V0-ACCEPTANCE-CHECKLIST.md new file mode 100644 index 00000000..7c145a27 --- /dev/null +++ b/docs/MOF-ENTITYGEN-V0-ACCEPTANCE-CHECKLIST.md @@ -0,0 +1,211 @@ +# EntityGen MoF v0 验收清单 + +> 对照 [10 · MOF 元建模与实体优先生成](./product-design/10-mof-entity-modeling.md) §6 四阶段路线。 +> 归档日期:2026-07-04 · **验收结论:全部通过(2026-07-04 手验确认)** +> OpenSpec 归档:`openspec/changes/archive/2026-07-04-ubml-{entity-metamodel,entity-modeling-ops,model-right-panel,page-metamodel,model-tab-pages}/` + +**图例** + +| 符号 | 含义 | +|------|------| +| ✅ | 已实现,可用下方「自动化命令」验证 | +| 🔶 | 已实现,需本机浏览器 / LLM / 人工确认 | +| ⏭ | 文档明确 defer 或 non-goal,本期不验收 | + +--- + +## 0. 一键预检(建议最先跑) + +```bash +bun install + +# E1 + E4 元模型一致性 +bun test packages/ubml-codegen/test/entity-metamodel-consistency.test.ts +bun test packages/ubml-codegen/test/page-metamodel-consistency.test.ts + +# E2 建模操作 +bun test packages/ubml-codegen/test/entity-edit-operations.test.ts +bun packages/knowledge/script/be-modeling-ops-smoke.ts +bun packages/knowledge/eval/eval-entity-generation-core.ts + +# E3 + 页面模型页签 +bun test packages/extension/src/lib/model-preview-model.test.ts + +# 金样三道闸 +bun run ubml:entity:verify packages/extension/assets/ubml-entity/exemplars/fybxd.be.yaml +bun run ubml:verify packages/extension/assets/ubml/exemplars/fybxdj-card.yaml +``` + +| 检查项 | 预期(2026-07-04 执行) | +|--------|-------------------------| +| 元模型 + 操作 + 模型页签测试 | **25 pass**,0 fail | +| `be-modeling-ops-smoke` | `be modeling ops smoke ok` | +| `eval-entity-generation-core` | `taskCount: 10`,`goldenExemplarOk: true` | +| FYBXD entity verify | ok | +| FYBXDJ card verify | ok | + +--- + +## E1 · 元模型显式化(`ubml-entity-metamodel`) + +| # | 验收项 | 状态 | 自动化验证 | +|---|--------|------|------------| +| E1-01 | `ubml-entity.metamodel.yaml` 覆盖 FYBXD 全部建模元素 | ✅ | 文件存在 + consistency test「FYBXD maps to metamodel」 | +| E1-02 | `metamodel/README.md` 维护边界说明 | ✅ | 打开 `packages/extension/assets/ubml-entity/metamodel/README.md` | +| E1-03 | 元模型读取器 `entity/metamodel/{types,load,prompt}.ts` | ✅ | `entity-metamodel-consistency.test.ts` loader 场景 | +| E1-04 | datatype 与 `datatypes.index.json` 一致 | ✅ | consistency test datatype 场景 | +| E1-05 | catalog 约束指向真实文件 | ✅ | consistency test catalog 场景 | +| E1-06 | `entitygen.md` 含 `METAMODEL:constraints` 段 | ✅ | `grep METAMODEL packages/extension/assets/ubml-entity/prompts/entitygen.md` | +| E1-07 | 现有编译/校验行为不变 | ✅ | `bun run ubml:entity:verify …/fybxd.be.yaml` | + +**Living spec**:[`openspec/specs/ubml-entity-metamodel/spec.md`](../openspec/specs/ubml-entity-metamodel/spec.md) + +**⏭ 本期不做**:从元模型生成 Zod / `gsp-business-entity.schema.json` + +--- + +## E2 · 建模操作集(`ubml-entity-modeling-ops`) + +| # | 验收项 | 状态 | 自动化验证 | +|---|--------|------|------------| +| E2-01 | 9 类 typed 操作注册 | ✅ | `entity-edit-operations.test.ts` | +| E2-02 | legacy 五操作仍可用 | ✅ | 同上 + MCP `kb_be_edit` description | +| E2-03 | 失败返回 `{ ok, yaml, issues, hint, changedPaths }` | ✅ | association unknown ref / decimal precision 测试 | +| E2-04 | `kb_be_edit` MCP op 枚举扩展 | ✅ | `packages/knowledge/src/mcp/server.ts` | +| E2-05 | `be-modeling-ops-smoke.ts` 冒烟 | ✅ | `bun packages/knowledge/script/be-modeling-ops-smoke.ts` | +| E2-06 | 10 条中文评测任务集 | ✅ | `entity-generation-tasks.json`(be-01~be-10) | +| E2-07 | eval runner 基线 | ✅ | `eval-entity-generation-core.ts` 输出 metrics | +| E2-08 | `entitygen.md` 骨架 + 受控精修工作流 | ✅ | 读 prompt 第 3~4 步 | +| E2-09 | Agent 自动化 eval(10/10 verifyPassRate) | ⏭ | 基线 runner 已交付;Agent 自动化列为后续 Issue | +| E2-10 | 操作参数 JSON Schema 从元模型生成 | ⏭ | §4.3 远期;当前为 TS 手写校验 | + +**Living spec**:[`openspec/specs/ubml-entity-modeling-ops/spec.md`](../openspec/specs/ubml-entity-modeling-ops/spec.md) +**Agent 工作流 spec 更新**:[`openspec/specs/ubml-entity-agent/spec.md`](../openspec/specs/ubml-entity-agent/spec.md) + +--- + +## E3 · 模型页签(`ubml-model-right-panel`) + +| # | 验收项 | 状态 | 自动化验证 | +|---|--------|------|------------| +| E3-01 | 右侧栏 `model` 页签类型(单实例) | ✅ | `right-panel.tsx` + `right-panel-labels.ts` | +| E3-02 | `ubml-model-tab-store` + `ubml-model-bridge` | ✅ | 文件存在 | +| E3-03 | `ubml-model-panel.tsx` 左导航 + 图/文件切换 | ✅ | 文件存在 | +| E3-04 | `model-preview-model.test.ts`(FYBXD refs/children) | ✅ | `bun test …/model-preview-model.test.ts` | +| E3-05 | `previewEntityYaml` → `openUbmlModelTab` | ✅ | `ubml-entity-artifact-actions.ts` | +| E3-06 | 对话 auto-preview → 模型页签 | ✅ | M3-01 手验通过 | +| E3-07 | FYBXD ER:子实体 + 外部关联 | ✅ | M3-02 手验通过 | +| E3-08 | 关闭 model tab 清理 payload | ✅ | M3-03 手验通过 | +| E3-09 | 工程全量扫描 / manifest 导航 | ⏭ | E3 首版 defer | +| E3-10 | 多实体合并全景 ER | ⏭ | 首版 partial;单 tab 内多条目切换 | + +**Living spec**:[`openspec/specs/ubml-model-right-panel/spec.md`](../openspec/specs/ubml-model-right-panel/spec.md) + +--- + +## E4 · 页面元模型(`ubml-page-metamodel`) + +| # | 验收项 | 状态 | 自动化验证 | +|---|--------|------|------------| +| E4-01 | `ubml-page.metamodel.yaml` + README | ✅ | 文件存在 | +| E4-02 | `page/metamodel/{types,load,prompt}.ts` | ✅ | `page-metamodel-consistency.test.ts` | +| E4-03 | FYBXDJ card 可映射到元类 | ✅ | consistency test | +| E4-04 | `pagegen.md` 元模型约束段 | ✅ | `grep METAMODEL packages/extension/assets/ubml/prompts/pagegen.md` | +| E4-05 | `docs/UBML-EXTERNAL-REFERENCE.md` | ✅ | 文件存在 | +| E4-06 | 现有 page verify 不变 | ✅ | `bun run ubml:verify …/fybxdj-card.yaml` | +| E4-07 | crossref.ts 元模型驱动替换 | ⏭ | E4 non-goal | + +**Living spec**:[`openspec/specs/ubml-page-metamodel/spec.md`](../openspec/specs/ubml-page-metamodel/spec.md) + +--- + +## 扩展 · 模型页签页面导航(`ubml-model-tab-pages`) + +| # | 验收项 | 状态 | 自动化验证 | +|---|--------|------|------------| +| MTP-01 | `UbmlModelEntry` 支持 `kind: entity \| page` | ✅ | `ubml-model-tab-store.ts` | +| MTP-02 | 导航分组「实体 be/」「页面 pages/」 | ✅ | `model-preview-model.test.ts` page 场景 | +| MTP-03 | 页面结构视图 `PagePreviewContent` | ✅ | `page-preview-model.ts` + component | +| MTP-04 | `previewUbmlYaml` → `openUbmlModelTab` | ✅ | `ubml-artifact-actions.ts` | +| MTP-05 | 页面预览手验 | ✅ | M3-04 手验通过 | + +**Living spec**:[`openspec/specs/ubml-model-tab-pages/spec.md`](../openspec/specs/ubml-model-tab-pages/spec.md) + +--- + +## 手验步骤(✅ 已于 2026-07-04 全部通过) + +前置:`bun run dev`(后端 :4096 + 前端 :3000),已配置 LLM provider。 + +### M3-01 · 对话生成实体 → 模型页签 + +1. 打开 Agent Home,发送 `@ubml-entitygen` 或粘贴含 `entity:` YAML 的回复(或触发 FYBXD 范例生成)。 +2. 消息完成后应 **自动 preview**(`UbmlAutoPreviewWatcher`)。 +3. 右侧栏出现或激活 **「模型」** 页签;左侧导航有 **FYBXD**。 +4. **关系图** 可见 Mermaid ER;**文件** 视图可见 YAML。 + +### M3-02 · 工作台 FYBXD 预览 + +1. 工作台 → 实体 → 打开 `fybxd.be.yaml` → **预览**。 +2. 模型页签导航:**FYBXD**;ER 含 **FYBXDMX**、**FXBXDFJ** 包含边。 +3. 外部关联节点/摘要含 **GspUser**、**SysOrg**、**Projects**。 + +### M3-03 · 关闭页签清理 + +1. 关闭「模型」页签。 +2. 重新打开 model tab → 空状态提示;**entity-preview** 页签不受影响。 + +### M3-04 · 页面进入模型页签 + +1. 工作台或对话预览 **fybxdj-card.yaml**(card 页)。 +2. 模型导航 **页面 pages/** 下出现 card 条目。 +3. **结构** 视图显示 title、entity 绑定、字段绑定表。 + +### M3-05 · 受控精修(可选,需 knowledge 服务) + +1. 启动 knowledge MCP / `bun run dev` 全栈。 +2. 对空实体 YAML 依次调用 `kb_be_edit`:`addScalarField` → `addAssociationField`(GspUser)→ `addUdtField`。 +3. 每步 `ok: true` 或失败时 `issues[].hint` 可定位参数。 + +--- + +## §6.7 里程碑完成度 + +| 里程碑条件 | 状态 | +|------------|------| +| FYBXD 元模型解释 + 一致性测试 | ✅ | +| ≥10 条中文 eval 任务 | ✅ | +| eval 输出通过率 / 修复轮次指标 | ⏭ | 基线 runner 已交付;Agent 自动化列为后续 | +| 生成/预览后模型页签可见 | ✅ | M3 手验通过 | +| OpenSpec 归档 + living spec | ✅ 2026-07-04 | +| `06-roadmap.md` 同步 MoF v0 | ⏭ 可选后续 PR | +| CI 强制元模型测试 | ⏭ 可选后续 PR | + +--- + +## 归档记录 + +| 变更 | 归档路径 | +|------|----------| +| E1 `ubml-entity-metamodel` | `openspec/changes/archive/2026-07-04-ubml-entity-metamodel/` | +| E2 `ubml-entity-modeling-ops` | `openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/` | +| E3 `ubml-model-right-panel` | `openspec/changes/archive/2026-07-04-ubml-model-right-panel/` | +| E4 `ubml-page-metamodel` | `openspec/changes/archive/2026-07-04-ubml-page-metamodel/` | +| 页面模型页签 | `openspec/changes/archive/2026-07-04-ubml-model-tab-pages/` | + +**同步至 main specs**: + +- `openspec/specs/ubml-entity-metamodel/spec.md`(新建) +- `openspec/specs/ubml-entity-modeling-ops/spec.md`(新建) +- `openspec/specs/ubml-model-right-panel/spec.md`(新建) +- `openspec/specs/ubml-page-metamodel/spec.md`(新建) +- `openspec/specs/ubml-model-tab-pages/spec.md`(新建) +- `openspec/specs/ubml-entity-agent/spec.md`(更新:骨架+受控精修工作流) + +--- + +## 相关文档 + +- [10-mof-entity-modeling.md](./product-design/10-mof-entity-modeling.md) +- [UBML-EXTERNAL-REFERENCE.md](./UBML-EXTERNAL-REFERENCE.md) +- [UBML-ENTITY-VERIFY.md](./UBML-ENTITY-VERIFY.md) diff --git a/docs/UBML-ENTITY-VERIFY.md b/docs/UBML-ENTITY-VERIFY.md index 06d2e548..fb2d2ffa 100644 --- a/docs/UBML-ENTITY-VERIFY.md +++ b/docs/UBML-ENTITY-VERIFY.md @@ -33,7 +33,17 @@ bun run --cwd packages/ubml-codegen entity-verify path/to/model.be.yaml } ``` -失败时含 `stage`、`issues[]`(可选 `path`、`hints[]`)、`attemptHint`。 +失败时含 `stage`、`issues[]`(`path`、`hints[]` 建议值列表、**`hint` 中文修复指引**、`suggestion` 首选建议)、`attemptHint`。 + +常见错误 hint 由 `packages/ubml-codegen/src/validation/verify-hints.ts` 生成,Agent 可消费 `formatVerifyIssuesForAgent(issues)` 输出 Markdown 表格。 + +| 错误类型 | hint 示例 | +|----------|-----------| +| 未知 editor | 可改用 input-group、date-picker… | +| 未知 association.ref | 检查 BE catalog / Refs | +| 未知 field type | 列出可用 type | +| page.entity 缺失 | 设置为 FYBXD 等 entity.code | +| YAML 语法 | 检查缩进与引号 | ## 反编译 diff --git a/docs/UBML-EXTERNAL-REFERENCE.md b/docs/UBML-EXTERNAL-REFERENCE.md new file mode 100644 index 00000000..4c1d3ca5 --- /dev/null +++ b/docs/UBML-EXTERNAL-REFERENCE.md @@ -0,0 +1,69 @@ +# UBML External Reference(交叉引用) + +> 说明 MOF 元模型中 `reference.external: true` 与 AppGen / page verify 中手写 crossref 校验的关系。E4 仅文档化与元模型声明,**不替换** `packages/ubml-codegen/src/app/validate/crossref.ts`。 + +## 1. 概念 + +**External 引用**:M2 元模型中指向**另一元模型或 catalog 数据**的引用,不包含于当前文档树内(`containment: false`,`external: true`)。 + +| 引用点 | 源元模型 | 目标 | 运行时校验(现状) | +|--------|----------|------|-------------------| +| `Page.entity` | ubml-page | BusinessEntity code | page verify / compile context | +| `FieldBinding.bind` | ubml-page | Entity 字段 code | page verify(entity 上下文) | +| `AssociationField.ref` | ubml-entity | referenceable-be catalog | entity DSL semantic validation | +| `Pageflow.pages.*` | ubml-app | Page frm code | AppGen `crossref.ts` | +| `Component.BeCode` | ubml-component | Entity code | AppGen `crossref.ts` | + +## 2. 元模型中的声明 + +### 页面域(`ubml-page.metamodel.yaml`) + +```yaml +# Page.entity → 外部业务实体 +- { name: entity, type: BusinessEntity, containment: false, external: true } + +# FieldBinding.bind → 实体字段 +- { name: bind, type: EntityField, containment: false, external: true } +constraints: + - name: bind-exists-on-entity + check: "page(self).entity.allFields().has(self.bind)" + - name: editor-in-catalog + catalog: components +``` + +### 实体域(`ubml-entity.metamodel.yaml`) + +关联、UDT 等字段的 catalog 约束已在实体元模型中声明(`ref-in-catalog`、`udt-in-catalog`)。 + +## 3. 与 AppGen crossref 的对应 + +`validateCrossref()`(`app/validate/crossref.ts`)当前覆盖: + +- `app.entities[]` ↔ 编译产物 BE 模型 +- `app.pages[]` ↔ 页面编译结果 +- `app.pageflow` ↔ pageflow 中引用的 frm +- `app.components` ↔ component BE 引用 +- `app.weblogic` ↔ webcmd / page 源 + +**演进路径(远期,非 E4):** + +1. 从各域元模型导出 external 引用清单(source class.attr → target type) +2. 生成 crossref 规则表或共享 TS 校验函数 +3. AppGen 与单页 verify 共用「解析 external 引用是否可落地」内核 +4. 新增模型种类时只扩展元模型 + 规则表,避免在 `crossref.ts` 堆 if 分支 + +## 4. E4 边界 + +| 项 | E4 | +|----|-----| +| 元模型 YAML 声明 external + constraint | ✓ | +| 一致性测试对齐 catalog / 金样 | ✓ | +| 替换 crossref.ts | ✗ | +| 模型页签展示 page 导航 | ✗(后续变更) | + +## 5. 相关文件 + +- `packages/extension/assets/ubml/metamodel/ubml-page.metamodel.yaml` +- `packages/extension/assets/ubml-entity/metamodel/ubml-entity.metamodel.yaml` +- `packages/ubml-codegen/src/app/validate/crossref.ts` +- `docs/product-design/10-mof-entity-modeling.md` §3.4、§6.5 diff --git a/docs/product-design/10-mof-entity-modeling.md b/docs/product-design/10-mof-entity-modeling.md new file mode 100644 index 00000000..bce18404 --- /dev/null +++ b/docs/product-design/10-mof-entity-modeling.md @@ -0,0 +1,724 @@ +# 10 · MOF 元建模与实体优先生成主线 + +> 本篇回答:**如何用 MOF 四层元建模架构收敛目前分散的生成方案?如何让大模型从一句自然语言稳定地生成符合平台格式的业务实体模型?模型页签在界面上长什么样、怎么实现?** +> +> 阅读前建议先读 [04 · UBML 主线](./04-ubml-pipeline.md)(五步闭环)与 [05 · 知识服务](./05-knowledge-and-context.md)(三库与 MCP 工具)。本篇是这两篇的深化:把「AI 写语义 DSL,编译器保正确性」升级为**元模型驱动**的体系,并以**业务实体**为第一个走通的模型。 + +--- + +## 0. 方案评估结论 + +总体判断:**方案方向合理,且适合当前仓库的演进阶段**。它没有推翻现有 `entity:` DSL、`ubml-codegen` 编译器和三道闸,而是在现有体系之上补一层显式元模型,把分散在 Zod、JSON Schema、catalog、提示词和预览组件里的规则收敛为可治理的事实源。这条路线能解决两个真实痛点:一是生成规则散落导致贡献者难以判断该改哪里;二是自然语言生成缺少可见结果,用户无法直观看到实体关系。 + +从技术可行性看,方案成立的关键有三点: + +| 判断点 | 评估 | +|--------|------| +| 与现有架构是否兼容 | 兼容。现有 `entity:` YAML 继续作为 M1 表示,`packages/ubml-codegen` 继续负责确定性编译,元模型先作为描述与一致性测试接入,不要求重写编译器。 | +| 是否能提高生成稳定性 | 能。稳定性提升不来自更长提示词,而来自缩小生成面、catalog 接地、受控编辑操作和三道闸修复回环的组合。尤其是「建模操作工具化」能把一次大文档生成拆成小步可校验操作。 | +| 是否容易被社区贡献者理解 | 基本可理解。MOF 术语需要控制,不应把完整 OMG MOF / OCL 引入项目;文档中把 M3 限定为 7 个构造,是必要的降复杂度设计。 | + +但需要收敛三个边界,避免方案过度工程: + +1. **首期不做完整 OCL 引擎**。`constraint.check` 先作为人读规约与测试用例命名,真正执行由 TypeScript 校验函数承载;等约束稳定后再考虑表达式解释器。 +2. **首期不从元模型生成完整平台 JSON Schema**。`gsp-business-entity.schema.json` 对应平台格式,字段多且历史包袱重。E1 只做一致性测试与 DSL/操作 schema 派生,完整产物 schema 生成放到远期。 +3. **首期不追求全自动 AppGen**。目标收窄为业务实体:一句自然语言生成 `.be.yaml`,校验通过后在模型页签可视化。页面、查询、应用编排只作为后续推广目标。 + +因此,本方案建议作为 `AppGen v1` 之前的**实体优先收敛线**推进:先把一个核心模型做稳、做可见,再向页面和完整应用扩展。 + +--- + +## 1. 背景:为什么要收敛,为什么从实体开始 + +当前仓库已经有一条能跑的生成链:`entity:` / `page:` / `app:` YAML DSL → `packages/ubml-codegen` 确定性编译 → 多道校验 → 修复回环 → 预览写回。但站在贡献者视角,这套方案存在两个明显问题: + +**方案分散**。「什么是合法的实体模型」这件事,目前散落在至少四处,且相互之间靠人工保持一致: + +| 载体 | 位置 | 角色 | +|------|------|------| +| Zod 解析规则 | `packages/ubml-codegen/src/entity/dsl/schema.ts` | DSL 语法与语义校验 | +| JSON Schema | `packages/extension/assets/ubml-entity/gsp-business-entity.schema.json` | 编译产物结构校验 | +| 类型/引用清单 | `datatypes.index.json`、`udt.catalog.json`、`referenceable-be.catalog.json`、`components.catalog.json` | 合法值域 | +| Agent 提示词 | `assets/ubml-entity/prompts/entitygen.md`、`agents/ubml-entitygen.md` | 告诉模型「该怎么写」 | + +四处各自演进,没有一个「唯一事实源」。新增一种字段类型,要同时改 Zod、改 JSON Schema、改 catalog、改提示词——漏改任何一处,生成质量就悄悄劣化,而且没人能一眼看出四处是否一致。 + +**看不到生成效果**。生成的模型目前以文件形式落在输出目录,或在工作台的编辑器里以 YAML 文本呈现。用户在对话面板发出需求后,没有一个直观的地方回答「生成了哪些模型?实体之间是什么关系?」——缺少一个面向模型的可视化视图。 + +因此本篇的策略是:**先聚焦业务实体这一个模型,把「自然语言 → 元模型约束下的生成 → 校验 → 可视化呈现」的完整闭环走通**,再把同一套架构推广到页面等其他模型。选实体作为突破口有三个理由: + +1. 实体是全链路的事实源——`.dbo`/`.vo`/`.eapi` 从它派生,页面绑定它的字段(见 [04 §3](./04-ubml-pipeline.md#3-应用模型长什么样生成顺序)),实体走通了,下游就有了地基; +2. 实体的结构复杂度适中且高度规则化(字段、子实体、关联、枚举、UDT、规则),是检验元建模方案覆盖能力的理想样本; +3. 仓库已有 FYBXD(报销单)金样:DSL 金样 `assets/ubml-entity/exemplars/fybxd.be.yaml`(77 行)与平台格式参考 `FYBXD.json`(约 1200 行 GspMetadata),验收有据可依。 + +--- + +## 2. MOF 四层架构总览 + +[MOF(Meta-Object Facility)](https://www.omg.org/mof/)是 OMG 的元建模标准,UML、BPMN 等建模语言都构建在它之上。它的核心思想是把「模型」分成四层,每一层是上一层的**实例**: + +```text +M3 元元模型 定义「什么是元模型」的最小语言 + ↓ 实例化 (MetaClass / MetaAttribute / MetaReference / MetaConstraint) +M2 元模型 用 M3 语言定义的 UBML 各领域元模型 + ↓ 实例化 (BusinessEntity / EntityObject / Field / Page / Section …) +M1 用户模型 用户建出来的具体模型 + ↓ 实例化 (FYBXD 报销单、FYBXDMX 明细、FYBXDJ 卡片页 …) +M0 运行时实例 跑起来之后的业务数据 + (张三 2026 年 6 月那张 1280 元的报销单) +``` + +对照本仓库,各层的落点是: + +| MOF 层 | 本仓库对应物 | 现状 | +|--------|--------------|------| +| M3 | 本篇 §3.2 定义的**元建模语言**(一份很小的 YAML 词汇表) | ✅ 已内嵌于 metamodel YAML(7 构造) | +| M2 | `ubml-entity.metamodel.yaml`、`ubml-page.metamodel.yaml` 等元模型定义文件 | ✅ EntityGen MoF v0(2026-07-04 归档) | +| M1 | `entity:` / `page:` YAML DSL 及其编译产物(`.be.json` / `.frm.json`) | ✅ 已有 | +| M0 | 运行时业务数据 | 平台职责,不在本仓库范围 | + +一句话概括本篇的核心动作:**把目前隐式的 M2 显式化为一份机器可读的元模型定义,让 Zod 规则、JSON Schema、catalog 校验、提示词片段、ER 图、编辑操作全部从它派生或与它对齐**。这不是推翻现有编译器,而是给现有体系补上缺失的「唯一事实源」。 + +MOF 还有一个常被忽略但对本项目极其关键的概念:**反射式 API(Reflective API)**——任何 M1 模型都可以通过一组统一的、由 M2 约束的操作(创建元素、设置属性、建立引用)来构造和修改,而不是直接手写序列化文本。§4 将说明:这正是让大模型稳定产出合法模型的钥匙。 + +--- + +## 3. UBML 元建模语言(M2 定义语言) + +### 3.1 设计原则 + +1. **小而完备**:M3 词汇控制在 7 个构造以内,能覆盖实体和页面两个领域即可,不追求 MOF/EMOF 的完整实现; +2. **YAML 承载,纯声明式**:与现有 DSL 同一技术栈,社区贡献者不需要学新工具; +3. **单一事实源**:所有派生物(校验、提示词、图、文档)从元模型生成或用一致性测试对齐,禁止双头维护; +4. **约束一等公民**:值域约束(「关联必须指向 catalog 里存在的实体」)与结构约束(「子实体不能再嵌套子实体」)都要能在元模型里声明,而不是埋在编译器代码里; +5. **渐进接入**:元模型先作为「描述性规约 + 一致性测试」上线,不要求第一天就改写编译器(见 §6 路线)。 + +### 3.2 元语言核心构造(M3) + +| 构造 | 含义 | 对应 MOF/EMOF 概念 | +|------|------|--------------------| +| `metamodel` | 一个领域元模型的根,含名称与版本 | Package | +| `class` | 元类:一类模型元素(如「字段」「子实体」) | Class | +| `attribute` | 元属性:元素自身的标量属性(code、name、length…) | Property (attribute) | +| `reference` | 元引用:元素之间的关系,**必须声明是否为包含(containment)** | Property (reference) | +| `enum` | 封闭取值集(如字段类型枚举) | Enumeration | +| `datatype` | 基础数据类型及其到平台类型的映射 | DataType | +| `constraint` | 约束:结构不变式或值域校验,可引用 catalog | Constraint (OCL 的轻量替代) | + +其中 `reference` 的 **containment** 区分是 MOF 的精髓,也刚好切中实体建模的两类关系: + +- **包含引用**(`containment: true`):整体-部分关系,删除整体级联删除部分。对应实体的 `children`(FYBXD 包含 FYBXDMX 明细)——平台格式里的 `ContainChildObjects`; +- **交叉引用**(`containment: false`):对等引用关系,只存外键。对应实体的 `association` 字段(报销人员 → GspUser)——平台格式里的 `ChildAssociations` + `Refs`。 + +ER 图的两种连线(实线菱形 / 箭头)、级联删除语义、`Refs[]` 依赖清单的生成,全部由这一个标志推导,不需要额外机制。 + +### 3.3 业务实体元模型(M2 实例) + +下面用元语言定义业务实体元模型的骨架。这份 YAML 就是未来的 `ubml-entity.metamodel.yaml`,每一行都能在 FYBXD 金样中找到对应物: + +```yaml +metamodel: + name: ubml-entity + version: "0.1" + + datatypes: + - { name: string, platform: { mdataType: String, requiresLength: true } } + - { name: text, platform: { mdataType: Text } } + - { name: integer, platform: { mdataType: Integer } } + - { name: decimal, platform: { mdataType: Decimal, requiresLength: true, requiresPrecision: true } } + - { name: datetime, platform: { mdataType: DateTime } } + - { name: boolean, platform: { mdataType: Boolean } } + + enums: + - name: EntityKind + literals: [bill, dictionary, config] + - name: RuleTiming + literals: [beforeSave, afterModify, afterCreate] + + classes: + - name: BusinessEntity + doc: 一个业务实体元数据,编译为 GspMetadata(Header + Refs + Content) + attributes: + - { name: code, type: Identifier, required: true } + - { name: name, type: LocalizedString, required: true } + - { name: namespace, type: QualifiedName, required: true } + - { name: kind, type: EntityKind, default: bill } + references: + - { name: root, type: EntityObject, containment: true, multiplicity: "1" } + - { name: managerActions, type: ManagerAction, containment: true, multiplicity: "0..*" } + + - name: EntityObject + doc: 实体对象节点;根对象编译为 MainObject,子对象编译为 ContainChildObjects[] + attributes: + - { name: code, type: Identifier, required: true } + - { name: name, type: LocalizedString, required: true } + references: + - { name: fields, type: Field, containment: true, multiplicity: "1..*" } + - { name: children, type: EntityObject, containment: true, multiplicity: "0..*" } + - { name: rules, type: Rule, containment: true, multiplicity: "0..*" } + constraints: + - name: no-grandchildren + doc: 子实体不允许再包含子实体(平台限制为两级对象树) + check: "self.parent != null implies self.children.isEmpty()" + + - name: Field + abstract: true + attributes: + - { name: code, type: Identifier, required: true } + - { name: name, type: LocalizedString, required: true } + + - name: ScalarField # { code: TotalSum, type: decimal, length: 18, precision: 2 } + superType: Field + attributes: + - { name: type, type: DataTypeRef, required: true } + - { name: length, type: Integer } + - { name: precision, type: Integer } + - { name: required, type: Boolean, default: false } + - { name: multiLanguage, type: Boolean, default: false } + - { name: billCode, type: Boolean, default: false } + constraints: + - { name: length-required-by-type, check: "datatype(self.type).requiresLength implies self.length != null" } + + - name: EnumField # BillType:差旅费 / 手机费 / 交通费 + superType: Field + references: + - { name: values, type: EnumValue, containment: true, multiplicity: "1..*" } + constraints: + - { name: single-default, check: "self.values->select(default).size() <= 1" } + + - name: AssociationField # Employee → GspUser,编译为 ChildAssociations + Refs 条目 + superType: Field + references: + - { name: ref, type: BusinessEntity, containment: false, external: true } + attributes: + - { name: refFields, type: IdentifierList, doc: 带出的引用字段,如 [Code, Name] } + constraints: + - { name: ref-in-catalog, check: "catalog('referenceable-be').has(self.ref)" } + - { name: refFields-exist, check: "catalog('referenceable-be').elements(self.ref).containsAll(self.refFields)" } + + - name: UdtField # BillState / ProcessInstance / AttachmentInfo + superType: Field + references: + - { name: udt, type: UnifiedDataType, containment: false, external: true } + constraints: + - { name: udt-in-catalog, check: "catalog('udt').has(self.udt)" } + + - name: Rule + abstract: true + # 派生出 Validation(校验)、Determination(联动计算)、Action(业务动作), + # 各自声明 timing / triggerFields / component 等属性与约束,此处从略 + + - name: ManagerAction # SubmitToApprove 等,common: true 时须命中 components.catalog + attributes: + - { name: code, type: Identifier, required: true } + - { name: common, type: Boolean, default: false } + constraints: + - { name: common-in-catalog, check: "self.common implies catalog('components').has(self.code)" } +``` + +对照检查:FYBXD 金样里的每一个语法现象——普通字段、`billCode`、多语言、枚举(BillType/AuditStatus)、关联(GspUser/SysOrg/Projects)、UDT(BillState/ProcessInstance/AttachmentInfo)、两个子实体、三类规则、七个管理动作——都能落在上述元类和约束上。而 `FYBXD.json` 里那些 GUID、`ColumnID`、`I18nResourceInfoPrefix`、`MappingRelation`、系统字段(ID/ParentID/Version)等机械内容,**不进入元模型**——它们是编译器(M1→平台格式的变换)的职责,这正是「AI 想语义、编译器保正确」原则在元模型层的体现。 + +`constraint` 的 `check` 表达式采用一个 OCL 风格的极简子集(导航、集合谓词、`catalog()` 函数),第一版甚至可以不做表达式引擎——每条约束绑定一个 TypeScript 校验函数,`check` 先作为**人读的规约文档**存在,后续再逐步机器化。 + +### 3.4 页面元模型:验证覆盖性 + +同一套 M3 词汇定义页面元模型,证明元语言不是为实体特制的: + +```yaml +metamodel: + name: ubml-page + version: "0.1" + classes: + - name: Page + attributes: + - { name: type, type: PageType, required: true } # card / management-list / … + - { name: title, type: LocalizedString, required: true } + references: + - { name: entity, type: BusinessEntity, containment: false, external: true } + - { name: sections, type: Section, containment: true, multiplicity: "1..*" } + - { name: toolbar, type: ToolbarAction, containment: true, multiplicity: "0..*" } + + - name: Section + references: + - { name: fields, type: FieldBinding, containment: true, multiplicity: "1..*" } + + - name: FieldBinding + attributes: + - { name: label, type: LocalizedString } + - { name: editor, type: EditorRef } # 值域来自 components.index.json + references: + - { name: bind, type: Field, containment: false, external: true } + constraints: + - { name: bind-exists-on-entity, check: "page(self).entity.allFields().has(self.bind)" } + - { name: editor-in-index, check: "catalog('components-index').has(self.editor)" } +``` + +注意 `FieldBinding.bind` 是一个跨元模型的交叉引用——页面字段绑定实体字段。目前 AppGen 的 crossref 校验(`packages/ubml-codegen/src/app/validate/crossref.ts`)用手写代码检查这件事;元模型化之后,crossref 就是「校验所有 `external: true` 引用可解析」这一条通用规则,新增模型种类不再需要新写 crossref 代码。 + +### 3.5 元模型驱动的派生物 + +显式化 M2 的价值在于它能「一源多用」。从一份元模型定义出发,以下产物可以生成或对齐: + +```text + ubml-entity.metamodel.yaml(唯一事实源) + │ + ┌──────────────┬────────────────┼────────────────┬──────────────┐ + ▼ ▼ ▼ ▼ ▼ + Zod 校验规则 JSON Schema 提示词片段 ER 图元数据 编辑操作集 + (DSL 三道闸 (产物结构 (合法类型/枚举/ (模型页签 (§4 建模工具的 + 的第一道) 校验) 约束清单,注入 可视化) 操作签名与 + entitygen 提示词) 校验依据) +``` + +| 派生物 | 现状载体 | 元模型化后 | +|--------|----------|-----------| +| DSL 校验 | 手写 Zod(`entity/dsl/schema.ts`) | 第一阶段:一致性测试对齐;远期:代码生成 | +| 产物校验 | 手工维护的 `gsp-business-entity.schema.json` | 同上 | +| 值域清单 | 四个 catalog JSON | catalog 保留为**数据**,元模型的 `catalog()` 约束声明**谁在什么位置必须查哪个 catalog** | +| 提示词 | 人工撰写 `entitygen.md` | 「合法字段类型表」「约束清单」等段落由元模型渲染生成,人工只写工作流部分 | +| ER 图 | `entity-preview-model.ts` 手工推导 | 由 containment / 交叉引用直接映射为图的边 | +| 文档 | 手写 | 元类与约束的 `doc` 字段可渲染为参考手册 | + +--- + +## 4. 稳定生成方案:让大模型可靠地产出合法实体模型 + +这是全篇技术上最关键的一节。先把问题拆开,再对比候选方案,最后给出推荐组合。 + +### 4.1 先分清三类「正确性」 + +「模型生成不稳定」其实混合了三个性质完全不同的问题,混在一起讨论就会得出「多写点规则试试」这类含糊结论: + +| 类别 | 含义 | 反例 | 适合的手段 | +|------|------|------|-----------| +| **格式正确性** | 产出是语法合法、结构完整的模型文档 | YAML 缩进错、缺 `code`、`ContainEnumValues` 拼错 | **不靠提示词**——靠缩小生成面(DSL)、结构化输出、确定性编译 | +| **值域正确性** | 引用的类型、实体、UDT、构件真实存在 | 编造 `type: money`、关联不存在的 `Customer` 实体 | catalog 检索 + 元模型 `catalog()` 约束即时校验 | +| **语义正确性** | 模型如实表达了自然语言需求 | 需求说「明细金额汇总到主表」,生成时漏了 determination | exemplar 检索、需求对照清单、校验-修复回环、人审预览 | + +一个直接推论:**知识体系和建模规则(提示词层面的手段)只能改善后两类,对第一类几乎无能为力**——提示词只能提高模型「倾向于」写对格式的概率,无法给出保证,且上下文越长遵从率越低。这就是单靠「知识体系 + 建模规则」方案不牢靠的根本原因。 + +### 4.2 候选方案对比 + +| # | 方案 | 解决哪类正确性 | 优点 | 局限 | 结论 | +|---|------|----------------|------|------|------| +| A | 提示词规则 + 知识体系(few-shot、建模守则) | 值域△ 语义△ | 零改造、易迭代 | 无保证;规则越多遵从越差;换模型效果漂移 | **辅助**,且内容应从元模型生成 | +| B | 微调 / 蒸馏专用模型 | 格式△ 语义△ | 输出风格稳定 | 样本量小(目前金样一位数);训练与维护成本高;基座升级即作废;仍无硬保证 | **现阶段不做**,留作数据积累后的远期选项 | +| C | 结构化输出 / 约束解码(JSON Schema strict mode、function calling) | 格式✓ | 语法层面硬保证 | 只保语法不保语义;对 YAML 不适用;依赖 provider 能力 | **采用**,用在工具调用参数上(见 E) | +| D | 语义 DSL + 确定性编译器 | 格式✓✓ | 生成面从 1200 行 JSON 缩到 77 行 YAML;机械细节全部编译器兜底 | DSL 本身仍可能写错(拼错枚举值、漏必填项) | **底座**,仓库已有 ✅ | +| E | **建模操作工具化**(MOF 反射式 API → MCP 工具) | 格式✓✓ 值域✓ | 每一步操作参数极小、可被 schema 硬约束、可即时校验即时反馈;错误定位到单步而非整个文档 | 需要设计操作集与会话内模型状态 | **本篇核心增量**,推荐 | +| F | 校验-修复回环(结构化 issues + hint,≤3 轮) | 三类兜底 | 已验证有效 | 每轮有 token 与时延成本;应作为兜底而非主路径 | **保留**,仓库已有 ✅ | +| G | 检索增强(catalog / exemplar / intent 检索) | 值域✓ 语义△ | 把「世界上有什么可用」注入上下文 | 覆盖面取决于策展质量 | **保留并元模型化**,仓库已有 ✅ | + +### 4.3 推荐:五层防线,核心是「大模型不直接产出最终文本」 + +推荐组合为 **D + E + C + G + F**,按生成时序组织成五层防线: + +```text +第 1 层 值域接地(G) 生成前:kb_be_exemplar 按意图检索范例; + kb_be_datatype / kb_be_ref / kb_be_udt 查出可用值域 +第 2 层 骨架草案(D+C) 一次性产出小的 entity: DSL 草案(只含字段/子实体骨架), + 立即过三道闸——草案小,错了也易修 +第 3 层 受控精修(E+C) 关联、枚举、UDT、规则等易错部分不靠一次生成, + 改为逐条调用建模操作,每个操作参数由 JSON Schema + 硬约束,执行时按元模型约束即时校验、即时报错 +第 4 层 整体校验(F) compile → schema → roundtrip 语义等价; + 失败给结构化 issues + hint,修复回环 ≤3 轮 +第 5 层 人审预览 模型页签展示 ER 图与字段表,人确认后写回 +``` + +**第 3 层是本篇相对现状的核心增量**,展开说明。 + +MOF 的反射式 API 思想是:模型不是「一篇文档」,而是「一组由元模型约束的对象」,构造模型的正规方式是调用 `createInstance` / `set` / `add` 这类细粒度操作。把这个思想搬到 LLM 场景,就是把「生成一个大文档」这个高风险动作,替换为「执行一系列小的建模操作」: + +```text +kb_be_create { code, name, namespace, kind } → 创建实体骨架 +kb_be_edit { op: addField, object, field } → 加标量字段 +kb_be_edit { op: addChild, code, name, fields } → 加子实体(含关系) +kb_be_edit { op: addAssociation, object, field, ref, refFields } + → 加关联(含关系) +kb_be_edit { op: addEnumField / addUdtField / addRule / patchField / removeField … } +``` + +仓库已有 `kb_be_edit` 的 `addField` / `addChild` / `addRule` / `patchField` / `removeField` 五个操作(见 [UBML-ENTITY-VERIFY.md](../UBML-ENTITY-VERIFY.md)),所以这不是从零建设,而是三点补强: + +1. **操作集补全**:按 §3.3 元模型补齐 `addAssociation`、`addEnumField`、`addUdtField`、`addManagerAction`,使每一类元类实例都有对应的构造操作; +2. **参数 schema 从元模型生成**:每个操作的入参 JSON Schema 由元模型元类定义渲染而来,配合 provider 的结构化输出能力(方案 C),模型在语法层面写不出非法参数; +3. **单步即时校验**:操作执行时立即跑该元类的约束(`ref-in-catalog`、`length-required-by-type`、`single-default`…),失败即返回带 hint 的结构化错误。错误反馈从「整个文档校验失败、issues 列表 20 条」变成「这一步的这个参数不对、应该从这 3 个候选里选」,修复成本相差一个数量级。 + +为什么骨架仍用一次性 DSL 草案(第 2 层)而不是全程工具调用?因为纯标量字段的出错率本来就低,逐字段调用工具徒增轮次与 token 成本。**易错的地方精修,不易错的地方批量**——关联、枚举、UDT、规则这些牵涉外部引用和交叉约束的部分才是工具化的主战场。两种模式的边界可以随实测数据调整。 + +### 4.4 知识体系与建模规则的正确定位 + +回到最初的疑问——「知识体系」「建模规则」是否合理?结论是:**合理,但它们是值域与语义层的养料,不是格式层的保险**。在推荐架构中它们各就其位: + +| 你列举的方案 | 在五层防线中的位置 | 形态 | +|--------------|--------------------|------| +| 知识体系 | 第 1 层(值域接地)与第 3 层的校验数据源 | catalog(数据)+ exemplar(范例)+ intent 索引(检索),由 [05 知识服务](./05-knowledge-and-context.md)承载 | +| 建模规则 | 拆成两半:**可机检的**进元模型 `constraint`(第 3、4 层硬校验);**启发式的**进提示词(第 2 层,如「单据类实体通常有编号、金额、状态字段」「明细金额通常需要汇总 determination」) | 元模型 YAML + `entitygen.md` | + +判别标准很简单:一条规则如果能写成断言,就不要写成叮嘱。「关联必须指向 catalog 中存在的实体」写成叮嘱是浪费,写成约束是保证;「报销类单据通常需要审批状态」无法断言,留在提示词里发挥启发作用。 + +### 4.5 端到端生成时序(实体优先版) + +```text +用户(对话面板):「创建差旅报销单:单头有报销人、部门、项目、总金额, + 明细记录费用日期、金额、发票号,金额需要汇总校验」 + │ + ▼ +① @ubml-entitygen 意图分析 + kb_be_exemplar("差旅报销") → 命中 FYBXD 范例 + kb_be_ref → GspUser / SysOrg / Projects 可关联 + │ + ▼ +② 骨架草案:产出 entity: DSL(code/name/namespace + 标量字段 + children) + → 立即 kb_be_verify(dsl 闸) + │ + ▼ +③ 受控精修(逐操作,每步即时校验) + addAssociation(Employee → GspUser) ✓ + addAssociation(Department → SysOrg) ✓ + addUdtField(BillState) ✓ + addRule(sumAmount: 明细 Amount → 主表 TotalSum, afterModify) ✓ + addRule(ValidateAmount, beforeSave) ✓ + │ + ▼ +④ 整体校验:compile → schema → roundtrip 语义等价 + 失败 → 结构化 issues + hint → 修复回环(≤3 轮) + │ + ▼ +⑤ 模型页签自动打开:左侧文件导航出现新实体, + 右侧 ER 图展示 FYBXD ─◆ FYBXDMX 包含关系、FYBXD ─▶ GspUser 关联 + 人工确认 → 写回工程 +``` + +--- + +## 5. 模型页签:对话面板右侧的模型视图 + +### 5.1 交互设计 + +在对话面板右侧栏新增「模型」页签,与既有的「更改 / 终端 / 编辑器」并列。页签内部左右分栏: + +```text +┌─ 右侧栏 ──────────────────────────────────────────────┐ +│ 更改 │ 终端 │ 编辑器 │ ★模型 │ + │ +├───────────────┬───────────────────────────────────────┤ +│ 模型导航 │ [ 关系图 ] [ 文件 ] ← 视图切换 │ +│ │ │ +│ ▾ 实体 be/ │ ┌────────┐ │ +│ ● FYBXD │ │ FYBXD │报销单 │ +│ 报销单 │ └──┬──┬──┘ │ +│ ▸ 页面 pages/ │ 包含◆│ │──▶ GspUser(关联) │ +│ ▸ 查询 query/ │ ┌──────┴┐ └─────▶ SysOrg │ +│ │ │FYBXDMX│ ┌────────┐ │ +│ │ │ 明细 │ │FXBXDFJ │附件 │ +│ │ └───────┘ └────────┘ │ +│ │ (点击节点 → 下方字段表 / 跳转文件视图)│ +└───────────────┴───────────────────────────────────────┘ +``` + +- **模型导航**:按模型种类分组列出当前工程 / 本次生成会话产出的模型文件(`*.be.yaml`、`*.be.json` 及后续的页面、查询等)。生成新模型后自动刷新并高亮新增项; +- **关系图视图**(默认):以 ER 图呈现实体关系——包含关系(子实体)与交叉关联(association)用不同线型区分,节点展示实体 code 与中文名,点击节点展开字段表(code / 名称 / 类型 / 长度 / 必填);关联到外部实体(GspUser 等 catalog 实体)以浅色节点呈现,表明「引用而非本工程定义」; +- **文件视图**:只读代码视图展示 DSL YAML 或编译后 JSON,可一键切换;后续可接入受控编辑; +- 图的语义直接来自元模型:`containment: true` 的引用画成包含边,`external: true` 的画成关联边——这就是 §3.5 说的「ER 图元数据由元模型派生」。 + +### 5.2 技术方案 + +右侧栏已有成熟的多页签机制,此功能是标准扩展(各文件均在 `packages/extension/src/` 下): + +| 步骤 | 改动点 | 参考既有实现 | +|------|--------|--------------| +| 1. 注册页签类型 | `pages/layout/right-panel.tsx`:`RightPanelTabType` 增加 `"model"`,补 `TAB_TYPE_ICONS` 与 `OPTIONAL_TAB_TYPES` | 既有 `"preview"` / `"entity-preview"` 类型 | +| 2. 状态 store | 新建 `lib/ubml-model-tab-store.ts`:当前选中文件、视图模式(图/文件)、文件清单 | `lib/ubml-entity-preview-tab-store.ts` | +| 3. 打开桥接 | 新建 `lib/ubml-model-bridge.ts`:`openUbmlModelTab()`,按 key 去重、生成后自动打开 | `lib/ubml-preview-bridge.ts` | +| 4. 内容组件 | 新建 `pages/workbench/ubml-model-panel.tsx`:左导航 + 右内容双栏;在 `project-workspace-shell.tsx` 的 `renderTabContent` 挂载 | — | +| 5. 文件导航数据 | 扫描工程内模型文件与生成输出目录(`manifest.json` 列出的产物) | AppGen 输出结构见 [UBML-APPGEN.md](../UBML-APPGEN.md) | +| 6. ER 图渲染 | 复用 `lib/entity-preview-model.ts` 的 `buildEntityPreviewFromDsl()` 产出的结构;多实体全景图可先用 Mermaid `erDiagram` 快速落地,交互增强(点击展开字段表)再换自绘 SVG | 工作台 `EntityPreviewContent` 已有单实体字段表 + ER | +| 7. 生成联动 | `@ubml-entitygen` 生成/校验通过后调用 `openUbmlModelTab()` 并刷新导航 | 既有生成完成后打开 preview 的联动 | + +关键取舍:**不新造预览体系**。实体的字段表与 ER 渲染在工作台 `EntityPreviewContent` 中已存在,模型页签的增量是「多文件导航 + 多实体全景关系图 + 与对话生成流程的联动」,单实体渲染逻辑全部复用。 + +--- + +## 6. 实施路线:实体优先,四个阶段 + +### 6.1 阶段总览 + +| 阶段 | 目标 | 核心产物 | 依赖 | 建议 OpenSpec 变更 | +|------|------|----------|------|--------------------| +| **E1 元模型显式化** ✅ | 把实体 DSL 的结构、类型和值域约束显式化 | `ubml-entity.metamodel.yaml`、元模型读取器、一致性测试、提示词片段渲染 | 无 | `ubml-entity-metamodel` | +| **E2 建模操作集** ✅ | 让大模型通过受控操作稳定构造实体模型 | 新增 `kb_be_edit` 操作、操作参数 schema、单步校验、实体生成评测集 | E1 | `ubml-entity-modeling-ops` | +| **E3 模型页签** ✅ | 在对话面板右侧看见模型文件与实体关系图 | `model` 右栏页签、模型导航、ER 图/文件双视图、生成联动 | 可与 E2 并行 | `ubml-model-right-panel` | +| **E4 页面推广** ✅ | 把同一套元模型机制推广到页面模型 | `ubml-page.metamodel.yaml`、通用 external 引用校验、页面进入模型导航 | E1–E3 | `ubml-page-metamodel` + `ubml-model-tab-pages` | + +端到端验收(E1–E3 完成时):在对话面板输入一句中文需求 → 生成实体模型三道闸通过 → 模型页签展示 ER 图 → 与 FYBXD 金样做 roundtrip 语义等价比对通过 → 写回工程。**这就是「用一个模型走通完整流程」的可演示定义。** + +每个阶段进入实现前按 [07 · 协作开发指南](./07-contributing.md)走 OpenSpec 流程(`/opsx:propose` → 审阅 → `/opsx:apply` → `/opsx:archive`),元模型文件本身也纳入 living spec 管理。 + +### 6.2 E1:元模型显式化 + +E1 是低风险起步阶段,原则是**只增加事实源与测试,不改变现有生成行为**。这一阶段完成后,仓库仍然用现有 Zod、catalog 和编译器跑实体生成,但社区贡献者已经能从一份文件看懂「实体模型语言到底由哪些元类、字段类型、引用和值域约束组成」。 + +建议文件结构: + +| 文件 | 类型 | 责任 | +|------|------|------| +| `packages/extension/assets/ubml-entity/metamodel/ubml-entity.metamodel.yaml` | 新建 | 业务实体 M2 元模型,覆盖 §3.3 的元类、类型、枚举、约束与 doc | +| `packages/extension/assets/ubml-entity/metamodel/README.md` | 新建 | 说明元模型文件如何维护,哪些内容是首期强约束,哪些只是文档约束 | +| `packages/ubml-codegen/src/entity/metamodel/load.ts` | 新建 | 读取并解析实体元模型,输出稳定的 TypeScript 数据结构 | +| `packages/ubml-codegen/src/entity/metamodel/types.ts` | 新建 | 定义 `UbmlMetamodel`、`MetaClass`、`MetaAttribute`、`MetaReference`、`MetaConstraint` 等类型 | +| `packages/ubml-codegen/src/entity/metamodel/prompt.ts` | 新建 | 从元模型渲染给 Agent 使用的「合法字段类型 / 关联 / UDT / 约束」提示词片段 | +| `packages/ubml-codegen/test/entity-metamodel-consistency.test.ts` | 新建 | 检查元模型与 `datatypes.index.json`、现有 DSL schema、FYBXD 金样保持一致 | +| `packages/extension/assets/ubml-entity/prompts/entitygen.md` | 修改 | 引入由元模型渲染的规则段落,人工提示词只保留工作流和策略 | + +首期一致性测试不用追求覆盖 `gsp-business-entity.schema.json` 的所有字段。建议只验证三类最有价值的不变量: + +| 不变量 | 检查方式 | +|--------|----------| +| 元模型声明的 `datatypes` 与 `datatypes.index.json` 中的 DSL 类型一致 | 读取两份文件,比对 `string/text/integer/decimal/datetime/boolean/enum/association/udt` 等类型集合 | +| 元模型字段分类能覆盖 FYBXD 金样 | 解析 `assets/ubml-entity/exemplars/fybxd.be.yaml`,确认普通字段、枚举、关联、UDT、子实体、规则、管理动作都能映射到元模型中的类 | +| 元模型 catalog 约束与 catalog 文件存在性一致 | `referenceable-be`、`udt`、`components` 三个 catalog 名称必须能解析到真实文件 | + +E1 验收命令建议: + +```bash +bun test packages/ubml-codegen/test/entity-metamodel-consistency.test.ts +bun run ubml:be:verify packages/extension/assets/ubml-entity/exemplars/fybxd.be.yaml +``` + +E1 的退出标准: + +- `ubml-entity.metamodel.yaml` 能覆盖 FYBXD 金样中的全部建模元素; +- 元模型读取器能在 Node/Bun 测试环境稳定解析; +- 一致性测试能在 catalog 缺项或类型名不一致时失败; +- 现有实体编译、校验、预览行为不发生变化。 + +### 6.3 E2:建模操作集 + +E2 是提升生成稳定性的核心阶段。目标不是让大模型「更会写 YAML」,而是让它尽量通过**小粒度、可校验、可回滚**的建模操作来构造实体。 + +现有 MCP 已有 `kb_be_edit`,输入大致是: + +```text +{ yaml, op: addField | addChild | addRule | patchField | removeField, args } +``` + +E2 在此基础上补齐实体元模型中的关键构造操作: + +| 操作 | 作用 | 关键校验 | +|------|------|----------| +| `addScalarField` | 添加 string/text/integer/decimal/datetime/boolean 字段 | 类型存在;decimal 必须有 precision;需要长度的类型必须有 length | +| `addEnumField` | 添加枚举字段 | 枚举值至少 1 个;默认值最多 1 个;code 不重复 | +| `addAssociationField` | 添加外部实体关联 | `ref` 必须命中 `referenceable-be.catalog.json`;`refFields` 必须存在于被引用实体 | +| `addUdtField` | 添加 UDT 字段 | `udt` 必须命中 `udt.catalog.json` | +| `addChildObject` | 添加子实体 | 子实体 code 不重复;不允许孙级子实体;自动补 ParentID 由编译器负责 | +| `addDetermination` | 添加联动计算规则 | source child/field、target field 必须存在;component 可命中 catalog 或按命名约定生成 | +| `addValidation` | 添加校验规则 | triggerFields 必须存在;timing 合法 | +| `addBizAction` | 添加对象动作 | 参数类型合法 | +| `addManagerAction` | 添加管理动作 | `common: true` 时 code 必须命中 `components.catalog.json` | + +实现上建议先保持 `kb_be_edit` 一个工具入口,但把内部拆成注册表: + +```text +beEdit(input) + ├─ parse current YAML + ├─ find operation handler by op + ├─ validate args with operation schema + ├─ apply immutable edit to DSL AST + ├─ run affected constraints from metamodel + ├─ run full kb_be_verify(可配置为 fast/full) + └─ return { yaml, ok, issues, hints, changedPaths } +``` + +建议文件结构: + +| 文件 | 类型 | 责任 | +|------|------|------| +| `packages/ubml-codegen/src/entity/edit/operations.ts` | 新建或扩展 | 操作注册表与 `EntityEditOperation` 类型 | +| `packages/ubml-codegen/src/entity/edit/add-association-field.ts` | 新建 | 关联字段操作 | +| `packages/ubml-codegen/src/entity/edit/add-enum-field.ts` | 新建 | 枚举字段操作 | +| `packages/ubml-codegen/src/entity/edit/add-udt-field.ts` | 新建 | UDT 字段操作 | +| `packages/ubml-codegen/src/entity/edit/add-manager-action.ts` | 新建 | 管理动作操作 | +| `packages/knowledge/src/mcp/server.ts` | 修改 | 扩展 `kb_be_edit` 的 `op` 枚举和 description | +| `packages/knowledge/src/service.ts` | 修改 | 调用新的编辑操作注册表 | +| `packages/knowledge/script/be-modeling-ops-smoke.ts` | 新建 | 以 FYBXD 为目标跑受控编辑冒烟 | +| `packages/knowledge/eval/entity-generation-tasks.json` | 新建 | 自然语言需求评测集 | +| `packages/knowledge/eval/eval-entity-generation-core.ts` | 新建 | 统计一次通过率、修复轮次、错误类型 | + +E2 需要先建立**生成质量基线**,否则无法证明方案变好。建议至少准备 10 条中文需求,覆盖: + +1. 单据 + 明细 + 汇总; +2. 字典主数据; +3. 有两个外部关联的实体; +4. 有枚举字段的实体; +5. 有 UDT 附件字段的实体; +6. 有 beforeSave 校验; +7. 有 afterCreate 默认值; +8. 有管理动作; +9. 刻意包含模糊字段名(测试命名规范); +10. 刻意包含不存在的引用(测试 catalog hint)。 + +评测指标建议: + +| 指标 | 含义 | 首期目标 | +|------|------|----------| +| `verifyPassRate` | 最终三道闸通过率 | 10/10 | +| `firstPassRate` | 首次生成不修复即通过比例 | 先跑现状基线,再要求 E2 高于基线 | +| `avgRepairRounds` | 平均修复轮次 | 低于现状基线 | +| `invalidCatalogReferenceCount` | 编造引用次数 | 接近 0 | +| `manualEditRequiredCount` | 需要人工改 YAML 的次数 | 0 或清楚记录原因 | + +E2 验收命令建议: + +```bash +bun test packages/ubml-codegen/test/entity-edit-operations.test.ts +bun packages/knowledge/script/be-modeling-ops-smoke.ts +bun packages/knowledge/eval/eval-entity-generation-core.ts +``` + +E2 的退出标准: + +- 新增操作能把空实体逐步编辑成 FYBXD 语义等价模型的核心结构; +- 每个操作失败时返回结构化 `issues` 与 `hints`,错误能定位到操作参数; +- `@ubml-entitygen` 提示词明确采用「骨架草案 + 受控精修」工作流; +- 生成质量评测集可以重复运行并产出指标。 + +### 6.4 E3:模型页签 + +E3 解决「生成效果不可见」的问题。它应当先做**只读可视化**,不要在首版同时引入图上编辑,否则 UI 状态、受控编辑和写回会互相放大复杂度。 + +建议首版范围: + +| 能力 | 首版是否做 | 说明 | +|------|------------|------| +| 右侧栏新增 `model` 页签 | 做 | 与 `preview` / `entity-preview` 并列 | +| 模型文件导航 | 做 | 首版可只展示本次生成/预览 payload 中的实体模型,工程全量扫描后置 | +| 实体 ER 图 | 做 | 复用 `entity-preview-model.ts` 的 Mermaid 输出,补外部关联节点 | +| 字段表 | 做 | 复用现有实体预览字段结构 | +| 文件视图 | 做 | YAML/JSON 只读展示 | +| 图上编辑 | 不做 | 后续接 E2 的受控编辑操作 | +| 多实体全景图 | 部分做 | 首版先支持单实体 + 外部引用;多文件聚合后置 | + +建议文件结构: + +| 文件 | 类型 | 责任 | +|------|------|------| +| `packages/extension/src/lib/ubml-model-tab-store.ts` | 新建 | 模型页签 payload:模型列表、选中文件、视图模式 | +| `packages/extension/src/lib/ubml-model-bridge.ts` | 新建 | `openUbmlModelTab()`,生成后打开或刷新模型页签 | +| `packages/extension/src/lib/model-preview-model.ts` | 新建 | 把一个或多个 `EntityPreviewModel` 聚合为模型导航与关系图数据 | +| `packages/extension/src/pages/workbench/ubml-model-panel.tsx` | 新建 | 左侧导航 + 右侧图/文件视图 | +| `packages/extension/src/pages/layout/right-panel.tsx` | 修改 | 增加 `model` tab 类型、图标、可添加类型 | +| `packages/extension/src/pages/layout/right-panel-labels.ts` | 修改 | 增加中英文 tab 文案 | +| `packages/extension/src/pages/layout/project-workspace-shell.tsx` | 修改 | 渲染 `ModelTabContent`,关闭页签时清理 payload | +| `packages/extension/src/lib/ubml-entity-artifact-actions.ts` | 修改 | `previewEntityYaml()` 成功后可同时刷新模型页签 | + +需要注意右侧栏现状:手动加号菜单对每种 tab 类型只保留一个实例;`preview` / `entity-preview` 的桥接函数可以通过 key 创建多个预览 tab。模型页签建议采用**一个常驻 tab + 内部多模型导航**,不要每个模型一个右栏 tab,这样更符合「模型空间」的心智,也避免右栏被多个实体页签挤满。 + +E3 验收命令建议: + +```bash +bun test packages/extension/src/lib/model-preview-model.test.ts +bun run typecheck +``` + +手工验收场景: + +1. 在实体工作台打开 FYBXD 金样,点击预览; +2. 右侧出现或刷新「模型」页签; +3. 左侧导航能看到 `FYBXD`; +4. 关系图能看到 FYBXD 包含 `FYBXDMX`、`FXBXDFJ`,并关联 `GspUser`、`SysOrg`、`Projects`; +5. 切换「文件」视图能看到原始 YAML; +6. 关闭页签后 payload 清理,不影响现有 `entity-preview` 页签。 + +### 6.5 E4:页面推广 + +E4 不应早于 E1–E3 的实体闭环。推广到页面时只复用两件经过验证的东西:元模型描述方式与模型页签导航;不要把页面编译器一次性改造成元模型驱动。 + +首版页面元模型只覆盖最小集合: + +| 页面概念 | 覆盖范围 | +|----------|----------| +| `Page` | `type/title/entity/sections/toolbar` | +| `Section` | `title/layout/fields` | +| `FieldBinding` | `label/bind/editor` | +| `Action` | 工具栏动作与按钮 | +| `RawNode` | 记录 `raw:` 逃逸节点,不解析内部结构 | + +E4 的主要收益是把 crossref 从「AppGen 内部手写校验」逐步抽象为通用 external 引用解析:页面绑定实体字段、查询引用实体、页面流引用页面,最终都能用一套引用解析模型描述。 + +### 6.6 推荐 Issue 拆分 + +为了便于社区推进,建议不要按「E1/E2/E3」各开一个大 Issue,而是拆成可独立合并的小任务: + +| Issue | 标题建议 | 产出 | +|-------|----------|------| +| M1 | Add explicit UBML entity metamodel asset | `ubml-entity.metamodel.yaml` + README | +| M2 | Add entity metamodel parser and consistency tests | parser/types/tests | +| M3 | Render entitygen prompt constraints from metamodel | prompt renderer + prompt 接入 | +| M4 | Add BE edit operations for association/enum/UDT fields | 三个操作 + 单测 | +| M5 | Add BE edit operations for determinations and manager actions | 规则/管理动作操作 + 单测 | +| M6 | Add entity generation evaluation dataset | 10 条中文需求 + eval runner | +| M7 | Add right-panel model tab shell | tab 类型、store、bridge、空状态 | +| M8 | Add model navigation and entity ER graph | 模型导航 + Mermaid 图 + 字段表 | +| M9 | Wire entity generation preview into model tab | `previewEntityYaml()` / 生成成功后的联动 | +| M10 | Draft page metamodel and generic external reference concept | 页面元模型草案 + crossref 设计文档 | + +推荐先做 M1–M3,再并行 M4–M6 与 M7–M9。M10 等实体闭环稳定后再启动。 + +### 6.7 首轮里程碑定义 + +首轮里程碑命名建议:`EntityGen MoF v0`。它只承诺一件事:**让业务实体生成有显式语言定义、可重复评测、可视化结果**。 + +> **状态(2026-07-04)**:E1–E4 与模型页签页面扩展已归档;验收见 [`docs/MOF-ENTITYGEN-V0-ACCEPTANCE-CHECKLIST.md`](../MOF-ENTITYGEN-V0-ACCEPTANCE-CHECKLIST.md)。 + +里程碑完成条件: + +- `FYBXD` 金样可由元模型解释并通过一致性测试; +- 至少 10 条中文实体需求进入 eval,能输出通过率与修复轮次; +- 对话/工作台生成或预览实体后,右侧「模型」页签展示实体结构和关系图; +- 文档、OpenSpec living spec、README 导航同步更新。 + +--- + +## 7. 与现有体系的关系:收敛而非推翻 + +| 现有能力 | 本方案下的去向 | +|----------|----------------| +| `entity:` YAML DSL | 保留,是 M1 的人读/AI 写表示;元模型是它的形式化定义 | +| `ubml-codegen` 确定性编译器 | 保留,负责 M1 → 平台格式(GUID、系统字段、Refs 等机械展开);不要求改写 | +| 三道闸 + 修复回环 | 保留为第 4 层防线;issues 的 hint 逐步改为从元模型约束生成 | +| 四个 catalog JSON | 保留为数据;「谁必须查哪个 catalog」的规则上收到元模型 | +| `kb_be_*` MCP 工具 | 扩展为完整建模操作集(§4.3) | +| Zod / JSON Schema | 短期用一致性测试对齐元模型,远期由元模型生成 | +| 工作台实体编辑器 / 预览 | 保留;模型页签复用其渲染组件 | +| 提示词 / Agent 定义 | 值域与约束段落由元模型渲染,工作流段落人工维护 | + +风险与对策: + +- **元模型与实现脱节**(最大风险):一致性测试是强制闸——E1 起任何改动 catalog/Zod/Schema 而不同步元模型的 PR 会挂 CI; +- **操作集会话状态管理**:受控精修要求会话内持有「当前模型」,需要明确定义模型状态的存取与并发边界(E2 的 OpenSpec design 重点); +- **过度工程**:M3 词汇冻结在 7 个构造,不实现完整 OCL;表达式引擎按需演进,首版允许约束以 TypeScript 函数落地。 + +--- + +## 8. 社区贡献切入点 + +| 方向 | 适合谁 | 起点 | +|------|--------|------| +| 元模型定义与一致性测试(E1) | 熟悉 TypeScript / 对建模理论有兴趣 | §3.3 骨架 + `packages/ubml-codegen/src/entity/dsl/schema.ts` 对照 | +| 建模操作集(E2) | 熟悉 MCP / `packages/knowledge` | `kb_be_edit` 现有五操作的实现 | +| 模型页签 UI(E3) | 熟悉 SolidJS / 前端可视化 | §5.2 步骤表 + `right-panel.tsx` | +| ER 图渲染 | 前端可视化 | `entity-preview-model.ts` + Mermaid erDiagram | +| 生成质量评测 | QA / 数据方向 | 为 E2 验收建自然语言需求评测集(参照 `packages/knowledge/eval/` 的做法) | + +任务将按本篇 §6 拆解为 Gitee Issue 登记(里程碑规划中),认领方式见 [07 · 协作开发指南](./07-contributing.md)。 + +--- + +## 9. 小结 + +- **一个事实源**:把散落四处的「什么是合法实体模型」收敛为一份显式元模型(M2),Zod、JSON Schema、catalog 约束、提示词、ER 图从它派生或与它对齐; +- **一把钥匙**:格式稳定性不靠提示词约束兜底,靠「大模型不直接产出最终文本」——语义 DSL 缩小生成面、建模操作工具化(MOF 反射式 API)让每一步都被 schema 硬约束并即时校验; +- **一条闭环**:实体优先,从一句自然语言到模型页签里的 ER 图,四个阶段走通全流程,再推广到页面模型。 + +上一篇:[09 · 路线图社区 Issue](./09-roadmap-community-issues.md) diff --git a/docs/product-design/README.md b/docs/product-design/README.md index cdf425ee..13e78b00 100644 --- a/docs/product-design/README.md +++ b/docs/product-design/README.md @@ -12,7 +12,7 @@ | 第一次了解项目 | 本页 → [01 背景与愿景](./01-background-and-vision.md) → [06 路线图](./06-roadmap.md) | | 准备搭环境、写代码 | [03 技术架构](./03-technical-architecture.md) → [07 协作开发指南](./07-contributing.md) | | 想领任务做功能 | [06 路线图 §5](./06-roadmap.md#5-社区-issue-清单) · [08 工作台 Issue](./08-development-activity-workbench.md#8-社区-issue-清单gitee-里程碑) · [09 路线图 Issue](./09-roadmap-community-issues.md) | -| 做 UBML 编译/生成 | [04 UBML 主线](./04-ubml-pipeline.md) | +| 做 UBML 编译/生成 | [04 UBML 主线](./04-ubml-pipeline.md) → [10 MOF 元建模](./10-mof-entity-modeling.md) | | 做知识库 / MCP | [05 知识服务](./05-knowledge-and-context.md) | **已在 Gitee 登记的可认领任务**:https://gitee.com/ubml/code-agent/issues(里程碑:`AppGen v1`、`开发活动工作台 v1`、`知识库 v1` 等) @@ -53,6 +53,7 @@ code-agent 是在 [OpenCode](https://github.com/anomalyco/opencode) 智能体引 | 07 | [协作开发指南](./07-contributing.md) | 环境、红线、OpenSpec、PR 清单 | | 08 | [开发活动工作台](./08-development-activity-workbench.md) | Mission 六阶段、页面流、W-xx Issue 全文 | | 09 | [路线图社区 Issue](./09-roadmap-community-issues.md) | P1/P2/P3/D Issue 全文与 Gitee 里程碑 | +| 10 | [MOF 元建模与实体优先生成](./10-mof-entity-modeling.md) | 元建模语言、稳定生成五层防线、模型页签([MoF v0 验收清单](../MOF-ENTITYGEN-V0-ACCEPTANCE-CHECKLIST.md)) | --- diff --git a/openspec/changes/archive/2026-07-04-ubml-entity-metamodel/.openspec.yaml b/openspec/changes/archive/2026-07-04-ubml-entity-metamodel/.openspec.yaml new file mode 100644 index 00000000..d86f1520 --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-entity-metamodel/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-07-04 diff --git a/openspec/changes/archive/2026-07-04-ubml-entity-metamodel/design.md b/openspec/changes/archive/2026-07-04-ubml-entity-metamodel/design.md new file mode 100644 index 00000000..4fa63654 --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-entity-metamodel/design.md @@ -0,0 +1,67 @@ +## Context + +`docs/product-design/10-mof-entity-modeling.md` 定义 MOF 四层架构,E1 目标是把实体 DSL 的 M2 显式化。当前实现: + +| 载体 | 位置 | +|------|------| +| Zod | `packages/ubml-codegen/src/entity/dsl/schema.ts` | +| 产物 Schema | `gsp-business-entity.schema.json` | +| Catalog | `datatypes.index.json` 等四个 JSON | +| 提示词 | `assets/ubml-entity/prompts/entitygen.md` | + +FYBXD 金样(`exemplars/fybxd.be.yaml`,77 行)是验收基准。 + +## Goals / Non-Goals + +**Goals:** + +- 一份 `ubml-entity.metamodel.yaml` 覆盖 FYBXD 全部建模元素 +- 稳定解析器输出 TypeScript 结构,供测试与提示词渲染使用 +- 一致性测试在 catalog/类型漂移时失败 +- `entitygen.md` 的「合法类型/约束」段落由元模型渲染 + +**Non-Goals:** + +- 不从元模型生成 Zod 或 `gsp-business-entity.schema.json`(远期) +- 不实现 OCL 表达式引擎;`constraint.check` 首期为人读规约 +- 不改动编译器、verify pipeline 或 MCP 操作集(属 E2) + +## Decisions + +### 1. M3 词汇:7 构造 YAML 声明式 + +采用 `metamodel / class / attribute / reference / enum / datatype / constraint`,与产品设计 §3.2 一致。不引入完整 MOF/EMOF。 + +### 2. 元模型文件位置 + +放在 `packages/extension/assets/ubml-entity/metamodel/`,与 catalog、exemplar 同包,由 `ubml-codegen` 通过相对路径或 asset 解析读取(与现有 `EntityCompileContext` 模式一致)。 + +**备选**: 放在 `ubml-codegen` 内 — 拒绝,因 Agent 提示词与 extension 策展知识包同域。 + +### 3. 约束首期不机器执行 + +`constraint.check` 字段保留 OCL 风格文档;执行由一致性测试 + 既有 Zod 校验承担。E2 操作集再绑定单步 TS 校验函数。 + +### 4. DSL `kind` 与 Zod 对齐 + +元模型 `EntityKind` 使用 `bill | basedata`(与 `schema.ts` 一致),而非设计文档草案中的 `dictionary | config`。 + +### 5. 提示词渲染策略 + +`prompt.ts` 渲染:datatype 表、catalog 约束清单、元类 doc 摘要。`entitygen.md` 保留 `` 占位或末尾追加渲染段,人工工作流段落不动。 + +## Risks / Trade-offs + +- [元模型与 Zod 脱节] → 一致性测试强制 PR 同步;测试覆盖 datatype 集合、FYBXD 元素映射、catalog 名称 +- [过度工程] → M3 冻结 7 构造;不生成完整产物 schema +- [kind/enum 命名漂移] → 测试以 `datatypes.index.json` 与 `schema.ts` 为 ground truth + +## Migration Plan + +纯增量:新文件 + 测试 + 提示词段落替换。无数据迁移。回滚删除新增路径即可。 + +**Upstream sync impact**: none + +## Open Questions + +- `date` 类型是否在元模型 datatypes 中单独列出(`datatypes.index.json` 有 `date` 映射 DateTime)— 首期与 index 保持一致 diff --git a/openspec/changes/archive/2026-07-04-ubml-entity-metamodel/proposal.md b/openspec/changes/archive/2026-07-04-ubml-entity-metamodel/proposal.md new file mode 100644 index 00000000..40b2d531 --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-entity-metamodel/proposal.md @@ -0,0 +1,27 @@ +## Why + +实体 DSL 的合法结构、字段类型与 catalog 约束目前散落在 Zod schema、JSON Schema、四个 catalog 与 Agent 提示词中,缺少唯一事实源。贡献者改一处易漏改其他处,生成稳定性也难以治理。E1 阶段(见 `docs/product-design/10-mof-entity-modeling.md` §6.2)需把隐式 M2 显式化为机器可读的元模型,并通过一致性测试与现有实现对齐,**不改变**现有编译与生成行为。 + +## What Changes + +- 新增 `packages/extension/assets/ubml-entity/metamodel/ubml-entity.metamodel.yaml` — 业务实体 M2 元模型(元类、datatype、enum、constraint、doc) +- 新增 `metamodel/README.md` — 维护说明与首期边界 +- 新增 `packages/ubml-codegen/src/entity/metamodel/` — 读取器、TypeScript 类型、提示词片段渲染 +- 新增 `packages/ubml-codegen/test/entity-metamodel-consistency.test.ts` — 与 `datatypes.index.json`、FYBXD 金样、catalog 文件对齐 +- 修改 `packages/extension/assets/ubml-entity/prompts/entitygen.md` — 引入元模型渲染的约束段落(工作流段落仍人工维护) + +## Capabilities + +### New Capabilities + +- `ubml-entity-metamodel`: 显式业务实体元模型资产、读取器、一致性测试与提示词片段渲染 + +### Modified Capabilities + +- (无)E1 仅增加描述性事实源与测试,不修改 `ubml-entity-dsl-compiler` 或 pipeline 的既有行为要求 + +## Impact + +- **包**: `packages/ubml-codegen`(metamodel 模块 + 测试)、`packages/extension`(assets + entitygen 提示词) +- **边界**: 仅 extension + ubml-codegen;不涉及 vendor fork/import +- **回滚**: 删除 `metamodel/` 目录与 `entity/metamodel/` 模块即可恢复;现有 Zod 编译路径不受影响 diff --git a/openspec/changes/archive/2026-07-04-ubml-entity-metamodel/specs/ubml-entity-metamodel/spec.md b/openspec/changes/archive/2026-07-04-ubml-entity-metamodel/specs/ubml-entity-metamodel/spec.md new file mode 100644 index 00000000..8ec50d66 --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-entity-metamodel/specs/ubml-entity-metamodel/spec.md @@ -0,0 +1,67 @@ +## ADDED Requirements + +### Requirement: Explicit entity metamodel asset + +The product SHALL ship a machine-readable business-entity metamodel at `packages/extension/assets/ubml-entity/metamodel/ubml-entity.metamodel.yaml` using the M3 vocabulary (`metamodel`, `class`, `attribute`, `reference`, `enum`, `datatype`, `constraint`). + +The metamodel SHALL declare: + +- Primitive and composite field datatypes aligned with `datatypes.index.json` +- Meta-classes for `BusinessEntity`, `EntityObject`, scalar/enum/association/UDT fields, rules (`Validation`, `Determination`, `Action`), and `ManagerAction` +- Containment vs external references (`containment: true|false`, `external: true` for catalog-backed refs) +- Catalog constraints naming `referenceable-be`, `udt`, and `components` +- Human-readable `doc` on meta-classes and constraints + +#### Scenario: FYBXD exemplar is explainable by metamodel + +- **WHEN** `exemplars/fybxd.be.yaml` is inspected against the metamodel class definitions +- **THEN** every field kind (scalar, enum, association, udt), both child objects, rules (validation, determination, action), and manager actions map to a declared meta-class + +#### Scenario: Metamodel README documents maintenance boundaries + +- **WHEN** a contributor opens `metamodel/README.md` +- **THEN** they find which constraints are enforced by tests vs documentation-only in E1 +- **AND** instructions to keep datatypes in sync with `datatypes.index.json` + +### Requirement: Entity metamodel loader + +`packages/ubml-codegen` SHALL provide a metamodel loader that parses `ubml-entity.metamodel.yaml` into stable TypeScript types (`UbmlMetamodel`, `MetaClass`, `MetaAttribute`, `MetaReference`, `MetaConstraint`, etc.) usable in Node/Bun tests without side effects. + +#### Scenario: Loader parses default asset path + +- **WHEN** the loader reads the bundled metamodel asset +- **THEN** it returns a non-empty `classes` array including `BusinessEntity` and `ScalarField` +- **AND** parsing completes without throwing + +### Requirement: Metamodel consistency tests + +The product SHALL include `packages/ubml-codegen/test/entity-metamodel-consistency.test.ts` verifying: + +1. Metamodel `datatypes` DSL type ids match `datatypes.index.json` `types[].id` +2. FYBXD exemplar elements map to metamodel classes (field kinds, children, rules, manager actions) +3. Catalog constraint names in the metamodel resolve to existing catalog files under `assets/ubml-entity/` + +#### Scenario: Test fails on datatype drift + +- **WHEN** a datatype exists in `datatypes.index.json` but not in the metamodel +- **THEN** the consistency test fails with a message naming the missing type + +#### Scenario: Test fails on missing catalog file + +- **WHEN** the metamodel references catalog `referenceable-be` but the catalog file is absent +- **THEN** the consistency test fails + +### Requirement: Entitygen prompt constraints from metamodel + +The product SHALL render agent-facing constraint fragments from the metamodel (legal field types, catalog rules, constraint summaries) and inject them into `assets/ubml-entity/prompts/entitygen.md`, while leaving workflow/strategy sections human-authored. + +#### Scenario: Rendered fragment lists DSL field types + +- **WHEN** prompt rendering runs against the loaded metamodel +- **THEN** output includes all primitive DSL types from the metamodel datatypes section +- **AND** includes enum, association, and udt as composite kinds + +#### Scenario: Existing entity verify behavior unchanged + +- **WHEN** E1 is complete +- **THEN** `bun run ubml:be:verify packages/extension/assets/ubml-entity/exemplars/fybxd.be.yaml` still passes with no change to compile/verify semantics diff --git a/openspec/changes/archive/2026-07-04-ubml-entity-metamodel/tasks.md b/openspec/changes/archive/2026-07-04-ubml-entity-metamodel/tasks.md new file mode 100644 index 00000000..d06144ba --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-entity-metamodel/tasks.md @@ -0,0 +1,21 @@ +## 1. 元模型资产(M1) + +- [x] 1.1 新增 `ubml-entity.metamodel.yaml`,覆盖 FYBXD 全部建模元素 +- [x] 1.2 新增 `metamodel/README.md`,说明维护边界与 catalog 同步规则 + +## 2. 读取器与类型(M2) + +- [x] 2.1 新增 `entity/metamodel/types.ts` — TypeScript 元模型类型 +- [x] 2.2 新增 `entity/metamodel/load.ts` — YAML 解析与默认 asset 路径 +- [x] 2.3 新增 `entity-metamodel-consistency.test.ts` — datatype / FYBXD / catalog 三类不变量 + +## 3. 提示词渲染(M3) + +- [x] 3.1 新增 `entity/metamodel/prompt.ts` — 渲染合法类型与约束片段 +- [x] 3.2 修改 `entitygen.md` 接入元模型渲染段落(工作流段落保留人工维护) + +## 4. 验证 + +- [x] 4.1 `bun test packages/ubml-codegen/test/entity-metamodel-consistency.test.ts` 通过 +- [x] 4.2 `bun run ubml:entity:verify packages/extension/assets/ubml-entity/exemplars/fybxd.be.yaml` 仍通过 +- [x] 4.3 `packages/ubml-codegen` typecheck 无新增错误 diff --git a/openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/.openspec.yaml b/openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/.openspec.yaml new file mode 100644 index 00000000..d86f1520 --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-07-04 diff --git a/openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/design.md b/openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/design.md new file mode 100644 index 00000000..9b6db14d --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/design.md @@ -0,0 +1,43 @@ +## Context + +`kb_be_edit` 现通过 `applyEntityEdit` 调用 `entity-dsl.ts` 五个低级函数。E1 元模型已声明 Field 子类与 catalog 约束。FYBXD 金样覆盖全部构造类型。 + +## Goals / Non-Goals + +**Goals:** + +- 专用操作在 apply 前校验参数(catalog、length/precision、enum default 等) +- 失败返回 `{ ok: false, issues: [{ message, hint, hints }] }` 而非裸 throw +- MCP 单入口 `kb_be_edit`,op 枚举扩展 +- 冒烟:从最小实体逐步 edit 到 FYBXD 核心结构 + +**Non-Goals:** + +- 会话内模型状态持久化(Agent 持 YAML) +- 从元模型自动生成 op JSON Schema(远期) +- LLM eval 自动化调用(首期仅任务集 + 指标骨架) + +## Decisions + +### 1. 注册表模式 + +`operations.ts` 注册 `Record`;legacy op 委托现有 `entity-dsl.ts`;新 op 在独立文件实现 validate + apply。 + +### 2. 校验复用 compile context + +操作校验使用 `EntityCompileContext` + `datatypes.index.json`,与 DSL parse 语义一致;hint 文案参考 verify-hints 风格。 + +### 3. addField 保留 + +`addField` 仍接受任意 FieldDsl;新 op 为推荐路径,参数更小、错误更准。 + +### 4. beEdit 返回值 + +成功:`{ ok: true, yaml, dsl, changedPaths }`;失败:`{ ok: false, issues, yaml: input }`。 + +**Upstream sync impact**: none + +## Risks / Trade-offs + +- [op 枚举膨胀] → 文档与 entitygen 表格化;legacy 5 op 保留 +- [duplicate validation] → 操作后仍可选 full verify;首期操作内校验 + 测试覆盖 diff --git a/openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/proposal.md b/openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/proposal.md new file mode 100644 index 00000000..a45591ec --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/proposal.md @@ -0,0 +1,29 @@ +## Why + +E1 已显式化实体元模型,但 `kb_be_edit` 仅有 5 个通用操作,关联/枚举/UDT/管理动作等易错构造缺少单步校验与结构化 hint。E2 按 MOF 反射式 API 思路补齐受控建模操作,支撑「骨架草案 + 受控精修」生成工作流。 + +## What Changes + +- `entity/edit/operations.ts` 操作注册表 + 结构化 `EntityEditResult` +- 新增 9 个专用操作:addScalarField / addEnumField / addAssociationField / addUdtField / addChildObject / addValidation / addDetermination / addBizAction / addManagerAction +- 保留既有 addField / addChild / addRule / patchField / removeField 向后兼容 +- `kb_be_edit` MCP 扩展 op 枚举;`beEdit` 返回 `{ ok, yaml, issues, hints, changedPaths }` +- `entity-edit-operations.test.ts` + `be-modeling-ops-smoke.ts` +- `entitygen.md` 明确骨架 + 受控精修工作流 +- 评测集 `entity-generation-tasks.json` + `eval-entity-generation-core.ts`(基线) + +## Capabilities + +### New Capabilities + +- `ubml-entity-modeling-ops`: BE 受控建模操作集、MCP 集成与生成评测基线 + +### Modified Capabilities + +- `ubml-entity-agent`: entitygen 工作流增加受控精修操作说明 + +## Impact + +- **包**: `packages/ubml-codegen`, `packages/knowledge`, `packages/extension`(prompt) +- **边界**: extension + ubml-codegen + knowledge;无 vendor 变更 +- **回滚**: 移除新 op 文件,MCP 恢复旧 enum 即可 diff --git a/openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/specs/ubml-entity-agent/spec.md b/openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/specs/ubml-entity-agent/spec.md new file mode 100644 index 00000000..6c4779e4 --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/specs/ubml-entity-agent/spec.md @@ -0,0 +1,18 @@ +## MODIFIED Requirements + +### Requirement: Entity generation agent workflow prompt + +The product SHALL ship `assets/ubml-entity/prompts/entitygen.md` describing the DSL-first BE workflow: retrieve exemplars via `kb_be_exemplar`, consult datatype/UDT/ref/component catalogs via MCP, produce `entity:` YAML DSL (not raw GspMetadata JSON), verify with `kb_be_verify`, and repair up to 3 rounds using structured hints; optional incremental edits via `kb_be_edit`. + +The workflow SHALL document a two-phase pattern: (1) skeleton draft with scalar fields and children via YAML, immediate `kb_be_verify`; (2) controlled refinement via typed `kb_be_edit` operations (`addAssociationField`, `addEnumField`, `addUdtField`, rules, manager actions) for catalog-sensitive constructs. + +#### Scenario: Prompt mentions typed edit operations + +- **WHEN** a contributor reads `entitygen.md` +- **THEN** it lists typed `kb_be_edit` operations for association, enum, UDT, and manager actions +- **AND** describes skeleton-then-refine sequencing + +#### Scenario: Agent consumes hint field + +- **WHEN** verify result is formatted for agent prompt via `formatVerifyIssuesForAgent` +- **THEN** output includes a hint column suitable for repair loops diff --git a/openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/specs/ubml-entity-modeling-ops/spec.md b/openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/specs/ubml-entity-modeling-ops/spec.md new file mode 100644 index 00000000..5f26e7ff --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/specs/ubml-entity-modeling-ops/spec.md @@ -0,0 +1,48 @@ +## ADDED Requirements + +### Requirement: Typed entity modeling edit operations + +The product SHALL extend `kb_be_edit` / `applyEntityEdit` with dedicated operations: + +- `addScalarField`, `addEnumField`, `addAssociationField`, `addUdtField` +- `addChildObject`, `addValidation`, `addDetermination`, `addBizAction`, `addManagerAction` + +Each operation SHALL validate arguments against entity compile context (datatypes, catalogs, field existence) before mutating DSL. + +Legacy operations (`addField`, `addChild`, `addRule`, `patchField`, `removeField`) SHALL remain supported. + +#### Scenario: addAssociationField rejects unknown ref with hints + +- **WHEN** `addAssociationField` is called with `ref: NoSuchBe` +- **THEN** result has `ok: false` +- **AND** issues include a message naming the unknown ref +- **AND** issues include hints listing valid catalog BE codes + +#### Scenario: addScalarField requires decimal precision + +- **WHEN** `addScalarField` is called with `type: decimal` without `precision` +- **THEN** result has `ok: false` with a hint about required precision + +#### Scenario: addChildObject rejects duplicate child code + +- **WHEN** `addChildObject` is called with a child code already present on root +- **THEN** result has `ok: false` naming the duplicate code + +### Requirement: Structured entity edit results + +Entity edit APIs SHALL return `{ ok, yaml, issues?, changedPaths?, dsl? }` where failed edits preserve input YAML and populate structured `issues` with optional Chinese `hint` fields. + +#### Scenario: Successful edit returns updated yaml + +- **WHEN** a valid `addUdtField` completes +- **THEN** `ok` is true and `yaml` contains the new field +- **AND** `changedPaths` identifies the affected object + +### Requirement: Entity generation eval baseline + +The product SHALL ship `packages/knowledge/eval/entity-generation-tasks.json` with at least 10 Chinese requirements and `eval-entity-generation-core.ts` reporting verify pass rate metrics. + +#### Scenario: Eval runner loads task file + +- **WHEN** eval script runs with default paths +- **THEN** it loads at least 10 tasks and prints summary metrics structure diff --git a/openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/tasks.md b/openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/tasks.md new file mode 100644 index 00000000..ffaf4eaa --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-entity-modeling-ops/tasks.md @@ -0,0 +1,32 @@ +## 1. 操作注册表与结果类型 + +- [x] 1.1 新增 `entity/edit/types.ts` — EntityEditResult / EntityEditIssue +- [x] 1.2 新增 `entity/edit/operations.ts` — 注册表 + `applyEntityEditOp` +- [x] 1.3 扩展 `entity-dsl.ts` — addManagerAction;legacy op 接入注册表 + +## 2. 字段类操作(M4) + +- [x] 2.1 addScalarField / addEnumField / addAssociationField / addUdtField +- [x] 2.2 addChildObject(含 no-grandchildren 校验) +- [x] 2.3 entity-edit-operations.test.ts + +## 3. 规则与管理动作(M5) + +- [x] 3.1 addValidation / addDetermination / addBizAction / addManagerAction +- [x] 3.2 测试覆盖 catalog 失败 hint + +## 4. MCP 与冒烟 + +- [x] 4.1 扩展 `kb_be_edit` op 枚举与 beEdit 结构化返回 +- [x] 4.2 be-modeling-ops-smoke.ts + +## 5. 工作流与评测(M6) + +- [x] 5.1 entitygen.md 骨架 + 受控精修段落 +- [x] 5.2 entity-generation-tasks.json + eval-entity-generation-core.ts + +## 6. 验证 + +- [x] 6.1 `bun test packages/ubml-codegen/test/entity-edit-operations.test.ts` +- [x] 6.2 `bun packages/knowledge/script/be-modeling-ops-smoke.ts` +- [x] 6.3 FYBXD verify 仍通过 diff --git a/openspec/changes/archive/2026-07-04-ubml-model-right-panel/.openspec.yaml b/openspec/changes/archive/2026-07-04-ubml-model-right-panel/.openspec.yaml new file mode 100644 index 00000000..d86f1520 --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-model-right-panel/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-07-04 diff --git a/openspec/changes/archive/2026-07-04-ubml-model-right-panel/design.md b/openspec/changes/archive/2026-07-04-ubml-model-right-panel/design.md new file mode 100644 index 00000000..8f8a175b --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-model-right-panel/design.md @@ -0,0 +1,17 @@ +## Context + +已有 `entity-preview` 多 tab(按 key)与 `EntityPreviewContent`。E3 采用**一个常驻 model tab + 内部导航**,避免右栏被多个实体页签挤满。 + +## Goals / Non-Goals + +**Goals:** 只读可视化;复用 `buildEntityPreviewFromDsl` / `EntityPreviewContent`;FYBXD 预览后可见 ER 图与 YAML。 + +**Non-Goals:** 图上编辑;工程全量扫描;多实体全景聚合(首版单实体 + 外部关联)。 + +## Decisions + +- 单 `model` tab 实例,entries 按 key upsert +- 视图模式:`graph` | `file`(YAML 只读 pre) +- `openUbmlModelTab()` 与 `openUbmlEntityPreviewTab()` 并行调用,不替换 entity-preview + +**Upstream sync impact**: none diff --git a/openspec/changes/archive/2026-07-04-ubml-model-right-panel/proposal.md b/openspec/changes/archive/2026-07-04-ubml-model-right-panel/proposal.md new file mode 100644 index 00000000..ce9167af --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-model-right-panel/proposal.md @@ -0,0 +1,26 @@ +## Why + +E1/E2 已建立元模型与受控建模操作,但生成结果仍以 YAML 文本或独立「实体预览」页签呈现,缺少统一的「模型空间」视图。E3 在右侧栏增加常驻「模型」页签,展示实体导航、ER 关系图与文件视图,并在 preview 成功时自动联动。 + +## What Changes + +- 右侧栏 `model` 页签类型(单实例 + 内部多模型导航) +- `ubml-model-tab-store.ts` / `ubml-model-bridge.ts` / `model-preview-model.ts` +- `ubml-model-panel.tsx` — 左导航 + 关系图/文件双视图 +- `previewEntityYaml()` 成功后刷新模型页签 +- `model-preview-model.test.ts` + +## Capabilities + +### New Capabilities + +- `ubml-model-right-panel`: 模型页签 UI 与生成/预览联动 + +### Modified Capabilities + +- `ubml-entity-workbench`: 预览成功后打开模型页签 + +## Impact + +- **包**: `packages/extension/` only +- **回滚**: 移除 model tab 类型与 bridge 调用即可 diff --git a/openspec/changes/archive/2026-07-04-ubml-model-right-panel/specs/ubml-model-right-panel/spec.md b/openspec/changes/archive/2026-07-04-ubml-model-right-panel/specs/ubml-model-right-panel/spec.md new file mode 100644 index 00000000..ce03f9e0 --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-model-right-panel/specs/ubml-model-right-panel/spec.md @@ -0,0 +1,28 @@ +## ADDED Requirements + +### Requirement: Right panel model tab + +The extension SHALL register a `model` right-panel tab type (single instance) with localized label 「模型」/ Model. + +The tab SHALL show a two-column layout: model navigation (grouped by kind, starting with entity) and a content area with graph/file view toggle. + +#### Scenario: User adds model tab from panel menu + +- **WHEN** user adds the model tab from the right panel + menu +- **THEN** an empty state prompts that models appear after entity preview or generation + +#### Scenario: FYBXD preview populates navigation + +- **WHEN** entity preview succeeds for FYBXD exemplar +- **THEN** the model tab lists FYBXD under entity navigation +- **AND** graph view shows containment (FYBXDMX, FXBXDFJ) and association refs (GspUser, SysOrg, Projects) via Mermaid ER output + +#### Scenario: File view shows YAML + +- **WHEN** user switches to file view on a selected model entry +- **THEN** the original YAML text is shown read-only + +#### Scenario: Closing model tab clears payload + +- **WHEN** user closes the model tab +- **THEN** model tab store entries are cleared without affecting entity-preview tabs diff --git a/openspec/changes/archive/2026-07-04-ubml-model-right-panel/tasks.md b/openspec/changes/archive/2026-07-04-ubml-model-right-panel/tasks.md new file mode 100644 index 00000000..0c1d2e41 --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-model-right-panel/tasks.md @@ -0,0 +1,20 @@ +## 1. Tab shell(M7) + +- [x] 1.1 `right-panel.tsx` / `right-panel-labels.ts` 增加 `model` 类型 +- [x] 1.2 `ubml-model-tab-store.ts` + `ubml-model-bridge.ts` +- [x] 1.3 `project-workspace-shell.tsx` 挂载与 cleanup + +## 2. 导航与 ER 图(M8) + +- [x] 2.1 `model-preview-model.ts` + test +- [x] 2.2 `ubml-model-panel.tsx`(导航 + graph/file 视图) + +## 3. 生成联动(M9) + +- [x] 3.1 `ubml-entity-artifact-actions.ts` 调用 `openUbmlModelTab` +- [x] 3.2 `right-panel-preview-utils.ts` 加号菜单支持 model + +## 4. 验证 + +- [x] 4.1 `bun test packages/extension/src/lib/model-preview-model.test.ts` +- [x] 4.2 extension typecheck(model 相关文件无新增错误) diff --git a/openspec/changes/archive/2026-07-04-ubml-model-tab-pages/.openspec.yaml b/openspec/changes/archive/2026-07-04-ubml-model-tab-pages/.openspec.yaml new file mode 100644 index 00000000..d86f1520 --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-model-tab-pages/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-07-04 diff --git a/openspec/changes/archive/2026-07-04-ubml-model-tab-pages/proposal.md b/openspec/changes/archive/2026-07-04-ubml-model-tab-pages/proposal.md new file mode 100644 index 00000000..423363b3 --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-model-tab-pages/proposal.md @@ -0,0 +1,25 @@ +## Why + +E3 模型页签已支持实体 ER 图;E4 页面元模型已就绪。产品设计中模型导航应分组展示 `be/` 与 `pages/`。本变更将页面预览接入同一模型空间,完成 MoF v0 可视化闭环的页面侧扩展。 + +## What Changes + +- `PagePreviewModel` + `buildPagePreviewFromDsl` +- `UbmlModelEntry` 扩展 `kind: page` +- `previewUbmlYaml()` 成功后 `openUbmlModelTab` +- 模型页签左导航增加「页面 pages/」分组;页面结构视图(字段绑定表) + +## Capabilities + +### New Capabilities + +- `ubml-model-tab-pages`: 模型页签页面导航与预览联动 + +### Modified Capabilities + +- (无)不改变 page verify / compile 行为 + +## Impact + +- **包**: `packages/extension/` only +- **回滚**: 移除 page kind 与 bridge 调用即可 diff --git a/openspec/changes/archive/2026-07-04-ubml-model-tab-pages/specs/ubml-model-tab-pages/spec.md b/openspec/changes/archive/2026-07-04-ubml-model-tab-pages/specs/ubml-model-tab-pages/spec.md new file mode 100644 index 00000000..098061ac --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-model-tab-pages/specs/ubml-model-tab-pages/spec.md @@ -0,0 +1,16 @@ +## ADDED Requirements + +### Requirement: Model tab page navigation + +The model tab SHALL support `page` entries alongside `entity` entries, grouped under a「页面 pages/」navigation label. + +#### Scenario: Page preview adds navigation item + +- **WHEN** `previewUbmlYaml` succeeds for a card page +- **THEN** the model tab lists the page under pages navigation +- **AND** structure view shows page type, entity binding, and field bindings + +#### Scenario: Entity entries unchanged + +- **WHEN** `previewEntityYaml` succeeds +- **THEN** entity entries still appear under「实体 be/」with ER graph view diff --git a/openspec/changes/archive/2026-07-04-ubml-model-tab-pages/tasks.md b/openspec/changes/archive/2026-07-04-ubml-model-tab-pages/tasks.md new file mode 100644 index 00000000..92c2f892 --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-model-tab-pages/tasks.md @@ -0,0 +1,19 @@ +## 1. 页面预览模型 + +- [x] 1.1 `page-preview-model.ts` + `PagePreviewContent` +- [x] 1.2 扩展 `UbmlModelEntry` 支持 `kind: page` + +## 2. 模型页签 UI + +- [x] 2.1 更新 `model-preview-model.ts` 导航分组 +- [x] 2.2 更新 `ubml-model-panel.tsx` 按 kind 渲染 + +## 3. 联动 + +- [x] 3.1 `previewUbmlYaml` → `openUbmlModelTab` +- [x] 3.2 `ubml-model-bridge` 接受 page/entity 两种 payload + +## 4. 验证 + +- [x] 4.1 `model-preview-model.test.ts` 覆盖 page 导航 +- [x] 4.2 extension 相关测试通过 diff --git a/openspec/changes/archive/2026-07-04-ubml-page-metamodel/.openspec.yaml b/openspec/changes/archive/2026-07-04-ubml-page-metamodel/.openspec.yaml new file mode 100644 index 00000000..d86f1520 --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-page-metamodel/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-07-04 diff --git a/openspec/changes/archive/2026-07-04-ubml-page-metamodel/design.md b/openspec/changes/archive/2026-07-04-ubml-page-metamodel/design.md new file mode 100644 index 00000000..8694f658 --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-page-metamodel/design.md @@ -0,0 +1,66 @@ +## Context + +实体 E1 已交付 `ubml-entity.metamodel.yaml` + `entity/metamodel/` + 一致性测试。页面 DSL 位于 `packages/ubml-codegen/src/dsl/schema.ts`,金样 `fybxdj-card.yaml`,editor 值域来自 `assets/ubml/components.index.json`。AppGen crossref 在 `packages/ubml-codegen/src/app/validate/crossref.ts` 手写校验 entity/page/pageflow 引用。 + +产品设计 §3.4 已给出页面元模型骨架;§6.5 明确 E4 只复用「元模型描述 + 模型页签导航」两件验证过的东西,**不**一次性元模型驱动编译器。 + +## Goals / Non-Goals + +**Goals:** + +- `ubml-page.metamodel.yaml` 覆盖 FYBXDJ card 金样的 Page / form Section / FieldBinding / toolbar / raw 逃逸 +- 读取器 + 一致性测试(page type、editor catalog、entity external ref、金样元素映射) +- 文档化 generic external reference 模型,说明如何收敛 crossref +- `pagegen.md` 约束段落由元模型渲染 + +**Non-Goals:** + +- 不从元模型生成 page Zod 或 Farris JSON Schema +- 不替换或重写 `crossref.ts`(仅设计文档 + 测试对齐) +- 不扩展模型页签 UI 至页面导航(留待 E4 之后独立变更) +- 不实现 page 建模操作集(类似 E2 entity ops) + +## Decisions + +### 1. 元模型资产位置 + +`packages/extension/assets/ubml/metamodel/`,与 page exemplar、catalog 同包。 + +### 2. 最小元类集 + +| 元类 | 覆盖 | +|------|------| +| Page | type, title, entity (external), sections, toolbar | +| Section | form / content-container / data-grid / list-nav / filter-bar / raw | +| FieldBinding | label, bind (external→entity field), editor (catalog) | +| ToolbarAction | toolbar 字符串项 | +| RawNode | `type: raw` + opaque payload | + +### 3. External 引用统一描述 + +元模型 `reference.external: true` + `constraint` 文档化三类 crossref: + +- Page.entity → BusinessEntity +- FieldBinding.bind → EntityField +- (远期)Pageflow → Page、Query → Entity + +首期 `constraint.check` 为人读规约;执行仍由 Zod + crossref.ts。 + +### 4. 与 entity 元模型共享 M3 词汇 + +不新建 M3 构造;页面元模型是 M3 的第二个 M2 实例,证明元语言领域无关。 + +### 5. 提示词渲染 + +`page/metamodel/prompt.ts` 渲染合法 page type、editor catalog 约束;`pagegen.md` 使用 `` 标记段。 + +**Upstream sync impact**: none + +## Risks / Trade-offs + +- [页面 section 类型比设计骨架更复杂] → 元模型用 abstract Section + 子类或 doc 标注实际 DSL discriminated union;测试以 Zod schema 为 ground truth +- [crossref 文档与代码脱节] → 文档引用现有 crossref.ts 函数清单;变 crossref 时同步更新文档与元模型 constraint + +## Open Questions + +- `content-container` 嵌套 section 是否在 E4 元模型显式建模 — 首期用 Section 子类 + doc,测试覆盖 FYBXDJ 金样即可 diff --git a/openspec/changes/archive/2026-07-04-ubml-page-metamodel/proposal.md b/openspec/changes/archive/2026-07-04-ubml-page-metamodel/proposal.md new file mode 100644 index 00000000..4901e500 --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-page-metamodel/proposal.md @@ -0,0 +1,28 @@ +## Why + +E1–E3 已在实体域走通 MOF 元模型、受控操作与模型页签。页面 DSL 的合法结构同样散落在 Zod schema、`components.index.json`、crossref 手写校验与 `pagegen.md` 中。E4(`10-mof-entity-modeling.md` §6.5 / M10)在**不改造页面编译器**前提下,显式化页面 M2 元模型草案,并定义通用 external 引用概念,为后续 crossref 抽象与模型页签扩展页面导航打地基。 + +## What Changes + +- 新增 `packages/extension/assets/ubml/metamodel/ubml-page.metamodel.yaml` — 页面 M2 元模型(Page / Section / FieldBinding / Action / RawNode 最小集) +- 新增 `metamodel/README.md` — 维护边界与 catalog 同步说明 +- 新增 `packages/ubml-codegen/src/page/metamodel/` — 读取器、类型、提示词片段渲染(复用 E1 模式) +- 新增 `packages/ubml-codegen/test/page-metamodel-consistency.test.ts` — 与 page DSL schema、FYBXDJ 金样、`components.index.json` 对齐 +- 新增 `docs/UBML-EXTERNAL-REFERENCE.md` — 通用 external 引用与 crossref 演进设计(文档规约,首期不替换 `crossref.ts`) +- 修改 `packages/extension/assets/ubml/prompts/pagegen.md` — 接入元模型渲染的约束段落(工作流仍人工维护) + +## Capabilities + +### New Capabilities + +- `ubml-page-metamodel`: 显式页面元模型资产、读取器、一致性测试、external 引用设计文档 + +### Modified Capabilities + +- (无)E4 为描述性事实源 + 测试 + 设计文档,不改变 `ubml-page-pipeline` 或 AppGen crossref 运行时行为 + +## Impact + +- **包**: `packages/ubml-codegen`(page/metamodel + 测试)、`packages/extension`(assets + pagegen 提示词)、`docs/` +- **边界**: extension + ubml-codegen + docs;无 vendor fork/import +- **回滚**: 删除 page metamodel 目录与测试即可;现有 page verify/compile 不受影响 diff --git a/openspec/changes/archive/2026-07-04-ubml-page-metamodel/specs/ubml-page-metamodel/spec.md b/openspec/changes/archive/2026-07-04-ubml-page-metamodel/specs/ubml-page-metamodel/spec.md new file mode 100644 index 00000000..22f97b84 --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-page-metamodel/specs/ubml-page-metamodel/spec.md @@ -0,0 +1,69 @@ +## ADDED Requirements + +### Requirement: Explicit page metamodel asset + +The product SHALL ship a machine-readable page metamodel at `packages/extension/assets/ubml/metamodel/ubml-page.metamodel.yaml` using the same M3 vocabulary as the entity metamodel. + +The metamodel SHALL declare meta-classes for Page, Section variants (including form and raw), FieldBinding, and toolbar actions, with: + +- `Page.entity` as external reference to BusinessEntity +- `FieldBinding.bind` as external reference to entity fields +- `FieldBinding.editor` constrained against `components.index.json` +- `RawNode` for `type: raw` escape sections without parsing inner structure +- Human-readable `doc` and `constraint.check` fields (documentation-first in E4) + +#### Scenario: FYBXDJ card exemplar is explainable by metamodel + +- **WHEN** `exemplars/fybxdj-card.yaml` is inspected against metamodel class definitions +- **THEN** page type, title, entity binding, toolbar actions, form sections, and field bindings map to declared meta-classes + +#### Scenario: Metamodel README documents E4 boundaries + +- **WHEN** a contributor opens `assets/ubml/metamodel/README.md` +- **THEN** they find that constraints are documentation-first and crossref.ts is not replaced in E4 + +### Requirement: Page metamodel loader + +`packages/ubml-codegen` SHALL provide a page metamodel loader parsing `ubml-page.metamodel.yaml` into stable TypeScript types, usable in Bun tests without side effects. + +#### Scenario: Loader parses default asset path + +- **WHEN** the loader reads the bundled page metamodel asset +- **THEN** it returns classes including Page and FieldBinding +- **AND** parsing completes without throwing + +### Requirement: Page metamodel consistency tests + +The product SHALL include `packages/ubml-codegen/test/page-metamodel-consistency.test.ts` verifying: + +1. Metamodel-declared page types and editor constraints align with page DSL schema and `components.index.json` +2. FYBXDJ card exemplar elements map to metamodel classes +3. Catalog/constraint names in the metamodel resolve to existing asset files under `assets/ubml/` + +#### Scenario: Test fails on editor catalog drift + +- **WHEN** the metamodel references editor catalog constraints but `components.index.json` is missing +- **THEN** the consistency test fails + +### Requirement: Generic external reference design document + +The product SHALL document the generic external reference model in `docs/UBML-EXTERNAL-REFERENCE.md`, describing how Page.entity, FieldBinding.bind, and future cross-model refs relate to metamodel `external: true` references and the evolution path from handwritten AppGen crossref checks. + +#### Scenario: Document lists current crossref coverage + +- **WHEN** a contributor reads `docs/UBML-EXTERNAL-REFERENCE.md` +- **THEN** they find mappings from metamodel external refs to existing crossref validation in `crossref.ts` + +### Requirement: Pagegen prompt constraints from metamodel + +The product SHALL render agent-facing page constraint fragments from the page metamodel and inject them into `assets/ubml/prompts/pagegen.md`, while leaving workflow/strategy sections human-authored. + +#### Scenario: Rendered fragment lists page types and editor rules + +- **WHEN** prompt rendering runs against the loaded page metamodel +- **THEN** output includes page type constraints and editor catalog guidance + +#### Scenario: Existing page verify behavior unchanged + +- **WHEN** E4 is complete +- **THEN** `bun run ubml:verify packages/extension/assets/ubml/exemplars/fybxdj-card.yaml` still passes with no change to compile/verify semantics diff --git a/openspec/changes/archive/2026-07-04-ubml-page-metamodel/tasks.md b/openspec/changes/archive/2026-07-04-ubml-page-metamodel/tasks.md new file mode 100644 index 00000000..16241e8e --- /dev/null +++ b/openspec/changes/archive/2026-07-04-ubml-page-metamodel/tasks.md @@ -0,0 +1,24 @@ +## 1. 页面元模型资产(M10-a) + +- [x] 1.1 新增 `ubml-page.metamodel.yaml`,覆盖 FYBXDJ card 金样 +- [x] 1.2 新增 `assets/ubml/metamodel/README.md` + +## 2. 读取器与一致性测试(M10-b) + +- [x] 2.1 新增 `page/metamodel/types.ts` + `load.ts` +- [x] 2.2 新增 `page-metamodel-consistency.test.ts`(schema / 金样 / components.index) + +## 3. External 引用设计(M10-c) + +- [x] 3.1 新增 `docs/UBML-EXTERNAL-REFERENCE.md`(crossref 演进路径,不替换 crossref.ts) + +## 4. 提示词渲染(M10-d) + +- [x] 4.1 新增 `page/metamodel/prompt.ts` + `syncPagegenMetamodelSection()` +- [x] 4.2 修改 `pagegen.md` 接入元模型约束段 + +## 5. 验证 + +- [x] 5.1 `bun test packages/ubml-codegen/test/page-metamodel-consistency.test.ts` +- [x] 5.2 FYBXDJ card verify 仍通过 +- [x] 5.3 `packages/ubml-codegen` 无新增 typecheck 错误(本变更范围) diff --git a/openspec/specs/ubml-entity-agent/spec.md b/openspec/specs/ubml-entity-agent/spec.md index f757c9c0..f8cd0d73 100644 --- a/openspec/specs/ubml-entity-agent/spec.md +++ b/openspec/specs/ubml-entity-agent/spec.md @@ -8,12 +8,25 @@ UBML 业务实体生成 Agent 指引、`ubml-entitygen` agent 资产、派发 pr The product SHALL ship `assets/ubml-entity/prompts/entitygen.md` describing the DSL-first BE workflow: retrieve exemplars via `kb_be_exemplar`, consult datatype/UDT/ref/component catalogs via MCP, produce `entity:` YAML DSL (not raw GspMetadata JSON), verify with `kb_be_verify`, and repair up to 3 rounds using structured hints; optional incremental edits via `kb_be_edit`. +The workflow SHALL document a two-phase pattern: (1) skeleton draft with scalar fields and children via YAML, immediate `kb_be_verify`; (2) controlled refinement via typed `kb_be_edit` operations (`addAssociationField`, `addEnumField`, `addUdtField`, rules, manager actions) for catalog-sensitive constructs. + #### Scenario: Prompt lists required BE MCP tools - **WHEN** a developer loads the entitygen prompt via `getUbmlEntityPrompt("entitygen")` - **THEN** the content references `kb_be_exemplar`, `kb_be_datatype`, `kb_be_udt`, `kb_be_ref`, `kb_be_component`, `kb_be_verify`, and optional `kb_be_edit` - **AND** instructs the agent to output YAML with root key `entity:` only +#### Scenario: Prompt mentions typed edit operations + +- **WHEN** a contributor reads `entitygen.md` +- **THEN** it lists typed `kb_be_edit` operations for association, enum, UDT, and manager actions +- **AND** describes skeleton-then-refine sequencing + +#### Scenario: Agent consumes hint field + +- **WHEN** verify result is formatted for agent prompt via `formatVerifyIssuesForAgent` +- **THEN** output includes a hint column suitable for repair loops + ### Requirement: UBML entitygen agent definition The product SHALL ship `assets/ubml-entity/agents/ubml-entitygen.md` as an installable OpenCode agent (subagent mode) whose body embeds the entitygen workflow. diff --git a/openspec/specs/ubml-entity-metamodel/spec.md b/openspec/specs/ubml-entity-metamodel/spec.md new file mode 100644 index 00000000..e6e3d955 --- /dev/null +++ b/openspec/specs/ubml-entity-metamodel/spec.md @@ -0,0 +1,71 @@ +# ubml-entity-metamodel + +显式业务实体 M2 元模型资产、读取器、一致性测试与 entitygen 提示词约束渲染。位于 `packages/extension/assets/ubml-entity/metamodel/` 与 `packages/ubml-codegen/src/entity/metamodel/`。 + +## Requirements + +### Requirement: Explicit entity metamodel asset + +The product SHALL ship a machine-readable business-entity metamodel at `packages/extension/assets/ubml-entity/metamodel/ubml-entity.metamodel.yaml` using the M3 vocabulary (`metamodel`, `class`, `attribute`, `reference`, `enum`, `datatype`, `constraint`). + +The metamodel SHALL declare: + +- Primitive and composite field datatypes aligned with `datatypes.index.json` +- Meta-classes for `BusinessEntity`, `EntityObject`, scalar/enum/association/UDT fields, rules (`Validation`, `Determination`, `Action`), and `ManagerAction` +- Containment vs external references (`containment: true|false`, `external: true` for catalog-backed refs) +- Catalog constraints naming `referenceable-be`, `udt`, and `components` +- Human-readable `doc` on meta-classes and constraints + +#### Scenario: FYBXD exemplar is explainable by metamodel + +- **WHEN** `exemplars/fybxd.be.yaml` is inspected against the metamodel class definitions +- **THEN** every field kind (scalar, enum, association, udt), both child objects, rules (validation, determination, action), and manager actions map to a declared meta-class + +#### Scenario: Metamodel README documents maintenance boundaries + +- **WHEN** a contributor opens `metamodel/README.md` +- **THEN** they find which constraints are enforced by tests vs documentation-only in E1 +- **AND** instructions to keep datatypes in sync with `datatypes.index.json` + +### Requirement: Entity metamodel loader + +`packages/ubml-codegen` SHALL provide a metamodel loader that parses `ubml-entity.metamodel.yaml` into stable TypeScript types (`UbmlMetamodel`, `MetaClass`, `MetaAttribute`, `MetaReference`, `MetaConstraint`, etc.) usable in Node/Bun tests without side effects. + +#### Scenario: Loader parses default asset path + +- **WHEN** the loader reads the bundled metamodel asset +- **THEN** it returns a non-empty `classes` array including `BusinessEntity` and `ScalarField` +- **AND** parsing completes without throwing + +### Requirement: Metamodel consistency tests + +The product SHALL include `packages/ubml-codegen/test/entity-metamodel-consistency.test.ts` verifying: + +1. Metamodel `datatypes` DSL type ids match `datatypes.index.json` `types[].id` +2. FYBXD exemplar elements map to metamodel classes (field kinds, children, rules, manager actions) +3. Catalog constraint names in the metamodel resolve to existing catalog files under `assets/ubml-entity/` + +#### Scenario: Test fails on datatype drift + +- **WHEN** a datatype exists in `datatypes.index.json` but not in the metamodel +- **THEN** the consistency test fails with a message naming the missing type + +#### Scenario: Test fails on missing catalog file + +- **WHEN** the metamodel references catalog `referenceable-be` but the catalog file is absent +- **THEN** the consistency test fails + +### Requirement: Entitygen prompt constraints from metamodel + +The product SHALL render agent-facing constraint fragments from the metamodel (legal field types, catalog rules, constraint summaries) and inject them into `assets/ubml-entity/prompts/entitygen.md`, while leaving workflow/strategy sections human-authored. + +#### Scenario: Rendered fragment lists DSL field types + +- **WHEN** prompt rendering runs against the loaded metamodel +- **THEN** output includes all primitive DSL types from the metamodel datatypes section +- **AND** includes enum, association, and udt as composite kinds + +#### Scenario: Existing entity verify behavior unchanged + +- **WHEN** E1 is complete +- **THEN** `bun run ubml:entity:verify packages/extension/assets/ubml-entity/exemplars/fybxd.be.yaml` still passes with no change to compile/verify semantics diff --git a/openspec/specs/ubml-entity-modeling-ops/spec.md b/openspec/specs/ubml-entity-modeling-ops/spec.md new file mode 100644 index 00000000..204c90f6 --- /dev/null +++ b/openspec/specs/ubml-entity-modeling-ops/spec.md @@ -0,0 +1,52 @@ +# ubml-entity-modeling-ops + +业务实体受控建模操作集、`kb_be_edit` MCP 扩展与实体生成评测基线。位于 `packages/ubml-codegen/src/entity/edit/` 与 `packages/knowledge/eval/`。 + +## Requirements + +### Requirement: Typed entity modeling edit operations + +The product SHALL extend `kb_be_edit` / `runEntityEdit` with dedicated operations: + +- `addScalarField`, `addEnumField`, `addAssociationField`, `addUdtField` +- `addChildObject`, `addValidation`, `addDetermination`, `addBizAction`, `addManagerAction` + +Each operation SHALL validate arguments against entity compile context (datatypes, catalogs, field existence) before mutating DSL. + +Legacy operations (`addField`, `addChild`, `addRule`, `patchField`, `removeField`) SHALL remain supported. + +#### Scenario: addAssociationField rejects unknown ref with hints + +- **WHEN** `addAssociationField` is called with `ref: NoSuchBe` +- **THEN** result has `ok: false` +- **AND** issues include a message naming the unknown ref +- **AND** issues include hints listing valid catalog BE codes + +#### Scenario: addScalarField requires decimal precision + +- **WHEN** `addScalarField` is called with `type: decimal` without `precision` +- **THEN** result has `ok: false` with a hint about required precision + +#### Scenario: addChildObject rejects duplicate child code + +- **WHEN** `addChildObject` is called with a child code already present on root +- **THEN** result has `ok: false` naming the duplicate code + +### Requirement: Structured entity edit results + +Entity edit APIs SHALL return `{ ok, yaml, issues?, changedPaths?, dsl? }` where failed edits preserve input YAML and populate structured `issues` with optional Chinese `hint` fields. + +#### Scenario: Successful edit returns updated yaml + +- **WHEN** a valid `addUdtField` completes +- **THEN** `ok` is true and `yaml` contains the new field +- **AND** `changedPaths` identifies the affected object + +### Requirement: Entity generation eval baseline + +The product SHALL ship `packages/knowledge/eval/entity-generation-tasks.json` with at least 10 Chinese requirements and `eval-entity-generation-core.ts` reporting verify pass rate metrics. + +#### Scenario: Eval runner loads task file + +- **WHEN** eval script runs with default paths +- **THEN** it loads at least 10 tasks and prints summary metrics structure diff --git a/openspec/specs/ubml-model-right-panel/spec.md b/openspec/specs/ubml-model-right-panel/spec.md new file mode 100644 index 00000000..9b43b9d3 --- /dev/null +++ b/openspec/specs/ubml-model-right-panel/spec.md @@ -0,0 +1,32 @@ +# ubml-model-right-panel + +对话面板右侧「模型」页签:多模型导航、实体 ER 图/文件双视图、生成与预览联动。位于 `packages/extension/src/lib/ubml-model-*` 与 `pages/workbench/ubml-model-panel.tsx`。 + +## Requirements + +### Requirement: Right panel model tab + +The extension SHALL register a `model` right-panel tab type (single instance) with localized label 「模型」/ Model. + +The tab SHALL show a two-column layout: model navigation (grouped by kind, starting with entity) and a content area with graph/file view toggle. + +#### Scenario: User adds model tab from panel menu + +- **WHEN** user adds the model tab from the right panel + menu +- **THEN** an empty state prompts that models appear after entity preview or generation + +#### Scenario: FYBXD preview populates navigation + +- **WHEN** entity preview succeeds for FYBXD exemplar +- **THEN** the model tab lists FYBXD under entity navigation +- **AND** graph view shows containment (FYBXDMX, FXBXDFJ) and association refs (GspUser, SysOrg, Projects) via Mermaid ER output + +#### Scenario: File view shows YAML + +- **WHEN** user switches to file view on a selected model entry +- **THEN** the original YAML text is shown read-only + +#### Scenario: Closing model tab clears payload + +- **WHEN** user closes the model tab +- **THEN** model tab store entries are cleared without affecting entity-preview tabs diff --git a/openspec/specs/ubml-model-tab-pages/spec.md b/openspec/specs/ubml-model-tab-pages/spec.md new file mode 100644 index 00000000..b78c962c --- /dev/null +++ b/openspec/specs/ubml-model-tab-pages/spec.md @@ -0,0 +1,20 @@ +# ubml-model-tab-pages + +模型页签扩展:页面模型导航与结构视图,与实体条目并列。位于 `packages/extension/src/lib/page-preview-model.ts` 与 `components/page-preview-content.tsx`。 + +## Requirements + +### Requirement: Model tab page navigation + +The model tab SHALL support `page` entries alongside `entity` entries, grouped under a「页面 pages/」navigation label. + +#### Scenario: Page preview adds navigation item + +- **WHEN** `previewUbmlYaml` succeeds for a card page +- **THEN** the model tab lists the page under pages navigation +- **AND** structure view shows page type, entity binding, and field bindings + +#### Scenario: Entity entries unchanged + +- **WHEN** `previewEntityYaml` succeeds +- **THEN** entity entries still appear under「实体 be/」with ER graph view diff --git a/openspec/specs/ubml-page-metamodel/spec.md b/openspec/specs/ubml-page-metamodel/spec.md new file mode 100644 index 00000000..8c21efa6 --- /dev/null +++ b/openspec/specs/ubml-page-metamodel/spec.md @@ -0,0 +1,73 @@ +# ubml-page-metamodel + +显式页面 M2 元模型资产、读取器、一致性测试、external 引用设计文档与 pagegen 提示词约束渲染。位于 `packages/extension/assets/ubml/metamodel/` 与 `packages/ubml-codegen/src/page/metamodel/`。 + +## Requirements + +### Requirement: Explicit page metamodel asset + +The product SHALL ship a machine-readable page metamodel at `packages/extension/assets/ubml/metamodel/ubml-page.metamodel.yaml` using the same M3 vocabulary as the entity metamodel. + +The metamodel SHALL declare meta-classes for Page, Section variants (including form and raw), FieldBinding, and toolbar actions, with: + +- `Page.entity` as external reference to BusinessEntity +- `FieldBinding.bind` as external reference to entity fields +- `FieldBinding.editor` constrained against `components.index.json` +- `RawNode` for `type: raw` escape sections without parsing inner structure +- Human-readable `doc` and `constraint.check` fields (documentation-first in E4) + +#### Scenario: FYBXDJ card exemplar is explainable by metamodel + +- **WHEN** `exemplars/fybxdj-card.yaml` is inspected against metamodel class definitions +- **THEN** page type, title, entity binding, toolbar actions, form sections, and field bindings map to declared meta-classes + +#### Scenario: Metamodel README documents E4 boundaries + +- **WHEN** a contributor opens `assets/ubml/metamodel/README.md` +- **THEN** they find that constraints are documentation-first and crossref.ts is not replaced in E4 + +### Requirement: Page metamodel loader + +`packages/ubml-codegen` SHALL provide a page metamodel loader parsing `ubml-page.metamodel.yaml` into stable TypeScript types, usable in Bun tests without side effects. + +#### Scenario: Loader parses default asset path + +- **WHEN** the loader reads the bundled page metamodel asset +- **THEN** it returns classes including Page and FieldBinding +- **AND** parsing completes without throwing + +### Requirement: Page metamodel consistency tests + +The product SHALL include `packages/ubml-codegen/test/page-metamodel-consistency.test.ts` verifying: + +1. Metamodel-declared page types and editor constraints align with page DSL schema and `components.index.json` +2. FYBXDJ card exemplar elements map to metamodel classes +3. Catalog/constraint names in the metamodel resolve to existing asset files under `assets/ubml/` + +#### Scenario: Test fails on editor catalog drift + +- **WHEN** the metamodel references editor catalog constraints but `components.index.json` is missing +- **THEN** the consistency test fails + +### Requirement: Generic external reference design document + +The product SHALL document the generic external reference model in `docs/UBML-EXTERNAL-REFERENCE.md`, describing how Page.entity, FieldBinding.bind, and future cross-model refs relate to metamodel `external: true` references and the evolution path from handwritten AppGen crossref checks. + +#### Scenario: Document lists current crossref coverage + +- **WHEN** a contributor reads `docs/UBML-EXTERNAL-REFERENCE.md` +- **THEN** they find mappings from metamodel external refs to existing crossref validation in `crossref.ts` + +### Requirement: Pagegen prompt constraints from metamodel + +The product SHALL render agent-facing page constraint fragments from the page metamodel and inject them into `assets/ubml/prompts/pagegen.md`, while leaving workflow/strategy sections human-authored. + +#### Scenario: Rendered fragment lists page types and editor rules + +- **WHEN** prompt rendering runs against the loaded page metamodel +- **THEN** output includes page type constraints and editor catalog guidance + +#### Scenario: Existing page verify behavior unchanged + +- **WHEN** E4 is complete +- **THEN** `bun run ubml:verify packages/extension/assets/ubml/exemplars/fybxdj-card.yaml` still passes with no change to compile/verify semantics diff --git a/packages/extension/assets/ubml-entity/metamodel/README.md b/packages/extension/assets/ubml-entity/metamodel/README.md new file mode 100644 index 00000000..76064633 --- /dev/null +++ b/packages/extension/assets/ubml-entity/metamodel/README.md @@ -0,0 +1,49 @@ +# 业务实体元模型(M2) + +本目录存放 **UBML 业务实体的显式元模型**(M2),对应产品设计 [10-mof-entity-modeling.md](../../../../docs/product-design/10-mof-entity-modeling.md) 的 E1 阶段。 + +## 文件 + +| 文件 | 说明 | +|------|------| +| `ubml-entity.metamodel.yaml` | 元类、datatype、enum、catalog 约束与 doc;FYBXD 金样应能完整映射 | + +## 与现有实现的关系 + +``` +ubml-entity.metamodel.yaml(事实源,E1 起) + │ + ├── 一致性测试 ←→ datatypes.index.json / Zod schema / FYBXD 金样 + ├── 提示词片段 ← entity/metamodel/prompt.ts → entitygen.md + └── (远期)Zod / JSON Schema / 编辑操作 schema 派生 +``` + +**E1 不改变**现有编译、verify 或 MCP 行为;Zod(`packages/ubml-codegen/src/entity/dsl/schema.ts`)仍是运行时第一道闸。 + +## 维护规则 + +1. **改 DSL 类型或 catalog 约束时**,必须同步更新本文件,并确保 `entity-metamodel-consistency.test.ts` 通过。 +2. **`constraint.check` 字段**首期为人读规约(OCL 风格);执行由 Zod + 既有 semantic validation 承担,E2 再绑定单步 TS 校验。 +3. **不要**在本文件中描述平台 JSON 的机械字段(GUID、ColumnID、系统字段等)——那些属于编译器 M1→平台格式职责。 +4. **`datatypes`** 必须与 `../datatypes.index.json` 的 `types[].id` 集合一致(含 `date`、`enum`、`association`、`udt`)。 +5. **`catalogs`** 三项必须对应真实文件: + - `referenceable-be.catalog.json` + - `udt.catalog.json` + - `components.catalog.json` + +## 首期强约束 vs 文档约束 + +| 类别 | E1 enforcement | +|------|-----------------| +| datatype 集合与 index 一致 | 一致性测试 | +| FYBXD 元素可映射到 meta-class | 一致性测试 | +| catalog 名称可解析到文件 | 一致性测试 | +| `constraint.check` 表达式 | 文档 only | +| 不从元模型生成 Zod / gsp-business-entity.schema.json | 明确 non-goal | + +## 验收 + +```bash +bun test packages/ubml-codegen/test/entity-metamodel-consistency.test.ts +bun run ubml:be:verify packages/extension/assets/ubml-entity/exemplars/fybxd.be.yaml +``` diff --git a/packages/extension/assets/ubml-entity/metamodel/ubml-entity.metamodel.yaml b/packages/extension/assets/ubml-entity/metamodel/ubml-entity.metamodel.yaml new file mode 100644 index 00000000..dfb0f385 --- /dev/null +++ b/packages/extension/assets/ubml-entity/metamodel/ubml-entity.metamodel.yaml @@ -0,0 +1,220 @@ +metamodel: + name: ubml-entity + version: "0.1" + doc: | + 业务实体(Business Entity)M2 元模型。 + M1 表示为 entity: YAML DSL;编译器负责 M1→平台 GspMetadata 的机械展开。 + 本文件是「什么是合法实体模型」的唯一事实源(E1);与 Zod schema、catalog 通过一致性测试对齐。 + + datatypes: + - name: string + platform: { mdataType: String, requiresLength: true } + - name: text + platform: { mdataType: Text, requiresLength: true } + - name: integer + platform: { mdataType: Integer } + - name: decimal + platform: { mdataType: Decimal, requiresLength: true, requiresPrecision: true } + - name: datetime + platform: { mdataType: DateTime } + - name: date + platform: { mdataType: DateTime } + - name: boolean + platform: { mdataType: Boolean } + - name: enum + dslKind: enum + requiresValues: true + - name: association + dslKind: association + requiresRef: true + - name: udt + dslKind: udt + requiresUdt: true + + enums: + - name: EntityKind + literals: [bill, basedata] + - name: RuleTiming + literals: [beforeSave, afterModify, afterCreate] + + catalogs: + - name: referenceable-be + file: referenceable-be.catalog.json + doc: 可关联的外部业务实体清单 + - name: udt + file: udt.catalog.json + doc: 统一数据类型(UDT)清单 + - name: components + file: components.catalog.json + doc: 公共构件(common manager action / rule component)清单 + + classes: + - name: BusinessEntity + doc: 业务实体元数据根;编译为 GspMetadata(Header + Refs + Content) + attributes: + - { name: code, type: Identifier, required: true } + - { name: name, type: LocalizedString, required: true } + - { name: namespace, type: QualifiedName, required: true } + - { name: kind, type: EntityKind, default: bill } + references: + - { name: root, type: EntityObject, containment: true, multiplicity: "1" } + - { name: managerActions, type: ManagerAction, containment: true, multiplicity: "0..*" } + + - name: EntityObject + doc: "实体对象节点;根对象编译为 MainObject,子对象编译为 ContainChildObjects[]" + attributes: + - { name: code, type: Identifier, required: true } + - { name: name, type: LocalizedString, required: true } + references: + - { name: fields, type: Field, containment: true, multiplicity: "1..*" } + - { name: children, type: EntityObject, containment: true, multiplicity: "0..*" } + - { name: rules, type: Rules, containment: true, multiplicity: "0..1" } + constraints: + - name: no-grandchildren + doc: 子实体不允许再包含子实体(平台限制为两级对象树) + check: "self.parent != null implies self.children.isEmpty()" + - name: unique-child-codes + doc: 同一实体下子对象 code 不可重复 + check: "self.children->forAll(c | self.children->select(code = c.code)->size() = 1)" + + - name: Field + abstract: true + doc: 字段基类 + attributes: + - { name: code, type: Identifier, required: true } + - { name: name, type: LocalizedString, required: true } + + - name: ScalarField + superType: Field + doc: 标量字段(string/text/integer/decimal/datetime/date/boolean) + attributes: + - { name: type, type: DataTypeRef, required: true } + - { name: length, type: Integer } + - { name: precision, type: Integer } + - { name: required, type: Boolean, default: false } + - { name: multiLanguage, type: Boolean, default: false } + - { name: billCode, type: Boolean, default: false } + constraints: + - name: length-required-by-type + doc: requiresLength 的类型必须声明 length + check: "datatype(self.type).requiresLength implies self.length != null" + - name: precision-required-by-decimal + doc: decimal 类型必须声明 precision + check: "self.type = 'decimal' implies self.precision != null" + + - name: EnumField + superType: Field + doc: 枚举字段;values 编译为 ContainEnumValues + attributes: + - { name: type, type: Literal, value: enum, required: true } + references: + - { name: values, type: EnumValue, containment: true, multiplicity: "1..*" } + constraints: + - name: single-default + doc: 枚举默认值最多一个 + check: "self.values->select(default)->size() <= 1" + + - name: EnumValue + doc: 枚举项 + attributes: + - { name: code, type: Identifier, required: true } + - { name: name, type: LocalizedString, required: true } + - { name: default, type: Boolean, default: false } + + - name: AssociationField + superType: Field + doc: 外部实体关联;编译为 ChildAssociations + Refs 条目 + attributes: + - { name: type, type: Literal, value: association, required: true } + - { name: refFields, type: IdentifierList, doc: "带出的引用字段,如 [Code, Name]" } + references: + - { name: ref, type: BusinessEntity, containment: false, external: true } + constraints: + - name: ref-in-catalog + doc: 关联目标必须存在于 referenceable-be catalog + check: "catalog('referenceable-be').has(self.ref)" + catalog: referenceable-be + - name: refFields-exist + doc: refFields 必须是被引用实体已声明的字段 code + check: "catalog('referenceable-be').elements(self.ref).containsAll(self.refFields)" + + - name: UdtField + superType: Field + doc: UDT 字段;编译时解析 udt catalog ID + attributes: + - { name: type, type: Literal, value: udt, required: true } + references: + - { name: udt, type: UnifiedDataType, containment: false, external: true } + constraints: + - name: udt-in-catalog + doc: UDT 必须存在于 udt catalog + check: "catalog('udt').has(self.udt)" + catalog: udt + + - name: Rules + doc: 根对象上的规则集合 + references: + - { name: validations, type: ValidationRule, containment: true, multiplicity: "0..*" } + - { name: determinations, type: DeterminationRule, containment: true, multiplicity: "0..*" } + - { name: actions, type: ActionRule, containment: true, multiplicity: "0..*" } + + - name: ValidationRule + doc: 保存前校验规则 + attributes: + - { name: code, type: Identifier, required: true } + - { name: name, type: LocalizedString, required: true } + - { name: timing, type: Literal, value: beforeSave, default: beforeSave } + - { name: triggerFields, type: IdentifierList, required: true } + - { name: component, type: ComponentRef, required: true } + constraints: + - name: trigger-fields-exist + doc: triggerFields 必须引用已声明字段 + check: "entity.allFields().containsAll(self.triggerFields)" + + - name: DeterminationRule + doc: 联动计算或默认值规则 + attributes: + - { name: code, type: Identifier, required: true } + - { name: name, type: LocalizedString, required: true } + - { name: timing, type: RuleTiming, required: true } + - { name: component, type: ComponentRef, required: true } + references: + - { name: source, type: DeterminationSource, containment: true, multiplicity: "0..1" } + constraints: + - name: source-fields-exist + doc: source.child 与 source.fields 必须存在 + check: "self.source implies childExists(self.source.child) and fieldsExist(self.source.fields)" + + - name: DeterminationSource + doc: 联动计算的来源子实体与字段 + attributes: + - { name: child, type: Identifier, required: true } + - { name: fields, type: IdentifierList, required: true } + + - name: ActionRule + doc: 对象级业务动作 + attributes: + - { name: code, type: Identifier, required: true } + - { name: name, type: LocalizedString, required: true } + references: + - { name: params, type: ActionParam, containment: true, multiplicity: "0..*" } + + - name: ActionParam + attributes: + - { name: code, type: Identifier, required: true } + - { name: name, type: LocalizedString } + - { name: type, type: Identifier, required: true } + + - name: ManagerAction + doc: 管理动作(SubmitToApprove 等);common:true 时须命中 components catalog + attributes: + - { name: code, type: Identifier, required: true } + - { name: name, type: LocalizedString, required: true } + - { name: common, type: Boolean, default: false } + references: + - { name: params, type: ActionParam, containment: true, multiplicity: "0..*" } + constraints: + - name: common-in-catalog + doc: common 管理动作 code 必须在 components catalog 中 + check: "self.common implies catalog('components').has(self.code)" + catalog: components diff --git a/packages/extension/assets/ubml-entity/prompts/entitygen.md b/packages/extension/assets/ubml-entity/prompts/entitygen.md index 7a7595a2..f25c6cd0 100644 --- a/packages/extension/assets/ubml-entity/prompts/entitygen.md +++ b/packages/extension/assets/ubml-entity/prompts/entitygen.md @@ -14,9 +14,26 @@ 1. **`kb_be_exemplar`** — 用用户意图检索 few-shot BE DSL 范例 2. **`kb_be_datatype`** / **`kb_be_udt`** / **`kb_be_ref`** / **`kb_be_component`** — 约束字段类型与引用 -3. 编写或修改 YAML(见下方 DSL 形状) -4. **`kb_be_verify`** — 三道闸:DSL → 编译 → Schema →(可选)反序列化 -5. 若需受控小改:**`kb_be_edit`**(addField / addChild 等)→ 再 verify +3. **骨架草案** — 一次性产出 `entity:` YAML(`code`/`name`/`namespace`/`kind` + 标量字段 + `children` 骨架),立即 **`kb_be_verify`** +4. **受控精修** — 对关联、枚举、UDT、规则、管理动作等易错部分,逐条调用 **`kb_be_edit`** typed 操作(每步校验,失败看 `issues[].hint`) +5. **`kb_be_verify`** — 三道闸:DSL → 编译 → Schema →(可选)反序列化 +6. 若需小改:legacy **`kb_be_edit`**(addField / patchField 等)→ 再 verify + +### kb_be_edit typed 操作(推荐) + +| op | 用途 | +|----|------| +| `addScalarField` | string/text/integer/decimal/datetime/boolean | +| `addEnumField` | 枚举字段(values,默认最多 1 个) | +| `addAssociationField` | 外部 BE 关联(ref + refFields,查 `kb_be_ref`) | +| `addUdtField` | UDT 字段(查 `kb_be_udt`) | +| `addChildObject` | 子实体(仅 root 下,不可重复 code) | +| `addValidation` | beforeSave 校验规则 | +| `addDetermination` | afterModify / afterCreate 联动 | +| `addBizAction` | 对象级业务动作 | +| `addManagerAction` | 管理动作(common:true 查 `kb_be_component`) | + +Legacy:`addField` / `addChild` / `addRule` / `patchField` / `removeField` 仍可用,但 typed op 错误提示更精确。 增量修改已有 JSON 时:先用 **`kb_be_decompile`** 得到 YAML,再 edit/verify。 @@ -60,3 +77,42 @@ entity: 1. 简要说明实体类型、主表字段与子实体设计 2. 完整 YAML(fenced code block 或文件写入) 3. verify 结果摘要(通过 / 未通过及 stage) + +## 元模型约束 + + +### 合法字段类型(DSL `type`) + +标量:`string`、`text`、`integer`、`decimal`、`datetime`、`date`、`boolean` +复合: +- `enum`(enum) +- `association`(association) +- `udt`(udt) + +### 实体种类(`entity.kind`) + +`bill` | `basedata` + +### Catalog 约束(必须通过 MCP 查询,禁止编造) + +- **referenceable-be**(`referenceable-be.catalog.json`):可关联的外部业务实体清单 +- **udt**(`udt.catalog.json`):统一数据类型(UDT)清单 +- **components**(`components.catalog.json`):公共构件(common manager action / rule component)清单 + +### 元模型 catalog 校验规则 + +- **AssociationField.ref-in-catalog**(catalog: referenceable-be):关联目标必须存在于 referenceable-be catalog +- **AssociationField.refFields-exist**(catalog()):refFields 必须是被引用实体已声明的字段 code +- **UdtField.udt-in-catalog**(catalog: udt):UDT 必须存在于 udt catalog +- **ManagerAction.common-in-catalog**(catalog: components):common 管理动作 code 必须在 components catalog 中 + +### 结构约束摘要 + +- **EntityObject.no-grandchildren**:子实体不允许再包含子实体(平台限制为两级对象树) +- **EntityObject.unique-child-codes**:同一实体下子对象 code 不可重复 +- **ScalarField.length-required-by-type**:requiresLength 的类型必须声明 length +- **ScalarField.precision-required-by-decimal**:decimal 类型必须声明 precision +- **EnumField.single-default**:枚举默认值最多一个 +- **ValidationRule.trigger-fields-exist**:triggerFields 必须引用已声明字段 +- **DeterminationRule.source-fields-exist**:source.child 与 source.fields 必须存在 + diff --git a/packages/extension/assets/ubml/metamodel/README.md b/packages/extension/assets/ubml/metamodel/README.md new file mode 100644 index 00000000..8934250b --- /dev/null +++ b/packages/extension/assets/ubml/metamodel/README.md @@ -0,0 +1,29 @@ +# 页面元模型(M2) + +本目录存放 **UBML 页面 DSL 的显式元模型**,对应 [10-mof-entity-modeling.md](../../../../docs/product-design/10-mof-entity-modeling.md) E4 阶段。 + +## 文件 + +| 文件 | 说明 | +|------|------| +| `ubml-page.metamodel.yaml` | Page / Section / FieldBinding / ToolbarAction / RawSection 等 | + +## 与实体元模型的关系 + +- 共用 M3 词汇(`metamodel` / `class` / `attribute` / `reference` / `enum` / `constraint`) +- `Page.entity`、`FieldBinding.bind` 为 **external** 交叉引用,指向实体域(见 [UBML-EXTERNAL-REFERENCE.md](../../../../docs/UBML-EXTERNAL-REFERENCE.md)) + +## 维护规则 + +1. 改 page DSL section 类型或 editor 约束时,同步更新本文件并通过 `page-metamodel-consistency.test.ts`。 +2. `PageType` enum 与 `../templates.index.json` 的模板 id 保持一致。 +3. `FieldBinding.editor` 约束对应 `../components.index.json`。 +4. **E4 不改变** Zod 校验、page verify 或 AppGen `crossref.ts` 运行时行为。 +5. `constraint.check` 首期为人读规约;执行由既有校验代码承担。 + +## 验收 + +```bash +bun test packages/ubml-codegen/test/page-metamodel-consistency.test.ts +bun run ubml:verify packages/extension/assets/ubml/exemplars/fybxdj-card.yaml +``` diff --git a/packages/extension/assets/ubml/metamodel/ubml-page.metamodel.yaml b/packages/extension/assets/ubml/metamodel/ubml-page.metamodel.yaml new file mode 100644 index 00000000..8524b852 --- /dev/null +++ b/packages/extension/assets/ubml/metamodel/ubml-page.metamodel.yaml @@ -0,0 +1,119 @@ +metamodel: + name: ubml-page + version: "0.1" + doc: | + 页面(Page)M2 元模型。M1 表示为 page: YAML DSL;编译器负责 M1→Farris JSON。 + 与 ubml-entity 共用 M3 词汇。E4 为描述性事实源 + 一致性测试,不替换 crossref.ts。 + + enums: + - name: PageType + literals: [card, list-card, management-list, list-dictionary, layout] + doc: 与 templates.index.json 模板 id 对齐;DSL 首版聚焦 card / list-card + + catalogs: + - name: components + file: components.index.json + doc: 字段 editor 合法值域(Farris 组件 id) + - name: templates + file: templates.index.json + doc: page.type 模板清单 + + classes: + - name: Page + doc: 页面根;编译为 Farris 页面 JSON + attributes: + - { name: type, type: PageType, required: true } + - { name: title, type: LocalizedString, required: true } + references: + - { name: entity, type: BusinessEntity, containment: false, external: true, multiplicity: "0..1" } + - { name: sections, type: Section, containment: true, multiplicity: "1..*" } + - { name: toolbar, type: ToolbarAction, containment: true, multiplicity: "0..*" } + constraints: + - name: type-in-templates + doc: page.type 应命中 templates catalog + check: "catalog('templates').has(self.type)" + catalog: templates + + - name: Section + abstract: true + doc: 页面区域基类;DSL 以 type 或 fields 形态 discriminated union + + - name: FormSection + superType: Section + doc: 默认 form 区域(可无 type,含 fields) + attributes: + - { name: title, type: LocalizedString, required: true } + - { name: layout, type: LayoutRef, default: form-col-4 } + - { name: type, type: Literal, value: form } + references: + - { name: fields, type: FieldBinding, containment: true, multiplicity: "1..*" } + + - name: ContentContainerSection + superType: Section + doc: 嵌套容器区域 + attributes: + - { name: title, type: LocalizedString, required: true } + - { name: type, type: Literal, value: content-container, required: true } + - { name: layout, type: LayoutRef } + references: + - { name: sections, type: Section, containment: true, multiplicity: "1..*" } + + - name: DataGridSection + superType: Section + attributes: + - { name: type, type: Literal, value: data-grid, required: true } + - { name: bind, type: Identifier, required: true } + references: + - { name: columns, type: GridColumnBinding, containment: true, multiplicity: "1..*" } + + - name: ListNavSection + superType: Section + attributes: + - { name: type, type: Literal, value: list-nav, required: true } + + - name: FilterBarSection + superType: Section + attributes: + - { name: type, type: Literal, value: filter-bar, required: true } + references: + - { name: fields, type: FieldBinding, containment: true, multiplicity: "0..*" } + + - name: RawSection + superType: Section + doc: raw 逃逸节点;不解析 raw 载荷内部结构 + attributes: + - { name: type, type: Literal, value: raw, required: true } + - { name: raw, type: OpaqueObject, required: true } + + - name: FieldBinding + doc: 表单字段绑定;编译为 Farris field editor + attributes: + - { name: label, type: LocalizedString, required: true } + - { name: editor, type: EditorRef, required: true } + references: + - { name: bind, type: EntityField, containment: false, external: true } + constraints: + - name: editor-in-catalog + doc: editor 必须存在于 components.index.json + check: "catalog('components').has(self.editor)" + catalog: components + - name: bind-exists-on-entity + doc: bind 必须引用 page.entity 已声明字段 + check: "page(self).entity.allFields().has(self.bind)" + + - name: GridColumnBinding + doc: 数据网格列绑定 + attributes: + - { name: title, type: LocalizedString, required: true } + - { name: editor, type: EditorRef, required: true } + references: + - { name: bind, type: EntityField, containment: false, external: true } + constraints: + - name: editor-in-catalog + catalog: components + check: "catalog('components').has(self.editor)" + + - name: ToolbarAction + doc: 工具栏动作 code(add/edit/save/cancel 等) + attributes: + - { name: code, type: Identifier, required: true } diff --git a/packages/extension/assets/ubml/prompts/pagegen.md b/packages/extension/assets/ubml/prompts/pagegen.md index bf8811d8..4d32ea6f 100644 --- a/packages/extension/assets/ubml/prompts/pagegen.md +++ b/packages/extension/assets/ubml/prompts/pagegen.md @@ -53,3 +53,30 @@ page: 1. 简要说明页面类型与字段设计 2. 完整 YAML( fenced code block 或文件写入) 3. verify 结果摘要(通过 / 未通过及 stage) + +## 元模型约束 + + +### 合法页面类型(`page.type`) + +`card` | `list-card` | `management-list` | `list-dictionary` | `layout` + +首版生成聚焦:`card`、`list-card`。 + +### Catalog 约束(必须通过 MCP 查询,禁止编造) + +- **components**(`components.index.json`):字段 editor 合法值域(Farris 组件 id) +- **templates**(`templates.index.json`):page.type 模板清单 + +### 元模型 catalog / external 校验规则 + +- **Page.type-in-templates**(catalog: templates):page.type 应命中 templates catalog +- **FieldBinding.editor-in-catalog**(catalog: components):editor 必须存在于 components.index.json +- **GridColumnBinding.editor-in-catalog**(catalog: components):catalog('components').has(self.editor) + +### 结构约束摘要 + +- **FieldBinding.bind-exists-on-entity**:bind 必须引用 page.entity 已声明字段 + +- **RawSection**:无法语义表达的 Farris 片段用 `type: raw` 透传,不解析内部结构。 + diff --git a/packages/extension/package.json b/packages/extension/package.json index 50281f10..e5944b0e 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -33,6 +33,7 @@ "@thisbeyond/solid-dnd": "0.7.5", "effect": "catalog:", "luxon": "catalog:", + "mermaid": "11.12.0", "solid-js": "catalog:", "virtua": "catalog:" }, diff --git a/packages/extension/src/components/dev-lab-compile-output.tsx b/packages/extension/src/components/dev-lab-compile-output.tsx new file mode 100644 index 00000000..4dc3e7f2 --- /dev/null +++ b/packages/extension/src/components/dev-lab-compile-output.tsx @@ -0,0 +1,72 @@ +import { Show, createMemo } from "solid-js" +import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2" + +export function downloadJsonFile(data: unknown, filename: string) { + const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json;charset=utf-8" }) + const url = URL.createObjectURL(blob) + const anchor = document.createElement("a") + anchor.href = url + anchor.download = filename + anchor.click() + URL.revokeObjectURL(url) +} + +export function DevLabCompileOutput(props: { + status?: string + issuesJson?: string + model?: Record + modelTitle?: string + exportFilename?: string + emptyHint?: string +}) { + const jsonText = createMemo(() => (props.model ? JSON.stringify(props.model, null, 2) : "")) + const hasIssues = () => { + const raw = props.issuesJson?.trim() + return Boolean(raw && raw !== "[]" && raw !== "null") + } + + return ( +
+
+
+
+ {props.modelTitle ?? "编译产物 JSON"} +
+ +
{props.status}
+
+
+ + downloadJsonFile(props.model!, props.exportFilename ?? "compiled.json")} + > + Export JSON + + +
+ + + {props.emptyHint ?? "Compile / Verify 成功后在此显示编译 JSON"} +
+ } + > +
+          {jsonText()}
+        
+ + + +
+ 校验问题 +
+            {props.issuesJson}
+          
+
+
+ + ) +} diff --git a/packages/extension/src/components/entity-preview-content.tsx b/packages/extension/src/components/entity-preview-content.tsx index ada3526e..c74934d8 100644 --- a/packages/extension/src/components/entity-preview-content.tsx +++ b/packages/extension/src/components/entity-preview-content.tsx @@ -1,21 +1,20 @@ import { For, Show } from "solid-js" import type { EntityPreviewModel } from "../lib/entity-preview-model" +import { MermaidDiagram } from "./mermaid-diagram" export function EntityPreviewContent(props: { model: EntityPreviewModel; compact?: boolean }) { - const model = () => props.model - return (

- {model().entityCode} - - {model().entityName} + {props.model.entityCode} + + {props.model.entityName}

- +

- {[model().entityKind, model().namespace].filter(Boolean).join(" · ")} + {[props.model.entityKind, props.model.namespace].filter(Boolean).join(" · ")}

@@ -33,7 +32,7 @@ export function EntityPreviewContent(props: { model: EntityPreviewModel; compact - + {(field) => ( {field.code} @@ -48,11 +47,11 @@ export function EntityPreviewContent(props: { model: EntityPreviewModel; compact
- 0}> + 0}>

子实体

- + {(child) => (
@@ -81,10 +80,13 @@ export function EntityPreviewContent(props: { model: EntityPreviewModel; compact
-

Mermaid ER

-
-          {model().mermaid}
-        
+

关系图

+

+ 包含关系(子实体)与关联引用(外部实体) +

+
+ +
) diff --git a/packages/extension/src/components/mermaid-diagram.tsx b/packages/extension/src/components/mermaid-diagram.tsx new file mode 100644 index 00000000..8270e4de --- /dev/null +++ b/packages/extension/src/components/mermaid-diagram.tsx @@ -0,0 +1,80 @@ +import { createEffect, createSignal, onCleanup } from "solid-js" + +let mermaidReady: Promise | undefined + +function loadMermaid() { + if (!mermaidReady) { + mermaidReady = import("mermaid").then((mod) => { + mod.default.initialize({ + startOnLoad: false, + securityLevel: "loose", + theme: "neutral", + er: { useMaxWidth: true }, + }) + return mod.default + }) + } + return mermaidReady +} + +let renderSeq = 0 + +export function MermaidDiagram(props: { chart: string; class?: string }) { + let containerRef: HTMLDivElement | undefined + const [error, setError] = createSignal() + + createEffect(() => { + const chart = props.chart.trim() + const container = containerRef + if (!container) return + + if (!chart) { + container.innerHTML = "" + setError(undefined) + return + } + + const seq = ++renderSeq + let cancelled = false + onCleanup(() => { + cancelled = true + }) + + void (async () => { + try { + setError(undefined) + const mermaid = await loadMermaid() + if (cancelled || seq !== renderSeq) return + const id = `mermaid-${seq}-${Math.random().toString(36).slice(2, 9)}` + const { svg } = await mermaid.render(id, chart) + if (cancelled || seq !== renderSeq || !containerRef) return + containerRef.innerHTML = svg + } catch (e) { + if (!cancelled && seq === renderSeq) { + setError(e instanceof Error ? e.message : String(e)) + if (containerRef) containerRef.innerHTML = "" + } + } + })() + }) + + return ( +
+
+ {error() ? ( +
+
+ Mermaid 渲染失败:{error()} +
+
+            {props.chart}
+          
+
+ ) : null} +
+ ) +} diff --git a/packages/extension/src/components/page-preview-content.tsx b/packages/extension/src/components/page-preview-content.tsx new file mode 100644 index 00000000..471919d3 --- /dev/null +++ b/packages/extension/src/components/page-preview-content.tsx @@ -0,0 +1,72 @@ +import { For, Show } from "solid-js" +import type { PagePreviewModel } from "../lib/page-preview-model" + +export function PagePreviewContent(props: { model: PagePreviewModel }) { + const model = () => props.model + + return ( +
+
+

+ {model().title} + {model().pageType} +

+ +

+ 绑定实体:{model().entity} +

+
+ 0}> +

+ 工具栏:{model().toolbar.join(", ")} +

+
+
+ +
+

区域

+
    + + {(section) => ( +
  • + {section.title} + + {section.type} + + ({section.fieldCount} 字段) +
  • + )} +
    +
+
+ +
+

字段绑定

+
+ + + + + + + + + + + + {(row) => ( + + + + + + + )} + + +
区域LabelBindEditor
{row.sectionTitle}{row.label}{row.bind}{row.editor}
+
+
+
+ ) +} diff --git a/packages/extension/src/components/ubml-artifact-card.tsx b/packages/extension/src/components/ubml-artifact-card.tsx index 4b55a3fb..fd4ebbc7 100644 --- a/packages/extension/src/components/ubml-artifact-card.tsx +++ b/packages/extension/src/components/ubml-artifact-card.tsx @@ -104,7 +104,7 @@ export function UbmlArtifactCard(props: { } function runDevLab() { - openUbmlInDevLab(props.artifact.yaml) + openUbmlInDevLab(props.artifact.yaml, props.artifact.kind) setStatus("已在 Dev Lab 打开") } @@ -200,6 +200,9 @@ export function UbmlArtifactCard(props: { > {issue.path ? `${issue.path}: ` : ""} {issue.message} + + {issue.hint} + )} diff --git a/packages/extension/src/components/ubml-assistant-text-part.tsx b/packages/extension/src/components/ubml-assistant-text-part.tsx index d56470bf..aa570288 100644 --- a/packages/extension/src/components/ubml-assistant-text-part.tsx +++ b/packages/extension/src/components/ubml-assistant-text-part.tsx @@ -66,9 +66,11 @@ export function UbmlAssistantTextPart(props: { deferToolContent?: boolean virtualizeDiff?: boolean }) { - const artifacts = () => (props.part.type === "text" ? extractUbmlArtifacts(props.part.text) : []) + const partComplete = () => Boolean(props.part.time?.end) + const artifacts = () => + props.part.type === "text" && partComplete() ? extractUbmlArtifacts(props.part.text) : [] const displayPart = () => { - if (props.part.type !== "text" || artifacts().length === 0) return props.part + if (props.part.type !== "text" || !partComplete() || artifacts().length === 0) return props.part const stripped = stripUbmlYamlFromText(props.part.text) if (!stripped) return { ...props.part, text: "" } return { ...props.part, text: stripped } @@ -76,6 +78,7 @@ export function UbmlAssistantTextPart(props: { const showMessagePart = () => { if (props.part.type !== "text") return true + if (!partComplete()) return true return stripUbmlYamlFromText(props.part.text).length > 0 } diff --git a/packages/extension/src/index.ts b/packages/extension/src/index.ts index 6b498851..b71302a4 100644 --- a/packages/extension/src/index.ts +++ b/packages/extension/src/index.ts @@ -38,6 +38,7 @@ export { export type { RequirementKind } from "./lib/requirement-control-client" export { openUbmlPreviewTab, pushUbmlPreview, getUbmlPreviewUrl } from "./lib/ubml-preview-bridge" export { openUbmlEntityPreviewTab } from "./lib/ubml-entity-preview-bridge" +export { openUbmlModelTab } from "./lib/ubml-model-bridge" export { getUbmlEntityPrompt, listUbmlEntityPromptIds, diff --git a/packages/extension/src/lib/entity-preview-model.ts b/packages/extension/src/lib/entity-preview-model.ts index 6472acfe..2b9643db 100644 --- a/packages/extension/src/lib/entity-preview-model.ts +++ b/packages/extension/src/lib/entity-preview-model.ts @@ -41,17 +41,39 @@ function sanitizeMermaidId(id: string): string { return id.replace(/[^a-zA-Z0-9_]/g, "_") } +function mermaidFieldType(field: EntityPreviewFieldRow): string { + if (field.type === "association") return "ref" + if (field.type === "udt") return "udt" + if (field.type === "enum") return "enum" + return field.type.replace(/[^a-zA-Z0-9_]/g, "_") || "string" +} + +function collectExternalRefs(rootFields: EntityPreviewFieldRow[], children: EntityPreviewChild[]): string[] { + const refs = new Set() + for (const field of rootFields) { + if (field.type === "association" && field.ref) refs.add(field.ref) + } + for (const child of children) { + for (const field of child.fields) { + if (field.type === "association" && field.ref) refs.add(field.ref) + } + } + return [...refs].sort() +} + function buildMermaid( entityCode: string, rootFields: EntityPreviewFieldRow[], children: EntityPreviewChild[], ): string { const rootId = sanitizeMermaidId(entityCode) + const defined = new Set([rootId, ...children.map((c) => sanitizeMermaidId(c.code))]) const lines = ["erDiagram"] lines.push(` ${rootId} {`) for (const field of rootFields) { - lines.push(` ${field.type} ${field.code}`) + if (field.type === "association") continue + lines.push(` ${mermaidFieldType(field)} ${field.code}`) } lines.push(" }") @@ -60,11 +82,22 @@ function buildMermaid( lines.push(` ${rootId} ||--o{ ${childId} : contains`) lines.push(` ${childId} {`) for (const field of child.fields) { - lines.push(` ${field.type} ${field.code}`) + if (field.type === "association") continue + lines.push(` ${mermaidFieldType(field)} ${field.code}`) } lines.push(" }") } + for (const ref of collectExternalRefs(rootFields, children)) { + const refId = sanitizeMermaidId(ref) + if (!defined.has(refId)) { + lines.push(` ${refId} {`) + lines.push(" string external") + lines.push(" }") + defined.add(refId) + } + } + for (const field of rootFields) { if (field.type === "association" && field.ref) { const refId = sanitizeMermaidId(field.ref) @@ -72,6 +105,16 @@ function buildMermaid( } } + for (const child of children) { + const childId = sanitizeMermaidId(child.code) + for (const field of child.fields) { + if (field.type === "association" && field.ref) { + const refId = sanitizeMermaidId(field.ref) + lines.push(` ${childId} }o--|| ${refId} : ${field.code}`) + } + } + } + return lines.join("\n") } diff --git a/packages/extension/src/lib/model-preview-model.test.ts b/packages/extension/src/lib/model-preview-model.test.ts new file mode 100644 index 00000000..ce63ee0e --- /dev/null +++ b/packages/extension/src/lib/model-preview-model.test.ts @@ -0,0 +1,89 @@ +import { describe, expect, test } from "bun:test" +import { readFileSync } from "node:fs" +import { resolve } from "node:path" +import { buildEntityPreviewFromDsl } from "./entity-preview-model" +import { buildPagePreviewFromDsl } from "./page-preview-model" +import { + buildModelNavigation, + collectExternalRefs, + graphViewLabel, + summarizeModelGraph, +} from "./model-preview-model" +import type { UbmlModelEntry } from "./ubml-model-tab-store" +import { + parseEntityYaml, + parseDslYaml, + validateEntityDsl, + validatePageDsl, +} from "@code-agent/ubml-codegen/runtime" +import { loadEntityCompileContext, loadCompileContext } from "@code-agent/ubml-codegen" + +const FYBXD = readFileSync( + resolve(import.meta.dir, "../../assets/ubml-entity/exemplars/fybxd.be.yaml"), + "utf8", +) + +const FYBXDJ_CARD = readFileSync( + resolve(import.meta.dir, "../../assets/ubml/exemplars/fybxdj-card.yaml"), + "utf8", +) + +describe("model-preview-model", () => { + test("FYBXD external refs and children summary", async () => { + const assets = resolve(import.meta.dir, "../../assets/ubml-entity") + const ctx = await loadEntityCompileContext(assets) + const dsl = validateEntityDsl(parseEntityYaml(FYBXD), ctx) + const model = buildEntityPreviewFromDsl(dsl) + const summary = summarizeModelGraph(model) + + expect(summary.childCodes.sort()).toEqual(["FXBXDFJ", "FYBXDMX"]) + expect(summary.externalRefs).toContain("GspUser") + expect(summary.externalRefs).toContain("SysOrg") + expect(summary.externalRefs).toContain("Projects") + expect(collectExternalRefs(model).length).toBeGreaterThanOrEqual(3) + }) + + test("buildModelNavigation groups entity entries", async () => { + const assets = resolve(import.meta.dir, "../../assets/ubml-entity") + const ctx = await loadEntityCompileContext(assets) + const dsl = validateEntityDsl(parseEntityYaml(FYBXD), ctx) + const model = buildEntityPreviewFromDsl(dsl) + const entries: UbmlModelEntry[] = [ + { key: "fybxd", label: "FYBXD", kind: "entity", yaml: FYBXD, model }, + ] + const nav = buildModelNavigation(entries) + expect(nav.length).toBe(1) + expect(nav[0]?.label).toBe("实体 be/") + expect(nav[0]?.items[0]?.code).toBe("FYBXD") + }) + + test("buildModelNavigation groups entity and page entries", async () => { + const entityAssets = resolve(import.meta.dir, "../../assets/ubml-entity") + const entityCtx = await loadEntityCompileContext(entityAssets) + const entityDsl = validateEntityDsl(parseEntityYaml(FYBXD), entityCtx) + const entityModel = buildEntityPreviewFromDsl(entityDsl) + + const pageAssets = resolve(import.meta.dir, "../../assets/ubml") + const pageCtx = await loadCompileContext(pageAssets) + const pageDsl = validatePageDsl(parseDslYaml(FYBXDJ_CARD), pageCtx) + const pageModel = buildPagePreviewFromDsl(pageDsl) + + const entries: UbmlModelEntry[] = [ + { key: "fybxd", label: "FYBXD", kind: "entity", yaml: FYBXD, model: entityModel }, + { key: "card", label: "报销单", kind: "page", yaml: FYBXDJ_CARD, model: pageModel }, + ] + const nav = buildModelNavigation(entries) + + expect(nav.length).toBe(2) + expect(nav[0]?.label).toBe("实体 be/") + expect(nav[0]?.items[0]?.code).toBe("FYBXD") + expect(nav[1]?.label).toBe("页面 pages/") + expect(nav[1]?.items[0]?.code).toBe("card") + expect(nav[1]?.items[0]?.name).toBe("报销单") + }) + + test("graphViewLabel distinguishes entity and page", () => { + expect(graphViewLabel("entity")).toBe("关系图") + expect(graphViewLabel("page")).toBe("结构") + }) +}) diff --git a/packages/extension/src/lib/model-preview-model.ts b/packages/extension/src/lib/model-preview-model.ts new file mode 100644 index 00000000..96d10cd5 --- /dev/null +++ b/packages/extension/src/lib/model-preview-model.ts @@ -0,0 +1,78 @@ +import type { EntityPreviewModel } from "./entity-preview-model" +import type { UbmlModelEntry, UbmlModelKind } from "./ubml-model-tab-store" + +export type ModelNavItem = { + key: string + code: string + name: string +} + +export type ModelNavGroup = { + kind: UbmlModelKind + label: string + items: ModelNavItem[] +} + +const KIND_LABELS: Record = { + entity: "实体 be/", + page: "页面 pages/", +} + +export function navItemFromEntry(entry: UbmlModelEntry): ModelNavItem { + if (entry.kind === "page") { + return { + key: entry.key, + code: entry.model.pageType, + name: entry.model.title, + } + } + return { + key: entry.key, + code: entry.model.entityCode, + name: entry.model.entityName, + } +} + +export function collectExternalRefs(model: EntityPreviewModel): string[] { + const refs = new Set() + for (const field of model.rootFields) { + if (field.type === "association" && field.ref) refs.add(field.ref) + } + for (const child of model.children) { + for (const field of child.fields) { + if (field.type === "association" && field.ref) refs.add(field.ref) + } + } + return [...refs].sort() +} + +export function buildModelNavigation(entries: UbmlModelEntry[]): ModelNavGroup[] { + const groups = new Map() + for (const entry of entries) { + const list = groups.get(entry.kind) ?? [] + list.push(navItemFromEntry(entry)) + groups.set(entry.kind, list) + } + const order: UbmlModelKind[] = ["entity", "page"] + return order + .filter((kind) => groups.has(kind)) + .map((kind) => ({ + kind, + label: KIND_LABELS[kind], + items: (groups.get(kind) ?? []).sort((a, b) => a.code.localeCompare(b.code)), + })) +} + +export function summarizeModelGraph(model: EntityPreviewModel): { + childCodes: string[] + externalRefs: string[] +} { + return { + childCodes: model.children.map((c) => c.code), + externalRefs: collectExternalRefs(model), + } +} + +export function graphViewLabel(kind: UbmlModelKind): string { + return kind === "entity" ? "关系图" : "结构" +} diff --git a/packages/extension/src/lib/page-preview-model.ts b/packages/extension/src/lib/page-preview-model.ts new file mode 100644 index 00000000..70cddb7d --- /dev/null +++ b/packages/extension/src/lib/page-preview-model.ts @@ -0,0 +1,89 @@ +import type { PageDsl, SectionDsl } from "@code-agent/ubml-codegen/runtime" + +export type PagePreviewBinding = { + sectionTitle: string + label: string + bind: string + editor: string +} + +export type PagePreviewSection = { + title: string + type?: string + fieldCount: number +} + +export type PagePreviewModel = { + pageType: string + title: string + entity?: string + toolbar: string[] + sections: PagePreviewSection[] + bindings: PagePreviewBinding[] +} + +function sectionTitle(section: SectionDsl, index: number): string { + if ("title" in section && section.title) return section.title + if ("type" in section && section.type) return section.type + return `Section ${index + 1}` +} + +function collectBindings(sections: SectionDsl[], sectionLabel: string): PagePreviewBinding[] { + const bindings: PagePreviewBinding[] = [] + for (let i = 0; i < sections.length; i++) { + const section = sections[i]! + const title = sectionTitle(section, i) + if ("fields" in section && section.fields) { + for (const field of section.fields) { + bindings.push({ + sectionTitle: sectionLabel || title, + label: field.label, + bind: field.bind, + editor: field.editor, + }) + } + } + if ("columns" in section) { + for (const col of section.columns) { + bindings.push({ + sectionTitle: sectionLabel || title, + label: col.title, + bind: col.bind, + editor: col.editor, + }) + } + } + if ("type" in section && section.type === "content-container" && "sections" in section) { + bindings.push(...collectBindings(section.sections, title)) + } + } + return bindings +} + +function summarizeSections(sections: SectionDsl[]): PagePreviewSection[] { + return sections.map((section, index) => { + let fieldCount = 0 + if ("fields" in section && section.fields) fieldCount = section.fields.length + if ("columns" in section) fieldCount = section.columns.length + if ("type" in section && section.type === "content-container" && "sections" in section) { + fieldCount = collectBindings(section.sections, "").length + } + return { + title: sectionTitle(section, index), + type: "type" in section ? section.type : "form", + fieldCount, + } + }) +} + +export function buildPagePreviewFromDsl(dsl: PageDsl): PagePreviewModel { + const page = dsl.page + return { + pageType: page.type, + title: page.title, + entity: page.entity, + toolbar: page.toolbar ?? [], + sections: summarizeSections(page.sections), + bindings: collectBindings(page.sections, ""), + } +} diff --git a/packages/extension/src/lib/right-panel-preview-utils.ts b/packages/extension/src/lib/right-panel-preview-utils.ts index 60022bdb..7db5f431 100644 --- a/packages/extension/src/lib/right-panel-preview-utils.ts +++ b/packages/extension/src/lib/right-panel-preview-utils.ts @@ -8,9 +8,9 @@ type PreviewTabLike = { type OpenTabLike = PreviewTabLike & { id: string; previewKey?: string } -const ADDABLE_TAB_TYPES: RightPanelTabType[] = ["changes", "terminal", "editor", "preview"] +const ADDABLE_TAB_TYPES: RightPanelTabType[] = ["changes", "terminal", "editor", "preview", "model"] -/** Tab types available from the right panel + menu (Preview excluded — artifact-driven). */ +/** Tab types available from the right panel + menu (Preview / entity-preview excluded — artifact-driven). */ export function closedTabTypesForAddMenu(openTabs: OpenTabLike[]): RightPanelTabType[] { const open = new Set(openTabs.map((tab) => tab.type)) return ADDABLE_TAB_TYPES.filter((type) => { diff --git a/packages/extension/src/lib/ubml-artifact-actions.ts b/packages/extension/src/lib/ubml-artifact-actions.ts index bf607220..b93639ee 100644 --- a/packages/extension/src/lib/ubml-artifact-actions.ts +++ b/packages/extension/src/lib/ubml-artifact-actions.ts @@ -1,8 +1,11 @@ -import { compilePageFromYaml, verifyPageFromYaml } from "@code-agent/ubml-codegen/runtime" +import { compilePageFromYaml, verifyPageFromYaml, parseDslYaml, validatePageDsl } from "@code-agent/ubml-codegen/runtime" import { getBrowserCompileContext } from "../ubml/browser-context" import { openUbmlPreviewTab } from "./ubml-preview-bridge" +import { buildPagePreviewFromDsl } from "./page-preview-model" +import { openUbmlModelTab } from "./ubml-model-bridge" import type { UbmlValidationIssue } from "./ubml-preview-tab-store" -import { openDevLabWithYaml } from "./ubml-dev-lab-bridge" +import { openDevLabWithYaml, devLabSurfaceFromArtifactKind } from "./ubml-dev-lab-bridge" +import type { UbmlArtifactKind } from "./ubml-artifact-detect" export { ubmlArtifactPreviewKey, ubmlArtifactTitle } from "./ubml-artifact-meta" export { openUbmlArtifactInIde, @@ -54,25 +57,85 @@ export async function verifyUbmlYaml(yaml: string): Promise<{ ok: boolean; stage } } +function mapVerifyIssues( + issues: { + stage?: string + path?: string + message: string + hint?: string + suggestion?: string + hints?: string[] + }[], + componentType: string, +): UbmlValidationIssue[] { + return issues.map((issue) => ({ + path: issue.path ?? "", + message: issue.message, + componentType, + stage: issue.stage, + hint: issue.hint, + suggestion: issue.suggestion, + hints: issue.hints, + })) +} + export async function previewUbmlYaml( yaml: string, opts: UbmlPreviewOptions, ): Promise { - const compiled = await compileUbmlYaml(yaml) - if (compiled.model) { - openUbmlPreviewTab({ - key: opts.previewKey, - title: opts.title, - model: compiled.model, - yaml, - projectDir: opts.projectDir, - }) + try { + const ctx = await getBrowserCompileContext() + const verify = await verifyPageFromYaml(yaml, undefined, { UBML_RENDER_SMOKE: "0" }, ctx) + const issues = mapVerifyIssues(verify.issues ?? [], "page") + let model = verify.model as Record | undefined + + if (!model && verify.ok === false) { + const compiled = await compilePageFromYaml(yaml, undefined, ctx).catch(() => null) + model = compiled?.model as Record | undefined + } + + if (model) { + openUbmlPreviewTab({ + key: opts.previewKey, + title: opts.title, + model, + yaml, + projectDir: opts.projectDir, + }) + try { + const dsl = validatePageDsl(parseDslYaml(yaml), ctx) + const pagePreview = buildPagePreviewFromDsl(dsl) + openUbmlModelTab({ + kind: "page", + key: opts.previewKey, + title: opts.title, + yaml, + model: pagePreview, + }) + } catch { + // model tab optional when DSL parse fails but compiled model exists + } + } + + return { + model, + valid: verify.ok, + issueCount: issues.length, + issues, + error: verify.ok ? undefined : `Verify failed at ${verify.stage ?? "unknown"}`, + } + } catch (e) { + return { + valid: false, + issueCount: 0, + issues: [], + error: e instanceof Error ? e.message : String(e), + } } - return compiled } -export function openUbmlInDevLab(yaml: string) { - openDevLabWithYaml(yaml) +export function openUbmlInDevLab(yaml: string, kind?: UbmlArtifactKind) { + openDevLabWithYaml(yaml, kind ? devLabSurfaceFromArtifactKind(kind) : undefined) } export async function copyUbmlYaml(yaml: string): Promise { diff --git a/packages/extension/src/lib/ubml-dev-lab-bridge.test.ts b/packages/extension/src/lib/ubml-dev-lab-bridge.test.ts new file mode 100644 index 00000000..e829a998 --- /dev/null +++ b/packages/extension/src/lib/ubml-dev-lab-bridge.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, test } from "bun:test" +import { detectDevLabSurface, devLabSurfaceFromArtifactKind } from "./ubml-dev-lab-bridge" + +const ENTITY_SAMPLE = `entity: + code: FYBXD + name: 报销单 + namespace: Inspur.GS + kind: bill + root: + code: FYBXD + name: 报销单 + fields: + - { code: Code, name: 编号, type: string, length: 50 } +` + +const PAGE_SAMPLE = `page: + type: card + title: 报销单 + entity: FYBXD + sections: + - title: 基本信息 + fields: + - { label: 编号, bind: Code, editor: input-group } +` + +describe("ubml-dev-lab-bridge", () => { + test("detectDevLabSurface routes entity yaml to entities", () => { + expect(detectDevLabSurface(ENTITY_SAMPLE)).toBe("entities") + expect(detectDevLabSurface(PAGE_SAMPLE)).toBe("pages") + }) + + test("devLabSurfaceFromArtifactKind maps entity card to entities", () => { + expect(devLabSurfaceFromArtifactKind("entity")).toBe("entities") + expect(devLabSurfaceFromArtifactKind("page")).toBe("pages") + expect(devLabSurfaceFromArtifactKind("app")).toBe("apps") + }) +}) diff --git a/packages/extension/src/lib/ubml-dev-lab-bridge.ts b/packages/extension/src/lib/ubml-dev-lab-bridge.ts index e1343582..b9bc140e 100644 --- a/packages/extension/src/lib/ubml-dev-lab-bridge.ts +++ b/packages/extension/src/lib/ubml-dev-lab-bridge.ts @@ -1,18 +1,46 @@ import { createStore } from "solid-js/store" +import { + looksLikeUbmlAppYaml, + looksLikeUbmlEntityYaml, + looksLikeUbmlPageYaml, + type UbmlArtifactKind, +} from "./ubml-artifact-detect" import { openDevLab } from "../pages/layout/project-surface-store" +export type DevLabSurface = "pages" | "entities" | "apps" + export const [devLabYamlStore, setDevLabYamlStore] = createStore({ pendingYaml: undefined as string | undefined, + pendingSurface: undefined as DevLabSurface | undefined, }) -export function openDevLabWithYaml(yaml: string) { - setDevLabYamlStore("pendingYaml", yaml) +export function devLabSurfaceFromArtifactKind(kind: UbmlArtifactKind): DevLabSurface { + if (kind === "entity") return "entities" + if (kind === "app") return "apps" + return "pages" +} + +export function detectDevLabSurface(yaml: string): DevLabSurface { + if (looksLikeUbmlAppYaml(yaml)) return "apps" + if (looksLikeUbmlEntityYaml(yaml)) return "entities" + if (looksLikeUbmlPageYaml(yaml)) return "pages" + return "pages" +} + +export function openDevLabWithYaml(yaml: string, surface?: DevLabSurface) { + setDevLabYamlStore({ + pendingYaml: yaml, + pendingSurface: surface ?? detectDevLabSurface(yaml), + }) openDevLab() } -/** Read and clear pending YAML (UbmlPageEditor on mount). */ -export function consumeDevLabPendingYaml(): string | undefined { +/** Read and clear pending YAML when surface matches (editor on mount). */ +export function consumeDevLabPendingYaml(expectedSurface: DevLabSurface): string | undefined { const yaml = devLabYamlStore.pendingYaml - if (yaml) setDevLabYamlStore("pendingYaml", undefined) + const surface = devLabYamlStore.pendingSurface + if (!yaml) return undefined + if (surface && surface !== expectedSurface) return undefined + setDevLabYamlStore({ pendingYaml: undefined, pendingSurface: undefined }) return yaml } diff --git a/packages/extension/src/lib/ubml-entity-artifact-actions.ts b/packages/extension/src/lib/ubml-entity-artifact-actions.ts index 06505a4b..80757af4 100644 --- a/packages/extension/src/lib/ubml-entity-artifact-actions.ts +++ b/packages/extension/src/lib/ubml-entity-artifact-actions.ts @@ -7,6 +7,7 @@ import { import { getBrowserEntityCompileContext } from "../ubml-entity/browser-context" import { buildEntityPreviewFromDsl, type EntityPreviewModel } from "./entity-preview-model" import { openUbmlEntityPreviewTab } from "./ubml-entity-preview-bridge" +import { openUbmlModelTab } from "./ubml-model-bridge" import type { UbmlValidationIssue } from "./ubml-preview-tab-store" export type UbmlEntityCompileResult = { @@ -24,12 +25,23 @@ export type UbmlEntityPreviewOptions = { } function mapEntityIssues( - issues: { path?: string; message: string }[], + issues: { + path?: string + message: string + stage?: string + hint?: string + suggestion?: string + hints?: string[] + }[], ): UbmlValidationIssue[] { return issues.map((issue) => ({ path: issue.path ?? "", message: issue.message, componentType: "entity", + stage: issue.stage, + hint: issue.hint, + suggestion: issue.suggestion, + hints: issue.hints, })) } @@ -97,6 +109,13 @@ export async function previewEntityYaml( yaml, model: preview, }) + openUbmlModelTab({ + kind: "entity", + key: opts.previewKey, + title: opts.title, + yaml, + model: preview, + }) } return { diff --git a/packages/extension/src/lib/ubml-model-bridge.ts b/packages/extension/src/lib/ubml-model-bridge.ts new file mode 100644 index 00000000..08c9770e --- /dev/null +++ b/packages/extension/src/lib/ubml-model-bridge.ts @@ -0,0 +1,83 @@ +import type { EntityPreviewModel } from "./entity-preview-model" +import type { PagePreviewModel } from "./page-preview-model" +import { + type UbmlModelEntry, + upsertUbmlModelEntry, + setUbmlModelViewMode, + clearUbmlModelTab, +} from "./ubml-model-tab-store" +import { createRightPanelTabs } from "../pages/layout/right-panel" +import type { RightPanelTab } from "../pages/layout/right-panel" +import { rightPanelStore, setRightPanelStore } from "../pages/layout/right-panel-store" + +export type OpenUbmlEntityModelTabInput = { + kind: "entity" + key: string + title: string + yaml: string + model: EntityPreviewModel + viewMode?: "graph" | "file" +} + +export type OpenUbmlPageModelTabInput = { + kind: "page" + key: string + title: string + yaml: string + model: PagePreviewModel + viewMode?: "graph" | "file" +} + +export type OpenUbmlModelTabInput = OpenUbmlEntityModelTabInput | OpenUbmlPageModelTabInput + +const { createTab } = createRightPanelTabs() + +function expandRightPanel() { + if (rightPanelStore.width === 0) { + setRightPanelStore("width", rightPanelStore.widthBeforeCollapse || 570) + setRightPanelStore("collapsed", false) + } +} + +function findOrCreateModelTab(): string { + const existing = rightPanelStore.tabs.find((tab) => tab.type === "model") + if (existing) { + setRightPanelStore("activeTab", existing.id) + return existing.id + } + const base = createTab("model") + const tab: RightPanelTab = { ...base, title: "" } + setRightPanelStore("tabs", [...rightPanelStore.tabs, tab]) + setRightPanelStore("activeTab", tab.id) + return tab.id +} + +function toEntry(input: OpenUbmlModelTabInput): UbmlModelEntry { + if (input.kind === "page") { + return { + key: input.key.trim(), + label: input.title.trim() || input.model.title, + kind: "page", + yaml: input.yaml, + model: input.model, + } + } + return { + key: input.key.trim(), + label: input.title.trim() || input.model.entityCode, + kind: "entity", + yaml: input.yaml, + model: input.model, + } +} + +export function openUbmlModelTab(input: OpenUbmlModelTabInput) { + findOrCreateModelTab() + expandRightPanel() + upsertUbmlModelEntry(toEntry(input)) + if (input.viewMode) setUbmlModelViewMode(input.viewMode) +} + +export function cleanupUbmlModelTab(_tabId: string) { + clearUbmlModelTab() +} diff --git a/packages/extension/src/lib/ubml-model-tab-store.ts b/packages/extension/src/lib/ubml-model-tab-store.ts new file mode 100644 index 00000000..88cc7bef --- /dev/null +++ b/packages/extension/src/lib/ubml-model-tab-store.ts @@ -0,0 +1,83 @@ +import type { EntityPreviewModel } from "./entity-preview-model" +import type { PagePreviewModel } from "./page-preview-model" + +export type UbmlModelKind = "entity" | "page" + +export type UbmlEntityModelEntry = { + key: string + label: string + kind: "entity" + yaml: string + model: EntityPreviewModel +} + +export type UbmlPageModelEntry = { + key: string + label: string + kind: "page" + yaml: string + model: PagePreviewModel +} + +export type UbmlModelEntry = UbmlEntityModelEntry | UbmlPageModelEntry + +export type UbmlModelViewMode = "graph" | "file" + +export type UbmlModelTabState = { + entries: UbmlModelEntry[] + selectedKey: string | null + viewMode: UbmlModelViewMode +} + +let state: UbmlModelTabState = { + entries: [], + selectedKey: null, + viewMode: "graph", +} + +const listeners = new Set<() => void>() + +function emit() { + for (const listener of listeners) listener() +} + +export function getUbmlModelTabState(): UbmlModelTabState { + return state +} + +export function subscribeUbmlModelTabState(listener: () => void): () => void { + listeners.add(listener) + return () => listeners.delete(listener) +} + +export function upsertUbmlModelEntry(entry: UbmlModelEntry) { + const idx = state.entries.findIndex((e) => e.key === entry.key) + const entries = + idx >= 0 + ? state.entries.map((e, i) => (i === idx ? entry : e)) + : [...state.entries, entry] + state = { ...state, entries, selectedKey: entry.key } + emit() +} + +export function setUbmlModelSelectedKey(key: string) { + if (!state.entries.some((e) => e.key === key)) return + state = { ...state, selectedKey: key } + emit() +} + +export function setUbmlModelViewMode(viewMode: UbmlModelViewMode) { + state = { ...state, viewMode } + emit() +} + +export function clearUbmlModelTab() { + state = { entries: [], selectedKey: null, viewMode: "graph" } + emit() +} + +export function getUbmlModelSelectedEntry(): UbmlModelEntry | undefined { + const key = state.selectedKey + if (!key) return state.entries[0] + return state.entries.find((e) => e.key === key) ?? state.entries[0] +} diff --git a/packages/extension/src/pages/layout/project-workspace-shell.tsx b/packages/extension/src/pages/layout/project-workspace-shell.tsx index 29dd3e2d..5d27cf23 100644 --- a/packages/extension/src/pages/layout/project-workspace-shell.tsx +++ b/packages/extension/src/pages/layout/project-workspace-shell.tsx @@ -17,8 +17,10 @@ import { TerminalTabContent } from "./right-panel-terminal" import { ChangesTabContent } from "./right-panel-changes" import { PreviewTabContent } from "./right-panel-preview" import { EntityPreviewTabContent } from "./right-panel-entity-preview" +import { ModelTabContent } from "./right-panel-model" import { cleanupUbmlPreviewTab } from "../../lib/ubml-preview-bridge" import { cleanupUbmlEntityPreviewTab } from "../../lib/ubml-entity-preview-bridge" +import { cleanupUbmlModelTab } from "../../lib/ubml-model-bridge" import { SessionColumnHeader } from "./session-column-header" import { MissionContextBar } from "./mission-context-bar" import { AdhocMissionPromoteBar } from "./adhoc-mission-promote-bar" @@ -118,6 +120,7 @@ export function ProjectWorkspaceShell(props: ProjectWorkspaceShellProps) { const closing = rightPanelStore.tabs.find((t: RightPanelTab) => t.id === id) if (closing?.type === "preview") cleanupUbmlPreviewTab(id) if (closing?.type === "entity-preview") cleanupUbmlEntityPreviewTab(id) + if (closing?.type === "model") cleanupUbmlModelTab(id) setRightPanelStore("tabs", (tabs: RightPanelTab[]) => { const next = tabs.filter((t: RightPanelTab) => t.id !== id) if (rightPanelStore.activeTab === id) { @@ -165,6 +168,8 @@ export function ProjectWorkspaceShell(props: ProjectWorkspaceShellProps) { return if (tab.type === "entity-preview") return + if (tab.type === "model") + return return null } diff --git a/packages/extension/src/pages/layout/right-panel-entity-preview.tsx b/packages/extension/src/pages/layout/right-panel-entity-preview.tsx index 02f4765a..2838ad50 100644 --- a/packages/extension/src/pages/layout/right-panel-entity-preview.tsx +++ b/packages/extension/src/pages/layout/right-panel-entity-preview.tsx @@ -1,20 +1,27 @@ -import { Show } from "solid-js" +import { Show, createMemo } from "solid-js" import { EntityPreviewContent } from "../../components/entity-preview-content" import { getUbmlEntityPreviewTabPayload } from "../../lib/ubml-entity-preview-tab-store" +import { rightPanelStore } from "./right-panel-store" export function EntityPreviewTabContent(props: { tabId: string; active?: boolean }) { - const payload = () => getUbmlEntityPreviewTabPayload(props.tabId) + const payload = createMemo(() => { + // Re-read when right panel tabs update (preview open / refresh). + rightPanelStore.tabs + rightPanelStore.activeTab + return getUbmlEntityPreviewTabPayload(props.tabId) + }) return ( 无实体预览数据
} > - {(data) => } + {(data) => } ) } diff --git a/packages/extension/src/pages/layout/right-panel-labels.ts b/packages/extension/src/pages/layout/right-panel-labels.ts index 05700084..67adfd7a 100644 --- a/packages/extension/src/pages/layout/right-panel-labels.ts +++ b/packages/extension/src/pages/layout/right-panel-labels.ts @@ -8,6 +8,7 @@ const TAB_DISPLAY_LABELS: Record> browser: { en: "Browser", zh: "浏览器" }, preview: { en: "Preview", zh: "预览" }, "entity-preview": { en: "Entity", zh: "实体预览" }, + model: { en: "Model", zh: "模型" }, } export function rightPanelTabLabel(type: RightPanelTabType, locale: Locale) { diff --git a/packages/extension/src/pages/layout/right-panel-model.tsx b/packages/extension/src/pages/layout/right-panel-model.tsx new file mode 100644 index 00000000..56e7db80 --- /dev/null +++ b/packages/extension/src/pages/layout/right-panel-model.tsx @@ -0,0 +1,5 @@ +import { UbmlModelPanel } from "../workbench/ubml-model-panel" + +export function ModelTabContent(_props: { tabId: string; active?: boolean }) { + return +} diff --git a/packages/extension/src/pages/layout/right-panel.tsx b/packages/extension/src/pages/layout/right-panel.tsx index 39651a07..ced13d20 100644 --- a/packages/extension/src/pages/layout/right-panel.tsx +++ b/packages/extension/src/pages/layout/right-panel.tsx @@ -6,7 +6,7 @@ import { useLanguage } from "@/context/language" import { closedTabTypesForAddMenu, previewTabLabel } from "../../lib/right-panel-preview-utils" import { rightPanelAddTabLabel, rightPanelTabLabel } from "./right-panel-labels" -export type RightPanelTabType = "editor" | "terminal" | "changes" | "browser" | "preview" | "entity-preview" +export type RightPanelTabType = "editor" | "terminal" | "changes" | "browser" | "preview" | "entity-preview" | "model" export type RightPanelTab = { id: string @@ -20,7 +20,7 @@ export type RightPanelTab = { export const CANONICAL_TAB_ORDER: RightPanelTabType[] = ["changes", "terminal", "editor"] -export const OPTIONAL_TAB_TYPES: RightPanelTabType[] = ["preview", "entity-preview"] +export const OPTIONAL_TAB_TYPES: RightPanelTabType[] = ["preview", "entity-preview", "model"] export const ADDABLE_TAB_TYPES: RightPanelTabType[] = [...CANONICAL_TAB_ORDER, ...OPTIONAL_TAB_TYPES] @@ -33,6 +33,7 @@ const TAB_TYPE_ICONS = { browser: "window-cursor", preview: "eye", "entity-preview": "brain", + model: "file-tree", } as const const tabCounter: Record = { @@ -42,6 +43,7 @@ const tabCounter: Record = { browser: 0, preview: 0, "entity-preview": 0, + model: 0, } export function sortTabsByCanonicalOrder(tabs: RightPanelTab[]): RightPanelTab[] { diff --git a/packages/extension/src/pages/workbench/ubml-app-editor.tsx b/packages/extension/src/pages/workbench/ubml-app-editor.tsx index df6941d8..d7b4953b 100644 --- a/packages/extension/src/pages/workbench/ubml-app-editor.tsx +++ b/packages/extension/src/pages/workbench/ubml-app-editor.tsx @@ -5,12 +5,23 @@ import type { AppGenerateResult } from "@code-agent/ubml-codegen/app-browser" import { useAgentHome } from "../agent-home/agent-home-context" import { closeUbmlNav } from "../layout/ubml-nav-store" import { buildAppPreviewModel } from "../../lib/app-preview-model" -import { buildUbmlAppgenAgentPrompt } from "../../lib/ubml-appgen-client" +import { + buildUbmlAppgenAgentPrompt, + buildUbmlAppgenRepairAgentPrompt, +} from "../../lib/ubml-appgen-client" +import { + canFixWithAgent, + canRepairRound, + MAX_APPGEN_REPAIR_ROUNDS, + nextRepairRound, + repairLimitMessage, +} from "../../lib/ubml-appgen-repair-loop" import { consumeDevLabPendingYaml } from "../../lib/ubml-dev-lab-bridge" import { generateAppFromYamlInBrowser } from "../../ubml-app/browser-generate" import { getAppExemplar, listAppExemplarIds } from "../../ubml-app/catalog" import { openUbmlAppWritebackDialog, UbmlAppWritebackDialog } from "../../components/ubml-app-writeback-dialog" import type { AppPreviewModel } from "../../lib/app-preview-model" +import { DevLabCompileOutput } from "../../components/dev-lab-compile-output" export type UbmlAppEditorProps = { onPreview?: (model: AppPreviewModel) => void @@ -26,9 +37,32 @@ export function UbmlAppEditor(props: UbmlAppEditorProps = {}) { const [issues, setIssues] = createSignal("") const [lastManifest, setLastManifest] = createSignal | undefined>() const [lastResult, setLastResult] = createSignal() + const [repairRound, setRepairRound] = createSignal(0) + + function resetRepairState() { + setRepairRound(0) + } + + function formatStatus(result: AppGenerateResult): string { + if (result.validation.valid) { + return repairRound() > 0 ? `Generate OK(修复第 ${repairRound()} 轮后通过)` : "Generate OK" + } + const roundHint = + repairRound() > 0 ? ` · 修复轮次 ${repairRound()}/${MAX_APPGEN_REPAIR_ROUNDS}` : "" + return `Generate failed (${result.validation.issues.length} issues)${roundHint}` + } + + function canUseFixWithAgent(): boolean { + const result = lastResult() + return canFixWithAgent({ + hasResult: Boolean(result), + validationValid: result?.validation.valid ?? true, + repairRound: repairRound(), + }) + } onMount(() => { - const pending = consumeDevLabPendingYaml() + const pending = consumeDevLabPendingYaml("apps") if (pending) { setYaml(pending) setSelectedExemplar("") @@ -43,7 +77,10 @@ export function UbmlAppEditor(props: UbmlAppEditorProps = {}) { const manifest = buildAppManifest(result) as Record setLastResult(result) setLastManifest(manifest) - setStatus(result.validation.valid ? "Generate OK" : `Generate failed (${result.validation.issues.length} issues)`) + if (result.validation.valid) { + if (repairRound() === 0) resetRepairState() + } + setStatus(formatStatus(result)) setIssues(JSON.stringify(result.validation.issues, null, 2)) if (props.onPreview) { props.onPreview(buildAppPreviewModel(result)) @@ -53,6 +90,7 @@ export function UbmlAppEditor(props: UbmlAppEditorProps = {}) { setIssues("") setLastManifest(undefined) setLastResult(undefined) + resetRepairState() } } @@ -64,6 +102,7 @@ export function UbmlAppEditor(props: UbmlAppEditorProps = {}) { setYaml(content) setLastManifest(undefined) setLastResult(undefined) + resetRepairState() setStatus(`Loaded exemplar: ${id}`) setIssues("") void runGenerate() @@ -80,6 +119,7 @@ export function UbmlAppEditor(props: UbmlAppEditorProps = {}) { setSelectedExemplar("") setLastManifest(undefined) setLastResult(undefined) + resetRepairState() setStatus(`Loaded: ${path}`) void runGenerate() }) @@ -123,11 +163,42 @@ export function UbmlAppEditor(props: UbmlAppEditorProps = {}) { setStatus("请先在左侧选择一个本地 Project,再派发给 Agent") return } - const prompt = buildUbmlAppgenAgentPrompt({ intent: text, yaml: yaml() }) + const failed = lastResult()?.validation.valid === false ? lastResult()?.validation.issues : undefined + const prompt = buildUbmlAppgenAgentPrompt({ + intent: text, + yaml: yaml(), + validationIssues: failed, + }) closeUbmlNav() home.startNewAgent(prompt) } + function fixWithAgent() { + const result = lastResult() + if (!canUseFixWithAgent() || !result) { + if (!canRepairRound(repairRound())) { + setStatus(repairLimitMessage()) + } + return + } + if (!home.activeProject()) { + setStatus("请先在左侧选择一个本地 Project,再派发给 Agent") + return + } + const round = nextRepairRound(repairRound()) + setRepairRound(round) + const prompt = buildUbmlAppgenRepairAgentPrompt({ + round, + maxRounds: MAX_APPGEN_REPAIR_ROUNDS, + issues: result.validation.issues, + lastYaml: yaml(), + intent: intent().trim() || undefined, + }) + closeUbmlNav() + home.startNewAgent(prompt) + setStatus(`已派发修复 Agent(第 ${round}/${MAX_APPGEN_REPAIR_ROUNDS} 轮)`) + } + function writebackToProject() { const project = home.activeProject() if (!project) { @@ -162,6 +233,9 @@ export function UbmlAppEditor(props: UbmlAppEditorProps = {}) { Ask Agent + + Fix with Agent +
@@ -198,26 +272,31 @@ export function UbmlAppEditor(props: UbmlAppEditorProps = {}) {
-