@memoarchitect/tools 0.6.7 → 0.7.2
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/package.json +19 -7
- package/packages/tools/lib/bin/memo.js +161 -10
- package/packages/tools/lib/bin/memo.js.map +1 -1
- package/packages/tools/lib/browser/index.d.ts +1 -1
- package/packages/tools/lib/browser/index.d.ts.map +1 -1
- package/packages/tools/lib/browser/index.js +1 -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 +208 -74
- 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-preview.js +1 -1
- package/packages/tools/lib/commands/dhf-preview.js.map +1 -1
- package/packages/tools/lib/commands/dhf.js +1 -1
- 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/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 +183 -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/template-resolver.js +1 -1
- 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/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 +196 -51
- package/packages/tools/lib/language/generated/ast.d.ts.map +1 -1
- package/packages/tools/lib/language/generated/ast.js +136 -15
- 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 +867 -295
- 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 +231 -19
- 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 +114 -1
- package/packages/tools/lib/model/kind-registry.d.ts.map +1 -1
- package/packages/tools/lib/model/kind-registry.js +148 -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/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/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 +570 -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 +340 -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 +10 -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 +16 -1
- package/packages/tools/lib/model/relationship-registry.d.ts.map +1 -1
- package/packages/tools/lib/model/relationship-registry.js.map +1 -1
- package/packages/tools/lib/model/semantic.d.ts +27 -0
- package/packages/tools/lib/model/semantic.d.ts.map +1 -1
- package/packages/tools/lib/model/semantic.js +3 -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.d.ts.map +1 -1
- package/packages/tools/lib/model/short-id.js +2 -0
- 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.map +1 -1
- package/packages/tools/lib/model/view-deriver.js +36 -6
- 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 +298 -11
- 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 +721 -53
- 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/persistor.d.ts +63 -8
- package/packages/tools/lib/server/persistor.d.ts.map +1 -1
- package/packages/tools/lib/server/persistor.js +152 -44
- package/packages/tools/lib/server/persistor.js.map +1 -1
- package/packages/tools/lib/server/relationship-writer.d.ts +28 -4
- package/packages/tools/lib/server/relationship-writer.d.ts.map +1 -1
- package/packages/tools/lib/server/relationship-writer.js +98 -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 +87 -0
- package/packages/tools/lib/server/sysml-notation.js.map +1 -0
- 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/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 +15 -0
- package/packages/tools/lib/validator/constraint-eval.d.ts.map +1 -1
- package/packages/tools/lib/validator/constraint-eval.js +62 -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 +104 -18
- 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,46 @@
|
|
|
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 { indexFromIdentityTable } from '../model/ir-identity.js';
|
|
14
|
+
import { recompileProject, writeElement } from '../operations/authoring.js';
|
|
13
15
|
import { isWritableRelationshipFile, removeRelationship, resolveRelationshipPlacement, writeRelationship, } from './relationship-writer.js';
|
|
16
|
+
import { removeElement } from './element-writer.js';
|
|
17
|
+
import { assertSingleDomainMutation, MixedMutationDomainError } from './mutation-domain.js';
|
|
18
|
+
import { classifyConflict } from './conflict-policy.js';
|
|
19
|
+
import { checkRulePolicy, insertRulePolicy, renderRulePolicy } from './rule-policy-writer.js';
|
|
14
20
|
import { loadViewLayouts, saveViewLayout } from './view-layout-store.js';
|
|
15
21
|
import { loadDhfDocs, saveDhfDoc, deleteDhfDoc, loadDhfSettings, saveDhfSettings, listRepoTemplates, readRepoTemplate, saveRepoTemplate, } from './dhf-doc-store.js';
|
|
22
|
+
const MODEL_MUTATION_MESSAGES = new Set([
|
|
23
|
+
'element:update', 'element:create', 'element:delete', 'element:remap-kinds',
|
|
24
|
+
'relationship:create', 'relationship:delete',
|
|
25
|
+
'diagram:create', 'diagram:update', 'diagram:delete', 'diagram:layout:update', 'diagram:source:save',
|
|
26
|
+
'methodology:source:save', 'csv:import', 'screen-capture:upload',
|
|
27
|
+
'ontology:install', 'ontology:remove', 'ontology:save-selection',
|
|
28
|
+
'dhf:doc:save', 'dhf:doc:delete', 'dhf:settings:save', 'dhf:template:save',
|
|
29
|
+
'llm:chat:apply', 'llm:settings:save',
|
|
30
|
+
]);
|
|
31
|
+
/** Exported so the lockout boundary is mechanically testable. */
|
|
32
|
+
export function isModelMutationMessage(type) {
|
|
33
|
+
return MODEL_MUTATION_MESSAGES.has(type);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Project/model state is reconciled over the MEMO WebSocket. Vite must never
|
|
37
|
+
* turn these writes into a browser reload, even when a local ontology package
|
|
38
|
+
* is symlinked beneath Vite's dependency graph.
|
|
39
|
+
*/
|
|
40
|
+
export function isModelOwnedWatchPath(path) {
|
|
41
|
+
const normalized = path.replaceAll('\\', '/');
|
|
42
|
+
return normalized.endsWith('.sysml')
|
|
43
|
+
|| normalized.endsWith('/memo.config.yaml')
|
|
44
|
+
|| normalized.endsWith('/memo.package.yaml')
|
|
45
|
+
|| normalized.includes('/.memo/')
|
|
46
|
+
|| normalized.includes('/model/assets/');
|
|
47
|
+
}
|
|
16
48
|
// ─── User-diagram persistence helpers ──────────────────────────────────────
|
|
17
49
|
function userDiagramsPath(projectRoot) {
|
|
18
50
|
return resolve(projectRoot, '.memo', 'user-diagrams.json');
|
|
@@ -41,6 +73,9 @@ const STATIC_MIME = {
|
|
|
41
73
|
'.json': 'application/json',
|
|
42
74
|
'.svg': 'image/svg+xml',
|
|
43
75
|
'.png': 'image/png',
|
|
76
|
+
'.jpg': 'image/jpeg',
|
|
77
|
+
'.jpeg': 'image/jpeg',
|
|
78
|
+
'.webp': 'image/webp',
|
|
44
79
|
'.ico': 'image/x-icon',
|
|
45
80
|
'.woff2': 'font/woff2',
|
|
46
81
|
'.woff': 'font/woff',
|
|
@@ -53,9 +88,79 @@ function streamFile(res, fullPath, status = 200) {
|
|
|
53
88
|
res.writeHead(status, { 'Content-Type': mime, 'Content-Length': size });
|
|
54
89
|
createReadStream(fullPath).pipe(res);
|
|
55
90
|
}
|
|
91
|
+
/** Resolve only project-owned screen captures under model/assets. */
|
|
92
|
+
export function resolveProjectAssetRequest(projectRoot, requestUrl) {
|
|
93
|
+
let pathname;
|
|
94
|
+
try {
|
|
95
|
+
pathname = decodeURIComponent(requestUrl.split('?')[0]);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
if (!pathname.startsWith('/model/assets/'))
|
|
101
|
+
return undefined;
|
|
102
|
+
const assetRoot = resolve(projectRoot, 'model', 'assets');
|
|
103
|
+
const requested = resolve(projectRoot, pathname.replace(/^\//, ''));
|
|
104
|
+
const fromAssetRoot = relative(assetRoot, requested);
|
|
105
|
+
if (!fromAssetRoot || fromAssetRoot === '..' || fromAssetRoot.startsWith('../') || fromAssetRoot.startsWith('..\\')) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
return requested;
|
|
109
|
+
}
|
|
56
110
|
export async function createDevServer(options) {
|
|
57
111
|
const { port, webPackagePath, initialMessages, transformClientHtml } = options;
|
|
58
112
|
const host = '127.0.0.1';
|
|
113
|
+
// State publications are ordered independently of model semantics. The
|
|
114
|
+
// three state payloads from a rebuild share one revision so the browser
|
|
115
|
+
// cannot combine a model from one build with validation from another.
|
|
116
|
+
const workspaceSessionId = randomUUID();
|
|
117
|
+
const writeTransactions = new Map();
|
|
118
|
+
let workspaceRevision = 1;
|
|
119
|
+
let mutationLockout;
|
|
120
|
+
/** Set while the project source does not compile; cleared by the next clean rebuild. */
|
|
121
|
+
let sourceIncoherence;
|
|
122
|
+
function stampWorkspace(messages, baseRevision, snapshot = false, changedSourceIds = []) {
|
|
123
|
+
const model = messages.find(message => message.type === 'model:update');
|
|
124
|
+
const sourceGraphHash = createHash('sha256')
|
|
125
|
+
.update(JSON.stringify(model?.payload.sourceGraph ?? {})).digest('hex').slice(0, 16);
|
|
126
|
+
const reusableRegistryHash = String(model?.payload?.ontologyHash ?? 'none');
|
|
127
|
+
const projectRegistryHash = createHash('sha256')
|
|
128
|
+
.update(JSON.stringify(model?.payload.registries ?? {})).digest('hex').slice(0, 16);
|
|
129
|
+
const previous = initialMessages.find(message => message.type === 'model:update');
|
|
130
|
+
const modelDelta = !snapshot && model && previous
|
|
131
|
+
? {
|
|
132
|
+
upsertElements: Object.fromEntries(Object.entries(model.payload.elements).filter(([id, element]) => JSON.stringify(previous.payload.elements[id]) !== JSON.stringify(element))),
|
|
133
|
+
removeElementIds: Object.keys(previous.payload.elements).filter(id => !(id in model.payload.elements)),
|
|
134
|
+
patch: Object.fromEntries(Object.entries(model.payload).filter(([key]) => key !== 'elements')),
|
|
135
|
+
}
|
|
136
|
+
: undefined;
|
|
137
|
+
return messages.map(message => {
|
|
138
|
+
if (message.type !== 'model:update' &&
|
|
139
|
+
message.type !== 'validation:update' &&
|
|
140
|
+
message.type !== 'completeness:update')
|
|
141
|
+
return message;
|
|
142
|
+
return {
|
|
143
|
+
...message,
|
|
144
|
+
revision: {
|
|
145
|
+
workspaceSessionId, revision: workspaceRevision, baseRevision, snapshot,
|
|
146
|
+
sourceGraphHash, reusableRegistryHash, projectRegistryHash,
|
|
147
|
+
changedSourceIds: [...changedSourceIds].sort(),
|
|
148
|
+
modelDelta,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const initialSnapshot = stampWorkspace(initialMessages, null, true);
|
|
154
|
+
initialMessages.length = 0;
|
|
155
|
+
initialMessages.push(...initialSnapshot);
|
|
156
|
+
function recordWriteTransaction(sourceFile, text) {
|
|
157
|
+
const id = randomUUID();
|
|
158
|
+
writeTransactions.set(sourceFile.replaceAll('\\', '/'), {
|
|
159
|
+
id, hash: createHash('sha256').update(text).digest('hex').slice(0, 16),
|
|
160
|
+
createdAt: Date.now(), unmatchedWrites: 0,
|
|
161
|
+
});
|
|
162
|
+
return id;
|
|
163
|
+
}
|
|
59
164
|
// A source client can use Vite middleware. A packaged client can provide a
|
|
60
165
|
// prebuilt dist/ directory. Tools does not resolve or depend on that client.
|
|
61
166
|
const hasWebSource = existsSync(resolve(webPackagePath, 'index.html'));
|
|
@@ -107,6 +212,20 @@ export async function createDevServer(options) {
|
|
|
107
212
|
streamFile(res, fullPath);
|
|
108
213
|
return true;
|
|
109
214
|
}
|
|
215
|
+
/** Serve captures referenced by project-relative ScreenCapture.imageUri. */
|
|
216
|
+
function serveProjectAsset(req, res) {
|
|
217
|
+
const requestUrl = req.url ?? '/';
|
|
218
|
+
if (!requestUrl.split('?')[0].startsWith('/model/assets/'))
|
|
219
|
+
return false;
|
|
220
|
+
const requested = resolveProjectAssetRequest(options.projectRoot, requestUrl);
|
|
221
|
+
if (!requested || !existsSync(requested) || !statSync(requested).isFile()) {
|
|
222
|
+
res.writeHead(404);
|
|
223
|
+
res.end('Capture not found');
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
streamFile(res, requested);
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
110
229
|
/** Serve the caller-provided prebuilt client as an SPA. */
|
|
111
230
|
function serveWebDist(req, res) {
|
|
112
231
|
if (!hasWebDist)
|
|
@@ -140,13 +259,19 @@ export async function createDevServer(options) {
|
|
|
140
259
|
// Create Vite dev server in middleware mode
|
|
141
260
|
viteServer = await vite.createServer({
|
|
142
261
|
root: webPackagePath,
|
|
143
|
-
server: {
|
|
262
|
+
server: {
|
|
263
|
+
middlewareMode: true,
|
|
264
|
+
host,
|
|
265
|
+
watch: { ignored: isModelOwnedWatchPath },
|
|
266
|
+
},
|
|
144
267
|
appType: 'spa',
|
|
145
268
|
plugins: transformClientHtml
|
|
146
269
|
? [{ name: 'memo-client-html', transformIndexHtml: transformClientHtml }]
|
|
147
270
|
: [],
|
|
148
271
|
});
|
|
149
272
|
server = createHttpServer((req, res) => {
|
|
273
|
+
if (serveProjectAsset(req, res))
|
|
274
|
+
return;
|
|
150
275
|
if (serveHelp(req, res))
|
|
151
276
|
return;
|
|
152
277
|
viteServer.middlewares(req, res);
|
|
@@ -155,6 +280,8 @@ export async function createDevServer(options) {
|
|
|
155
280
|
else if (hasWebDist) {
|
|
156
281
|
// Packaged install: serve the prebuilt web app statically
|
|
157
282
|
server = createHttpServer((req, res) => {
|
|
283
|
+
if (serveProjectAsset(req, res))
|
|
284
|
+
return;
|
|
158
285
|
if (serveHelp(req, res))
|
|
159
286
|
return;
|
|
160
287
|
serveWebDist(req, res);
|
|
@@ -163,6 +290,8 @@ export async function createDevServer(options) {
|
|
|
163
290
|
else {
|
|
164
291
|
// Fallback: serve a basic page that connects via WebSocket
|
|
165
292
|
server = createHttpServer((req, res) => {
|
|
293
|
+
if (serveProjectAsset(req, res))
|
|
294
|
+
return;
|
|
166
295
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
167
296
|
res.end(`
|
|
168
297
|
<!DOCTYPE html>
|
|
@@ -188,6 +317,20 @@ export async function createDevServer(options) {
|
|
|
188
317
|
const { WebSocketServer } = await import('ws');
|
|
189
318
|
const wss = new WebSocketServer({ server });
|
|
190
319
|
const clients = new Set();
|
|
320
|
+
// User-created views are project state, not session state. Merge them into
|
|
321
|
+
// the initial model payload so a UI screen created from the workbench is
|
|
322
|
+
// still present after the dev server restarts.
|
|
323
|
+
const initialModelIndex = initialMessages.findIndex(message => message.type === 'model:update');
|
|
324
|
+
if (initialModelIndex >= 0) {
|
|
325
|
+
const initialModel = initialMessages[initialModelIndex];
|
|
326
|
+
const authored = initialModel.payload.diagrams ?? [];
|
|
327
|
+
const authoredIds = new Set(authored.map(diagram => diagram.id));
|
|
328
|
+
const userDiagrams = loadUserDiagrams(options.projectRoot).filter(diagram => !authoredIds.has(diagram.id));
|
|
329
|
+
initialMessages[initialModelIndex] = {
|
|
330
|
+
type: 'model:update',
|
|
331
|
+
payload: { ...initialModel.payload, diagrams: [...authored, ...userDiagrams] },
|
|
332
|
+
};
|
|
333
|
+
}
|
|
191
334
|
const currentDiagrams = () => {
|
|
192
335
|
const model = initialMessages.find(m => m.type === 'model:update');
|
|
193
336
|
return model?.payload.diagrams ?? [];
|
|
@@ -215,23 +358,156 @@ export async function createDevServer(options) {
|
|
|
215
358
|
client.send(JSON.stringify(updatedMsg));
|
|
216
359
|
}
|
|
217
360
|
}
|
|
361
|
+
function broadcastEditConflict(payload) {
|
|
362
|
+
const message = { type: 'app:edit-conflict', payload };
|
|
363
|
+
for (const client of clients) {
|
|
364
|
+
if (client.readyState === 1)
|
|
365
|
+
client.send(JSON.stringify(message));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Provenance origin of a file being written.
|
|
370
|
+
*
|
|
371
|
+
* The caller has already established the path is inside the project root,
|
|
372
|
+
* so the only distinction left that changes the conflict route is whether
|
|
373
|
+
* it is reusable source the project happens to vendor — a methodology or
|
|
374
|
+
* extension package living in the workspace. Those are frozen for the
|
|
375
|
+
* session exactly as an installed package is, so a change to one escalates.
|
|
376
|
+
*/
|
|
377
|
+
function originOf(sourcePath) {
|
|
378
|
+
const methodology = initialMessages.find(m => m.type === 'methodology:update');
|
|
379
|
+
const methodologyFiles = new Set((methodology?.payload?.folders ?? [])
|
|
380
|
+
.flatMap((folder) => (folder.sourceFiles ?? [])
|
|
381
|
+
.map((file) => resolve(options.projectRoot, file))));
|
|
382
|
+
if (methodologyFiles.has(sourcePath))
|
|
383
|
+
return 'methodology';
|
|
384
|
+
return 'project';
|
|
385
|
+
}
|
|
386
|
+
function checkMutationPrecondition(precondition, rejectedCommandId, rejectedDraft) {
|
|
387
|
+
if (!precondition)
|
|
388
|
+
return 'The mutation has no source precondition; reload before saving.';
|
|
389
|
+
const projectPath = realpathSync(resolve(options.projectRoot));
|
|
390
|
+
const sourcePath = resolve(projectPath, precondition.sourceFile);
|
|
391
|
+
if (!sourcePath.startsWith(`${projectPath}/`))
|
|
392
|
+
return 'Mutation source is outside the project.';
|
|
393
|
+
const currentHash = existsSync(sourcePath) ? fileRevision(readFileSync(sourcePath, 'utf8')) : '';
|
|
394
|
+
// Section 13.5's two routes. The decision is in `conflict-policy.ts`
|
|
395
|
+
// so it can be tested without driving a live server; this applies it.
|
|
396
|
+
const decision = classifyConflict({
|
|
397
|
+
commandSessionId: precondition.workspaceSessionId,
|
|
398
|
+
currentSessionId: workspaceSessionId,
|
|
399
|
+
expectedSourceHash: precondition.expectedSourceHash,
|
|
400
|
+
currentSourceHash: currentHash,
|
|
401
|
+
origin: originOf(sourcePath),
|
|
402
|
+
// Source incoherence is deliberately NOT folded in here. It is held
|
|
403
|
+
// earlier, on the message handler, so no mutation reaches this
|
|
404
|
+
// classifier while the project does not parse. Routing it through
|
|
405
|
+
// here instead would classify a stale hash as
|
|
406
|
+
// `dependency-closure-uncomputable` and demand a relaunch — which
|
|
407
|
+
// is the one outcome a source typo must never produce.
|
|
408
|
+
dependencyClosureComputable: !mutationLockout,
|
|
409
|
+
});
|
|
410
|
+
if (decision.outcome === 'accept')
|
|
411
|
+
return undefined;
|
|
412
|
+
if (decision.outcome === 'lockout') {
|
|
413
|
+
const lockout = {
|
|
414
|
+
type: 'app:restart-required',
|
|
415
|
+
reason: decision.escalation === 'reusable-source-changed'
|
|
416
|
+
? 'ontology-source-changed' : 'dependency-closure-uncomputable',
|
|
417
|
+
changedFile: precondition.sourceFile,
|
|
418
|
+
instruction: decision.reason,
|
|
419
|
+
};
|
|
420
|
+
mutationLockout = lockout;
|
|
421
|
+
for (const client of clients) {
|
|
422
|
+
if (client.readyState === 1)
|
|
423
|
+
client.send(JSON.stringify(lockout));
|
|
424
|
+
}
|
|
425
|
+
return decision.reason;
|
|
426
|
+
}
|
|
427
|
+
broadcastEditConflict({
|
|
428
|
+
sourceFile: precondition.sourceFile,
|
|
429
|
+
targetElementIds: precondition.targetElementIds,
|
|
430
|
+
baseRevision: precondition.baseRevision,
|
|
431
|
+
currentRevision: workspaceRevision,
|
|
432
|
+
expectedSourceHash: precondition.expectedSourceHash,
|
|
433
|
+
currentSourceHash: currentHash,
|
|
434
|
+
rejectedCommandId,
|
|
435
|
+
rejectedDraft,
|
|
436
|
+
});
|
|
437
|
+
return `${precondition.sourceFile} changed on disk; no write was made.`;
|
|
438
|
+
}
|
|
218
439
|
/** Latest model DTO — broadcast() replaces initialMessages on every rebuild. */
|
|
219
440
|
const currentModel = () => initialMessages.find(m => m.type === 'model:update')?.payload;
|
|
441
|
+
/**
|
|
442
|
+
* Identities minted by the most recent authoring recompile.
|
|
443
|
+
*
|
|
444
|
+
* A write-back recompiles through the lowering provider and therefore knows
|
|
445
|
+
* the addresses its own edit produced before the file watcher has caught
|
|
446
|
+
* up. Holding them here is what lets a user make two edits in a row without
|
|
447
|
+
* the second one being addressed to a revision that no longer exists.
|
|
448
|
+
* Cleared by `broadcast`, because a published model supersedes it.
|
|
449
|
+
*/
|
|
450
|
+
let writeBackIndex;
|
|
451
|
+
/** The index every write resolves its identities against. */
|
|
452
|
+
const irIndex = () => writeBackIndex ?? indexFromIdentityTable(currentModel()?.irIdentities);
|
|
453
|
+
/**
|
|
454
|
+
* Recompile after a semantic write, through the *selected* lowering
|
|
455
|
+
* provider (§6.2).
|
|
456
|
+
*
|
|
457
|
+
* Not a refresh optimisation: the watcher rebuild that follows is what
|
|
458
|
+
* publishes the new model. This exists so the identities handed back with
|
|
459
|
+
* the write are the ones the write created, rather than whatever the client
|
|
460
|
+
* happened to be holding. It costs a project lowering per authored change,
|
|
461
|
+
* which is the price of an address that is never a revision behind.
|
|
462
|
+
*/
|
|
463
|
+
/** What every authoring operation is given: the project, its settings, and the index. */
|
|
464
|
+
async function authoringContext() {
|
|
465
|
+
const { loadAndResolveConfig } = await import('./config-resolver.js');
|
|
466
|
+
const { findConfigFile } = await import('@memoarchitect/tools');
|
|
467
|
+
const configPath = findConfigFile(options.projectRoot);
|
|
468
|
+
return {
|
|
469
|
+
projectDir: options.projectRoot,
|
|
470
|
+
config: configPath ? loadAndResolveConfig(configPath) : undefined,
|
|
471
|
+
irIndex: irIndex(),
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
async function recompileAfterWrite() {
|
|
475
|
+
try {
|
|
476
|
+
writeBackIndex = (await recompileProject(await authoringContext())).index;
|
|
477
|
+
}
|
|
478
|
+
catch (e) {
|
|
479
|
+
// A recompile that fails leaves the previous index in place: the
|
|
480
|
+
// write itself already succeeded, and refusing to answer would be
|
|
481
|
+
// reporting a failure that did not happen. The next watcher rebuild
|
|
482
|
+
// republishes the identities either way.
|
|
483
|
+
console.warn(`[Authoring] Recompile after write failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
220
486
|
/**
|
|
221
487
|
* Build the query context the LLM engines read from, against whatever the
|
|
222
488
|
* server currently holds. Every LLM handler needs the same four pieces, so
|
|
223
489
|
* they share this rather than each re-deriving them.
|
|
224
490
|
*/
|
|
225
491
|
async function llmQueryContext() {
|
|
226
|
-
const { createQueryContext, findConfigFile } = await import('@memoarchitect/tools');
|
|
492
|
+
const { createQueryContext, findConfigFile, loadOntologyRegistries, ontologyViewFrom, EMPTY_ONTOLOGY_VIEW } = await import('@memoarchitect/tools');
|
|
227
493
|
const { loadAndResolveConfig } = await import('./config-resolver.js');
|
|
228
494
|
const modelMsg = initialMessages.find(m => m.type === 'model:update');
|
|
229
495
|
const validMsg = initialMessages.find(m => m.type === 'validation:update');
|
|
230
496
|
const compMsg = initialMessages.find(m => m.type === 'completeness:update');
|
|
231
497
|
const configPath = findConfigFile(options.projectRoot);
|
|
232
|
-
const config = configPath ?
|
|
498
|
+
const config = configPath ? loadAndResolveConfig(configPath) : { projectName: 'untitled' };
|
|
499
|
+
// Kinds and relationships come from the resolved SysML, not from the
|
|
500
|
+
// settings file the config path points at.
|
|
501
|
+
let ontology = EMPTY_ONTOLOGY_VIEW;
|
|
502
|
+
try {
|
|
503
|
+
const loaded = await loadOntologyRegistries(options.projectRoot);
|
|
504
|
+
ontology = ontologyViewFrom(loaded.registries.kindRegistry, loaded.registries.relationshipRegistry);
|
|
505
|
+
}
|
|
506
|
+
catch {
|
|
507
|
+
// A project whose ontology does not resolve still has a model to query.
|
|
508
|
+
}
|
|
233
509
|
const ctx = createQueryContext(modelMsg?.payload ?? {}, validMsg?.payload ?? { violations: [] }, compMsg?.payload ?? { overall: 0, layers: [] }, config);
|
|
234
|
-
return { ctx, config };
|
|
510
|
+
return { ctx, config, ontology };
|
|
235
511
|
}
|
|
236
512
|
/** Settings status for the client — provider/model/origin, never the key. */
|
|
237
513
|
async function llmSettings() {
|
|
@@ -250,9 +526,9 @@ export async function createDevServer(options) {
|
|
|
250
526
|
const { saveElementToFile } = await import('./persistor.js');
|
|
251
527
|
switch (change.kind) {
|
|
252
528
|
case 'create-element': {
|
|
253
|
-
const {
|
|
254
|
-
const kindDef =
|
|
255
|
-
const result = saveElementToFile(options.projectRoot, {
|
|
529
|
+
const { ontology } = await llmQueryContext();
|
|
530
|
+
const kindDef = ontology.kinds[change.elementKind];
|
|
531
|
+
const result = await saveElementToFile(options.projectRoot, {
|
|
256
532
|
id: change.elementId,
|
|
257
533
|
name: change.name,
|
|
258
534
|
kind: change.elementKind,
|
|
@@ -263,20 +539,22 @@ export async function createDevServer(options) {
|
|
|
263
539
|
});
|
|
264
540
|
if (!result.success)
|
|
265
541
|
throw new Error(result.error ?? 'Failed to write the element.');
|
|
542
|
+
await recompileAfterWrite();
|
|
266
543
|
return;
|
|
267
544
|
}
|
|
268
545
|
case 'update-element': {
|
|
269
546
|
const existing = currentModel()?.elements?.[change.elementId];
|
|
270
547
|
if (!existing)
|
|
271
548
|
throw new Error(`Element "${change.elementId}" no longer exists.`);
|
|
272
|
-
const result = saveElementToFile(options.projectRoot, {
|
|
549
|
+
const result = await saveElementToFile(options.projectRoot, {
|
|
273
550
|
...existing,
|
|
274
551
|
...(change.changes.name !== undefined ? { name: change.changes.name } : {}),
|
|
275
552
|
...(change.changes.doc !== undefined ? { doc: change.changes.doc } : {}),
|
|
276
553
|
attributes: { ...existing.attributes, ...(change.changes.attributes ?? {}) },
|
|
277
|
-
});
|
|
554
|
+
}, irIndex());
|
|
278
555
|
if (!result.success)
|
|
279
556
|
throw new Error(result.error ?? 'Failed to write the element.');
|
|
557
|
+
await recompileAfterWrite();
|
|
280
558
|
return;
|
|
281
559
|
}
|
|
282
560
|
case 'create-relationship': {
|
|
@@ -328,6 +606,7 @@ export async function createDevServer(options) {
|
|
|
328
606
|
ontologyRoots: options.ontologyRoots,
|
|
329
607
|
designatedFiles: options.relationshipFiles,
|
|
330
608
|
canonicalFile: options.canonicalRelationshipFile,
|
|
609
|
+
irIndex: irIndex(),
|
|
331
610
|
};
|
|
332
611
|
}
|
|
333
612
|
/**
|
|
@@ -344,6 +623,9 @@ export async function createDevServer(options) {
|
|
|
344
623
|
type: 'relationship:create:result',
|
|
345
624
|
payload: { requestId, success: false, error, diagnostics },
|
|
346
625
|
});
|
|
626
|
+
const conflict = checkMutationPrecondition(payload.precondition, requestId, payload);
|
|
627
|
+
if (conflict)
|
|
628
|
+
return reject(conflict);
|
|
347
629
|
const model = currentModel();
|
|
348
630
|
if (!model)
|
|
349
631
|
return reject('The model is not loaded yet.');
|
|
@@ -377,8 +659,12 @@ export async function createDevServer(options) {
|
|
|
377
659
|
console.error(`[Relationship] Write failed: ${result.error}`);
|
|
378
660
|
return reject(result.error ?? 'The relationship could not be written.', validation.diagnostics);
|
|
379
661
|
}
|
|
380
|
-
console.log(`[Persisted] relationship ${result.relationshipId
|
|
381
|
-
`to ${result.sourceFile} [${result.placementReason}]`);
|
|
662
|
+
console.log(`[Persisted] relationship ${result.relationshipId ?? `(${result.notation})`} ` +
|
|
663
|
+
`(${validation.normalizedType}) to ${result.sourceFile} [${result.placementReason}]`);
|
|
664
|
+
if (result.sourceFile) {
|
|
665
|
+
recordWriteTransaction(result.sourceFile, readFileSync(resolve(options.projectRoot, result.sourceFile), 'utf8'));
|
|
666
|
+
}
|
|
667
|
+
await recompileAfterWrite();
|
|
382
668
|
// The file watcher rebuilds and broadcasts the canonical model; this
|
|
383
669
|
// response only confirms the write and carries the IDs the client needs
|
|
384
670
|
// to replace its pending row.
|
|
@@ -388,6 +674,8 @@ export async function createDevServer(options) {
|
|
|
388
674
|
requestId,
|
|
389
675
|
success: true,
|
|
390
676
|
relationshipId: result.relationshipId,
|
|
677
|
+
notation: result.notation,
|
|
678
|
+
declaration: result.declaration,
|
|
391
679
|
type: validation.normalizedType,
|
|
392
680
|
sourceId: payload.sourceId,
|
|
393
681
|
targetId: payload.targetId,
|
|
@@ -404,6 +692,9 @@ export async function createDevServer(options) {
|
|
|
404
692
|
type: 'relationship:delete:result',
|
|
405
693
|
payload: { requestId, success: false, relationshipId, error, diagnostics },
|
|
406
694
|
});
|
|
695
|
+
const conflict = checkMutationPrecondition(payload.precondition, requestId, payload);
|
|
696
|
+
if (conflict)
|
|
697
|
+
return reject(conflict);
|
|
407
698
|
const model = currentModel();
|
|
408
699
|
if (!model)
|
|
409
700
|
return reject('The model is not loaded yet.');
|
|
@@ -420,6 +711,10 @@ export async function createDevServer(options) {
|
|
|
420
711
|
return reject(result.error ?? 'The relationship could not be removed.');
|
|
421
712
|
}
|
|
422
713
|
console.log(`[Persisted] removed relationship ${relationshipId} from ${result.sourceFile}`);
|
|
714
|
+
if (result.sourceFile) {
|
|
715
|
+
recordWriteTransaction(result.sourceFile, readFileSync(resolve(options.projectRoot, result.sourceFile), 'utf8'));
|
|
716
|
+
}
|
|
717
|
+
await recompileAfterWrite();
|
|
423
718
|
return {
|
|
424
719
|
type: 'relationship:delete:result',
|
|
425
720
|
payload: {
|
|
@@ -431,6 +726,57 @@ export async function createDevServer(options) {
|
|
|
431
726
|
},
|
|
432
727
|
};
|
|
433
728
|
}
|
|
729
|
+
async function handleElementDelete(payload) {
|
|
730
|
+
const requestId = payload?.requestId ?? '';
|
|
731
|
+
const elementId = payload?.elementId ?? '';
|
|
732
|
+
const reject = (error) => ({
|
|
733
|
+
type: 'element:delete:result',
|
|
734
|
+
payload: { requestId, elementId, success: false, error },
|
|
735
|
+
});
|
|
736
|
+
const conflict = checkMutationPrecondition(payload.precondition, requestId, payload);
|
|
737
|
+
if (conflict)
|
|
738
|
+
return reject(conflict);
|
|
739
|
+
const model = currentModel();
|
|
740
|
+
if (!model)
|
|
741
|
+
return reject('The model is not loaded yet.');
|
|
742
|
+
const element = model.elements[elementId];
|
|
743
|
+
if (!element)
|
|
744
|
+
return reject(`Element "${elementId}" was not found.`);
|
|
745
|
+
if (element.provenance && !element.provenance.declaration.writable) {
|
|
746
|
+
return reject(`Element "${elementId}" is read-only ${element.provenance.declaration.origin} content.`);
|
|
747
|
+
}
|
|
748
|
+
const result = await removeElement(element, model, options.projectRoot);
|
|
749
|
+
if (result.success) {
|
|
750
|
+
for (const sourceFile of result.sourceFiles ?? []) {
|
|
751
|
+
const path = resolve(options.projectRoot, sourceFile);
|
|
752
|
+
if (existsSync(path))
|
|
753
|
+
recordWriteTransaction(sourceFile, readFileSync(path, 'utf8'));
|
|
754
|
+
}
|
|
755
|
+
// User-authored diagrams are sidecars rather than semantic
|
|
756
|
+
// relationships, but they must not retain a stale element ID.
|
|
757
|
+
const diagrams = loadUserDiagrams(options.projectRoot);
|
|
758
|
+
let changed = false;
|
|
759
|
+
const cleaned = diagrams.map(diagram => {
|
|
760
|
+
if (!diagram.elementIds?.includes(elementId))
|
|
761
|
+
return diagram;
|
|
762
|
+
changed = true;
|
|
763
|
+
return { ...diagram, elementIds: diagram.elementIds.filter(id => id !== elementId) };
|
|
764
|
+
});
|
|
765
|
+
if (changed)
|
|
766
|
+
saveUserDiagrams(options.projectRoot, cleaned);
|
|
767
|
+
}
|
|
768
|
+
return {
|
|
769
|
+
type: 'element:delete:result',
|
|
770
|
+
payload: {
|
|
771
|
+
requestId,
|
|
772
|
+
elementId,
|
|
773
|
+
success: result.success,
|
|
774
|
+
sourceFiles: result.sourceFiles,
|
|
775
|
+
removedRelationshipIds: result.removedRelationshipIds,
|
|
776
|
+
error: result.error,
|
|
777
|
+
},
|
|
778
|
+
};
|
|
779
|
+
}
|
|
434
780
|
/** Convert a DhfBlock (from document IR) to a markdown string */
|
|
435
781
|
function blockToMarkdown(block) {
|
|
436
782
|
switch (block.type) {
|
|
@@ -505,10 +851,17 @@ export async function createDevServer(options) {
|
|
|
505
851
|
}
|
|
506
852
|
wss.on('connection', (ws) => {
|
|
507
853
|
clients.add(ws);
|
|
854
|
+
options.onClientCountChanged?.(clients.size);
|
|
508
855
|
// Send initial state to new connections
|
|
509
856
|
for (const msg of initialMessages) {
|
|
510
857
|
ws.send(JSON.stringify(msg));
|
|
511
858
|
}
|
|
859
|
+
if (mutationLockout)
|
|
860
|
+
ws.send(JSON.stringify(mutationLockout));
|
|
861
|
+
// A client that connects while the source is broken must learn that the
|
|
862
|
+
// model it just received is the last good one, not the current file.
|
|
863
|
+
if (sourceIncoherence)
|
|
864
|
+
ws.send(JSON.stringify(sourceIncoherence));
|
|
512
865
|
// Send all sidecar layouts on connect
|
|
513
866
|
const layouts = loadViewLayouts(options.projectRoot, currentDiagrams());
|
|
514
867
|
if (Object.keys(layouts).length > 0) {
|
|
@@ -542,25 +895,194 @@ export async function createDevServer(options) {
|
|
|
542
895
|
console.error('[LLM] settings load failed:', e);
|
|
543
896
|
}
|
|
544
897
|
})();
|
|
545
|
-
ws.on('close', () =>
|
|
898
|
+
ws.on('close', () => {
|
|
899
|
+
clients.delete(ws);
|
|
900
|
+
options.onClientCountChanged?.(clients.size);
|
|
901
|
+
});
|
|
546
902
|
ws.on('message', async (data) => {
|
|
547
903
|
try {
|
|
548
904
|
const msg = JSON.parse(data.toString());
|
|
905
|
+
if (mutationLockout && isModelMutationMessage(msg.type)) {
|
|
906
|
+
// A section 13.5 escalation is a hard server boundary;
|
|
907
|
+
// stale and hand-authored clients cannot write through it.
|
|
908
|
+
ws.send(JSON.stringify(mutationLockout));
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
if (sourceIncoherence && isModelMutationMessage(msg.type)) {
|
|
912
|
+
// Held, not locked out: the write cannot be placed while the
|
|
913
|
+
// closure is unknown, but the hold lifts by itself as soon
|
|
914
|
+
// as the source parses again.
|
|
915
|
+
ws.send(JSON.stringify(sourceIncoherence));
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
// One action writes one store (§6.2). A semantic mutation
|
|
919
|
+
// carrying canvas geometry is refused here rather than at the
|
|
920
|
+
// writer, so no handler has to remember the rule.
|
|
921
|
+
try {
|
|
922
|
+
assertSingleDomainMutation(msg.type, msg.payload);
|
|
923
|
+
}
|
|
924
|
+
catch (e) {
|
|
925
|
+
if (!(e instanceof MixedMutationDomainError))
|
|
926
|
+
throw e;
|
|
927
|
+
console.warn(`[Mutation] ${e.message}`);
|
|
928
|
+
ws.send(JSON.stringify({
|
|
929
|
+
type: 'app:error',
|
|
930
|
+
payload: { requestId: msg.payload?.requestId, error: e.message },
|
|
931
|
+
}));
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
549
934
|
if (msg.type === 'request:refresh') {
|
|
550
|
-
//
|
|
551
|
-
|
|
935
|
+
// A missed publication always falls back to one coherent
|
|
936
|
+
// snapshot, never a partial replay of old deltas.
|
|
937
|
+
for (const m of stampWorkspace(initialMessages, null, true)) {
|
|
552
938
|
ws.send(JSON.stringify(m));
|
|
553
939
|
}
|
|
554
940
|
}
|
|
555
941
|
else if (msg.type === 'element:update' || msg.type === 'element:create') {
|
|
556
|
-
// 1. Persist to FS
|
|
557
|
-
const { saveElementToFile } = await import('./persistor.js');
|
|
558
942
|
const { projectRoot } = options;
|
|
559
|
-
const
|
|
943
|
+
const { requestId = '', id = '' } = msg.payload ?? {};
|
|
944
|
+
const reply = (payload) => ws.send(JSON.stringify({
|
|
945
|
+
type: 'element:mutation:result',
|
|
946
|
+
payload: { requestId, elementId: id, ...payload },
|
|
947
|
+
}));
|
|
948
|
+
const existing = currentModel()?.elements[msg.payload?.id];
|
|
949
|
+
if (existing?.provenance && !existing.provenance.declaration.writable) {
|
|
950
|
+
reply({ success: false, rejectedDraft: msg.payload,
|
|
951
|
+
error: `Element "${existing.id}" is read-only ${existing.provenance.declaration.origin} content.` });
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
const precondition = msg.payload?.precondition;
|
|
955
|
+
if (!precondition) {
|
|
956
|
+
reply({ success: false, conflict: true, rejectedDraft: msg.payload,
|
|
957
|
+
error: 'The edit has no source precondition; reload it before saving.' });
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
if (precondition) {
|
|
961
|
+
if (precondition.workspaceSessionId !== workspaceSessionId) {
|
|
962
|
+
reply({ success: false, conflict: true, rejectedDraft: msg.payload,
|
|
963
|
+
error: 'The workspace runtime changed; no write was made.' });
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
const sourcePath = resolve(projectRoot, precondition.sourceFile);
|
|
967
|
+
const projectPath = realpathSync(resolve(projectRoot));
|
|
968
|
+
// A precondition that does not name a project file is
|
|
969
|
+
// untrustworthy: do not silently turn it into a write.
|
|
970
|
+
if (!sourcePath.startsWith(`${projectPath}/`) ||
|
|
971
|
+
(msg.type === 'element:update' && !existsSync(sourcePath))) {
|
|
972
|
+
reply({ success: false, conflict: true, rejectedDraft: msg.payload,
|
|
973
|
+
error: 'Mutation source is not a current project file.' });
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
const currentHash = existsSync(sourcePath)
|
|
977
|
+
? fileRevision(readFileSync(sourcePath, 'utf8')) : '';
|
|
978
|
+
if (precondition.expectedSourceHash !== currentHash) {
|
|
979
|
+
broadcastEditConflict({
|
|
980
|
+
sourceFile: precondition.sourceFile,
|
|
981
|
+
targetElementIds: precondition.targetElementIds,
|
|
982
|
+
baseRevision: precondition.baseRevision,
|
|
983
|
+
currentRevision: workspaceRevision,
|
|
984
|
+
expectedSourceHash: precondition.expectedSourceHash,
|
|
985
|
+
currentSourceHash: currentHash,
|
|
986
|
+
rejectedCommandId: requestId,
|
|
987
|
+
rejectedDraft: msg.payload,
|
|
988
|
+
});
|
|
989
|
+
reply({ success: false, conflict: true, rejectedDraft: msg.payload,
|
|
990
|
+
sourceFile: precondition.sourceFile,
|
|
991
|
+
expectedSourceHash: precondition.expectedSourceHash,
|
|
992
|
+
currentSourceHash: currentHash,
|
|
993
|
+
error: `${precondition.sourceFile} changed on disk; no write was made.`,
|
|
994
|
+
});
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
// One operation: write the source, then recompile through
|
|
999
|
+
// the selected lowering provider so the identity the client
|
|
1000
|
+
// edits next is the one this write produced. The file
|
|
1001
|
+
// watcher still publishes the model.
|
|
1002
|
+
const result = await writeElement(await authoringContext(), msg.payload);
|
|
1003
|
+
let transactionId;
|
|
560
1004
|
if (result.success) {
|
|
561
|
-
|
|
1005
|
+
transactionId = recordWriteTransaction(result.filePath, readFileSync(resolve(projectRoot, result.filePath), 'utf8'));
|
|
1006
|
+
if (result.revision)
|
|
1007
|
+
writeBackIndex = result.revision.index;
|
|
562
1008
|
console.log(`[Persisted] ${msg.type} to ${result.filePath}`);
|
|
563
1009
|
}
|
|
1010
|
+
if (result.stale) {
|
|
1011
|
+
// The file may be byte-identical and the edit still
|
|
1012
|
+
// wrong, so this is announced as its own kind of
|
|
1013
|
+
// conflict rather than as "the file changed".
|
|
1014
|
+
broadcastEditConflict({
|
|
1015
|
+
reason: 'stale-identity',
|
|
1016
|
+
detail: result.error,
|
|
1017
|
+
sourceFile: result.filePath,
|
|
1018
|
+
targetElementIds: [id],
|
|
1019
|
+
baseRevision: precondition.baseRevision,
|
|
1020
|
+
currentRevision: workspaceRevision,
|
|
1021
|
+
expectedSourceHash: precondition.expectedSourceHash,
|
|
1022
|
+
currentSourceHash: precondition.expectedSourceHash,
|
|
1023
|
+
rejectedCommandId: requestId,
|
|
1024
|
+
rejectedDraft: msg.payload,
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
reply({
|
|
1028
|
+
success: result.success,
|
|
1029
|
+
sourceFile: result.filePath,
|
|
1030
|
+
transactionId,
|
|
1031
|
+
error: result.error,
|
|
1032
|
+
// A stale address is a conflict, not a malformed request:
|
|
1033
|
+
// the client's answer to both is to reload and retry.
|
|
1034
|
+
...(result.stale ? { conflict: true, stale: true, rejectedDraft: msg.payload } : {}),
|
|
1035
|
+
...(result.warnings ? { warnings: result.warnings } : {}),
|
|
1036
|
+
...(result.success ? { irIdentity: irIndex().byMemoElement[id] } : {}),
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
else if (msg.type === 'screen-capture:upload') {
|
|
1040
|
+
const { requestId, viewName, fileName, base64, mediaType } = msg.payload ?? {};
|
|
1041
|
+
const reply = (payload) => ws.send(JSON.stringify({
|
|
1042
|
+
type: 'screen-capture:upload:result',
|
|
1043
|
+
payload: { requestId, ...payload },
|
|
1044
|
+
}));
|
|
1045
|
+
try {
|
|
1046
|
+
if (!requestId || typeof base64 !== 'string')
|
|
1047
|
+
throw new Error('The image payload is missing.');
|
|
1048
|
+
const extensions = {
|
|
1049
|
+
'image/png': '.png',
|
|
1050
|
+
'image/jpeg': '.jpg',
|
|
1051
|
+
'image/webp': '.webp',
|
|
1052
|
+
};
|
|
1053
|
+
const extension = extensions[mediaType];
|
|
1054
|
+
if (!extension)
|
|
1055
|
+
throw new Error('Only PNG, JPEG, and WebP captures are supported.');
|
|
1056
|
+
const bytes = Buffer.from(base64, 'base64');
|
|
1057
|
+
if (!bytes.length)
|
|
1058
|
+
throw new Error('The selected image is empty.');
|
|
1059
|
+
if (bytes.length > 25 * 1024 * 1024)
|
|
1060
|
+
throw new Error('Screen captures must be 25 MB or smaller.');
|
|
1061
|
+
const safeView = String(viewName || 'ui-screen')
|
|
1062
|
+
.normalize('NFKD').replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/^-+|-+$/g, '')
|
|
1063
|
+
|| 'ui-screen';
|
|
1064
|
+
const rawStem = String(fileName || 'capture').replace(/\.[^.]+$/, '');
|
|
1065
|
+
const safeStem = rawStem
|
|
1066
|
+
.normalize('NFKD').replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/^-+|-+$/g, '')
|
|
1067
|
+
|| 'capture';
|
|
1068
|
+
const assetDir = resolve(options.projectRoot, 'model', 'assets', safeView);
|
|
1069
|
+
const assetPath = resolve(assetDir, `${safeStem}${extension}`);
|
|
1070
|
+
const modelRoot = resolve(options.projectRoot, 'model');
|
|
1071
|
+
if (relative(modelRoot, assetPath).startsWith('..'))
|
|
1072
|
+
throw new Error('Invalid capture path.');
|
|
1073
|
+
mkdirSync(assetDir, { recursive: true });
|
|
1074
|
+
writeFileSync(assetPath, bytes);
|
|
1075
|
+
const imageUri = relative(options.projectRoot, assetPath).replaceAll('\\', '/');
|
|
1076
|
+
reply({
|
|
1077
|
+
success: true,
|
|
1078
|
+
imageUri,
|
|
1079
|
+
imageHash: createHash('sha256').update(bytes).digest('hex'),
|
|
1080
|
+
});
|
|
1081
|
+
console.log(`[Screen Capture] Saved ${imageUri}`);
|
|
1082
|
+
}
|
|
1083
|
+
catch (e) {
|
|
1084
|
+
reply({ success: false, error: e?.message ?? String(e) });
|
|
1085
|
+
}
|
|
564
1086
|
}
|
|
565
1087
|
else if (msg.type === 'diagram:create') {
|
|
566
1088
|
const { projectRoot } = options;
|
|
@@ -610,6 +1132,9 @@ export async function createDevServer(options) {
|
|
|
610
1132
|
else if (msg.type === 'relationship:delete') {
|
|
611
1133
|
ws.send(JSON.stringify(await handleRelationshipDelete(msg.payload)));
|
|
612
1134
|
}
|
|
1135
|
+
else if (msg.type === 'element:delete') {
|
|
1136
|
+
ws.send(JSON.stringify(await handleElementDelete(msg.payload)));
|
|
1137
|
+
}
|
|
613
1138
|
else if (msg.type === 'element:remap-kinds') {
|
|
614
1139
|
// Remap orphaned kind references: for each element whose kind is in the
|
|
615
1140
|
// mappings, persist the element with the new kind. The file watcher will
|
|
@@ -624,11 +1149,13 @@ export async function createDevServer(options) {
|
|
|
624
1149
|
for (const element of Object.values(elements)) {
|
|
625
1150
|
const newKind = mappings[element.kind];
|
|
626
1151
|
if (newKind) {
|
|
627
|
-
const result = saveElementToFile(projectRoot, { ...element, kind: newKind });
|
|
1152
|
+
const result = await saveElementToFile(projectRoot, { ...element, kind: newKind }, irIndex());
|
|
628
1153
|
if (result.success)
|
|
629
1154
|
remappedCount++;
|
|
630
1155
|
}
|
|
631
1156
|
}
|
|
1157
|
+
if (remappedCount > 0)
|
|
1158
|
+
await recompileAfterWrite();
|
|
632
1159
|
console.log(`[Remap] Remapped ${remappedCount} elements across ${Object.keys(mappings).length} kind(s)`);
|
|
633
1160
|
ws.send(JSON.stringify({ type: 'remap:result', payload: { success: true, count: remappedCount } }));
|
|
634
1161
|
}
|
|
@@ -678,6 +1205,97 @@ export async function createDevServer(options) {
|
|
|
678
1205
|
payload: { diagramId: msg.payload.diagramId, elementIds, errors: [] },
|
|
679
1206
|
}));
|
|
680
1207
|
}
|
|
1208
|
+
else if (msg.type === 'methodology:source:request' || msg.type === 'methodology:source:save') {
|
|
1209
|
+
const { requestId, sourceFile } = msg.payload ?? {};
|
|
1210
|
+
const operation = msg.type === 'methodology:source:save' ? 'save' : 'load';
|
|
1211
|
+
const reply = (payload) => ws.send(JSON.stringify({
|
|
1212
|
+
type: 'methodology:source:result',
|
|
1213
|
+
payload: { requestId, sourceFile, operation, ...payload },
|
|
1214
|
+
}));
|
|
1215
|
+
try {
|
|
1216
|
+
const methodology = initialMessages.find(message => message.type === 'methodology:update');
|
|
1217
|
+
const allowed = new Set((methodology?.payload?.folders ?? []).flatMap((folder) => (folder.sourceFiles ?? []).map((file) => resolve(options.projectRoot, file))));
|
|
1218
|
+
const path = resolve(options.projectRoot, String(sourceFile ?? ''));
|
|
1219
|
+
if (!allowed.has(path) || !path.endsWith('.sysml')) {
|
|
1220
|
+
throw new Error('The requested file is not in the resolved methodology source set.');
|
|
1221
|
+
}
|
|
1222
|
+
const onDisk = readFileSync(path, 'utf8');
|
|
1223
|
+
const revision = fileRevision(onDisk);
|
|
1224
|
+
if (operation === 'load') {
|
|
1225
|
+
reply({ success: true, text: onDisk, revision });
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
const { text, baseRevision } = msg.payload;
|
|
1229
|
+
if (typeof text !== 'string' || typeof baseRevision !== 'string') {
|
|
1230
|
+
throw new Error('Text and source revision are required.');
|
|
1231
|
+
}
|
|
1232
|
+
if (baseRevision !== revision) {
|
|
1233
|
+
const lockout = {
|
|
1234
|
+
type: 'app:restart-required',
|
|
1235
|
+
reason: 'ontology-source-changed',
|
|
1236
|
+
changedFile: String(sourceFile),
|
|
1237
|
+
instruction: 'Reusable methodology source changed externally. Model mutations are locked; Relaunch Memo Architect.',
|
|
1238
|
+
};
|
|
1239
|
+
mutationLockout = lockout;
|
|
1240
|
+
for (const client of clients) {
|
|
1241
|
+
if (client.readyState === 1)
|
|
1242
|
+
client.send(JSON.stringify(lockout));
|
|
1243
|
+
}
|
|
1244
|
+
reply({ success: false, conflict: true, text: onDisk, revision,
|
|
1245
|
+
error: `${sourceFile} changed on disk; no write was made.` });
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
writeFileSync(path, text, 'utf8');
|
|
1249
|
+
const transactionId = recordWriteTransaction(String(sourceFile), text);
|
|
1250
|
+
reply({ success: true, revision: fileRevision(text), transactionId });
|
|
1251
|
+
}
|
|
1252
|
+
catch (error) {
|
|
1253
|
+
reply({ success: false, error: error instanceof Error ? error.message : String(error) });
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
else if (msg.type === 'rule:policy:write') {
|
|
1257
|
+
// Tailoring a rule from the browser goes through exactly
|
|
1258
|
+
// the path a hand edit does: the same allowed-source check,
|
|
1259
|
+
// the same base-revision precondition, the same transaction
|
|
1260
|
+
// ID. The editor supplies a decision; the SysML is rendered
|
|
1261
|
+
// here, so the browser never composes model source.
|
|
1262
|
+
const { requestId } = msg.payload ?? {};
|
|
1263
|
+
const reply = (payload) => ws.send(JSON.stringify({
|
|
1264
|
+
type: 'rule:policy:write:result', payload: { requestId, ...payload },
|
|
1265
|
+
}));
|
|
1266
|
+
try {
|
|
1267
|
+
const request = msg.payload;
|
|
1268
|
+
const methodology = initialMessages.find(message => message.type === 'methodology:update');
|
|
1269
|
+
const effectiveRules = initialMessages.find(message => message.type === 'rules:update')?.payload?.rules ?? [];
|
|
1270
|
+
const refusal = checkRulePolicy(request, effectiveRules);
|
|
1271
|
+
if (refusal) {
|
|
1272
|
+
reply({ success: false, code: refusal.code, error: refusal.message });
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
const allowed = new Set((methodology?.payload?.folders ?? []).flatMap((folder) => (folder.sourceFiles ?? []).map((file) => resolve(options.projectRoot, file))));
|
|
1276
|
+
const path = resolve(options.projectRoot, String(request.sourceFile ?? ''));
|
|
1277
|
+
if (!allowed.has(path) || !path.endsWith('.sysml')) {
|
|
1278
|
+
throw new Error('The target file is not in the resolved methodology source set.');
|
|
1279
|
+
}
|
|
1280
|
+
const onDisk = readFileSync(path, 'utf8');
|
|
1281
|
+
const revision = fileRevision(onDisk);
|
|
1282
|
+
if (request.baseRevision !== revision) {
|
|
1283
|
+
reply({ success: false, conflict: true, revision,
|
|
1284
|
+
error: `${request.sourceFile} changed on disk; no write was made.` });
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
const updated = insertRulePolicy(onDisk, renderRulePolicy(request));
|
|
1288
|
+
if (!updated) {
|
|
1289
|
+
throw new Error(`${request.sourceFile} declares no MethodologyDefinition to attach the policy to.`);
|
|
1290
|
+
}
|
|
1291
|
+
writeFileSync(path, updated, 'utf8');
|
|
1292
|
+
const transactionId = recordWriteTransaction(String(request.sourceFile), updated);
|
|
1293
|
+
reply({ success: true, revision: fileRevision(updated), transactionId });
|
|
1294
|
+
}
|
|
1295
|
+
catch (error) {
|
|
1296
|
+
reply({ success: false, error: error instanceof Error ? error.message : String(error) });
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
681
1299
|
else if (msg.type === 'diagram:source:request') {
|
|
682
1300
|
const { requestId, diagramId } = msg.payload ?? {};
|
|
683
1301
|
try {
|
|
@@ -703,15 +1321,27 @@ export async function createDevServer(options) {
|
|
|
703
1321
|
try {
|
|
704
1322
|
if (typeof text !== 'string')
|
|
705
1323
|
throw new Error('SysML source text is required.');
|
|
1324
|
+
if (typeof baseRevision !== 'string')
|
|
1325
|
+
throw new Error('A source revision is required; reload the file before saving.');
|
|
706
1326
|
const source = diagramSource(String(diagramId ?? ''));
|
|
707
1327
|
// Refuse to overwrite work that arrived after this edit
|
|
708
1328
|
// began. The client gets the current file back so it can
|
|
709
1329
|
// show the conflict instead of losing one of the two.
|
|
710
1330
|
const onDisk = readFileSync(source.path, 'utf8');
|
|
711
1331
|
const currentRevision = fileRevision(onDisk);
|
|
712
|
-
if (
|
|
1332
|
+
if (baseRevision !== currentRevision) {
|
|
713
1333
|
console.warn(`[Diagram] Refused stale save of ${source.sourceFile} ` +
|
|
714
1334
|
`(based on ${baseRevision}, disk is ${currentRevision})`);
|
|
1335
|
+
broadcastEditConflict({
|
|
1336
|
+
sourceFile: source.sourceFile,
|
|
1337
|
+
targetElementIds: currentDiagrams().find(d => d.id === diagramId)?.elementIds ?? [],
|
|
1338
|
+
baseRevision,
|
|
1339
|
+
currentRevision,
|
|
1340
|
+
expectedSourceHash: baseRevision,
|
|
1341
|
+
currentSourceHash: currentRevision,
|
|
1342
|
+
rejectedCommandId: requestId,
|
|
1343
|
+
rejectedDraft: { diagramId, text },
|
|
1344
|
+
});
|
|
715
1345
|
ws.send(JSON.stringify({
|
|
716
1346
|
type: 'diagram:source:result',
|
|
717
1347
|
payload: {
|
|
@@ -723,11 +1353,13 @@ export async function createDevServer(options) {
|
|
|
723
1353
|
return;
|
|
724
1354
|
}
|
|
725
1355
|
writeFileSync(source.path, text, 'utf8');
|
|
1356
|
+
const transactionId = recordWriteTransaction(source.sourceFile, text);
|
|
726
1357
|
ws.send(JSON.stringify({
|
|
727
1358
|
type: 'diagram:source:result',
|
|
728
1359
|
payload: {
|
|
729
1360
|
requestId, diagramId, operation: 'save', success: true,
|
|
730
1361
|
sourceFile: source.sourceFile, revision: fileRevision(text),
|
|
1362
|
+
transactionId,
|
|
731
1363
|
parseErrors: await sysmlParseErrors(text),
|
|
732
1364
|
},
|
|
733
1365
|
}));
|
|
@@ -881,17 +1513,9 @@ export async function createDevServer(options) {
|
|
|
881
1513
|
const { projectRoot } = options;
|
|
882
1514
|
const { elementsCsv, relationshipsCsv, packageName, targetFile } = msg.payload ?? {};
|
|
883
1515
|
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);
|
|
1516
|
+
const { parseElementsCsv, parseRelationshipsCsv, generateFile, attachProvenance, loadProjectSettings, } = await import('@memoarchitect/tools');
|
|
1517
|
+
const config = loadProjectSettings(projectRoot);
|
|
1518
|
+
const { ontology } = await llmQueryContext();
|
|
895
1519
|
const errors = [];
|
|
896
1520
|
const warnings = [];
|
|
897
1521
|
let elementsImported = 0;
|
|
@@ -899,7 +1523,7 @@ export async function createDevServer(options) {
|
|
|
899
1523
|
let elements = [];
|
|
900
1524
|
let relationships = [];
|
|
901
1525
|
if (elementsCsv) {
|
|
902
|
-
const result = parseElementsCsv(elementsCsv,
|
|
1526
|
+
const result = parseElementsCsv(elementsCsv, ontology);
|
|
903
1527
|
errors.push(...result.errors);
|
|
904
1528
|
warnings.push(...result.warnings);
|
|
905
1529
|
// Attach provenance
|
|
@@ -915,7 +1539,7 @@ export async function createDevServer(options) {
|
|
|
915
1539
|
const knownIds = elementsImported > 0
|
|
916
1540
|
? new Set(elements.map((e) => e.id))
|
|
917
1541
|
: undefined;
|
|
918
|
-
const result = parseRelationshipsCsv(relationshipsCsv,
|
|
1542
|
+
const result = parseRelationshipsCsv(relationshipsCsv, ontology, knownIds);
|
|
919
1543
|
errors.push(...result.errors);
|
|
920
1544
|
warnings.push(...result.warnings);
|
|
921
1545
|
relationships = result.items;
|
|
@@ -986,10 +1610,10 @@ export async function createDevServer(options) {
|
|
|
986
1610
|
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
1611
|
}
|
|
988
1612
|
else {
|
|
989
|
-
const { ctx,
|
|
1613
|
+
const { ctx, ontology } = await llmQueryContext();
|
|
990
1614
|
const provider = createProvider(llmConfig);
|
|
991
1615
|
const result = await runChatTurn({
|
|
992
|
-
question, history, ctx, provider,
|
|
1616
|
+
question, history, ctx, provider, ontology,
|
|
993
1617
|
allowEdits: allowEdits === true,
|
|
994
1618
|
});
|
|
995
1619
|
ws.send(JSON.stringify({
|
|
@@ -1068,22 +1692,13 @@ export async function createDevServer(options) {
|
|
|
1068
1692
|
}
|
|
1069
1693
|
}
|
|
1070
1694
|
else if (msg.type === 'llm:generate') {
|
|
1071
|
-
//
|
|
1695
|
+
// Argument marshalling only: memo-tools owns generation,
|
|
1696
|
+
// validation, repair, and normalized diagnostics.
|
|
1072
1697
|
const { requestId, description } = msg.payload ?? {};
|
|
1073
1698
|
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
|
-
}
|
|
1699
|
+
const { generateValidatedSysml } = await import('../operations/ai-authoring.js');
|
|
1700
|
+
const result = await generateValidatedSysml({ projectRoot: options.projectRoot, description });
|
|
1701
|
+
ws.send(JSON.stringify({ type: 'llm:generate:result', payload: { requestId, ...result } }));
|
|
1087
1702
|
}
|
|
1088
1703
|
catch (e) {
|
|
1089
1704
|
console.error('[LLM] generate failed:', e);
|
|
@@ -1291,14 +1906,22 @@ Return ONLY a JSON array of strings. Each string is a concise, actionable sugges
|
|
|
1291
1906
|
await new Promise((resolve) => {
|
|
1292
1907
|
server.listen(port, host, () => resolve());
|
|
1293
1908
|
});
|
|
1909
|
+
const address = server.address();
|
|
1294
1910
|
return {
|
|
1295
|
-
|
|
1911
|
+
port: typeof address === 'object' && address ? address.port : port,
|
|
1912
|
+
broadcast(messages, changedSourceIds = []) {
|
|
1296
1913
|
// Update initial messages for new connections
|
|
1914
|
+
const baseRevision = workspaceRevision;
|
|
1915
|
+
workspaceRevision++;
|
|
1916
|
+
const publication = stampWorkspace(messages, baseRevision, false, changedSourceIds);
|
|
1917
|
+
// A published model carries its own identities; the write-back's
|
|
1918
|
+
// provisional index has been superseded.
|
|
1919
|
+
writeBackIndex = undefined;
|
|
1297
1920
|
initialMessages.length = 0;
|
|
1298
|
-
initialMessages.push(...
|
|
1921
|
+
initialMessages.push(...publication);
|
|
1299
1922
|
for (const client of clients) {
|
|
1300
1923
|
if (client.readyState === 1) { // WebSocket.OPEN
|
|
1301
|
-
for (const msg of
|
|
1924
|
+
for (const msg of publication) {
|
|
1302
1925
|
client.send(JSON.stringify(msg));
|
|
1303
1926
|
}
|
|
1304
1927
|
}
|
|
@@ -1312,6 +1935,51 @@ Return ONLY a JSON array of strings. Each string is a concise, actionable sugges
|
|
|
1312
1935
|
}
|
|
1313
1936
|
}
|
|
1314
1937
|
},
|
|
1938
|
+
consumeWriteTransactions(files) {
|
|
1939
|
+
const matched = {};
|
|
1940
|
+
let escalationFile;
|
|
1941
|
+
for (const file of files) {
|
|
1942
|
+
const normalized = file.replaceAll('\\', '/');
|
|
1943
|
+
const transaction = writeTransactions.get(normalized);
|
|
1944
|
+
if (!transaction)
|
|
1945
|
+
continue;
|
|
1946
|
+
const path = resolve(options.projectRoot, normalized);
|
|
1947
|
+
const currentHash = existsSync(path) ? fileRevision(readFileSync(path, 'utf8')) : '';
|
|
1948
|
+
if (currentHash === transaction.hash) {
|
|
1949
|
+
matched[normalized] = transaction.id;
|
|
1950
|
+
writeTransactions.delete(normalized);
|
|
1951
|
+
continue;
|
|
1952
|
+
}
|
|
1953
|
+
transaction.unmatchedWrites++;
|
|
1954
|
+
if (Date.now() - transaction.createdAt > 5000) {
|
|
1955
|
+
writeTransactions.delete(normalized);
|
|
1956
|
+
}
|
|
1957
|
+
else if (transaction.unmatchedWrites >= 2) {
|
|
1958
|
+
escalationFile = normalized;
|
|
1959
|
+
writeTransactions.delete(normalized);
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
return { matched, escalationFile };
|
|
1963
|
+
},
|
|
1964
|
+
lockMutations(message) {
|
|
1965
|
+
mutationLockout = message;
|
|
1966
|
+
for (const client of clients) {
|
|
1967
|
+
if (client.readyState === 1)
|
|
1968
|
+
client.send(JSON.stringify(message));
|
|
1969
|
+
}
|
|
1970
|
+
},
|
|
1971
|
+
setSourceCoherence(payload) {
|
|
1972
|
+
const message = { type: 'source:coherence', payload };
|
|
1973
|
+
// Recovery is announced once, not on every clean rebuild after it —
|
|
1974
|
+
// a coherent project is the ordinary case and says nothing.
|
|
1975
|
+
if (payload.coherent && !sourceIncoherence)
|
|
1976
|
+
return;
|
|
1977
|
+
sourceIncoherence = payload.coherent ? undefined : message;
|
|
1978
|
+
for (const client of clients) {
|
|
1979
|
+
if (client.readyState === 1)
|
|
1980
|
+
client.send(JSON.stringify(message));
|
|
1981
|
+
}
|
|
1982
|
+
},
|
|
1315
1983
|
close() {
|
|
1316
1984
|
wss.close();
|
|
1317
1985
|
viteServer?.close();
|