@memoarchitect/tools 0.6.7 → 0.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -11
- package/corpus/README.md +63 -0
- package/corpus/sysml-v2-release/bnf/SysML-graphical-bnf.kgbnf +2264 -0
- package/package.json +20 -7
- package/packages/tools/lib/analysis/dsm.d.ts +1 -1
- package/packages/tools/lib/analysis/dsm.d.ts.map +1 -1
- package/packages/tools/lib/analysis/dsm.js +1 -1
- package/packages/tools/lib/analysis/dsm.js.map +1 -1
- package/packages/tools/lib/bin/memo.js +186 -10
- package/packages/tools/lib/bin/memo.js.map +1 -1
- package/packages/tools/lib/browser/index.d.ts +3 -1
- package/packages/tools/lib/browser/index.d.ts.map +1 -1
- package/packages/tools/lib/browser/index.js +2 -1
- package/packages/tools/lib/browser/index.js.map +1 -1
- package/packages/tools/lib/commands/ask.js +1 -1
- package/packages/tools/lib/commands/ask.js.map +1 -1
- package/packages/tools/lib/commands/check.d.ts.map +1 -1
- package/packages/tools/lib/commands/check.js +3 -9
- package/packages/tools/lib/commands/check.js.map +1 -1
- package/packages/tools/lib/commands/conformance.d.ts +36 -0
- package/packages/tools/lib/commands/conformance.d.ts.map +1 -0
- package/packages/tools/lib/commands/conformance.js +212 -0
- package/packages/tools/lib/commands/conformance.js.map +1 -0
- package/packages/tools/lib/commands/convert.d.ts +12 -0
- package/packages/tools/lib/commands/convert.d.ts.map +1 -0
- package/packages/tools/lib/commands/convert.js +103 -0
- package/packages/tools/lib/commands/convert.js.map +1 -0
- package/packages/tools/lib/commands/create-package.d.ts +1 -2
- package/packages/tools/lib/commands/create-package.d.ts.map +1 -1
- package/packages/tools/lib/commands/create-package.js.map +1 -1
- package/packages/tools/lib/commands/dev.d.ts +13 -0
- package/packages/tools/lib/commands/dev.d.ts.map +1 -1
- package/packages/tools/lib/commands/dev.js +245 -89
- package/packages/tools/lib/commands/dev.js.map +1 -1
- package/packages/tools/lib/commands/dhf-draft.js +1 -1
- package/packages/tools/lib/commands/dhf-draft.js.map +1 -1
- package/packages/tools/lib/commands/dhf-lint.d.ts +10 -0
- package/packages/tools/lib/commands/dhf-lint.d.ts.map +1 -0
- package/packages/tools/lib/commands/dhf-lint.js +72 -0
- package/packages/tools/lib/commands/dhf-lint.js.map +1 -0
- package/packages/tools/lib/commands/dhf-preview.js +1 -1
- package/packages/tools/lib/commands/dhf-preview.js.map +1 -1
- package/packages/tools/lib/commands/dhf.d.ts.map +1 -1
- package/packages/tools/lib/commands/dhf.js +8 -2
- package/packages/tools/lib/commands/dhf.js.map +1 -1
- package/packages/tools/lib/commands/element.d.ts +34 -0
- package/packages/tools/lib/commands/element.d.ts.map +1 -0
- package/packages/tools/lib/commands/element.js +174 -0
- package/packages/tools/lib/commands/element.js.map +1 -0
- package/packages/tools/lib/commands/export.d.ts.map +1 -1
- package/packages/tools/lib/commands/export.js +39 -40
- package/packages/tools/lib/commands/export.js.map +1 -1
- package/packages/tools/lib/commands/generate.d.ts.map +1 -1
- package/packages/tools/lib/commands/generate.js +10 -7
- package/packages/tools/lib/commands/generate.js.map +1 -1
- package/packages/tools/lib/commands/import.d.ts.map +1 -1
- package/packages/tools/lib/commands/import.js +25 -12
- package/packages/tools/lib/commands/import.js.map +1 -1
- package/packages/tools/lib/commands/init.d.ts.map +1 -1
- package/packages/tools/lib/commands/init.js +34 -14
- package/packages/tools/lib/commands/init.js.map +1 -1
- package/packages/tools/lib/commands/inspect.d.ts +5 -0
- package/packages/tools/lib/commands/inspect.d.ts.map +1 -0
- package/packages/tools/lib/commands/inspect.js +56 -0
- package/packages/tools/lib/commands/inspect.js.map +1 -0
- package/packages/tools/lib/commands/lock.d.ts.map +1 -1
- package/packages/tools/lib/commands/lock.js +7 -2
- package/packages/tools/lib/commands/lock.js.map +1 -1
- package/packages/tools/lib/commands/ontology.d.ts.map +1 -1
- package/packages/tools/lib/commands/ontology.js +69 -116
- package/packages/tools/lib/commands/ontology.js.map +1 -1
- package/packages/tools/lib/commands/pack.d.ts +1 -1
- package/packages/tools/lib/commands/pack.d.ts.map +1 -1
- package/packages/tools/lib/commands/pack.js +20 -78
- package/packages/tools/lib/commands/pack.js.map +1 -1
- package/packages/tools/lib/commands/package.d.ts +10 -0
- package/packages/tools/lib/commands/package.d.ts.map +1 -0
- package/packages/tools/lib/commands/package.js +113 -0
- package/packages/tools/lib/commands/package.js.map +1 -0
- package/packages/tools/lib/commands/plugin.js +1 -1
- package/packages/tools/lib/commands/plugin.js.map +1 -1
- package/packages/tools/lib/commands/rules.d.ts.map +1 -1
- package/packages/tools/lib/commands/rules.js +120 -30
- package/packages/tools/lib/commands/rules.js.map +1 -1
- package/packages/tools/lib/commands/standards.d.ts +28 -0
- package/packages/tools/lib/commands/standards.d.ts.map +1 -0
- package/packages/tools/lib/commands/standards.js +203 -0
- package/packages/tools/lib/commands/standards.js.map +1 -0
- package/packages/tools/lib/commands/sysand-publish.d.ts.map +1 -1
- package/packages/tools/lib/commands/sysand-publish.js +14 -16
- package/packages/tools/lib/commands/sysand-publish.js.map +1 -1
- package/packages/tools/lib/commands/toolchain.d.ts +8 -0
- package/packages/tools/lib/commands/toolchain.d.ts.map +1 -0
- package/packages/tools/lib/commands/toolchain.js +64 -0
- package/packages/tools/lib/commands/toolchain.js.map +1 -0
- package/packages/tools/lib/commands/validate.d.ts +1 -1
- package/packages/tools/lib/commands/validate.d.ts.map +1 -1
- package/packages/tools/lib/commands/validate.js +194 -56
- package/packages/tools/lib/commands/validate.js.map +1 -1
- package/packages/tools/lib/completeness/tracker.d.ts +10 -2
- package/packages/tools/lib/completeness/tracker.d.ts.map +1 -1
- package/packages/tools/lib/completeness/tracker.js +16 -21
- package/packages/tools/lib/completeness/tracker.js.map +1 -1
- package/packages/tools/lib/conformance/baseline.d.ts +26 -0
- package/packages/tools/lib/conformance/baseline.d.ts.map +1 -0
- package/packages/tools/lib/conformance/baseline.js +170 -0
- package/packages/tools/lib/conformance/baseline.js.map +1 -0
- package/packages/tools/lib/conformance/bnf.d.ts +84 -0
- package/packages/tools/lib/conformance/bnf.d.ts.map +1 -0
- package/packages/tools/lib/conformance/bnf.js +166 -0
- package/packages/tools/lib/conformance/bnf.js.map +1 -0
- package/packages/tools/lib/conformance/corpus.d.ts +125 -0
- package/packages/tools/lib/conformance/corpus.d.ts.map +1 -0
- package/packages/tools/lib/conformance/corpus.js +190 -0
- package/packages/tools/lib/conformance/corpus.js.map +1 -0
- package/packages/tools/lib/conformance/diff-xmi.d.ts +118 -0
- package/packages/tools/lib/conformance/diff-xmi.d.ts.map +1 -0
- package/packages/tools/lib/conformance/diff-xmi.js +276 -0
- package/packages/tools/lib/conformance/diff-xmi.js.map +1 -0
- package/packages/tools/lib/conformance/generated/syside-rules.d.ts +10 -0
- package/packages/tools/lib/conformance/generated/syside-rules.d.ts.map +1 -0
- package/packages/tools/lib/conformance/generated/syside-rules.js +1142 -0
- package/packages/tools/lib/conformance/generated/syside-rules.js.map +1 -0
- package/packages/tools/lib/conformance/index.d.ts +7 -0
- package/packages/tools/lib/conformance/index.d.ts.map +1 -0
- package/packages/tools/lib/conformance/index.js +19 -0
- package/packages/tools/lib/conformance/index.js.map +1 -0
- package/packages/tools/lib/conformance/run.d.ts +117 -0
- package/packages/tools/lib/conformance/run.d.ts.map +1 -0
- package/packages/tools/lib/conformance/run.js +0 -0
- package/packages/tools/lib/conformance/run.js.map +1 -0
- package/packages/tools/lib/conformance/xmi.d.ts +45 -0
- package/packages/tools/lib/conformance/xmi.d.ts.map +1 -0
- package/packages/tools/lib/conformance/xmi.js +148 -0
- package/packages/tools/lib/conformance/xmi.js.map +1 -0
- package/packages/tools/lib/dhf/document-compiler.d.ts.map +1 -1
- package/packages/tools/lib/dhf/document-compiler.js +13 -8
- package/packages/tools/lib/dhf/document-compiler.js.map +1 -1
- package/packages/tools/lib/dhf/document-registry.d.ts +16 -2
- package/packages/tools/lib/dhf/document-registry.d.ts.map +1 -1
- package/packages/tools/lib/dhf/document-registry.js +74 -23
- package/packages/tools/lib/dhf/document-registry.js.map +1 -1
- package/packages/tools/lib/dhf/index.d.ts +3 -0
- package/packages/tools/lib/dhf/index.d.ts.map +1 -1
- package/packages/tools/lib/dhf/index.js +4 -0
- package/packages/tools/lib/dhf/index.js.map +1 -1
- package/packages/tools/lib/dhf/query-engine.d.ts +12 -1
- package/packages/tools/lib/dhf/query-engine.d.ts.map +1 -1
- package/packages/tools/lib/dhf/query-engine.js +2 -1
- package/packages/tools/lib/dhf/query-engine.js.map +1 -1
- package/packages/tools/lib/dhf/query-executor.d.ts +72 -5
- package/packages/tools/lib/dhf/query-executor.d.ts.map +1 -1
- package/packages/tools/lib/dhf/query-executor.js +471 -81
- package/packages/tools/lib/dhf/query-executor.js.map +1 -1
- package/packages/tools/lib/dhf/script-runner.js +6 -6
- package/packages/tools/lib/dhf/script-runner.js.map +1 -1
- package/packages/tools/lib/dhf/standards-block.d.ts +40 -0
- package/packages/tools/lib/dhf/standards-block.d.ts.map +1 -0
- package/packages/tools/lib/dhf/standards-block.js +223 -0
- package/packages/tools/lib/dhf/standards-block.js.map +1 -0
- package/packages/tools/lib/dhf/standards-library.d.ts +123 -0
- package/packages/tools/lib/dhf/standards-library.d.ts.map +1 -0
- package/packages/tools/lib/dhf/standards-library.js +295 -0
- package/packages/tools/lib/dhf/standards-library.js.map +1 -0
- package/packages/tools/lib/dhf/standards-report.d.ts +189 -0
- package/packages/tools/lib/dhf/standards-report.d.ts.map +1 -0
- package/packages/tools/lib/dhf/standards-report.js +384 -0
- package/packages/tools/lib/dhf/standards-report.js.map +1 -0
- package/packages/tools/lib/dhf/template-lint.d.ts +42 -0
- package/packages/tools/lib/dhf/template-lint.d.ts.map +1 -0
- package/packages/tools/lib/dhf/template-lint.js +421 -0
- package/packages/tools/lib/dhf/template-lint.js.map +1 -0
- package/packages/tools/lib/dhf/template-resolver.d.ts.map +1 -1
- package/packages/tools/lib/dhf/template-resolver.js +18 -6
- package/packages/tools/lib/dhf/template-resolver.js.map +1 -1
- package/packages/tools/lib/import/column-mapper.d.ts +2 -2
- package/packages/tools/lib/import/column-mapper.d.ts.map +1 -1
- package/packages/tools/lib/import/column-mapper.js +3 -3
- package/packages/tools/lib/import/column-mapper.js.map +1 -1
- package/packages/tools/lib/importer/cameo-importer.js +2 -2
- package/packages/tools/lib/importer/cameo-importer.js.map +1 -1
- package/packages/tools/lib/importer/ea-importer.d.ts.map +1 -1
- package/packages/tools/lib/importer/ea-importer.js +4 -3
- package/packages/tools/lib/importer/ea-importer.js.map +1 -1
- package/packages/tools/lib/importer/owl-importer.d.ts.map +1 -1
- package/packages/tools/lib/importer/owl-importer.js +2 -3
- package/packages/tools/lib/importer/owl-importer.js.map +1 -1
- package/packages/tools/lib/index.d.ts +20 -3
- package/packages/tools/lib/index.d.ts.map +1 -1
- package/packages/tools/lib/index.js +19 -2
- package/packages/tools/lib/index.js.map +1 -1
- package/packages/tools/lib/language/generated/ast.d.ts +1344 -99
- package/packages/tools/lib/language/generated/ast.d.ts.map +1 -1
- package/packages/tools/lib/language/generated/ast.js +1066 -32
- package/packages/tools/lib/language/generated/ast.js.map +1 -1
- package/packages/tools/lib/language/generated/grammar.d.ts.map +1 -1
- package/packages/tools/lib/language/generated/grammar.js +5242 -1454
- package/packages/tools/lib/language/generated/grammar.js.map +1 -1
- package/packages/tools/lib/llm/chat-engine.d.ts +4 -3
- package/packages/tools/lib/llm/chat-engine.d.ts.map +1 -1
- package/packages/tools/lib/llm/chat-engine.js +15 -12
- package/packages/tools/lib/llm/chat-engine.js.map +1 -1
- package/packages/tools/lib/llm/generate-engine.d.ts +7 -2
- package/packages/tools/lib/llm/generate-engine.d.ts.map +1 -1
- package/packages/tools/lib/llm/generate-engine.js +22 -4
- package/packages/tools/lib/llm/generate-engine.js.map +1 -1
- package/packages/tools/lib/llm/index.d.ts +1 -0
- package/packages/tools/lib/llm/index.d.ts.map +1 -1
- package/packages/tools/lib/llm/index.js +1 -0
- package/packages/tools/lib/llm/index.js.map +1 -1
- package/packages/tools/lib/llm/model-context.d.ts +2 -2
- package/packages/tools/lib/llm/model-context.d.ts.map +1 -1
- package/packages/tools/lib/llm/model-context.js +7 -13
- package/packages/tools/lib/llm/model-context.js.map +1 -1
- package/packages/tools/lib/llm/sysml-guidance.d.ts +8 -0
- package/packages/tools/lib/llm/sysml-guidance.d.ts.map +1 -0
- package/packages/tools/lib/llm/sysml-guidance.js +28 -0
- package/packages/tools/lib/llm/sysml-guidance.js.map +1 -0
- package/packages/tools/lib/lock.d.ts +14 -8
- package/packages/tools/lib/lock.d.ts.map +1 -1
- package/packages/tools/lib/lock.js +97 -108
- package/packages/tools/lib/lock.js.map +1 -1
- package/packages/tools/lib/mcp/model-loader.d.ts +3 -0
- package/packages/tools/lib/mcp/model-loader.d.ts.map +1 -1
- package/packages/tools/lib/mcp/model-loader.js +9 -5
- package/packages/tools/lib/mcp/model-loader.js.map +1 -1
- package/packages/tools/lib/mcp/tools.d.ts +3 -0
- package/packages/tools/lib/mcp/tools.d.ts.map +1 -1
- package/packages/tools/lib/mcp/tools.js +8 -9
- package/packages/tools/lib/mcp/tools.js.map +1 -1
- package/packages/tools/lib/model/activity-notation.d.ts +54 -0
- package/packages/tools/lib/model/activity-notation.d.ts.map +1 -0
- package/packages/tools/lib/model/activity-notation.js +208 -0
- package/packages/tools/lib/model/activity-notation.js.map +1 -0
- package/packages/tools/lib/model/builder.d.ts +3 -0
- package/packages/tools/lib/model/builder.d.ts.map +1 -1
- package/packages/tools/lib/model/builder.js +956 -62
- package/packages/tools/lib/model/builder.js.map +1 -1
- package/packages/tools/lib/model/config-loader.d.ts +19 -25
- package/packages/tools/lib/model/config-loader.d.ts.map +1 -1
- package/packages/tools/lib/model/config-loader.js +51 -233
- package/packages/tools/lib/model/config-loader.js.map +1 -1
- package/packages/tools/lib/model/config.d.ts +42 -213
- package/packages/tools/lib/model/config.d.ts.map +1 -1
- package/packages/tools/lib/model/config.js +11 -9
- package/packages/tools/lib/model/config.js.map +1 -1
- package/packages/tools/lib/model/effective-scope.d.ts +74 -0
- package/packages/tools/lib/model/effective-scope.d.ts.map +1 -0
- package/packages/tools/lib/model/effective-scope.js +133 -0
- package/packages/tools/lib/model/effective-scope.js.map +1 -0
- package/packages/tools/lib/model/ir-identity.d.ts +94 -0
- package/packages/tools/lib/model/ir-identity.d.ts.map +1 -0
- package/packages/tools/lib/model/ir-identity.js +178 -0
- package/packages/tools/lib/model/ir-identity.js.map +1 -0
- package/packages/tools/lib/model/kind-registry.d.ts +134 -1
- package/packages/tools/lib/model/kind-registry.d.ts.map +1 -1
- package/packages/tools/lib/model/kind-registry.js +186 -4
- package/packages/tools/lib/model/kind-registry.js.map +1 -1
- package/packages/tools/lib/model/layer-resolver.js +1 -1
- package/packages/tools/lib/model/layer-resolver.js.map +1 -1
- package/packages/tools/lib/model/manifest.d.ts +8 -0
- package/packages/tools/lib/model/manifest.d.ts.map +1 -1
- package/packages/tools/lib/model/manifest.js +3 -0
- package/packages/tools/lib/model/manifest.js.map +1 -1
- package/packages/tools/lib/model/methodology-loader.d.ts +19 -24
- package/packages/tools/lib/model/methodology-loader.d.ts.map +1 -1
- package/packages/tools/lib/model/methodology-loader.js +179 -176
- package/packages/tools/lib/model/methodology-loader.js.map +1 -1
- package/packages/tools/lib/model/methodology-resolver.d.ts +115 -0
- package/packages/tools/lib/model/methodology-resolver.d.ts.map +1 -0
- package/packages/tools/lib/model/methodology-resolver.js +317 -0
- package/packages/tools/lib/model/methodology-resolver.js.map +1 -0
- package/packages/tools/lib/model/naming.d.ts +57 -0
- package/packages/tools/lib/model/naming.d.ts.map +1 -0
- package/packages/tools/lib/model/naming.js +69 -0
- package/packages/tools/lib/model/naming.js.map +1 -0
- package/packages/tools/lib/model/native-project.d.ts +130 -0
- package/packages/tools/lib/model/native-project.d.ts.map +1 -0
- package/packages/tools/lib/model/native-project.js +580 -0
- package/packages/tools/lib/model/native-project.js.map +1 -0
- package/packages/tools/lib/model/ontology-loader.d.ts +34 -21
- package/packages/tools/lib/model/ontology-loader.d.ts.map +1 -1
- package/packages/tools/lib/model/ontology-loader.js +343 -614
- package/packages/tools/lib/model/ontology-loader.js.map +1 -1
- package/packages/tools/lib/model/package-manifest.d.ts +37 -0
- package/packages/tools/lib/model/package-manifest.d.ts.map +1 -0
- package/packages/tools/lib/model/package-manifest.js +96 -0
- package/packages/tools/lib/model/package-manifest.js.map +1 -0
- package/packages/tools/lib/model/parser-utils.d.ts +31 -0
- package/packages/tools/lib/model/parser-utils.d.ts.map +1 -1
- package/packages/tools/lib/model/parser-utils.js +89 -1
- package/packages/tools/lib/model/parser-utils.js.map +1 -1
- package/packages/tools/lib/model/paths.d.ts +10 -4
- package/packages/tools/lib/model/paths.d.ts.map +1 -1
- package/packages/tools/lib/model/paths.js +10 -4
- package/packages/tools/lib/model/paths.js.map +1 -1
- package/packages/tools/lib/model/project-conversion.d.ts +226 -0
- package/packages/tools/lib/model/project-conversion.d.ts.map +1 -0
- package/packages/tools/lib/model/project-conversion.js +946 -0
- package/packages/tools/lib/model/project-conversion.js.map +1 -0
- package/packages/tools/lib/model/relationship-legality.d.ts +27 -0
- package/packages/tools/lib/model/relationship-legality.d.ts.map +1 -1
- package/packages/tools/lib/model/relationship-legality.js.map +1 -1
- package/packages/tools/lib/model/relationship-registry.d.ts +93 -1
- package/packages/tools/lib/model/relationship-registry.d.ts.map +1 -1
- package/packages/tools/lib/model/relationship-registry.js +217 -2
- package/packages/tools/lib/model/relationship-registry.js.map +1 -1
- package/packages/tools/lib/model/semantic.d.ts +73 -0
- package/packages/tools/lib/model/semantic.d.ts.map +1 -1
- package/packages/tools/lib/model/semantic.js +6 -0
- package/packages/tools/lib/model/semantic.js.map +1 -1
- package/packages/tools/lib/model/settings-boundary.d.ts +19 -0
- package/packages/tools/lib/model/settings-boundary.d.ts.map +1 -0
- package/packages/tools/lib/model/settings-boundary.js +118 -0
- package/packages/tools/lib/model/settings-boundary.js.map +1 -0
- package/packages/tools/lib/model/short-id.js +3 -3
- package/packages/tools/lib/model/short-id.js.map +1 -1
- package/packages/tools/lib/model/source-graph.d.ts.map +1 -1
- package/packages/tools/lib/model/source-graph.js +5 -1
- package/packages/tools/lib/model/source-graph.js.map +1 -1
- package/packages/tools/lib/model/source-provenance.d.ts +112 -0
- package/packages/tools/lib/model/source-provenance.d.ts.map +1 -0
- package/packages/tools/lib/model/source-provenance.js +169 -0
- package/packages/tools/lib/model/source-provenance.js.map +1 -0
- package/packages/tools/lib/model/sysml-ir.d.ts +9 -0
- package/packages/tools/lib/model/sysml-ir.d.ts.map +1 -0
- package/packages/tools/lib/model/sysml-ir.js +105 -0
- package/packages/tools/lib/model/sysml-ir.js.map +1 -0
- package/packages/tools/lib/model/view-deriver.d.ts +10 -1
- package/packages/tools/lib/model/view-deriver.d.ts.map +1 -1
- package/packages/tools/lib/model/view-deriver.js +139 -11
- package/packages/tools/lib/model/view-deriver.js.map +1 -1
- package/packages/tools/lib/model/view-kinds.d.ts +2 -0
- package/packages/tools/lib/model/view-kinds.d.ts.map +1 -1
- package/packages/tools/lib/model/view-kinds.js +11 -0
- package/packages/tools/lib/model/view-kinds.js.map +1 -1
- package/packages/tools/lib/ontology/owl-exporter.d.ts +2 -2
- package/packages/tools/lib/ontology/owl-exporter.d.ts.map +1 -1
- package/packages/tools/lib/ontology/owl-exporter.js +12 -12
- package/packages/tools/lib/ontology/owl-exporter.js.map +1 -1
- package/packages/tools/lib/operations/ai-authoring.d.ts +24 -0
- package/packages/tools/lib/operations/ai-authoring.d.ts.map +1 -0
- package/packages/tools/lib/operations/ai-authoring.js +77 -0
- package/packages/tools/lib/operations/ai-authoring.js.map +1 -0
- package/packages/tools/lib/operations/authoring.d.ts +40 -0
- package/packages/tools/lib/operations/authoring.d.ts.map +1 -0
- package/packages/tools/lib/operations/authoring.js +56 -0
- package/packages/tools/lib/operations/authoring.js.map +1 -0
- package/packages/tools/lib/operations/index.d.ts +2 -0
- package/packages/tools/lib/operations/index.d.ts.map +1 -1
- package/packages/tools/lib/operations/index.js +2 -0
- package/packages/tools/lib/operations/index.js.map +1 -1
- package/packages/tools/lib/operations/project-snapshot.d.ts +28 -3
- package/packages/tools/lib/operations/project-snapshot.d.ts.map +1 -1
- package/packages/tools/lib/operations/project-snapshot.js +79 -30
- package/packages/tools/lib/operations/project-snapshot.js.map +1 -1
- package/packages/tools/lib/protocol/messages.d.ts +381 -12
- package/packages/tools/lib/protocol/messages.d.ts.map +1 -1
- package/packages/tools/lib/serializer/csv-io.d.ts +7 -7
- package/packages/tools/lib/serializer/csv-io.d.ts.map +1 -1
- package/packages/tools/lib/serializer/csv-io.js +11 -11
- package/packages/tools/lib/serializer/csv-io.js.map +1 -1
- package/packages/tools/lib/serializer/sysml-generator.d.ts.map +1 -1
- package/packages/tools/lib/serializer/sysml-generator.js +34 -2
- package/packages/tools/lib/serializer/sysml-generator.js.map +1 -1
- package/packages/tools/lib/server/config-resolver.d.ts +8 -4
- package/packages/tools/lib/server/config-resolver.d.ts.map +1 -1
- package/packages/tools/lib/server/config-resolver.js +25 -44
- package/packages/tools/lib/server/config-resolver.js.map +1 -1
- package/packages/tools/lib/server/conflict-policy.d.ts +44 -0
- package/packages/tools/lib/server/conflict-policy.d.ts.map +1 -0
- package/packages/tools/lib/server/conflict-policy.js +94 -0
- package/packages/tools/lib/server/conflict-policy.js.map +1 -0
- package/packages/tools/lib/server/dev-server.d.ts +32 -2
- package/packages/tools/lib/server/dev-server.d.ts.map +1 -1
- package/packages/tools/lib/server/dev-server.js +875 -54
- package/packages/tools/lib/server/dev-server.js.map +1 -1
- package/packages/tools/lib/server/element-writer.d.ts +11 -0
- package/packages/tools/lib/server/element-writer.d.ts.map +1 -0
- package/packages/tools/lib/server/element-writer.js +123 -0
- package/packages/tools/lib/server/element-writer.js.map +1 -0
- package/packages/tools/lib/server/file-watcher.d.ts +4 -1
- package/packages/tools/lib/server/file-watcher.d.ts.map +1 -1
- package/packages/tools/lib/server/file-watcher.js +17 -7
- package/packages/tools/lib/server/file-watcher.js.map +1 -1
- package/packages/tools/lib/server/mutation-domain.d.ts +22 -0
- package/packages/tools/lib/server/mutation-domain.d.ts.map +1 -0
- package/packages/tools/lib/server/mutation-domain.js +87 -0
- package/packages/tools/lib/server/mutation-domain.js.map +1 -0
- package/packages/tools/lib/server/package-source.d.ts +26 -0
- package/packages/tools/lib/server/package-source.d.ts.map +1 -0
- package/packages/tools/lib/server/package-source.js +81 -0
- package/packages/tools/lib/server/package-source.js.map +1 -0
- package/packages/tools/lib/server/package-writer.d.ts +94 -0
- package/packages/tools/lib/server/package-writer.d.ts.map +1 -0
- package/packages/tools/lib/server/package-writer.js +317 -0
- package/packages/tools/lib/server/package-writer.js.map +1 -0
- package/packages/tools/lib/server/persistor.d.ts +81 -6
- package/packages/tools/lib/server/persistor.d.ts.map +1 -1
- package/packages/tools/lib/server/persistor.js +173 -44
- package/packages/tools/lib/server/persistor.js.map +1 -1
- package/packages/tools/lib/server/relationship-writer.d.ts +41 -5
- package/packages/tools/lib/server/relationship-writer.d.ts.map +1 -1
- package/packages/tools/lib/server/relationship-writer.js +157 -13
- package/packages/tools/lib/server/relationship-writer.js.map +1 -1
- package/packages/tools/lib/server/rule-policy-writer.d.ts +40 -0
- package/packages/tools/lib/server/rule-policy-writer.d.ts.map +1 -0
- package/packages/tools/lib/server/rule-policy-writer.js +124 -0
- package/packages/tools/lib/server/rule-policy-writer.js.map +1 -0
- package/packages/tools/lib/server/runtime-budget.d.ts +14 -0
- package/packages/tools/lib/server/runtime-budget.d.ts.map +1 -0
- package/packages/tools/lib/server/runtime-budget.js +15 -0
- package/packages/tools/lib/server/runtime-budget.js.map +1 -0
- package/packages/tools/lib/server/sysml-notation.d.ts +36 -0
- package/packages/tools/lib/server/sysml-notation.d.ts.map +1 -0
- package/packages/tools/lib/server/sysml-notation.js +104 -0
- package/packages/tools/lib/server/sysml-notation.js.map +1 -0
- package/packages/tools/lib/server/view-layout-store.d.ts.map +1 -1
- package/packages/tools/lib/server/view-layout-store.js +1 -0
- package/packages/tools/lib/server/view-layout-store.js.map +1 -1
- package/packages/tools/lib/sysml-ir/generated/sysml-derivations.d.ts +9 -0
- package/packages/tools/lib/sysml-ir/generated/sysml-derivations.d.ts.map +1 -0
- package/packages/tools/lib/sysml-ir/generated/sysml-derivations.js +315 -0
- package/packages/tools/lib/sysml-ir/generated/sysml-derivations.js.map +1 -0
- package/packages/tools/lib/sysml-ir/generated/sysml-metamodel.d.ts +5206 -0
- package/packages/tools/lib/sysml-ir/generated/sysml-metamodel.d.ts.map +1 -0
- package/packages/tools/lib/sysml-ir/generated/sysml-metamodel.js +1933 -0
- package/packages/tools/lib/sysml-ir/generated/sysml-metamodel.js.map +1 -0
- package/packages/tools/lib/sysml-ir/index.d.ts +3 -0
- package/packages/tools/lib/sysml-ir/index.d.ts.map +1 -0
- package/packages/tools/lib/sysml-ir/index.js +3 -0
- package/packages/tools/lib/sysml-ir/index.js.map +1 -0
- package/packages/tools/lib/sysml-ir/ir.d.ts +50 -0
- package/packages/tools/lib/sysml-ir/ir.d.ts.map +1 -0
- package/packages/tools/lib/sysml-ir/ir.js +2 -0
- package/packages/tools/lib/sysml-ir/ir.js.map +1 -0
- package/packages/tools/lib/sysmlc/bin/memo-sysmlc.d.ts +3 -0
- package/packages/tools/lib/sysmlc/bin/memo-sysmlc.d.ts.map +1 -0
- package/packages/tools/lib/sysmlc/bin/memo-sysmlc.js +97 -0
- package/packages/tools/lib/sysmlc/bin/memo-sysmlc.js.map +1 -0
- package/packages/tools/lib/sysmlc/index.d.ts +2 -0
- package/packages/tools/lib/sysmlc/index.d.ts.map +1 -0
- package/packages/tools/lib/sysmlc/index.js +7 -0
- package/packages/tools/lib/sysmlc/index.js.map +1 -0
- package/packages/tools/lib/sysmlc/server.d.ts +29 -0
- package/packages/tools/lib/sysmlc/server.d.ts.map +1 -0
- package/packages/tools/lib/sysmlc/server.js +168 -0
- package/packages/tools/lib/sysmlc/server.js.map +1 -0
- package/packages/tools/lib/toolchain/default-registry.d.ts +12 -0
- package/packages/tools/lib/toolchain/default-registry.d.ts.map +1 -0
- package/packages/tools/lib/toolchain/default-registry.js +28 -0
- package/packages/tools/lib/toolchain/default-registry.js.map +1 -0
- package/packages/tools/lib/toolchain/diagnostic.d.ts +57 -0
- package/packages/tools/lib/toolchain/diagnostic.d.ts.map +1 -0
- package/packages/tools/lib/toolchain/diagnostic.js +69 -0
- package/packages/tools/lib/toolchain/diagnostic.js.map +1 -0
- package/packages/tools/lib/toolchain/effective.d.ts +36 -0
- package/packages/tools/lib/toolchain/effective.d.ts.map +1 -0
- package/packages/tools/lib/toolchain/effective.js +91 -0
- package/packages/tools/lib/toolchain/effective.js.map +1 -0
- package/packages/tools/lib/toolchain/index.d.ts +11 -0
- package/packages/tools/lib/toolchain/index.d.ts.map +1 -0
- package/packages/tools/lib/toolchain/index.js +21 -0
- package/packages/tools/lib/toolchain/index.js.map +1 -0
- package/packages/tools/lib/toolchain/lowering.d.ts +63 -0
- package/packages/tools/lib/toolchain/lowering.d.ts.map +1 -0
- package/packages/tools/lib/toolchain/lowering.js +100 -0
- package/packages/tools/lib/toolchain/lowering.js.map +1 -0
- package/packages/tools/lib/toolchain/operations.d.ts +87 -0
- package/packages/tools/lib/toolchain/operations.d.ts.map +1 -0
- package/packages/tools/lib/toolchain/operations.js +111 -0
- package/packages/tools/lib/toolchain/operations.js.map +1 -0
- package/packages/tools/lib/toolchain/process.d.ts +45 -0
- package/packages/tools/lib/toolchain/process.d.ts.map +1 -0
- package/packages/tools/lib/toolchain/process.js +125 -0
- package/packages/tools/lib/toolchain/process.js.map +1 -0
- package/packages/tools/lib/toolchain/protocol.d.ts +122 -0
- package/packages/tools/lib/toolchain/protocol.d.ts.map +1 -0
- package/packages/tools/lib/toolchain/protocol.js +74 -0
- package/packages/tools/lib/toolchain/protocol.js.map +1 -0
- package/packages/tools/lib/toolchain/providers/internal-kpar.d.ts +9 -0
- package/packages/tools/lib/toolchain/providers/internal-kpar.d.ts.map +1 -0
- package/packages/tools/lib/toolchain/providers/internal-kpar.js +84 -0
- package/packages/tools/lib/toolchain/providers/internal-kpar.js.map +1 -0
- package/packages/tools/lib/toolchain/providers/internal.d.ts +33 -0
- package/packages/tools/lib/toolchain/providers/internal.d.ts.map +1 -0
- package/packages/tools/lib/toolchain/providers/internal.js +286 -0
- package/packages/tools/lib/toolchain/providers/internal.js.map +1 -0
- package/packages/tools/lib/toolchain/providers/sysand.d.ts +12 -0
- package/packages/tools/lib/toolchain/providers/sysand.d.ts.map +1 -0
- package/packages/tools/lib/toolchain/providers/sysand.js +110 -0
- package/packages/tools/lib/toolchain/providers/sysand.js.map +1 -0
- package/packages/tools/lib/toolchain/providers/syside.d.ts +24 -0
- package/packages/tools/lib/toolchain/providers/syside.d.ts.map +1 -0
- package/packages/tools/lib/toolchain/providers/syside.js +208 -0
- package/packages/tools/lib/toolchain/providers/syside.js.map +1 -0
- package/packages/tools/lib/toolchain/registry.d.ts +192 -0
- package/packages/tools/lib/toolchain/registry.d.ts.map +1 -0
- package/packages/tools/lib/toolchain/registry.js +130 -0
- package/packages/tools/lib/toolchain/registry.js.map +1 -0
- package/packages/tools/lib/toolchain/schema.d.ts +30 -0
- package/packages/tools/lib/toolchain/schema.d.ts.map +1 -0
- package/packages/tools/lib/toolchain/schema.js +112 -0
- package/packages/tools/lib/toolchain/schema.js.map +1 -0
- package/packages/tools/lib/toolchain/sysmlc-client.d.ts +77 -0
- package/packages/tools/lib/toolchain/sysmlc-client.d.ts.map +1 -0
- package/packages/tools/lib/toolchain/sysmlc-client.js +0 -0
- package/packages/tools/lib/toolchain/sysmlc-client.js.map +1 -0
- package/packages/tools/lib/validator/architecture-validator.d.ts +8 -1
- package/packages/tools/lib/validator/architecture-validator.d.ts.map +1 -1
- package/packages/tools/lib/validator/architecture-validator.js +25 -1
- package/packages/tools/lib/validator/architecture-validator.js.map +1 -1
- package/packages/tools/lib/validator/behavior-validator.d.ts +1 -1
- package/packages/tools/lib/validator/behavior-validator.js +1 -1
- package/packages/tools/lib/validator/builtin-rules.d.ts +16 -0
- package/packages/tools/lib/validator/builtin-rules.d.ts.map +1 -0
- package/packages/tools/lib/validator/builtin-rules.js +62 -0
- package/packages/tools/lib/validator/builtin-rules.js.map +1 -0
- package/packages/tools/lib/validator/constraint-eval.d.ts +31 -0
- package/packages/tools/lib/validator/constraint-eval.d.ts.map +1 -1
- package/packages/tools/lib/validator/constraint-eval.js +164 -7
- package/packages/tools/lib/validator/constraint-eval.js.map +1 -1
- package/packages/tools/lib/validator/constraint-loader.d.ts +22 -4
- package/packages/tools/lib/validator/constraint-loader.d.ts.map +1 -1
- package/packages/tools/lib/validator/constraint-loader.js +100 -27
- package/packages/tools/lib/validator/constraint-loader.js.map +1 -1
- package/packages/tools/lib/validator/rule-engine.d.ts +2 -1
- package/packages/tools/lib/validator/rule-engine.d.ts.map +1 -1
- package/packages/tools/lib/validator/rule-engine.js +56 -5
- package/packages/tools/lib/validator/rule-engine.js.map +1 -1
- package/packages/tools/lib/validator/sysml-constraints.d.ts +64 -0
- package/packages/tools/lib/validator/sysml-constraints.d.ts.map +1 -0
- package/packages/tools/lib/validator/sysml-constraints.js +251 -0
- package/packages/tools/lib/validator/sysml-constraints.js.map +1 -0
- package/packages/tools/lib/validator/types.d.ts +33 -0
- package/packages/tools/lib/validator/types.d.ts.map +1 -1
- package/packages/tools/lib/version.d.ts +2 -0
- package/packages/tools/lib/version.d.ts.map +1 -0
- package/packages/tools/lib/version.js +26 -0
- package/packages/tools/lib/version.js.map +1 -0
- package/packages/tools/src/grammar/memo-sysml.langium +517 -41
- package/packages/tools/lib/model/dimension-filter.d.ts +0 -33
- package/packages/tools/lib/model/dimension-filter.d.ts.map +0 -1
- package/packages/tools/lib/model/dimension-filter.js +0 -57
- package/packages/tools/lib/model/dimension-filter.js.map +0 -1
- package/packages/tools/lib/model/toolchain.d.ts +0 -12
- package/packages/tools/lib/model/toolchain.d.ts.map +0 -1
- package/packages/tools/lib/model/toolchain.js +0 -92
- package/packages/tools/lib/model/toolchain.js.map +0 -1
|
@@ -5,14 +5,49 @@
|
|
|
5
5
|
// - WebSocket endpoint for pushing model updates to browser
|
|
6
6
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
7
7
|
import { createServer as createHttpServer } from 'node:http';
|
|
8
|
-
import { createHash } from 'node:crypto';
|
|
8
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
9
9
|
import { isAbsolute, relative, resolve } from 'node:path';
|
|
10
10
|
import { existsSync, readFileSync, realpathSync, writeFileSync, mkdirSync, createReadStream, statSync } from 'node:fs';
|
|
11
11
|
import { extname } from 'node:path';
|
|
12
12
|
import { findMemoManifests, validateRelationshipMutation, validateRelationshipDeletion } from '@memoarchitect/tools';
|
|
13
|
-
import {
|
|
13
|
+
import { indexFromIdentityTable } from '../model/ir-identity.js';
|
|
14
|
+
import { recompileProject, writeElement } from '../operations/authoring.js';
|
|
15
|
+
import { isWritableRelationshipFile, removeRelationship, resolveRelationshipPlacement, updateRelationship, writeRelationship, } from './relationship-writer.js';
|
|
16
|
+
import { removeElement } from './element-writer.js';
|
|
17
|
+
import { DEFAULT_ELEMENT_FILE } from './persistor.js';
|
|
18
|
+
import { createPackage, deletePackage, moveElementToPackage, movePackage, renamePackage, } from './package-writer.js';
|
|
19
|
+
import { assertSingleDomainMutation, MixedMutationDomainError } from './mutation-domain.js';
|
|
20
|
+
import { classifyConflict } from './conflict-policy.js';
|
|
21
|
+
import { checkRulePolicy, insertRulePolicy, renderRulePolicy } from './rule-policy-writer.js';
|
|
14
22
|
import { loadViewLayouts, saveViewLayout } from './view-layout-store.js';
|
|
15
23
|
import { loadDhfDocs, saveDhfDoc, deleteDhfDoc, loadDhfSettings, saveDhfSettings, listRepoTemplates, readRepoTemplate, saveRepoTemplate, } from './dhf-doc-store.js';
|
|
24
|
+
const MODEL_MUTATION_MESSAGES = new Set([
|
|
25
|
+
'element:update', 'element:create', 'element:delete', 'element:remap-kinds', 'element:move-package',
|
|
26
|
+
'package:create', 'package:rename', 'package:delete', 'package:move',
|
|
27
|
+
'relationship:create', 'relationship:delete', 'relationship:update',
|
|
28
|
+
'diagram:create', 'diagram:update', 'diagram:delete', 'diagram:layout:update', 'diagram:source:save',
|
|
29
|
+
'methodology:source:save', 'csv:import', 'screen-capture:upload',
|
|
30
|
+
'ontology:install', 'ontology:remove', 'ontology:save-selection',
|
|
31
|
+
'dhf:doc:save', 'dhf:doc:delete', 'dhf:settings:save', 'dhf:template:save',
|
|
32
|
+
'llm:chat:apply', 'llm:settings:save',
|
|
33
|
+
]);
|
|
34
|
+
/** Exported so the lockout boundary is mechanically testable. */
|
|
35
|
+
export function isModelMutationMessage(type) {
|
|
36
|
+
return MODEL_MUTATION_MESSAGES.has(type);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Project/model state is reconciled over the MEMO WebSocket. Vite must never
|
|
40
|
+
* turn these writes into a browser reload, even when a local ontology package
|
|
41
|
+
* is symlinked beneath Vite's dependency graph.
|
|
42
|
+
*/
|
|
43
|
+
export function isModelOwnedWatchPath(path) {
|
|
44
|
+
const normalized = path.replaceAll('\\', '/');
|
|
45
|
+
return normalized.endsWith('.sysml')
|
|
46
|
+
|| normalized.endsWith('/memo.config.yaml')
|
|
47
|
+
|| normalized.endsWith('/memo.package.yaml')
|
|
48
|
+
|| normalized.includes('/.memo/')
|
|
49
|
+
|| normalized.includes('/model/assets/');
|
|
50
|
+
}
|
|
16
51
|
// ─── User-diagram persistence helpers ──────────────────────────────────────
|
|
17
52
|
function userDiagramsPath(projectRoot) {
|
|
18
53
|
return resolve(projectRoot, '.memo', 'user-diagrams.json');
|
|
@@ -41,6 +76,9 @@ const STATIC_MIME = {
|
|
|
41
76
|
'.json': 'application/json',
|
|
42
77
|
'.svg': 'image/svg+xml',
|
|
43
78
|
'.png': 'image/png',
|
|
79
|
+
'.jpg': 'image/jpeg',
|
|
80
|
+
'.jpeg': 'image/jpeg',
|
|
81
|
+
'.webp': 'image/webp',
|
|
44
82
|
'.ico': 'image/x-icon',
|
|
45
83
|
'.woff2': 'font/woff2',
|
|
46
84
|
'.woff': 'font/woff',
|
|
@@ -53,9 +91,79 @@ function streamFile(res, fullPath, status = 200) {
|
|
|
53
91
|
res.writeHead(status, { 'Content-Type': mime, 'Content-Length': size });
|
|
54
92
|
createReadStream(fullPath).pipe(res);
|
|
55
93
|
}
|
|
94
|
+
/** Resolve only project-owned screen captures under model/assets. */
|
|
95
|
+
export function resolveProjectAssetRequest(projectRoot, requestUrl) {
|
|
96
|
+
let pathname;
|
|
97
|
+
try {
|
|
98
|
+
pathname = decodeURIComponent(requestUrl.split('?')[0]);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
if (!pathname.startsWith('/model/'))
|
|
104
|
+
return undefined;
|
|
105
|
+
const assetRoot = resolve(projectRoot, 'model', 'assets');
|
|
106
|
+
const requested = resolve(projectRoot, pathname.replace(/^\//, ''));
|
|
107
|
+
const fromAssetRoot = relative(assetRoot, requested);
|
|
108
|
+
if (!fromAssetRoot || fromAssetRoot === '..' || fromAssetRoot.startsWith('../') || fromAssetRoot.startsWith('..\\')) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
return requested;
|
|
112
|
+
}
|
|
56
113
|
export async function createDevServer(options) {
|
|
57
114
|
const { port, webPackagePath, initialMessages, transformClientHtml } = options;
|
|
58
115
|
const host = '127.0.0.1';
|
|
116
|
+
// State publications are ordered independently of model semantics. The
|
|
117
|
+
// three state payloads from a rebuild share one revision so the browser
|
|
118
|
+
// cannot combine a model from one build with validation from another.
|
|
119
|
+
const workspaceSessionId = randomUUID();
|
|
120
|
+
const writeTransactions = new Map();
|
|
121
|
+
let workspaceRevision = 1;
|
|
122
|
+
let mutationLockout;
|
|
123
|
+
/** Set while the project source does not compile; cleared by the next clean rebuild. */
|
|
124
|
+
let sourceIncoherence;
|
|
125
|
+
function stampWorkspace(messages, baseRevision, snapshot = false, changedSourceIds = []) {
|
|
126
|
+
const model = messages.find(message => message.type === 'model:update');
|
|
127
|
+
const sourceGraphHash = createHash('sha256')
|
|
128
|
+
.update(JSON.stringify(model?.payload.sourceGraph ?? {})).digest('hex').slice(0, 16);
|
|
129
|
+
const reusableRegistryHash = String(model?.payload?.ontologyHash ?? 'none');
|
|
130
|
+
const projectRegistryHash = createHash('sha256')
|
|
131
|
+
.update(JSON.stringify(model?.payload.registries ?? {})).digest('hex').slice(0, 16);
|
|
132
|
+
const previous = initialMessages.find(message => message.type === 'model:update');
|
|
133
|
+
const modelDelta = !snapshot && model && previous
|
|
134
|
+
? {
|
|
135
|
+
upsertElements: Object.fromEntries(Object.entries(model.payload.elements).filter(([id, element]) => JSON.stringify(previous.payload.elements[id]) !== JSON.stringify(element))),
|
|
136
|
+
removeElementIds: Object.keys(previous.payload.elements).filter(id => !(id in model.payload.elements)),
|
|
137
|
+
patch: Object.fromEntries(Object.entries(model.payload).filter(([key]) => key !== 'elements')),
|
|
138
|
+
}
|
|
139
|
+
: undefined;
|
|
140
|
+
return messages.map(message => {
|
|
141
|
+
if (message.type !== 'model:update' &&
|
|
142
|
+
message.type !== 'validation:update' &&
|
|
143
|
+
message.type !== 'completeness:update')
|
|
144
|
+
return message;
|
|
145
|
+
return {
|
|
146
|
+
...message,
|
|
147
|
+
revision: {
|
|
148
|
+
workspaceSessionId, revision: workspaceRevision, baseRevision, snapshot,
|
|
149
|
+
sourceGraphHash, reusableRegistryHash, projectRegistryHash,
|
|
150
|
+
changedSourceIds: [...changedSourceIds].sort(),
|
|
151
|
+
modelDelta,
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const initialSnapshot = stampWorkspace(initialMessages, null, true);
|
|
157
|
+
initialMessages.length = 0;
|
|
158
|
+
initialMessages.push(...initialSnapshot);
|
|
159
|
+
function recordWriteTransaction(sourceFile, text) {
|
|
160
|
+
const id = randomUUID();
|
|
161
|
+
writeTransactions.set(sourceFile.replaceAll('\\', '/'), {
|
|
162
|
+
id, hash: createHash('sha256').update(text).digest('hex').slice(0, 16),
|
|
163
|
+
createdAt: Date.now(), unmatchedWrites: 0,
|
|
164
|
+
});
|
|
165
|
+
return id;
|
|
166
|
+
}
|
|
59
167
|
// A source client can use Vite middleware. A packaged client can provide a
|
|
60
168
|
// prebuilt dist/ directory. Tools does not resolve or depend on that client.
|
|
61
169
|
const hasWebSource = existsSync(resolve(webPackagePath, 'index.html'));
|
|
@@ -107,6 +215,20 @@ export async function createDevServer(options) {
|
|
|
107
215
|
streamFile(res, fullPath);
|
|
108
216
|
return true;
|
|
109
217
|
}
|
|
218
|
+
/** Serve captures referenced by project-relative ScreenCapture.imageUri. */
|
|
219
|
+
function serveProjectAsset(req, res) {
|
|
220
|
+
const requestUrl = req.url ?? '/';
|
|
221
|
+
if (!requestUrl.split('?')[0].startsWith('/model/'))
|
|
222
|
+
return false;
|
|
223
|
+
const requested = resolveProjectAssetRequest(options.projectRoot, requestUrl);
|
|
224
|
+
if (!requested || !existsSync(requested) || !statSync(requested).isFile()) {
|
|
225
|
+
res.writeHead(404);
|
|
226
|
+
res.end('Capture not found');
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
streamFile(res, requested);
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
110
232
|
/** Serve the caller-provided prebuilt client as an SPA. */
|
|
111
233
|
function serveWebDist(req, res) {
|
|
112
234
|
if (!hasWebDist)
|
|
@@ -140,13 +262,19 @@ export async function createDevServer(options) {
|
|
|
140
262
|
// Create Vite dev server in middleware mode
|
|
141
263
|
viteServer = await vite.createServer({
|
|
142
264
|
root: webPackagePath,
|
|
143
|
-
server: {
|
|
265
|
+
server: {
|
|
266
|
+
middlewareMode: true,
|
|
267
|
+
host,
|
|
268
|
+
watch: { ignored: isModelOwnedWatchPath },
|
|
269
|
+
},
|
|
144
270
|
appType: 'spa',
|
|
145
271
|
plugins: transformClientHtml
|
|
146
272
|
? [{ name: 'memo-client-html', transformIndexHtml: transformClientHtml }]
|
|
147
273
|
: [],
|
|
148
274
|
});
|
|
149
275
|
server = createHttpServer((req, res) => {
|
|
276
|
+
if (serveProjectAsset(req, res))
|
|
277
|
+
return;
|
|
150
278
|
if (serveHelp(req, res))
|
|
151
279
|
return;
|
|
152
280
|
viteServer.middlewares(req, res);
|
|
@@ -155,6 +283,8 @@ export async function createDevServer(options) {
|
|
|
155
283
|
else if (hasWebDist) {
|
|
156
284
|
// Packaged install: serve the prebuilt web app statically
|
|
157
285
|
server = createHttpServer((req, res) => {
|
|
286
|
+
if (serveProjectAsset(req, res))
|
|
287
|
+
return;
|
|
158
288
|
if (serveHelp(req, res))
|
|
159
289
|
return;
|
|
160
290
|
serveWebDist(req, res);
|
|
@@ -163,6 +293,8 @@ export async function createDevServer(options) {
|
|
|
163
293
|
else {
|
|
164
294
|
// Fallback: serve a basic page that connects via WebSocket
|
|
165
295
|
server = createHttpServer((req, res) => {
|
|
296
|
+
if (serveProjectAsset(req, res))
|
|
297
|
+
return;
|
|
166
298
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
167
299
|
res.end(`
|
|
168
300
|
<!DOCTYPE html>
|
|
@@ -188,6 +320,20 @@ export async function createDevServer(options) {
|
|
|
188
320
|
const { WebSocketServer } = await import('ws');
|
|
189
321
|
const wss = new WebSocketServer({ server });
|
|
190
322
|
const clients = new Set();
|
|
323
|
+
// User-created views are project state, not session state. Merge them into
|
|
324
|
+
// the initial model payload so a UI screen created from the workbench is
|
|
325
|
+
// still present after the dev server restarts.
|
|
326
|
+
const initialModelIndex = initialMessages.findIndex(message => message.type === 'model:update');
|
|
327
|
+
if (initialModelIndex >= 0) {
|
|
328
|
+
const initialModel = initialMessages[initialModelIndex];
|
|
329
|
+
const authored = initialModel.payload.diagrams ?? [];
|
|
330
|
+
const authoredIds = new Set(authored.map(diagram => diagram.id));
|
|
331
|
+
const userDiagrams = loadUserDiagrams(options.projectRoot).filter(diagram => !authoredIds.has(diagram.id));
|
|
332
|
+
initialMessages[initialModelIndex] = {
|
|
333
|
+
type: 'model:update',
|
|
334
|
+
payload: { ...initialModel.payload, diagrams: [...authored, ...userDiagrams] },
|
|
335
|
+
};
|
|
336
|
+
}
|
|
191
337
|
const currentDiagrams = () => {
|
|
192
338
|
const model = initialMessages.find(m => m.type === 'model:update');
|
|
193
339
|
return model?.payload.diagrams ?? [];
|
|
@@ -215,23 +361,156 @@ export async function createDevServer(options) {
|
|
|
215
361
|
client.send(JSON.stringify(updatedMsg));
|
|
216
362
|
}
|
|
217
363
|
}
|
|
364
|
+
function broadcastEditConflict(payload) {
|
|
365
|
+
const message = { type: 'app:edit-conflict', payload };
|
|
366
|
+
for (const client of clients) {
|
|
367
|
+
if (client.readyState === 1)
|
|
368
|
+
client.send(JSON.stringify(message));
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Provenance origin of a file being written.
|
|
373
|
+
*
|
|
374
|
+
* The caller has already established the path is inside the project root,
|
|
375
|
+
* so the only distinction left that changes the conflict route is whether
|
|
376
|
+
* it is reusable source the project happens to vendor — a methodology or
|
|
377
|
+
* extension package living in the workspace. Those are frozen for the
|
|
378
|
+
* session exactly as an installed package is, so a change to one escalates.
|
|
379
|
+
*/
|
|
380
|
+
function originOf(sourcePath) {
|
|
381
|
+
const methodology = initialMessages.find(m => m.type === 'methodology:update');
|
|
382
|
+
const methodologyFiles = new Set((methodology?.payload?.folders ?? [])
|
|
383
|
+
.flatMap((folder) => (folder.sourceFiles ?? [])
|
|
384
|
+
.map((file) => resolve(options.projectRoot, file))));
|
|
385
|
+
if (methodologyFiles.has(sourcePath))
|
|
386
|
+
return 'methodology';
|
|
387
|
+
return 'project';
|
|
388
|
+
}
|
|
389
|
+
function checkMutationPrecondition(precondition, rejectedCommandId, rejectedDraft) {
|
|
390
|
+
if (!precondition)
|
|
391
|
+
return 'The mutation has no source precondition; reload before saving.';
|
|
392
|
+
const projectPath = realpathSync(resolve(options.projectRoot));
|
|
393
|
+
const sourcePath = resolve(projectPath, precondition.sourceFile);
|
|
394
|
+
if (!sourcePath.startsWith(`${projectPath}/`))
|
|
395
|
+
return 'Mutation source is outside the project.';
|
|
396
|
+
const currentHash = existsSync(sourcePath) ? fileRevision(readFileSync(sourcePath, 'utf8')) : '';
|
|
397
|
+
// Section 13.5's two routes. The decision is in `conflict-policy.ts`
|
|
398
|
+
// so it can be tested without driving a live server; this applies it.
|
|
399
|
+
const decision = classifyConflict({
|
|
400
|
+
commandSessionId: precondition.workspaceSessionId,
|
|
401
|
+
currentSessionId: workspaceSessionId,
|
|
402
|
+
expectedSourceHash: precondition.expectedSourceHash,
|
|
403
|
+
currentSourceHash: currentHash,
|
|
404
|
+
origin: originOf(sourcePath),
|
|
405
|
+
// Source incoherence is deliberately NOT folded in here. It is held
|
|
406
|
+
// earlier, on the message handler, so no mutation reaches this
|
|
407
|
+
// classifier while the project does not parse. Routing it through
|
|
408
|
+
// here instead would classify a stale hash as
|
|
409
|
+
// `dependency-closure-uncomputable` and demand a relaunch — which
|
|
410
|
+
// is the one outcome a source typo must never produce.
|
|
411
|
+
dependencyClosureComputable: !mutationLockout,
|
|
412
|
+
});
|
|
413
|
+
if (decision.outcome === 'accept')
|
|
414
|
+
return undefined;
|
|
415
|
+
if (decision.outcome === 'lockout') {
|
|
416
|
+
const lockout = {
|
|
417
|
+
type: 'app:restart-required',
|
|
418
|
+
reason: decision.escalation === 'reusable-source-changed'
|
|
419
|
+
? 'ontology-source-changed' : 'dependency-closure-uncomputable',
|
|
420
|
+
changedFile: precondition.sourceFile,
|
|
421
|
+
instruction: decision.reason,
|
|
422
|
+
};
|
|
423
|
+
mutationLockout = lockout;
|
|
424
|
+
for (const client of clients) {
|
|
425
|
+
if (client.readyState === 1)
|
|
426
|
+
client.send(JSON.stringify(lockout));
|
|
427
|
+
}
|
|
428
|
+
return decision.reason;
|
|
429
|
+
}
|
|
430
|
+
broadcastEditConflict({
|
|
431
|
+
sourceFile: precondition.sourceFile,
|
|
432
|
+
targetElementIds: precondition.targetElementIds,
|
|
433
|
+
baseRevision: precondition.baseRevision,
|
|
434
|
+
currentRevision: workspaceRevision,
|
|
435
|
+
expectedSourceHash: precondition.expectedSourceHash,
|
|
436
|
+
currentSourceHash: currentHash,
|
|
437
|
+
rejectedCommandId,
|
|
438
|
+
rejectedDraft,
|
|
439
|
+
});
|
|
440
|
+
return `${precondition.sourceFile} changed on disk; no write was made.`;
|
|
441
|
+
}
|
|
218
442
|
/** Latest model DTO — broadcast() replaces initialMessages on every rebuild. */
|
|
219
443
|
const currentModel = () => initialMessages.find(m => m.type === 'model:update')?.payload;
|
|
444
|
+
/**
|
|
445
|
+
* Identities minted by the most recent authoring recompile.
|
|
446
|
+
*
|
|
447
|
+
* A write-back recompiles through the lowering provider and therefore knows
|
|
448
|
+
* the addresses its own edit produced before the file watcher has caught
|
|
449
|
+
* up. Holding them here is what lets a user make two edits in a row without
|
|
450
|
+
* the second one being addressed to a revision that no longer exists.
|
|
451
|
+
* Cleared by `broadcast`, because a published model supersedes it.
|
|
452
|
+
*/
|
|
453
|
+
let writeBackIndex;
|
|
454
|
+
/** The index every write resolves its identities against. */
|
|
455
|
+
const irIndex = () => writeBackIndex ?? indexFromIdentityTable(currentModel()?.irIdentities);
|
|
456
|
+
/**
|
|
457
|
+
* Recompile after a semantic write, through the *selected* lowering
|
|
458
|
+
* provider (§6.2).
|
|
459
|
+
*
|
|
460
|
+
* Not a refresh optimisation: the watcher rebuild that follows is what
|
|
461
|
+
* publishes the new model. This exists so the identities handed back with
|
|
462
|
+
* the write are the ones the write created, rather than whatever the client
|
|
463
|
+
* happened to be holding. It costs a project lowering per authored change,
|
|
464
|
+
* which is the price of an address that is never a revision behind.
|
|
465
|
+
*/
|
|
466
|
+
/** What every authoring operation is given: the project, its settings, and the index. */
|
|
467
|
+
async function authoringContext() {
|
|
468
|
+
const { loadAndResolveConfig } = await import('./config-resolver.js');
|
|
469
|
+
const { findConfigFile } = await import('@memoarchitect/tools');
|
|
470
|
+
const configPath = findConfigFile(options.projectRoot);
|
|
471
|
+
return {
|
|
472
|
+
projectDir: options.projectRoot,
|
|
473
|
+
config: configPath ? loadAndResolveConfig(configPath) : undefined,
|
|
474
|
+
irIndex: irIndex(),
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
async function recompileAfterWrite() {
|
|
478
|
+
try {
|
|
479
|
+
writeBackIndex = (await recompileProject(await authoringContext())).index;
|
|
480
|
+
}
|
|
481
|
+
catch (e) {
|
|
482
|
+
// A recompile that fails leaves the previous index in place: the
|
|
483
|
+
// write itself already succeeded, and refusing to answer would be
|
|
484
|
+
// reporting a failure that did not happen. The next watcher rebuild
|
|
485
|
+
// republishes the identities either way.
|
|
486
|
+
console.warn(`[Authoring] Recompile after write failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
220
489
|
/**
|
|
221
490
|
* Build the query context the LLM engines read from, against whatever the
|
|
222
491
|
* server currently holds. Every LLM handler needs the same four pieces, so
|
|
223
492
|
* they share this rather than each re-deriving them.
|
|
224
493
|
*/
|
|
225
494
|
async function llmQueryContext() {
|
|
226
|
-
const { createQueryContext, findConfigFile } = await import('@memoarchitect/tools');
|
|
495
|
+
const { createQueryContext, findConfigFile, loadOntologyRegistries, ontologyViewFrom, EMPTY_ONTOLOGY_VIEW } = await import('@memoarchitect/tools');
|
|
227
496
|
const { loadAndResolveConfig } = await import('./config-resolver.js');
|
|
228
497
|
const modelMsg = initialMessages.find(m => m.type === 'model:update');
|
|
229
498
|
const validMsg = initialMessages.find(m => m.type === 'validation:update');
|
|
230
499
|
const compMsg = initialMessages.find(m => m.type === 'completeness:update');
|
|
231
500
|
const configPath = findConfigFile(options.projectRoot);
|
|
232
|
-
const config = configPath ?
|
|
501
|
+
const config = configPath ? loadAndResolveConfig(configPath) : { projectName: 'untitled' };
|
|
502
|
+
// Kinds and relationships come from the resolved SysML, not from the
|
|
503
|
+
// settings file the config path points at.
|
|
504
|
+
let ontology = EMPTY_ONTOLOGY_VIEW;
|
|
505
|
+
try {
|
|
506
|
+
const loaded = await loadOntologyRegistries(options.projectRoot);
|
|
507
|
+
ontology = ontologyViewFrom(loaded.registries.kindRegistry, loaded.registries.relationshipRegistry);
|
|
508
|
+
}
|
|
509
|
+
catch {
|
|
510
|
+
// A project whose ontology does not resolve still has a model to query.
|
|
511
|
+
}
|
|
233
512
|
const ctx = createQueryContext(modelMsg?.payload ?? {}, validMsg?.payload ?? { violations: [] }, compMsg?.payload ?? { overall: 0, layers: [] }, config);
|
|
234
|
-
return { ctx, config };
|
|
513
|
+
return { ctx, config, ontology };
|
|
235
514
|
}
|
|
236
515
|
/** Settings status for the client — provider/model/origin, never the key. */
|
|
237
516
|
async function llmSettings() {
|
|
@@ -250,9 +529,9 @@ export async function createDevServer(options) {
|
|
|
250
529
|
const { saveElementToFile } = await import('./persistor.js');
|
|
251
530
|
switch (change.kind) {
|
|
252
531
|
case 'create-element': {
|
|
253
|
-
const {
|
|
254
|
-
const kindDef =
|
|
255
|
-
const result = saveElementToFile(options.projectRoot, {
|
|
532
|
+
const { ontology } = await llmQueryContext();
|
|
533
|
+
const kindDef = ontology.kinds[change.elementKind];
|
|
534
|
+
const result = await saveElementToFile(options.projectRoot, {
|
|
256
535
|
id: change.elementId,
|
|
257
536
|
name: change.name,
|
|
258
537
|
kind: change.elementKind,
|
|
@@ -263,20 +542,22 @@ export async function createDevServer(options) {
|
|
|
263
542
|
});
|
|
264
543
|
if (!result.success)
|
|
265
544
|
throw new Error(result.error ?? 'Failed to write the element.');
|
|
545
|
+
await recompileAfterWrite();
|
|
266
546
|
return;
|
|
267
547
|
}
|
|
268
548
|
case 'update-element': {
|
|
269
549
|
const existing = currentModel()?.elements?.[change.elementId];
|
|
270
550
|
if (!existing)
|
|
271
551
|
throw new Error(`Element "${change.elementId}" no longer exists.`);
|
|
272
|
-
const result = saveElementToFile(options.projectRoot, {
|
|
552
|
+
const result = await saveElementToFile(options.projectRoot, {
|
|
273
553
|
...existing,
|
|
274
554
|
...(change.changes.name !== undefined ? { name: change.changes.name } : {}),
|
|
275
555
|
...(change.changes.doc !== undefined ? { doc: change.changes.doc } : {}),
|
|
276
556
|
attributes: { ...existing.attributes, ...(change.changes.attributes ?? {}) },
|
|
277
|
-
});
|
|
557
|
+
}, irIndex());
|
|
278
558
|
if (!result.success)
|
|
279
559
|
throw new Error(result.error ?? 'Failed to write the element.');
|
|
560
|
+
await recompileAfterWrite();
|
|
280
561
|
return;
|
|
281
562
|
}
|
|
282
563
|
case 'create-relationship': {
|
|
@@ -328,6 +609,7 @@ export async function createDevServer(options) {
|
|
|
328
609
|
ontologyRoots: options.ontologyRoots,
|
|
329
610
|
designatedFiles: options.relationshipFiles,
|
|
330
611
|
canonicalFile: options.canonicalRelationshipFile,
|
|
612
|
+
irIndex: irIndex(),
|
|
331
613
|
};
|
|
332
614
|
}
|
|
333
615
|
/**
|
|
@@ -344,6 +626,9 @@ export async function createDevServer(options) {
|
|
|
344
626
|
type: 'relationship:create:result',
|
|
345
627
|
payload: { requestId, success: false, error, diagnostics },
|
|
346
628
|
});
|
|
629
|
+
const conflict = checkMutationPrecondition(payload.precondition, requestId, payload);
|
|
630
|
+
if (conflict)
|
|
631
|
+
return reject(conflict);
|
|
347
632
|
const model = currentModel();
|
|
348
633
|
if (!model)
|
|
349
634
|
return reject('The model is not loaded yet.');
|
|
@@ -377,8 +662,12 @@ export async function createDevServer(options) {
|
|
|
377
662
|
console.error(`[Relationship] Write failed: ${result.error}`);
|
|
378
663
|
return reject(result.error ?? 'The relationship could not be written.', validation.diagnostics);
|
|
379
664
|
}
|
|
380
|
-
console.log(`[Persisted] relationship ${result.relationshipId
|
|
381
|
-
`to ${result.sourceFile} [${result.placementReason}]`);
|
|
665
|
+
console.log(`[Persisted] relationship ${result.relationshipId ?? `(${result.notation})`} ` +
|
|
666
|
+
`(${validation.normalizedType}) to ${result.sourceFile} [${result.placementReason}]`);
|
|
667
|
+
if (result.sourceFile) {
|
|
668
|
+
recordWriteTransaction(result.sourceFile, readFileSync(resolve(options.projectRoot, result.sourceFile), 'utf8'));
|
|
669
|
+
}
|
|
670
|
+
await recompileAfterWrite();
|
|
382
671
|
// The file watcher rebuilds and broadcasts the canonical model; this
|
|
383
672
|
// response only confirms the write and carries the IDs the client needs
|
|
384
673
|
// to replace its pending row.
|
|
@@ -388,6 +677,8 @@ export async function createDevServer(options) {
|
|
|
388
677
|
requestId,
|
|
389
678
|
success: true,
|
|
390
679
|
relationshipId: result.relationshipId,
|
|
680
|
+
notation: result.notation,
|
|
681
|
+
declaration: result.declaration,
|
|
391
682
|
type: validation.normalizedType,
|
|
392
683
|
sourceId: payload.sourceId,
|
|
393
684
|
targetId: payload.targetId,
|
|
@@ -404,6 +695,9 @@ export async function createDevServer(options) {
|
|
|
404
695
|
type: 'relationship:delete:result',
|
|
405
696
|
payload: { requestId, success: false, relationshipId, error, diagnostics },
|
|
406
697
|
});
|
|
698
|
+
const conflict = checkMutationPrecondition(payload.precondition, requestId, payload);
|
|
699
|
+
if (conflict)
|
|
700
|
+
return reject(conflict);
|
|
407
701
|
const model = currentModel();
|
|
408
702
|
if (!model)
|
|
409
703
|
return reject('The model is not loaded yet.');
|
|
@@ -420,6 +714,10 @@ export async function createDevServer(options) {
|
|
|
420
714
|
return reject(result.error ?? 'The relationship could not be removed.');
|
|
421
715
|
}
|
|
422
716
|
console.log(`[Persisted] removed relationship ${relationshipId} from ${result.sourceFile}`);
|
|
717
|
+
if (result.sourceFile) {
|
|
718
|
+
recordWriteTransaction(result.sourceFile, readFileSync(resolve(options.projectRoot, result.sourceFile), 'utf8'));
|
|
719
|
+
}
|
|
720
|
+
await recompileAfterWrite();
|
|
423
721
|
return {
|
|
424
722
|
type: 'relationship:delete:result',
|
|
425
723
|
payload: {
|
|
@@ -431,6 +729,181 @@ export async function createDevServer(options) {
|
|
|
431
729
|
},
|
|
432
730
|
};
|
|
433
731
|
}
|
|
732
|
+
/** Move relationship endpoints as one source edit, including anonymous flows. */
|
|
733
|
+
async function handleRelationshipUpdate(payload) {
|
|
734
|
+
const requestId = payload?.requestId ?? '';
|
|
735
|
+
const relationshipId = payload?.relationshipId ?? '';
|
|
736
|
+
const reject = (error, diagnostics) => ({
|
|
737
|
+
type: 'relationship:update:result',
|
|
738
|
+
payload: { requestId, relationshipId, success: false, error, diagnostics },
|
|
739
|
+
});
|
|
740
|
+
const conflict = checkMutationPrecondition(payload.precondition, requestId, payload);
|
|
741
|
+
if (conflict)
|
|
742
|
+
return reject(conflict);
|
|
743
|
+
const model = currentModel();
|
|
744
|
+
if (!model)
|
|
745
|
+
return reject('The model is not loaded yet.');
|
|
746
|
+
const relationship = model.relationships.find(candidate => candidate.id === relationshipId);
|
|
747
|
+
if (!relationship)
|
|
748
|
+
return reject(`Relationship "${relationshipId}" was not found.`);
|
|
749
|
+
const mutation = {
|
|
750
|
+
...payload,
|
|
751
|
+
type: relationship.type,
|
|
752
|
+
direction: 'outgoing',
|
|
753
|
+
selectedElementId: payload.sourceId,
|
|
754
|
+
owningFile: relationship.file,
|
|
755
|
+
flowItem: relationship.flowItem,
|
|
756
|
+
};
|
|
757
|
+
// The relationship being replaced cannot consume its own uniqueness or
|
|
758
|
+
// endpoint multiplicity while validating the proposed endpoints.
|
|
759
|
+
const validationModel = {
|
|
760
|
+
...model,
|
|
761
|
+
relationships: model.relationships.filter(candidate => candidate.id !== relationshipId),
|
|
762
|
+
};
|
|
763
|
+
const validation = validateRelationshipMutation(mutation, validationModel, serverRegistries());
|
|
764
|
+
if (!validation.valid || !validation.definition) {
|
|
765
|
+
const first = validation.diagnostics.find(diagnostic => diagnostic.severity === 'error');
|
|
766
|
+
return reject(first?.message ?? 'The relationship update is not valid.', validation.diagnostics);
|
|
767
|
+
}
|
|
768
|
+
const result = await updateRelationship(payload, relationship, validation.definition, model, relationshipWriterOptions());
|
|
769
|
+
if (!result.success)
|
|
770
|
+
return reject(result.error ?? 'The relationship could not be updated.');
|
|
771
|
+
if (result.sourceFile) {
|
|
772
|
+
recordWriteTransaction(result.sourceFile, readFileSync(resolve(options.projectRoot, result.sourceFile), 'utf8'));
|
|
773
|
+
}
|
|
774
|
+
await recompileAfterWrite();
|
|
775
|
+
return {
|
|
776
|
+
type: 'relationship:update:result',
|
|
777
|
+
payload: {
|
|
778
|
+
requestId,
|
|
779
|
+
relationshipId,
|
|
780
|
+
success: true,
|
|
781
|
+
sourceId: payload.sourceId,
|
|
782
|
+
targetId: payload.targetId,
|
|
783
|
+
sourceFile: result.sourceFile,
|
|
784
|
+
diagnostics: validation.diagnostics.filter(diagnostic => diagnostic.severity === 'warning'),
|
|
785
|
+
},
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Apply one package mutation, resolving what the client named to a file.
|
|
790
|
+
*
|
|
791
|
+
* The client addresses a package by qualified name because that is what its
|
|
792
|
+
* tree is built from; which file declares it is the server's question, and
|
|
793
|
+
* answering it here keeps the browser from guessing at project layout. A
|
|
794
|
+
* package with no members yet still has a file — the model carries the
|
|
795
|
+
* declared packages, not only the ones with contents.
|
|
796
|
+
*/
|
|
797
|
+
async function handlePackageMutation(type, payload) {
|
|
798
|
+
const { projectRoot } = options;
|
|
799
|
+
const model = currentModel();
|
|
800
|
+
if (!model)
|
|
801
|
+
return { success: false, filePaths: [], error: 'The model is not loaded yet.' };
|
|
802
|
+
/** The file declaring a package: its own declaration, else its members'. */
|
|
803
|
+
const fileOf = (qualifiedName) => model.packages?.find(pkg => pkg.qualifiedName === qualifiedName)?.file
|
|
804
|
+
?? Object.values(model.elements).find(el => el.package === qualifiedName)?.file;
|
|
805
|
+
if (type === 'package:create') {
|
|
806
|
+
const parent = payload.parent || undefined;
|
|
807
|
+
const file = payload.file || (parent ? fileOf(parent) : undefined) || DEFAULT_ELEMENT_FILE;
|
|
808
|
+
if (parent && !fileOf(parent)) {
|
|
809
|
+
return { success: false, filePaths: [], error: `Package "${parent}" was not found.` };
|
|
810
|
+
}
|
|
811
|
+
return createPackage(projectRoot, { file, parent, name: String(payload.name ?? '') });
|
|
812
|
+
}
|
|
813
|
+
const qualifiedName = String(payload.qualifiedName ?? '');
|
|
814
|
+
if (type === 'package:rename' || type === 'package:delete' || type === 'package:move') {
|
|
815
|
+
const file = fileOf(qualifiedName);
|
|
816
|
+
if (!file)
|
|
817
|
+
return { success: false, filePaths: [], error: `Package "${qualifiedName}" was not found.` };
|
|
818
|
+
if (type === 'package:rename') {
|
|
819
|
+
return renamePackage(projectRoot, { file, qualifiedName, name: String(payload.name ?? '') });
|
|
820
|
+
}
|
|
821
|
+
if (type === 'package:delete')
|
|
822
|
+
return deletePackage(projectRoot, { file, qualifiedName });
|
|
823
|
+
const targetParent = payload.targetParent || undefined;
|
|
824
|
+
const targetFile = targetParent ? fileOf(targetParent) : file;
|
|
825
|
+
if (targetParent && !targetFile) {
|
|
826
|
+
return { success: false, filePaths: [], error: `Package "${targetParent}" was not found.` };
|
|
827
|
+
}
|
|
828
|
+
return movePackage(projectRoot, { file, qualifiedName, targetParent, targetFile: targetFile });
|
|
829
|
+
}
|
|
830
|
+
const elementId = String(payload.elementId ?? '');
|
|
831
|
+
const element = model.elements[elementId];
|
|
832
|
+
if (!element)
|
|
833
|
+
return { success: false, filePaths: [], error: `Element "${elementId}" was not found.` };
|
|
834
|
+
if (element.provenance && !element.provenance.declaration.writable) {
|
|
835
|
+
return {
|
|
836
|
+
success: false, filePaths: [],
|
|
837
|
+
error: `Element "${elementId}" is read-only ${element.provenance.declaration.origin} content.`,
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
const targetPackage = payload.targetPackage || undefined;
|
|
841
|
+
const targetFile = targetPackage ? fileOf(targetPackage) : element.file;
|
|
842
|
+
if (targetPackage && !targetFile) {
|
|
843
|
+
return { success: false, filePaths: [], error: `Package "${targetPackage}" was not found.` };
|
|
844
|
+
}
|
|
845
|
+
const irIdentity = irIndex().byMemoElement[elementId];
|
|
846
|
+
if (!irIdentity) {
|
|
847
|
+
return {
|
|
848
|
+
success: false, filePaths: [],
|
|
849
|
+
error: `"${elementId}" has no address in the current revision; reload before moving it.`,
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
return moveElementToPackage(projectRoot, {
|
|
853
|
+
elementId, irIdentity, sourceFile: element.file, targetFile: targetFile, targetPackage,
|
|
854
|
+
}, irIndex());
|
|
855
|
+
}
|
|
856
|
+
async function handleElementDelete(payload) {
|
|
857
|
+
const requestId = payload?.requestId ?? '';
|
|
858
|
+
const elementId = payload?.elementId ?? '';
|
|
859
|
+
const reject = (error) => ({
|
|
860
|
+
type: 'element:delete:result',
|
|
861
|
+
payload: { requestId, elementId, success: false, error },
|
|
862
|
+
});
|
|
863
|
+
const conflict = checkMutationPrecondition(payload.precondition, requestId, payload);
|
|
864
|
+
if (conflict)
|
|
865
|
+
return reject(conflict);
|
|
866
|
+
const model = currentModel();
|
|
867
|
+
if (!model)
|
|
868
|
+
return reject('The model is not loaded yet.');
|
|
869
|
+
const element = model.elements[elementId];
|
|
870
|
+
if (!element)
|
|
871
|
+
return reject(`Element "${elementId}" was not found.`);
|
|
872
|
+
if (element.provenance && !element.provenance.declaration.writable) {
|
|
873
|
+
return reject(`Element "${elementId}" is read-only ${element.provenance.declaration.origin} content.`);
|
|
874
|
+
}
|
|
875
|
+
const result = await removeElement(element, model, options.projectRoot);
|
|
876
|
+
if (result.success) {
|
|
877
|
+
for (const sourceFile of result.sourceFiles ?? []) {
|
|
878
|
+
const path = resolve(options.projectRoot, sourceFile);
|
|
879
|
+
if (existsSync(path))
|
|
880
|
+
recordWriteTransaction(sourceFile, readFileSync(path, 'utf8'));
|
|
881
|
+
}
|
|
882
|
+
// User-authored diagrams are sidecars rather than semantic
|
|
883
|
+
// relationships, but they must not retain a stale element ID.
|
|
884
|
+
const diagrams = loadUserDiagrams(options.projectRoot);
|
|
885
|
+
let changed = false;
|
|
886
|
+
const cleaned = diagrams.map(diagram => {
|
|
887
|
+
if (!diagram.elementIds?.includes(elementId))
|
|
888
|
+
return diagram;
|
|
889
|
+
changed = true;
|
|
890
|
+
return { ...diagram, elementIds: diagram.elementIds.filter(id => id !== elementId) };
|
|
891
|
+
});
|
|
892
|
+
if (changed)
|
|
893
|
+
saveUserDiagrams(options.projectRoot, cleaned);
|
|
894
|
+
}
|
|
895
|
+
return {
|
|
896
|
+
type: 'element:delete:result',
|
|
897
|
+
payload: {
|
|
898
|
+
requestId,
|
|
899
|
+
elementId,
|
|
900
|
+
success: result.success,
|
|
901
|
+
sourceFiles: result.sourceFiles,
|
|
902
|
+
removedRelationshipIds: result.removedRelationshipIds,
|
|
903
|
+
error: result.error,
|
|
904
|
+
},
|
|
905
|
+
};
|
|
906
|
+
}
|
|
434
907
|
/** Convert a DhfBlock (from document IR) to a markdown string */
|
|
435
908
|
function blockToMarkdown(block) {
|
|
436
909
|
switch (block.type) {
|
|
@@ -505,10 +978,17 @@ export async function createDevServer(options) {
|
|
|
505
978
|
}
|
|
506
979
|
wss.on('connection', (ws) => {
|
|
507
980
|
clients.add(ws);
|
|
981
|
+
options.onClientCountChanged?.(clients.size);
|
|
508
982
|
// Send initial state to new connections
|
|
509
983
|
for (const msg of initialMessages) {
|
|
510
984
|
ws.send(JSON.stringify(msg));
|
|
511
985
|
}
|
|
986
|
+
if (mutationLockout)
|
|
987
|
+
ws.send(JSON.stringify(mutationLockout));
|
|
988
|
+
// A client that connects while the source is broken must learn that the
|
|
989
|
+
// model it just received is the last good one, not the current file.
|
|
990
|
+
if (sourceIncoherence)
|
|
991
|
+
ws.send(JSON.stringify(sourceIncoherence));
|
|
512
992
|
// Send all sidecar layouts on connect
|
|
513
993
|
const layouts = loadViewLayouts(options.projectRoot, currentDiagrams());
|
|
514
994
|
if (Object.keys(layouts).length > 0) {
|
|
@@ -542,25 +1022,217 @@ export async function createDevServer(options) {
|
|
|
542
1022
|
console.error('[LLM] settings load failed:', e);
|
|
543
1023
|
}
|
|
544
1024
|
})();
|
|
545
|
-
ws.on('close', () =>
|
|
1025
|
+
ws.on('close', () => {
|
|
1026
|
+
clients.delete(ws);
|
|
1027
|
+
options.onClientCountChanged?.(clients.size);
|
|
1028
|
+
});
|
|
546
1029
|
ws.on('message', async (data) => {
|
|
547
1030
|
try {
|
|
548
1031
|
const msg = JSON.parse(data.toString());
|
|
1032
|
+
if (mutationLockout && isModelMutationMessage(msg.type)) {
|
|
1033
|
+
// A section 13.5 escalation is a hard server boundary;
|
|
1034
|
+
// stale and hand-authored clients cannot write through it.
|
|
1035
|
+
ws.send(JSON.stringify(mutationLockout));
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
if (sourceIncoherence && isModelMutationMessage(msg.type)) {
|
|
1039
|
+
// Held, not locked out: the write cannot be placed while the
|
|
1040
|
+
// closure is unknown, but the hold lifts by itself as soon
|
|
1041
|
+
// as the source parses again.
|
|
1042
|
+
ws.send(JSON.stringify(sourceIncoherence));
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
// One action writes one store (§6.2). A semantic mutation
|
|
1046
|
+
// carrying canvas geometry is refused here rather than at the
|
|
1047
|
+
// writer, so no handler has to remember the rule.
|
|
1048
|
+
try {
|
|
1049
|
+
assertSingleDomainMutation(msg.type, msg.payload);
|
|
1050
|
+
}
|
|
1051
|
+
catch (e) {
|
|
1052
|
+
if (!(e instanceof MixedMutationDomainError))
|
|
1053
|
+
throw e;
|
|
1054
|
+
console.warn(`[Mutation] ${e.message}`);
|
|
1055
|
+
ws.send(JSON.stringify({
|
|
1056
|
+
type: 'app:error',
|
|
1057
|
+
payload: { requestId: msg.payload?.requestId, error: e.message },
|
|
1058
|
+
}));
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
549
1061
|
if (msg.type === 'request:refresh') {
|
|
550
|
-
//
|
|
551
|
-
|
|
1062
|
+
// A missed publication always falls back to one coherent
|
|
1063
|
+
// snapshot, never a partial replay of old deltas.
|
|
1064
|
+
for (const m of stampWorkspace(initialMessages, null, true)) {
|
|
552
1065
|
ws.send(JSON.stringify(m));
|
|
553
1066
|
}
|
|
554
1067
|
}
|
|
555
1068
|
else if (msg.type === 'element:update' || msg.type === 'element:create') {
|
|
556
|
-
// 1. Persist to FS
|
|
557
|
-
const { saveElementToFile } = await import('./persistor.js');
|
|
558
1069
|
const { projectRoot } = options;
|
|
559
|
-
const
|
|
1070
|
+
const { requestId = '', id = '' } = msg.payload ?? {};
|
|
1071
|
+
const reply = (payload) => ws.send(JSON.stringify({
|
|
1072
|
+
type: 'element:mutation:result',
|
|
1073
|
+
payload: { requestId, elementId: id, ...payload },
|
|
1074
|
+
}));
|
|
1075
|
+
const existing = currentModel()?.elements[msg.payload?.id];
|
|
1076
|
+
if (existing?.provenance && !existing.provenance.declaration.writable) {
|
|
1077
|
+
reply({ success: false, rejectedDraft: msg.payload,
|
|
1078
|
+
error: `Element "${existing.id}" is read-only ${existing.provenance.declaration.origin} content.` });
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
const precondition = msg.payload?.precondition;
|
|
1082
|
+
if (!precondition) {
|
|
1083
|
+
reply({ success: false, conflict: true, rejectedDraft: msg.payload,
|
|
1084
|
+
error: 'The edit has no source precondition; reload it before saving.' });
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
if (precondition) {
|
|
1088
|
+
if (precondition.workspaceSessionId !== workspaceSessionId) {
|
|
1089
|
+
reply({ success: false, conflict: true, rejectedDraft: msg.payload,
|
|
1090
|
+
error: 'The workspace runtime changed; no write was made.' });
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
const sourcePath = resolve(projectRoot, precondition.sourceFile);
|
|
1094
|
+
const projectPath = realpathSync(resolve(projectRoot));
|
|
1095
|
+
// A precondition that does not name a project file is
|
|
1096
|
+
// untrustworthy: do not silently turn it into a write.
|
|
1097
|
+
if (!sourcePath.startsWith(`${projectPath}/`) ||
|
|
1098
|
+
(msg.type === 'element:update' && !existsSync(sourcePath))) {
|
|
1099
|
+
reply({ success: false, conflict: true, rejectedDraft: msg.payload,
|
|
1100
|
+
error: 'Mutation source is not a current project file.' });
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
const currentHash = existsSync(sourcePath)
|
|
1104
|
+
? fileRevision(readFileSync(sourcePath, 'utf8')) : '';
|
|
1105
|
+
if (precondition.expectedSourceHash !== currentHash) {
|
|
1106
|
+
broadcastEditConflict({
|
|
1107
|
+
sourceFile: precondition.sourceFile,
|
|
1108
|
+
targetElementIds: precondition.targetElementIds,
|
|
1109
|
+
baseRevision: precondition.baseRevision,
|
|
1110
|
+
currentRevision: workspaceRevision,
|
|
1111
|
+
expectedSourceHash: precondition.expectedSourceHash,
|
|
1112
|
+
currentSourceHash: currentHash,
|
|
1113
|
+
rejectedCommandId: requestId,
|
|
1114
|
+
rejectedDraft: msg.payload,
|
|
1115
|
+
});
|
|
1116
|
+
reply({ success: false, conflict: true, rejectedDraft: msg.payload,
|
|
1117
|
+
sourceFile: precondition.sourceFile,
|
|
1118
|
+
expectedSourceHash: precondition.expectedSourceHash,
|
|
1119
|
+
currentSourceHash: currentHash,
|
|
1120
|
+
error: `${precondition.sourceFile} changed on disk; no write was made.`,
|
|
1121
|
+
});
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
// One operation: write the source, then recompile through
|
|
1126
|
+
// the selected lowering provider so the identity the client
|
|
1127
|
+
// edits next is the one this write produced. The file
|
|
1128
|
+
// watcher still publishes the model.
|
|
1129
|
+
const result = await writeElement(await authoringContext(), msg.payload);
|
|
1130
|
+
let transactionId;
|
|
560
1131
|
if (result.success) {
|
|
561
|
-
|
|
1132
|
+
transactionId = recordWriteTransaction(result.filePath, readFileSync(resolve(projectRoot, result.filePath), 'utf8'));
|
|
1133
|
+
if (result.revision)
|
|
1134
|
+
writeBackIndex = result.revision.index;
|
|
562
1135
|
console.log(`[Persisted] ${msg.type} to ${result.filePath}`);
|
|
563
1136
|
}
|
|
1137
|
+
if (result.stale) {
|
|
1138
|
+
// The file may be byte-identical and the edit still
|
|
1139
|
+
// wrong, so this is announced as its own kind of
|
|
1140
|
+
// conflict rather than as "the file changed".
|
|
1141
|
+
broadcastEditConflict({
|
|
1142
|
+
reason: 'stale-identity',
|
|
1143
|
+
detail: result.error,
|
|
1144
|
+
sourceFile: result.filePath,
|
|
1145
|
+
targetElementIds: [id],
|
|
1146
|
+
baseRevision: precondition.baseRevision,
|
|
1147
|
+
currentRevision: workspaceRevision,
|
|
1148
|
+
expectedSourceHash: precondition.expectedSourceHash,
|
|
1149
|
+
currentSourceHash: precondition.expectedSourceHash,
|
|
1150
|
+
rejectedCommandId: requestId,
|
|
1151
|
+
rejectedDraft: msg.payload,
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
reply({
|
|
1155
|
+
success: result.success,
|
|
1156
|
+
sourceFile: result.filePath,
|
|
1157
|
+
transactionId,
|
|
1158
|
+
error: result.error,
|
|
1159
|
+
// A stale address is a conflict, not a malformed request:
|
|
1160
|
+
// the client's answer to both is to reload and retry.
|
|
1161
|
+
...(result.stale ? { conflict: true, stale: true, rejectedDraft: msg.payload } : {}),
|
|
1162
|
+
...(result.warnings ? { warnings: result.warnings } : {}),
|
|
1163
|
+
...(result.success ? { irIdentity: irIndex().byMemoElement[id] } : {}),
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
else if (msg.type === 'package:create' || msg.type === 'package:rename' ||
|
|
1167
|
+
msg.type === 'package:delete' || msg.type === 'package:move' ||
|
|
1168
|
+
msg.type === 'element:move-package') {
|
|
1169
|
+
// Containment is package membership, so every one of these is
|
|
1170
|
+
// a source edit on a `package` declaration — never a
|
|
1171
|
+
// directory, and never an attribute standing in for one.
|
|
1172
|
+
const { requestId = '' } = msg.payload ?? {};
|
|
1173
|
+
const reply = (payload) => ws.send(JSON.stringify({
|
|
1174
|
+
type: 'package:mutation:result',
|
|
1175
|
+
payload: { requestId, ...payload },
|
|
1176
|
+
}));
|
|
1177
|
+
try {
|
|
1178
|
+
const result = await handlePackageMutation(msg.type, msg.payload ?? {});
|
|
1179
|
+
if (result.success) {
|
|
1180
|
+
await recompileAfterWrite();
|
|
1181
|
+
console.log(`[Persisted] ${msg.type} to ${result.filePaths.join(', ')}`);
|
|
1182
|
+
}
|
|
1183
|
+
reply({ ...result });
|
|
1184
|
+
}
|
|
1185
|
+
catch (e) {
|
|
1186
|
+
reply({ success: false, filePaths: [], error: e?.message ?? String(e) });
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
else if (msg.type === 'screen-capture:upload') {
|
|
1190
|
+
const { requestId, viewName, fileName, base64, mediaType } = msg.payload ?? {};
|
|
1191
|
+
const reply = (payload) => ws.send(JSON.stringify({
|
|
1192
|
+
type: 'screen-capture:upload:result',
|
|
1193
|
+
payload: { requestId, ...payload },
|
|
1194
|
+
}));
|
|
1195
|
+
try {
|
|
1196
|
+
if (!requestId || typeof base64 !== 'string')
|
|
1197
|
+
throw new Error('The image payload is missing.');
|
|
1198
|
+
const extensions = {
|
|
1199
|
+
'image/png': '.png',
|
|
1200
|
+
'image/jpeg': '.jpg',
|
|
1201
|
+
'image/webp': '.webp',
|
|
1202
|
+
};
|
|
1203
|
+
const extension = extensions[mediaType];
|
|
1204
|
+
if (!extension)
|
|
1205
|
+
throw new Error('Only PNG, JPEG, and WebP captures are supported.');
|
|
1206
|
+
const bytes = Buffer.from(base64, 'base64');
|
|
1207
|
+
if (!bytes.length)
|
|
1208
|
+
throw new Error('The selected image is empty.');
|
|
1209
|
+
if (bytes.length > 25 * 1024 * 1024)
|
|
1210
|
+
throw new Error('Screen captures must be 25 MB or smaller.');
|
|
1211
|
+
const safeView = String(viewName || 'ui-screen')
|
|
1212
|
+
.normalize('NFKD').replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/^-+|-+$/g, '')
|
|
1213
|
+
|| 'ui-screen';
|
|
1214
|
+
const rawStem = String(fileName || 'capture').replace(/\.[^.]+$/, '');
|
|
1215
|
+
const safeStem = rawStem
|
|
1216
|
+
.normalize('NFKD').replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/^-+|-+$/g, '')
|
|
1217
|
+
|| 'capture';
|
|
1218
|
+
const assetDir = resolve(options.projectRoot, 'model', 'assets', safeView);
|
|
1219
|
+
const assetPath = resolve(assetDir, `${safeStem}${extension}`);
|
|
1220
|
+
const modelRoot = resolve(options.projectRoot, 'model');
|
|
1221
|
+
if (relative(modelRoot, assetPath).startsWith('..'))
|
|
1222
|
+
throw new Error('Invalid capture path.');
|
|
1223
|
+
mkdirSync(assetDir, { recursive: true });
|
|
1224
|
+
writeFileSync(assetPath, bytes);
|
|
1225
|
+
const imageUri = relative(options.projectRoot, assetPath).replaceAll('\\', '/');
|
|
1226
|
+
reply({
|
|
1227
|
+
success: true,
|
|
1228
|
+
imageUri,
|
|
1229
|
+
imageHash: createHash('sha256').update(bytes).digest('hex'),
|
|
1230
|
+
});
|
|
1231
|
+
console.log(`[Screen Capture] Saved ${imageUri}`);
|
|
1232
|
+
}
|
|
1233
|
+
catch (e) {
|
|
1234
|
+
reply({ success: false, error: e?.message ?? String(e) });
|
|
1235
|
+
}
|
|
564
1236
|
}
|
|
565
1237
|
else if (msg.type === 'diagram:create') {
|
|
566
1238
|
const { projectRoot } = options;
|
|
@@ -610,6 +1282,12 @@ export async function createDevServer(options) {
|
|
|
610
1282
|
else if (msg.type === 'relationship:delete') {
|
|
611
1283
|
ws.send(JSON.stringify(await handleRelationshipDelete(msg.payload)));
|
|
612
1284
|
}
|
|
1285
|
+
else if (msg.type === 'relationship:update') {
|
|
1286
|
+
ws.send(JSON.stringify(await handleRelationshipUpdate(msg.payload)));
|
|
1287
|
+
}
|
|
1288
|
+
else if (msg.type === 'element:delete') {
|
|
1289
|
+
ws.send(JSON.stringify(await handleElementDelete(msg.payload)));
|
|
1290
|
+
}
|
|
613
1291
|
else if (msg.type === 'element:remap-kinds') {
|
|
614
1292
|
// Remap orphaned kind references: for each element whose kind is in the
|
|
615
1293
|
// mappings, persist the element with the new kind. The file watcher will
|
|
@@ -624,11 +1302,13 @@ export async function createDevServer(options) {
|
|
|
624
1302
|
for (const element of Object.values(elements)) {
|
|
625
1303
|
const newKind = mappings[element.kind];
|
|
626
1304
|
if (newKind) {
|
|
627
|
-
const result = saveElementToFile(projectRoot, { ...element, kind: newKind });
|
|
1305
|
+
const result = await saveElementToFile(projectRoot, { ...element, kind: newKind }, irIndex());
|
|
628
1306
|
if (result.success)
|
|
629
1307
|
remappedCount++;
|
|
630
1308
|
}
|
|
631
1309
|
}
|
|
1310
|
+
if (remappedCount > 0)
|
|
1311
|
+
await recompileAfterWrite();
|
|
632
1312
|
console.log(`[Remap] Remapped ${remappedCount} elements across ${Object.keys(mappings).length} kind(s)`);
|
|
633
1313
|
ws.send(JSON.stringify({ type: 'remap:result', payload: { success: true, count: remappedCount } }));
|
|
634
1314
|
}
|
|
@@ -678,6 +1358,97 @@ export async function createDevServer(options) {
|
|
|
678
1358
|
payload: { diagramId: msg.payload.diagramId, elementIds, errors: [] },
|
|
679
1359
|
}));
|
|
680
1360
|
}
|
|
1361
|
+
else if (msg.type === 'methodology:source:request' || msg.type === 'methodology:source:save') {
|
|
1362
|
+
const { requestId, sourceFile } = msg.payload ?? {};
|
|
1363
|
+
const operation = msg.type === 'methodology:source:save' ? 'save' : 'load';
|
|
1364
|
+
const reply = (payload) => ws.send(JSON.stringify({
|
|
1365
|
+
type: 'methodology:source:result',
|
|
1366
|
+
payload: { requestId, sourceFile, operation, ...payload },
|
|
1367
|
+
}));
|
|
1368
|
+
try {
|
|
1369
|
+
const methodology = initialMessages.find(message => message.type === 'methodology:update');
|
|
1370
|
+
const allowed = new Set((methodology?.payload?.folders ?? []).flatMap((folder) => (folder.sourceFiles ?? []).map((file) => resolve(options.projectRoot, file))));
|
|
1371
|
+
const path = resolve(options.projectRoot, String(sourceFile ?? ''));
|
|
1372
|
+
if (!allowed.has(path) || !path.endsWith('.sysml')) {
|
|
1373
|
+
throw new Error('The requested file is not in the resolved methodology source set.');
|
|
1374
|
+
}
|
|
1375
|
+
const onDisk = readFileSync(path, 'utf8');
|
|
1376
|
+
const revision = fileRevision(onDisk);
|
|
1377
|
+
if (operation === 'load') {
|
|
1378
|
+
reply({ success: true, text: onDisk, revision });
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
const { text, baseRevision } = msg.payload;
|
|
1382
|
+
if (typeof text !== 'string' || typeof baseRevision !== 'string') {
|
|
1383
|
+
throw new Error('Text and source revision are required.');
|
|
1384
|
+
}
|
|
1385
|
+
if (baseRevision !== revision) {
|
|
1386
|
+
const lockout = {
|
|
1387
|
+
type: 'app:restart-required',
|
|
1388
|
+
reason: 'ontology-source-changed',
|
|
1389
|
+
changedFile: String(sourceFile),
|
|
1390
|
+
instruction: 'Reusable methodology source changed externally. Model mutations are locked; Relaunch Memo Architect.',
|
|
1391
|
+
};
|
|
1392
|
+
mutationLockout = lockout;
|
|
1393
|
+
for (const client of clients) {
|
|
1394
|
+
if (client.readyState === 1)
|
|
1395
|
+
client.send(JSON.stringify(lockout));
|
|
1396
|
+
}
|
|
1397
|
+
reply({ success: false, conflict: true, text: onDisk, revision,
|
|
1398
|
+
error: `${sourceFile} changed on disk; no write was made.` });
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
writeFileSync(path, text, 'utf8');
|
|
1402
|
+
const transactionId = recordWriteTransaction(String(sourceFile), text);
|
|
1403
|
+
reply({ success: true, revision: fileRevision(text), transactionId });
|
|
1404
|
+
}
|
|
1405
|
+
catch (error) {
|
|
1406
|
+
reply({ success: false, error: error instanceof Error ? error.message : String(error) });
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
else if (msg.type === 'rule:policy:write') {
|
|
1410
|
+
// Tailoring a rule from the browser goes through exactly
|
|
1411
|
+
// the path a hand edit does: the same allowed-source check,
|
|
1412
|
+
// the same base-revision precondition, the same transaction
|
|
1413
|
+
// ID. The editor supplies a decision; the SysML is rendered
|
|
1414
|
+
// here, so the browser never composes model source.
|
|
1415
|
+
const { requestId } = msg.payload ?? {};
|
|
1416
|
+
const reply = (payload) => ws.send(JSON.stringify({
|
|
1417
|
+
type: 'rule:policy:write:result', payload: { requestId, ...payload },
|
|
1418
|
+
}));
|
|
1419
|
+
try {
|
|
1420
|
+
const request = msg.payload;
|
|
1421
|
+
const methodology = initialMessages.find(message => message.type === 'methodology:update');
|
|
1422
|
+
const effectiveRules = initialMessages.find(message => message.type === 'rules:update')?.payload?.rules ?? [];
|
|
1423
|
+
const refusal = checkRulePolicy(request, effectiveRules);
|
|
1424
|
+
if (refusal) {
|
|
1425
|
+
reply({ success: false, code: refusal.code, error: refusal.message });
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
const allowed = new Set((methodology?.payload?.folders ?? []).flatMap((folder) => (folder.sourceFiles ?? []).map((file) => resolve(options.projectRoot, file))));
|
|
1429
|
+
const path = resolve(options.projectRoot, String(request.sourceFile ?? ''));
|
|
1430
|
+
if (!allowed.has(path) || !path.endsWith('.sysml')) {
|
|
1431
|
+
throw new Error('The target file is not in the resolved methodology source set.');
|
|
1432
|
+
}
|
|
1433
|
+
const onDisk = readFileSync(path, 'utf8');
|
|
1434
|
+
const revision = fileRevision(onDisk);
|
|
1435
|
+
if (request.baseRevision !== revision) {
|
|
1436
|
+
reply({ success: false, conflict: true, revision,
|
|
1437
|
+
error: `${request.sourceFile} changed on disk; no write was made.` });
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
const updated = insertRulePolicy(onDisk, renderRulePolicy(request));
|
|
1441
|
+
if (!updated) {
|
|
1442
|
+
throw new Error(`${request.sourceFile} declares no MethodologyDefinition to attach the policy to.`);
|
|
1443
|
+
}
|
|
1444
|
+
writeFileSync(path, updated, 'utf8');
|
|
1445
|
+
const transactionId = recordWriteTransaction(String(request.sourceFile), updated);
|
|
1446
|
+
reply({ success: true, revision: fileRevision(updated), transactionId });
|
|
1447
|
+
}
|
|
1448
|
+
catch (error) {
|
|
1449
|
+
reply({ success: false, error: error instanceof Error ? error.message : String(error) });
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
681
1452
|
else if (msg.type === 'diagram:source:request') {
|
|
682
1453
|
const { requestId, diagramId } = msg.payload ?? {};
|
|
683
1454
|
try {
|
|
@@ -703,15 +1474,27 @@ export async function createDevServer(options) {
|
|
|
703
1474
|
try {
|
|
704
1475
|
if (typeof text !== 'string')
|
|
705
1476
|
throw new Error('SysML source text is required.');
|
|
1477
|
+
if (typeof baseRevision !== 'string')
|
|
1478
|
+
throw new Error('A source revision is required; reload the file before saving.');
|
|
706
1479
|
const source = diagramSource(String(diagramId ?? ''));
|
|
707
1480
|
// Refuse to overwrite work that arrived after this edit
|
|
708
1481
|
// began. The client gets the current file back so it can
|
|
709
1482
|
// show the conflict instead of losing one of the two.
|
|
710
1483
|
const onDisk = readFileSync(source.path, 'utf8');
|
|
711
1484
|
const currentRevision = fileRevision(onDisk);
|
|
712
|
-
if (
|
|
1485
|
+
if (baseRevision !== currentRevision) {
|
|
713
1486
|
console.warn(`[Diagram] Refused stale save of ${source.sourceFile} ` +
|
|
714
1487
|
`(based on ${baseRevision}, disk is ${currentRevision})`);
|
|
1488
|
+
broadcastEditConflict({
|
|
1489
|
+
sourceFile: source.sourceFile,
|
|
1490
|
+
targetElementIds: currentDiagrams().find(d => d.id === diagramId)?.elementIds ?? [],
|
|
1491
|
+
baseRevision,
|
|
1492
|
+
currentRevision,
|
|
1493
|
+
expectedSourceHash: baseRevision,
|
|
1494
|
+
currentSourceHash: currentRevision,
|
|
1495
|
+
rejectedCommandId: requestId,
|
|
1496
|
+
rejectedDraft: { diagramId, text },
|
|
1497
|
+
});
|
|
715
1498
|
ws.send(JSON.stringify({
|
|
716
1499
|
type: 'diagram:source:result',
|
|
717
1500
|
payload: {
|
|
@@ -723,11 +1506,13 @@ export async function createDevServer(options) {
|
|
|
723
1506
|
return;
|
|
724
1507
|
}
|
|
725
1508
|
writeFileSync(source.path, text, 'utf8');
|
|
1509
|
+
const transactionId = recordWriteTransaction(source.sourceFile, text);
|
|
726
1510
|
ws.send(JSON.stringify({
|
|
727
1511
|
type: 'diagram:source:result',
|
|
728
1512
|
payload: {
|
|
729
1513
|
requestId, diagramId, operation: 'save', success: true,
|
|
730
1514
|
sourceFile: source.sourceFile, revision: fileRevision(text),
|
|
1515
|
+
transactionId,
|
|
731
1516
|
parseErrors: await sysmlParseErrors(text),
|
|
732
1517
|
},
|
|
733
1518
|
}));
|
|
@@ -881,17 +1666,9 @@ export async function createDevServer(options) {
|
|
|
881
1666
|
const { projectRoot } = options;
|
|
882
1667
|
const { elementsCsv, relationshipsCsv, packageName, targetFile } = msg.payload ?? {};
|
|
883
1668
|
try {
|
|
884
|
-
const { parseElementsCsv, parseRelationshipsCsv, generateFile, attachProvenance,
|
|
885
|
-
const
|
|
886
|
-
const
|
|
887
|
-
if (!configPath) {
|
|
888
|
-
ws.send(JSON.stringify({
|
|
889
|
-
type: 'import:result',
|
|
890
|
-
payload: { success: false, elementsImported: 0, relationshipsImported: 0, errors: ['No memo config found'], warnings: [] },
|
|
891
|
-
}));
|
|
892
|
-
return;
|
|
893
|
-
}
|
|
894
|
-
const config = await loadAndResolveConfig(configPath);
|
|
1669
|
+
const { parseElementsCsv, parseRelationshipsCsv, generateFile, attachProvenance, loadProjectSettings, } = await import('@memoarchitect/tools');
|
|
1670
|
+
const config = loadProjectSettings(projectRoot);
|
|
1671
|
+
const { ontology } = await llmQueryContext();
|
|
895
1672
|
const errors = [];
|
|
896
1673
|
const warnings = [];
|
|
897
1674
|
let elementsImported = 0;
|
|
@@ -899,7 +1676,7 @@ export async function createDevServer(options) {
|
|
|
899
1676
|
let elements = [];
|
|
900
1677
|
let relationships = [];
|
|
901
1678
|
if (elementsCsv) {
|
|
902
|
-
const result = parseElementsCsv(elementsCsv,
|
|
1679
|
+
const result = parseElementsCsv(elementsCsv, ontology);
|
|
903
1680
|
errors.push(...result.errors);
|
|
904
1681
|
warnings.push(...result.warnings);
|
|
905
1682
|
// Attach provenance
|
|
@@ -915,7 +1692,7 @@ export async function createDevServer(options) {
|
|
|
915
1692
|
const knownIds = elementsImported > 0
|
|
916
1693
|
? new Set(elements.map((e) => e.id))
|
|
917
1694
|
: undefined;
|
|
918
|
-
const result = parseRelationshipsCsv(relationshipsCsv,
|
|
1695
|
+
const result = parseRelationshipsCsv(relationshipsCsv, ontology, knownIds);
|
|
919
1696
|
errors.push(...result.errors);
|
|
920
1697
|
warnings.push(...result.warnings);
|
|
921
1698
|
relationships = result.items;
|
|
@@ -986,10 +1763,10 @@ export async function createDevServer(options) {
|
|
|
986
1763
|
ws.send(JSON.stringify({ type: 'llm:chat:result', payload: { requestId, error: 'No LLM provider configured. Add an API key in Settings, set ANTHROPIC_API_KEY or OPENAI_API_KEY, or put one in a project .env file.' } }));
|
|
987
1764
|
}
|
|
988
1765
|
else {
|
|
989
|
-
const { ctx,
|
|
1766
|
+
const { ctx, ontology } = await llmQueryContext();
|
|
990
1767
|
const provider = createProvider(llmConfig);
|
|
991
1768
|
const result = await runChatTurn({
|
|
992
|
-
question, history, ctx, provider,
|
|
1769
|
+
question, history, ctx, provider, ontology,
|
|
993
1770
|
allowEdits: allowEdits === true,
|
|
994
1771
|
});
|
|
995
1772
|
ws.send(JSON.stringify({
|
|
@@ -1068,22 +1845,13 @@ export async function createDevServer(options) {
|
|
|
1068
1845
|
}
|
|
1069
1846
|
}
|
|
1070
1847
|
else if (msg.type === 'llm:generate') {
|
|
1071
|
-
//
|
|
1848
|
+
// Argument marshalling only: memo-tools owns generation,
|
|
1849
|
+
// validation, repair, and normalized diagnostics.
|
|
1072
1850
|
const { requestId, description } = msg.payload ?? {};
|
|
1073
1851
|
try {
|
|
1074
|
-
const {
|
|
1075
|
-
const
|
|
1076
|
-
|
|
1077
|
-
if (!llmConfig) {
|
|
1078
|
-
ws.send(JSON.stringify({ type: 'llm:generate:result', payload: { requestId, error: 'No LLM provider configured.' } }));
|
|
1079
|
-
}
|
|
1080
|
-
else {
|
|
1081
|
-
const configPath = findConfigFile(options.projectRoot);
|
|
1082
|
-
const config = configPath ? await loadAndResolveConfig(configPath) : {};
|
|
1083
|
-
const provider = createProvider(llmConfig);
|
|
1084
|
-
const result = await generateSysml(description, config, provider);
|
|
1085
|
-
ws.send(JSON.stringify({ type: 'llm:generate:result', payload: { requestId, sysml: result.sysml, explanation: result.explanation, suggestedFile: result.suggestedFile } }));
|
|
1086
|
-
}
|
|
1852
|
+
const { generateValidatedSysml } = await import('../operations/ai-authoring.js');
|
|
1853
|
+
const result = await generateValidatedSysml({ projectRoot: options.projectRoot, description });
|
|
1854
|
+
ws.send(JSON.stringify({ type: 'llm:generate:result', payload: { requestId, ...result } }));
|
|
1087
1855
|
}
|
|
1088
1856
|
catch (e) {
|
|
1089
1857
|
console.error('[LLM] generate failed:', e);
|
|
@@ -1291,14 +2059,22 @@ Return ONLY a JSON array of strings. Each string is a concise, actionable sugges
|
|
|
1291
2059
|
await new Promise((resolve) => {
|
|
1292
2060
|
server.listen(port, host, () => resolve());
|
|
1293
2061
|
});
|
|
2062
|
+
const address = server.address();
|
|
1294
2063
|
return {
|
|
1295
|
-
|
|
2064
|
+
port: typeof address === 'object' && address ? address.port : port,
|
|
2065
|
+
broadcast(messages, changedSourceIds = []) {
|
|
1296
2066
|
// Update initial messages for new connections
|
|
2067
|
+
const baseRevision = workspaceRevision;
|
|
2068
|
+
workspaceRevision++;
|
|
2069
|
+
const publication = stampWorkspace(messages, baseRevision, false, changedSourceIds);
|
|
2070
|
+
// A published model carries its own identities; the write-back's
|
|
2071
|
+
// provisional index has been superseded.
|
|
2072
|
+
writeBackIndex = undefined;
|
|
1297
2073
|
initialMessages.length = 0;
|
|
1298
|
-
initialMessages.push(...
|
|
2074
|
+
initialMessages.push(...publication);
|
|
1299
2075
|
for (const client of clients) {
|
|
1300
2076
|
if (client.readyState === 1) { // WebSocket.OPEN
|
|
1301
|
-
for (const msg of
|
|
2077
|
+
for (const msg of publication) {
|
|
1302
2078
|
client.send(JSON.stringify(msg));
|
|
1303
2079
|
}
|
|
1304
2080
|
}
|
|
@@ -1312,6 +2088,51 @@ Return ONLY a JSON array of strings. Each string is a concise, actionable sugges
|
|
|
1312
2088
|
}
|
|
1313
2089
|
}
|
|
1314
2090
|
},
|
|
2091
|
+
consumeWriteTransactions(files) {
|
|
2092
|
+
const matched = {};
|
|
2093
|
+
let escalationFile;
|
|
2094
|
+
for (const file of files) {
|
|
2095
|
+
const normalized = file.replaceAll('\\', '/');
|
|
2096
|
+
const transaction = writeTransactions.get(normalized);
|
|
2097
|
+
if (!transaction)
|
|
2098
|
+
continue;
|
|
2099
|
+
const path = resolve(options.projectRoot, normalized);
|
|
2100
|
+
const currentHash = existsSync(path) ? fileRevision(readFileSync(path, 'utf8')) : '';
|
|
2101
|
+
if (currentHash === transaction.hash) {
|
|
2102
|
+
matched[normalized] = transaction.id;
|
|
2103
|
+
writeTransactions.delete(normalized);
|
|
2104
|
+
continue;
|
|
2105
|
+
}
|
|
2106
|
+
transaction.unmatchedWrites++;
|
|
2107
|
+
if (Date.now() - transaction.createdAt > 5000) {
|
|
2108
|
+
writeTransactions.delete(normalized);
|
|
2109
|
+
}
|
|
2110
|
+
else if (transaction.unmatchedWrites >= 2) {
|
|
2111
|
+
escalationFile = normalized;
|
|
2112
|
+
writeTransactions.delete(normalized);
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
return { matched, escalationFile };
|
|
2116
|
+
},
|
|
2117
|
+
lockMutations(message) {
|
|
2118
|
+
mutationLockout = message;
|
|
2119
|
+
for (const client of clients) {
|
|
2120
|
+
if (client.readyState === 1)
|
|
2121
|
+
client.send(JSON.stringify(message));
|
|
2122
|
+
}
|
|
2123
|
+
},
|
|
2124
|
+
setSourceCoherence(payload) {
|
|
2125
|
+
const message = { type: 'source:coherence', payload };
|
|
2126
|
+
// Recovery is announced once, not on every clean rebuild after it —
|
|
2127
|
+
// a coherent project is the ordinary case and says nothing.
|
|
2128
|
+
if (payload.coherent && !sourceIncoherence)
|
|
2129
|
+
return;
|
|
2130
|
+
sourceIncoherence = payload.coherent ? undefined : message;
|
|
2131
|
+
for (const client of clients) {
|
|
2132
|
+
if (client.readyState === 1)
|
|
2133
|
+
client.send(JSON.stringify(message));
|
|
2134
|
+
}
|
|
2135
|
+
},
|
|
1315
2136
|
close() {
|
|
1316
2137
|
wss.close();
|
|
1317
2138
|
viteServer?.close();
|