@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.
Files changed (549) hide show
  1. package/README.md +36 -11
  2. package/corpus/README.md +63 -0
  3. package/corpus/sysml-v2-release/bnf/SysML-graphical-bnf.kgbnf +2264 -0
  4. package/package.json +20 -7
  5. package/packages/tools/lib/analysis/dsm.d.ts +1 -1
  6. package/packages/tools/lib/analysis/dsm.d.ts.map +1 -1
  7. package/packages/tools/lib/analysis/dsm.js +1 -1
  8. package/packages/tools/lib/analysis/dsm.js.map +1 -1
  9. package/packages/tools/lib/bin/memo.js +186 -10
  10. package/packages/tools/lib/bin/memo.js.map +1 -1
  11. package/packages/tools/lib/browser/index.d.ts +3 -1
  12. package/packages/tools/lib/browser/index.d.ts.map +1 -1
  13. package/packages/tools/lib/browser/index.js +2 -1
  14. package/packages/tools/lib/browser/index.js.map +1 -1
  15. package/packages/tools/lib/commands/ask.js +1 -1
  16. package/packages/tools/lib/commands/ask.js.map +1 -1
  17. package/packages/tools/lib/commands/check.d.ts.map +1 -1
  18. package/packages/tools/lib/commands/check.js +3 -9
  19. package/packages/tools/lib/commands/check.js.map +1 -1
  20. package/packages/tools/lib/commands/conformance.d.ts +36 -0
  21. package/packages/tools/lib/commands/conformance.d.ts.map +1 -0
  22. package/packages/tools/lib/commands/conformance.js +212 -0
  23. package/packages/tools/lib/commands/conformance.js.map +1 -0
  24. package/packages/tools/lib/commands/convert.d.ts +12 -0
  25. package/packages/tools/lib/commands/convert.d.ts.map +1 -0
  26. package/packages/tools/lib/commands/convert.js +103 -0
  27. package/packages/tools/lib/commands/convert.js.map +1 -0
  28. package/packages/tools/lib/commands/create-package.d.ts +1 -2
  29. package/packages/tools/lib/commands/create-package.d.ts.map +1 -1
  30. package/packages/tools/lib/commands/create-package.js.map +1 -1
  31. package/packages/tools/lib/commands/dev.d.ts +13 -0
  32. package/packages/tools/lib/commands/dev.d.ts.map +1 -1
  33. package/packages/tools/lib/commands/dev.js +245 -89
  34. package/packages/tools/lib/commands/dev.js.map +1 -1
  35. package/packages/tools/lib/commands/dhf-draft.js +1 -1
  36. package/packages/tools/lib/commands/dhf-draft.js.map +1 -1
  37. package/packages/tools/lib/commands/dhf-lint.d.ts +10 -0
  38. package/packages/tools/lib/commands/dhf-lint.d.ts.map +1 -0
  39. package/packages/tools/lib/commands/dhf-lint.js +72 -0
  40. package/packages/tools/lib/commands/dhf-lint.js.map +1 -0
  41. package/packages/tools/lib/commands/dhf-preview.js +1 -1
  42. package/packages/tools/lib/commands/dhf-preview.js.map +1 -1
  43. package/packages/tools/lib/commands/dhf.d.ts.map +1 -1
  44. package/packages/tools/lib/commands/dhf.js +8 -2
  45. package/packages/tools/lib/commands/dhf.js.map +1 -1
  46. package/packages/tools/lib/commands/element.d.ts +34 -0
  47. package/packages/tools/lib/commands/element.d.ts.map +1 -0
  48. package/packages/tools/lib/commands/element.js +174 -0
  49. package/packages/tools/lib/commands/element.js.map +1 -0
  50. package/packages/tools/lib/commands/export.d.ts.map +1 -1
  51. package/packages/tools/lib/commands/export.js +39 -40
  52. package/packages/tools/lib/commands/export.js.map +1 -1
  53. package/packages/tools/lib/commands/generate.d.ts.map +1 -1
  54. package/packages/tools/lib/commands/generate.js +10 -7
  55. package/packages/tools/lib/commands/generate.js.map +1 -1
  56. package/packages/tools/lib/commands/import.d.ts.map +1 -1
  57. package/packages/tools/lib/commands/import.js +25 -12
  58. package/packages/tools/lib/commands/import.js.map +1 -1
  59. package/packages/tools/lib/commands/init.d.ts.map +1 -1
  60. package/packages/tools/lib/commands/init.js +34 -14
  61. package/packages/tools/lib/commands/init.js.map +1 -1
  62. package/packages/tools/lib/commands/inspect.d.ts +5 -0
  63. package/packages/tools/lib/commands/inspect.d.ts.map +1 -0
  64. package/packages/tools/lib/commands/inspect.js +56 -0
  65. package/packages/tools/lib/commands/inspect.js.map +1 -0
  66. package/packages/tools/lib/commands/lock.d.ts.map +1 -1
  67. package/packages/tools/lib/commands/lock.js +7 -2
  68. package/packages/tools/lib/commands/lock.js.map +1 -1
  69. package/packages/tools/lib/commands/ontology.d.ts.map +1 -1
  70. package/packages/tools/lib/commands/ontology.js +69 -116
  71. package/packages/tools/lib/commands/ontology.js.map +1 -1
  72. package/packages/tools/lib/commands/pack.d.ts +1 -1
  73. package/packages/tools/lib/commands/pack.d.ts.map +1 -1
  74. package/packages/tools/lib/commands/pack.js +20 -78
  75. package/packages/tools/lib/commands/pack.js.map +1 -1
  76. package/packages/tools/lib/commands/package.d.ts +10 -0
  77. package/packages/tools/lib/commands/package.d.ts.map +1 -0
  78. package/packages/tools/lib/commands/package.js +113 -0
  79. package/packages/tools/lib/commands/package.js.map +1 -0
  80. package/packages/tools/lib/commands/plugin.js +1 -1
  81. package/packages/tools/lib/commands/plugin.js.map +1 -1
  82. package/packages/tools/lib/commands/rules.d.ts.map +1 -1
  83. package/packages/tools/lib/commands/rules.js +120 -30
  84. package/packages/tools/lib/commands/rules.js.map +1 -1
  85. package/packages/tools/lib/commands/standards.d.ts +28 -0
  86. package/packages/tools/lib/commands/standards.d.ts.map +1 -0
  87. package/packages/tools/lib/commands/standards.js +203 -0
  88. package/packages/tools/lib/commands/standards.js.map +1 -0
  89. package/packages/tools/lib/commands/sysand-publish.d.ts.map +1 -1
  90. package/packages/tools/lib/commands/sysand-publish.js +14 -16
  91. package/packages/tools/lib/commands/sysand-publish.js.map +1 -1
  92. package/packages/tools/lib/commands/toolchain.d.ts +8 -0
  93. package/packages/tools/lib/commands/toolchain.d.ts.map +1 -0
  94. package/packages/tools/lib/commands/toolchain.js +64 -0
  95. package/packages/tools/lib/commands/toolchain.js.map +1 -0
  96. package/packages/tools/lib/commands/validate.d.ts +1 -1
  97. package/packages/tools/lib/commands/validate.d.ts.map +1 -1
  98. package/packages/tools/lib/commands/validate.js +194 -56
  99. package/packages/tools/lib/commands/validate.js.map +1 -1
  100. package/packages/tools/lib/completeness/tracker.d.ts +10 -2
  101. package/packages/tools/lib/completeness/tracker.d.ts.map +1 -1
  102. package/packages/tools/lib/completeness/tracker.js +16 -21
  103. package/packages/tools/lib/completeness/tracker.js.map +1 -1
  104. package/packages/tools/lib/conformance/baseline.d.ts +26 -0
  105. package/packages/tools/lib/conformance/baseline.d.ts.map +1 -0
  106. package/packages/tools/lib/conformance/baseline.js +170 -0
  107. package/packages/tools/lib/conformance/baseline.js.map +1 -0
  108. package/packages/tools/lib/conformance/bnf.d.ts +84 -0
  109. package/packages/tools/lib/conformance/bnf.d.ts.map +1 -0
  110. package/packages/tools/lib/conformance/bnf.js +166 -0
  111. package/packages/tools/lib/conformance/bnf.js.map +1 -0
  112. package/packages/tools/lib/conformance/corpus.d.ts +125 -0
  113. package/packages/tools/lib/conformance/corpus.d.ts.map +1 -0
  114. package/packages/tools/lib/conformance/corpus.js +190 -0
  115. package/packages/tools/lib/conformance/corpus.js.map +1 -0
  116. package/packages/tools/lib/conformance/diff-xmi.d.ts +118 -0
  117. package/packages/tools/lib/conformance/diff-xmi.d.ts.map +1 -0
  118. package/packages/tools/lib/conformance/diff-xmi.js +276 -0
  119. package/packages/tools/lib/conformance/diff-xmi.js.map +1 -0
  120. package/packages/tools/lib/conformance/generated/syside-rules.d.ts +10 -0
  121. package/packages/tools/lib/conformance/generated/syside-rules.d.ts.map +1 -0
  122. package/packages/tools/lib/conformance/generated/syside-rules.js +1142 -0
  123. package/packages/tools/lib/conformance/generated/syside-rules.js.map +1 -0
  124. package/packages/tools/lib/conformance/index.d.ts +7 -0
  125. package/packages/tools/lib/conformance/index.d.ts.map +1 -0
  126. package/packages/tools/lib/conformance/index.js +19 -0
  127. package/packages/tools/lib/conformance/index.js.map +1 -0
  128. package/packages/tools/lib/conformance/run.d.ts +117 -0
  129. package/packages/tools/lib/conformance/run.d.ts.map +1 -0
  130. package/packages/tools/lib/conformance/run.js +0 -0
  131. package/packages/tools/lib/conformance/run.js.map +1 -0
  132. package/packages/tools/lib/conformance/xmi.d.ts +45 -0
  133. package/packages/tools/lib/conformance/xmi.d.ts.map +1 -0
  134. package/packages/tools/lib/conformance/xmi.js +148 -0
  135. package/packages/tools/lib/conformance/xmi.js.map +1 -0
  136. package/packages/tools/lib/dhf/document-compiler.d.ts.map +1 -1
  137. package/packages/tools/lib/dhf/document-compiler.js +13 -8
  138. package/packages/tools/lib/dhf/document-compiler.js.map +1 -1
  139. package/packages/tools/lib/dhf/document-registry.d.ts +16 -2
  140. package/packages/tools/lib/dhf/document-registry.d.ts.map +1 -1
  141. package/packages/tools/lib/dhf/document-registry.js +74 -23
  142. package/packages/tools/lib/dhf/document-registry.js.map +1 -1
  143. package/packages/tools/lib/dhf/index.d.ts +3 -0
  144. package/packages/tools/lib/dhf/index.d.ts.map +1 -1
  145. package/packages/tools/lib/dhf/index.js +4 -0
  146. package/packages/tools/lib/dhf/index.js.map +1 -1
  147. package/packages/tools/lib/dhf/query-engine.d.ts +12 -1
  148. package/packages/tools/lib/dhf/query-engine.d.ts.map +1 -1
  149. package/packages/tools/lib/dhf/query-engine.js +2 -1
  150. package/packages/tools/lib/dhf/query-engine.js.map +1 -1
  151. package/packages/tools/lib/dhf/query-executor.d.ts +72 -5
  152. package/packages/tools/lib/dhf/query-executor.d.ts.map +1 -1
  153. package/packages/tools/lib/dhf/query-executor.js +471 -81
  154. package/packages/tools/lib/dhf/query-executor.js.map +1 -1
  155. package/packages/tools/lib/dhf/script-runner.js +6 -6
  156. package/packages/tools/lib/dhf/script-runner.js.map +1 -1
  157. package/packages/tools/lib/dhf/standards-block.d.ts +40 -0
  158. package/packages/tools/lib/dhf/standards-block.d.ts.map +1 -0
  159. package/packages/tools/lib/dhf/standards-block.js +223 -0
  160. package/packages/tools/lib/dhf/standards-block.js.map +1 -0
  161. package/packages/tools/lib/dhf/standards-library.d.ts +123 -0
  162. package/packages/tools/lib/dhf/standards-library.d.ts.map +1 -0
  163. package/packages/tools/lib/dhf/standards-library.js +295 -0
  164. package/packages/tools/lib/dhf/standards-library.js.map +1 -0
  165. package/packages/tools/lib/dhf/standards-report.d.ts +189 -0
  166. package/packages/tools/lib/dhf/standards-report.d.ts.map +1 -0
  167. package/packages/tools/lib/dhf/standards-report.js +384 -0
  168. package/packages/tools/lib/dhf/standards-report.js.map +1 -0
  169. package/packages/tools/lib/dhf/template-lint.d.ts +42 -0
  170. package/packages/tools/lib/dhf/template-lint.d.ts.map +1 -0
  171. package/packages/tools/lib/dhf/template-lint.js +421 -0
  172. package/packages/tools/lib/dhf/template-lint.js.map +1 -0
  173. package/packages/tools/lib/dhf/template-resolver.d.ts.map +1 -1
  174. package/packages/tools/lib/dhf/template-resolver.js +18 -6
  175. package/packages/tools/lib/dhf/template-resolver.js.map +1 -1
  176. package/packages/tools/lib/import/column-mapper.d.ts +2 -2
  177. package/packages/tools/lib/import/column-mapper.d.ts.map +1 -1
  178. package/packages/tools/lib/import/column-mapper.js +3 -3
  179. package/packages/tools/lib/import/column-mapper.js.map +1 -1
  180. package/packages/tools/lib/importer/cameo-importer.js +2 -2
  181. package/packages/tools/lib/importer/cameo-importer.js.map +1 -1
  182. package/packages/tools/lib/importer/ea-importer.d.ts.map +1 -1
  183. package/packages/tools/lib/importer/ea-importer.js +4 -3
  184. package/packages/tools/lib/importer/ea-importer.js.map +1 -1
  185. package/packages/tools/lib/importer/owl-importer.d.ts.map +1 -1
  186. package/packages/tools/lib/importer/owl-importer.js +2 -3
  187. package/packages/tools/lib/importer/owl-importer.js.map +1 -1
  188. package/packages/tools/lib/index.d.ts +20 -3
  189. package/packages/tools/lib/index.d.ts.map +1 -1
  190. package/packages/tools/lib/index.js +19 -2
  191. package/packages/tools/lib/index.js.map +1 -1
  192. package/packages/tools/lib/language/generated/ast.d.ts +1344 -99
  193. package/packages/tools/lib/language/generated/ast.d.ts.map +1 -1
  194. package/packages/tools/lib/language/generated/ast.js +1066 -32
  195. package/packages/tools/lib/language/generated/ast.js.map +1 -1
  196. package/packages/tools/lib/language/generated/grammar.d.ts.map +1 -1
  197. package/packages/tools/lib/language/generated/grammar.js +5242 -1454
  198. package/packages/tools/lib/language/generated/grammar.js.map +1 -1
  199. package/packages/tools/lib/llm/chat-engine.d.ts +4 -3
  200. package/packages/tools/lib/llm/chat-engine.d.ts.map +1 -1
  201. package/packages/tools/lib/llm/chat-engine.js +15 -12
  202. package/packages/tools/lib/llm/chat-engine.js.map +1 -1
  203. package/packages/tools/lib/llm/generate-engine.d.ts +7 -2
  204. package/packages/tools/lib/llm/generate-engine.d.ts.map +1 -1
  205. package/packages/tools/lib/llm/generate-engine.js +22 -4
  206. package/packages/tools/lib/llm/generate-engine.js.map +1 -1
  207. package/packages/tools/lib/llm/index.d.ts +1 -0
  208. package/packages/tools/lib/llm/index.d.ts.map +1 -1
  209. package/packages/tools/lib/llm/index.js +1 -0
  210. package/packages/tools/lib/llm/index.js.map +1 -1
  211. package/packages/tools/lib/llm/model-context.d.ts +2 -2
  212. package/packages/tools/lib/llm/model-context.d.ts.map +1 -1
  213. package/packages/tools/lib/llm/model-context.js +7 -13
  214. package/packages/tools/lib/llm/model-context.js.map +1 -1
  215. package/packages/tools/lib/llm/sysml-guidance.d.ts +8 -0
  216. package/packages/tools/lib/llm/sysml-guidance.d.ts.map +1 -0
  217. package/packages/tools/lib/llm/sysml-guidance.js +28 -0
  218. package/packages/tools/lib/llm/sysml-guidance.js.map +1 -0
  219. package/packages/tools/lib/lock.d.ts +14 -8
  220. package/packages/tools/lib/lock.d.ts.map +1 -1
  221. package/packages/tools/lib/lock.js +97 -108
  222. package/packages/tools/lib/lock.js.map +1 -1
  223. package/packages/tools/lib/mcp/model-loader.d.ts +3 -0
  224. package/packages/tools/lib/mcp/model-loader.d.ts.map +1 -1
  225. package/packages/tools/lib/mcp/model-loader.js +9 -5
  226. package/packages/tools/lib/mcp/model-loader.js.map +1 -1
  227. package/packages/tools/lib/mcp/tools.d.ts +3 -0
  228. package/packages/tools/lib/mcp/tools.d.ts.map +1 -1
  229. package/packages/tools/lib/mcp/tools.js +8 -9
  230. package/packages/tools/lib/mcp/tools.js.map +1 -1
  231. package/packages/tools/lib/model/activity-notation.d.ts +54 -0
  232. package/packages/tools/lib/model/activity-notation.d.ts.map +1 -0
  233. package/packages/tools/lib/model/activity-notation.js +208 -0
  234. package/packages/tools/lib/model/activity-notation.js.map +1 -0
  235. package/packages/tools/lib/model/builder.d.ts +3 -0
  236. package/packages/tools/lib/model/builder.d.ts.map +1 -1
  237. package/packages/tools/lib/model/builder.js +956 -62
  238. package/packages/tools/lib/model/builder.js.map +1 -1
  239. package/packages/tools/lib/model/config-loader.d.ts +19 -25
  240. package/packages/tools/lib/model/config-loader.d.ts.map +1 -1
  241. package/packages/tools/lib/model/config-loader.js +51 -233
  242. package/packages/tools/lib/model/config-loader.js.map +1 -1
  243. package/packages/tools/lib/model/config.d.ts +42 -213
  244. package/packages/tools/lib/model/config.d.ts.map +1 -1
  245. package/packages/tools/lib/model/config.js +11 -9
  246. package/packages/tools/lib/model/config.js.map +1 -1
  247. package/packages/tools/lib/model/effective-scope.d.ts +74 -0
  248. package/packages/tools/lib/model/effective-scope.d.ts.map +1 -0
  249. package/packages/tools/lib/model/effective-scope.js +133 -0
  250. package/packages/tools/lib/model/effective-scope.js.map +1 -0
  251. package/packages/tools/lib/model/ir-identity.d.ts +94 -0
  252. package/packages/tools/lib/model/ir-identity.d.ts.map +1 -0
  253. package/packages/tools/lib/model/ir-identity.js +178 -0
  254. package/packages/tools/lib/model/ir-identity.js.map +1 -0
  255. package/packages/tools/lib/model/kind-registry.d.ts +134 -1
  256. package/packages/tools/lib/model/kind-registry.d.ts.map +1 -1
  257. package/packages/tools/lib/model/kind-registry.js +186 -4
  258. package/packages/tools/lib/model/kind-registry.js.map +1 -1
  259. package/packages/tools/lib/model/layer-resolver.js +1 -1
  260. package/packages/tools/lib/model/layer-resolver.js.map +1 -1
  261. package/packages/tools/lib/model/manifest.d.ts +8 -0
  262. package/packages/tools/lib/model/manifest.d.ts.map +1 -1
  263. package/packages/tools/lib/model/manifest.js +3 -0
  264. package/packages/tools/lib/model/manifest.js.map +1 -1
  265. package/packages/tools/lib/model/methodology-loader.d.ts +19 -24
  266. package/packages/tools/lib/model/methodology-loader.d.ts.map +1 -1
  267. package/packages/tools/lib/model/methodology-loader.js +179 -176
  268. package/packages/tools/lib/model/methodology-loader.js.map +1 -1
  269. package/packages/tools/lib/model/methodology-resolver.d.ts +115 -0
  270. package/packages/tools/lib/model/methodology-resolver.d.ts.map +1 -0
  271. package/packages/tools/lib/model/methodology-resolver.js +317 -0
  272. package/packages/tools/lib/model/methodology-resolver.js.map +1 -0
  273. package/packages/tools/lib/model/naming.d.ts +57 -0
  274. package/packages/tools/lib/model/naming.d.ts.map +1 -0
  275. package/packages/tools/lib/model/naming.js +69 -0
  276. package/packages/tools/lib/model/naming.js.map +1 -0
  277. package/packages/tools/lib/model/native-project.d.ts +130 -0
  278. package/packages/tools/lib/model/native-project.d.ts.map +1 -0
  279. package/packages/tools/lib/model/native-project.js +580 -0
  280. package/packages/tools/lib/model/native-project.js.map +1 -0
  281. package/packages/tools/lib/model/ontology-loader.d.ts +34 -21
  282. package/packages/tools/lib/model/ontology-loader.d.ts.map +1 -1
  283. package/packages/tools/lib/model/ontology-loader.js +343 -614
  284. package/packages/tools/lib/model/ontology-loader.js.map +1 -1
  285. package/packages/tools/lib/model/package-manifest.d.ts +37 -0
  286. package/packages/tools/lib/model/package-manifest.d.ts.map +1 -0
  287. package/packages/tools/lib/model/package-manifest.js +96 -0
  288. package/packages/tools/lib/model/package-manifest.js.map +1 -0
  289. package/packages/tools/lib/model/parser-utils.d.ts +31 -0
  290. package/packages/tools/lib/model/parser-utils.d.ts.map +1 -1
  291. package/packages/tools/lib/model/parser-utils.js +89 -1
  292. package/packages/tools/lib/model/parser-utils.js.map +1 -1
  293. package/packages/tools/lib/model/paths.d.ts +10 -4
  294. package/packages/tools/lib/model/paths.d.ts.map +1 -1
  295. package/packages/tools/lib/model/paths.js +10 -4
  296. package/packages/tools/lib/model/paths.js.map +1 -1
  297. package/packages/tools/lib/model/project-conversion.d.ts +226 -0
  298. package/packages/tools/lib/model/project-conversion.d.ts.map +1 -0
  299. package/packages/tools/lib/model/project-conversion.js +946 -0
  300. package/packages/tools/lib/model/project-conversion.js.map +1 -0
  301. package/packages/tools/lib/model/relationship-legality.d.ts +27 -0
  302. package/packages/tools/lib/model/relationship-legality.d.ts.map +1 -1
  303. package/packages/tools/lib/model/relationship-legality.js.map +1 -1
  304. package/packages/tools/lib/model/relationship-registry.d.ts +93 -1
  305. package/packages/tools/lib/model/relationship-registry.d.ts.map +1 -1
  306. package/packages/tools/lib/model/relationship-registry.js +217 -2
  307. package/packages/tools/lib/model/relationship-registry.js.map +1 -1
  308. package/packages/tools/lib/model/semantic.d.ts +73 -0
  309. package/packages/tools/lib/model/semantic.d.ts.map +1 -1
  310. package/packages/tools/lib/model/semantic.js +6 -0
  311. package/packages/tools/lib/model/semantic.js.map +1 -1
  312. package/packages/tools/lib/model/settings-boundary.d.ts +19 -0
  313. package/packages/tools/lib/model/settings-boundary.d.ts.map +1 -0
  314. package/packages/tools/lib/model/settings-boundary.js +118 -0
  315. package/packages/tools/lib/model/settings-boundary.js.map +1 -0
  316. package/packages/tools/lib/model/short-id.js +3 -3
  317. package/packages/tools/lib/model/short-id.js.map +1 -1
  318. package/packages/tools/lib/model/source-graph.d.ts.map +1 -1
  319. package/packages/tools/lib/model/source-graph.js +5 -1
  320. package/packages/tools/lib/model/source-graph.js.map +1 -1
  321. package/packages/tools/lib/model/source-provenance.d.ts +112 -0
  322. package/packages/tools/lib/model/source-provenance.d.ts.map +1 -0
  323. package/packages/tools/lib/model/source-provenance.js +169 -0
  324. package/packages/tools/lib/model/source-provenance.js.map +1 -0
  325. package/packages/tools/lib/model/sysml-ir.d.ts +9 -0
  326. package/packages/tools/lib/model/sysml-ir.d.ts.map +1 -0
  327. package/packages/tools/lib/model/sysml-ir.js +105 -0
  328. package/packages/tools/lib/model/sysml-ir.js.map +1 -0
  329. package/packages/tools/lib/model/view-deriver.d.ts +10 -1
  330. package/packages/tools/lib/model/view-deriver.d.ts.map +1 -1
  331. package/packages/tools/lib/model/view-deriver.js +139 -11
  332. package/packages/tools/lib/model/view-deriver.js.map +1 -1
  333. package/packages/tools/lib/model/view-kinds.d.ts +2 -0
  334. package/packages/tools/lib/model/view-kinds.d.ts.map +1 -1
  335. package/packages/tools/lib/model/view-kinds.js +11 -0
  336. package/packages/tools/lib/model/view-kinds.js.map +1 -1
  337. package/packages/tools/lib/ontology/owl-exporter.d.ts +2 -2
  338. package/packages/tools/lib/ontology/owl-exporter.d.ts.map +1 -1
  339. package/packages/tools/lib/ontology/owl-exporter.js +12 -12
  340. package/packages/tools/lib/ontology/owl-exporter.js.map +1 -1
  341. package/packages/tools/lib/operations/ai-authoring.d.ts +24 -0
  342. package/packages/tools/lib/operations/ai-authoring.d.ts.map +1 -0
  343. package/packages/tools/lib/operations/ai-authoring.js +77 -0
  344. package/packages/tools/lib/operations/ai-authoring.js.map +1 -0
  345. package/packages/tools/lib/operations/authoring.d.ts +40 -0
  346. package/packages/tools/lib/operations/authoring.d.ts.map +1 -0
  347. package/packages/tools/lib/operations/authoring.js +56 -0
  348. package/packages/tools/lib/operations/authoring.js.map +1 -0
  349. package/packages/tools/lib/operations/index.d.ts +2 -0
  350. package/packages/tools/lib/operations/index.d.ts.map +1 -1
  351. package/packages/tools/lib/operations/index.js +2 -0
  352. package/packages/tools/lib/operations/index.js.map +1 -1
  353. package/packages/tools/lib/operations/project-snapshot.d.ts +28 -3
  354. package/packages/tools/lib/operations/project-snapshot.d.ts.map +1 -1
  355. package/packages/tools/lib/operations/project-snapshot.js +79 -30
  356. package/packages/tools/lib/operations/project-snapshot.js.map +1 -1
  357. package/packages/tools/lib/protocol/messages.d.ts +381 -12
  358. package/packages/tools/lib/protocol/messages.d.ts.map +1 -1
  359. package/packages/tools/lib/serializer/csv-io.d.ts +7 -7
  360. package/packages/tools/lib/serializer/csv-io.d.ts.map +1 -1
  361. package/packages/tools/lib/serializer/csv-io.js +11 -11
  362. package/packages/tools/lib/serializer/csv-io.js.map +1 -1
  363. package/packages/tools/lib/serializer/sysml-generator.d.ts.map +1 -1
  364. package/packages/tools/lib/serializer/sysml-generator.js +34 -2
  365. package/packages/tools/lib/serializer/sysml-generator.js.map +1 -1
  366. package/packages/tools/lib/server/config-resolver.d.ts +8 -4
  367. package/packages/tools/lib/server/config-resolver.d.ts.map +1 -1
  368. package/packages/tools/lib/server/config-resolver.js +25 -44
  369. package/packages/tools/lib/server/config-resolver.js.map +1 -1
  370. package/packages/tools/lib/server/conflict-policy.d.ts +44 -0
  371. package/packages/tools/lib/server/conflict-policy.d.ts.map +1 -0
  372. package/packages/tools/lib/server/conflict-policy.js +94 -0
  373. package/packages/tools/lib/server/conflict-policy.js.map +1 -0
  374. package/packages/tools/lib/server/dev-server.d.ts +32 -2
  375. package/packages/tools/lib/server/dev-server.d.ts.map +1 -1
  376. package/packages/tools/lib/server/dev-server.js +875 -54
  377. package/packages/tools/lib/server/dev-server.js.map +1 -1
  378. package/packages/tools/lib/server/element-writer.d.ts +11 -0
  379. package/packages/tools/lib/server/element-writer.d.ts.map +1 -0
  380. package/packages/tools/lib/server/element-writer.js +123 -0
  381. package/packages/tools/lib/server/element-writer.js.map +1 -0
  382. package/packages/tools/lib/server/file-watcher.d.ts +4 -1
  383. package/packages/tools/lib/server/file-watcher.d.ts.map +1 -1
  384. package/packages/tools/lib/server/file-watcher.js +17 -7
  385. package/packages/tools/lib/server/file-watcher.js.map +1 -1
  386. package/packages/tools/lib/server/mutation-domain.d.ts +22 -0
  387. package/packages/tools/lib/server/mutation-domain.d.ts.map +1 -0
  388. package/packages/tools/lib/server/mutation-domain.js +87 -0
  389. package/packages/tools/lib/server/mutation-domain.js.map +1 -0
  390. package/packages/tools/lib/server/package-source.d.ts +26 -0
  391. package/packages/tools/lib/server/package-source.d.ts.map +1 -0
  392. package/packages/tools/lib/server/package-source.js +81 -0
  393. package/packages/tools/lib/server/package-source.js.map +1 -0
  394. package/packages/tools/lib/server/package-writer.d.ts +94 -0
  395. package/packages/tools/lib/server/package-writer.d.ts.map +1 -0
  396. package/packages/tools/lib/server/package-writer.js +317 -0
  397. package/packages/tools/lib/server/package-writer.js.map +1 -0
  398. package/packages/tools/lib/server/persistor.d.ts +81 -6
  399. package/packages/tools/lib/server/persistor.d.ts.map +1 -1
  400. package/packages/tools/lib/server/persistor.js +173 -44
  401. package/packages/tools/lib/server/persistor.js.map +1 -1
  402. package/packages/tools/lib/server/relationship-writer.d.ts +41 -5
  403. package/packages/tools/lib/server/relationship-writer.d.ts.map +1 -1
  404. package/packages/tools/lib/server/relationship-writer.js +157 -13
  405. package/packages/tools/lib/server/relationship-writer.js.map +1 -1
  406. package/packages/tools/lib/server/rule-policy-writer.d.ts +40 -0
  407. package/packages/tools/lib/server/rule-policy-writer.d.ts.map +1 -0
  408. package/packages/tools/lib/server/rule-policy-writer.js +124 -0
  409. package/packages/tools/lib/server/rule-policy-writer.js.map +1 -0
  410. package/packages/tools/lib/server/runtime-budget.d.ts +14 -0
  411. package/packages/tools/lib/server/runtime-budget.d.ts.map +1 -0
  412. package/packages/tools/lib/server/runtime-budget.js +15 -0
  413. package/packages/tools/lib/server/runtime-budget.js.map +1 -0
  414. package/packages/tools/lib/server/sysml-notation.d.ts +36 -0
  415. package/packages/tools/lib/server/sysml-notation.d.ts.map +1 -0
  416. package/packages/tools/lib/server/sysml-notation.js +104 -0
  417. package/packages/tools/lib/server/sysml-notation.js.map +1 -0
  418. package/packages/tools/lib/server/view-layout-store.d.ts.map +1 -1
  419. package/packages/tools/lib/server/view-layout-store.js +1 -0
  420. package/packages/tools/lib/server/view-layout-store.js.map +1 -1
  421. package/packages/tools/lib/sysml-ir/generated/sysml-derivations.d.ts +9 -0
  422. package/packages/tools/lib/sysml-ir/generated/sysml-derivations.d.ts.map +1 -0
  423. package/packages/tools/lib/sysml-ir/generated/sysml-derivations.js +315 -0
  424. package/packages/tools/lib/sysml-ir/generated/sysml-derivations.js.map +1 -0
  425. package/packages/tools/lib/sysml-ir/generated/sysml-metamodel.d.ts +5206 -0
  426. package/packages/tools/lib/sysml-ir/generated/sysml-metamodel.d.ts.map +1 -0
  427. package/packages/tools/lib/sysml-ir/generated/sysml-metamodel.js +1933 -0
  428. package/packages/tools/lib/sysml-ir/generated/sysml-metamodel.js.map +1 -0
  429. package/packages/tools/lib/sysml-ir/index.d.ts +3 -0
  430. package/packages/tools/lib/sysml-ir/index.d.ts.map +1 -0
  431. package/packages/tools/lib/sysml-ir/index.js +3 -0
  432. package/packages/tools/lib/sysml-ir/index.js.map +1 -0
  433. package/packages/tools/lib/sysml-ir/ir.d.ts +50 -0
  434. package/packages/tools/lib/sysml-ir/ir.d.ts.map +1 -0
  435. package/packages/tools/lib/sysml-ir/ir.js +2 -0
  436. package/packages/tools/lib/sysml-ir/ir.js.map +1 -0
  437. package/packages/tools/lib/sysmlc/bin/memo-sysmlc.d.ts +3 -0
  438. package/packages/tools/lib/sysmlc/bin/memo-sysmlc.d.ts.map +1 -0
  439. package/packages/tools/lib/sysmlc/bin/memo-sysmlc.js +97 -0
  440. package/packages/tools/lib/sysmlc/bin/memo-sysmlc.js.map +1 -0
  441. package/packages/tools/lib/sysmlc/index.d.ts +2 -0
  442. package/packages/tools/lib/sysmlc/index.d.ts.map +1 -0
  443. package/packages/tools/lib/sysmlc/index.js +7 -0
  444. package/packages/tools/lib/sysmlc/index.js.map +1 -0
  445. package/packages/tools/lib/sysmlc/server.d.ts +29 -0
  446. package/packages/tools/lib/sysmlc/server.d.ts.map +1 -0
  447. package/packages/tools/lib/sysmlc/server.js +168 -0
  448. package/packages/tools/lib/sysmlc/server.js.map +1 -0
  449. package/packages/tools/lib/toolchain/default-registry.d.ts +12 -0
  450. package/packages/tools/lib/toolchain/default-registry.d.ts.map +1 -0
  451. package/packages/tools/lib/toolchain/default-registry.js +28 -0
  452. package/packages/tools/lib/toolchain/default-registry.js.map +1 -0
  453. package/packages/tools/lib/toolchain/diagnostic.d.ts +57 -0
  454. package/packages/tools/lib/toolchain/diagnostic.d.ts.map +1 -0
  455. package/packages/tools/lib/toolchain/diagnostic.js +69 -0
  456. package/packages/tools/lib/toolchain/diagnostic.js.map +1 -0
  457. package/packages/tools/lib/toolchain/effective.d.ts +36 -0
  458. package/packages/tools/lib/toolchain/effective.d.ts.map +1 -0
  459. package/packages/tools/lib/toolchain/effective.js +91 -0
  460. package/packages/tools/lib/toolchain/effective.js.map +1 -0
  461. package/packages/tools/lib/toolchain/index.d.ts +11 -0
  462. package/packages/tools/lib/toolchain/index.d.ts.map +1 -0
  463. package/packages/tools/lib/toolchain/index.js +21 -0
  464. package/packages/tools/lib/toolchain/index.js.map +1 -0
  465. package/packages/tools/lib/toolchain/lowering.d.ts +63 -0
  466. package/packages/tools/lib/toolchain/lowering.d.ts.map +1 -0
  467. package/packages/tools/lib/toolchain/lowering.js +100 -0
  468. package/packages/tools/lib/toolchain/lowering.js.map +1 -0
  469. package/packages/tools/lib/toolchain/operations.d.ts +87 -0
  470. package/packages/tools/lib/toolchain/operations.d.ts.map +1 -0
  471. package/packages/tools/lib/toolchain/operations.js +111 -0
  472. package/packages/tools/lib/toolchain/operations.js.map +1 -0
  473. package/packages/tools/lib/toolchain/process.d.ts +45 -0
  474. package/packages/tools/lib/toolchain/process.d.ts.map +1 -0
  475. package/packages/tools/lib/toolchain/process.js +125 -0
  476. package/packages/tools/lib/toolchain/process.js.map +1 -0
  477. package/packages/tools/lib/toolchain/protocol.d.ts +122 -0
  478. package/packages/tools/lib/toolchain/protocol.d.ts.map +1 -0
  479. package/packages/tools/lib/toolchain/protocol.js +74 -0
  480. package/packages/tools/lib/toolchain/protocol.js.map +1 -0
  481. package/packages/tools/lib/toolchain/providers/internal-kpar.d.ts +9 -0
  482. package/packages/tools/lib/toolchain/providers/internal-kpar.d.ts.map +1 -0
  483. package/packages/tools/lib/toolchain/providers/internal-kpar.js +84 -0
  484. package/packages/tools/lib/toolchain/providers/internal-kpar.js.map +1 -0
  485. package/packages/tools/lib/toolchain/providers/internal.d.ts +33 -0
  486. package/packages/tools/lib/toolchain/providers/internal.d.ts.map +1 -0
  487. package/packages/tools/lib/toolchain/providers/internal.js +286 -0
  488. package/packages/tools/lib/toolchain/providers/internal.js.map +1 -0
  489. package/packages/tools/lib/toolchain/providers/sysand.d.ts +12 -0
  490. package/packages/tools/lib/toolchain/providers/sysand.d.ts.map +1 -0
  491. package/packages/tools/lib/toolchain/providers/sysand.js +110 -0
  492. package/packages/tools/lib/toolchain/providers/sysand.js.map +1 -0
  493. package/packages/tools/lib/toolchain/providers/syside.d.ts +24 -0
  494. package/packages/tools/lib/toolchain/providers/syside.d.ts.map +1 -0
  495. package/packages/tools/lib/toolchain/providers/syside.js +208 -0
  496. package/packages/tools/lib/toolchain/providers/syside.js.map +1 -0
  497. package/packages/tools/lib/toolchain/registry.d.ts +192 -0
  498. package/packages/tools/lib/toolchain/registry.d.ts.map +1 -0
  499. package/packages/tools/lib/toolchain/registry.js +130 -0
  500. package/packages/tools/lib/toolchain/registry.js.map +1 -0
  501. package/packages/tools/lib/toolchain/schema.d.ts +30 -0
  502. package/packages/tools/lib/toolchain/schema.d.ts.map +1 -0
  503. package/packages/tools/lib/toolchain/schema.js +112 -0
  504. package/packages/tools/lib/toolchain/schema.js.map +1 -0
  505. package/packages/tools/lib/toolchain/sysmlc-client.d.ts +77 -0
  506. package/packages/tools/lib/toolchain/sysmlc-client.d.ts.map +1 -0
  507. package/packages/tools/lib/toolchain/sysmlc-client.js +0 -0
  508. package/packages/tools/lib/toolchain/sysmlc-client.js.map +1 -0
  509. package/packages/tools/lib/validator/architecture-validator.d.ts +8 -1
  510. package/packages/tools/lib/validator/architecture-validator.d.ts.map +1 -1
  511. package/packages/tools/lib/validator/architecture-validator.js +25 -1
  512. package/packages/tools/lib/validator/architecture-validator.js.map +1 -1
  513. package/packages/tools/lib/validator/behavior-validator.d.ts +1 -1
  514. package/packages/tools/lib/validator/behavior-validator.js +1 -1
  515. package/packages/tools/lib/validator/builtin-rules.d.ts +16 -0
  516. package/packages/tools/lib/validator/builtin-rules.d.ts.map +1 -0
  517. package/packages/tools/lib/validator/builtin-rules.js +62 -0
  518. package/packages/tools/lib/validator/builtin-rules.js.map +1 -0
  519. package/packages/tools/lib/validator/constraint-eval.d.ts +31 -0
  520. package/packages/tools/lib/validator/constraint-eval.d.ts.map +1 -1
  521. package/packages/tools/lib/validator/constraint-eval.js +164 -7
  522. package/packages/tools/lib/validator/constraint-eval.js.map +1 -1
  523. package/packages/tools/lib/validator/constraint-loader.d.ts +22 -4
  524. package/packages/tools/lib/validator/constraint-loader.d.ts.map +1 -1
  525. package/packages/tools/lib/validator/constraint-loader.js +100 -27
  526. package/packages/tools/lib/validator/constraint-loader.js.map +1 -1
  527. package/packages/tools/lib/validator/rule-engine.d.ts +2 -1
  528. package/packages/tools/lib/validator/rule-engine.d.ts.map +1 -1
  529. package/packages/tools/lib/validator/rule-engine.js +56 -5
  530. package/packages/tools/lib/validator/rule-engine.js.map +1 -1
  531. package/packages/tools/lib/validator/sysml-constraints.d.ts +64 -0
  532. package/packages/tools/lib/validator/sysml-constraints.d.ts.map +1 -0
  533. package/packages/tools/lib/validator/sysml-constraints.js +251 -0
  534. package/packages/tools/lib/validator/sysml-constraints.js.map +1 -0
  535. package/packages/tools/lib/validator/types.d.ts +33 -0
  536. package/packages/tools/lib/validator/types.d.ts.map +1 -1
  537. package/packages/tools/lib/version.d.ts +2 -0
  538. package/packages/tools/lib/version.d.ts.map +1 -0
  539. package/packages/tools/lib/version.js +26 -0
  540. package/packages/tools/lib/version.js.map +1 -0
  541. package/packages/tools/src/grammar/memo-sysml.langium +517 -41
  542. package/packages/tools/lib/model/dimension-filter.d.ts +0 -33
  543. package/packages/tools/lib/model/dimension-filter.d.ts.map +0 -1
  544. package/packages/tools/lib/model/dimension-filter.js +0 -57
  545. package/packages/tools/lib/model/dimension-filter.js.map +0 -1
  546. package/packages/tools/lib/model/toolchain.d.ts +0 -12
  547. package/packages/tools/lib/model/toolchain.d.ts.map +0 -1
  548. package/packages/tools/lib/model/toolchain.js +0 -92
  549. 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 { isWritableRelationshipFile, removeRelationship, resolveRelationshipPlacement, writeRelationship, } from './relationship-writer.js';
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: { middlewareMode: true, host },
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 ? await loadAndResolveConfig(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 { config } = await llmQueryContext();
254
- const kindDef = config.kinds?.[change.elementKind];
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} (${validation.normalizedType}) ` +
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', () => clients.delete(ws));
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
- // Re-send current state
551
- for (const m of initialMessages) {
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 result = saveElementToFile(projectRoot, msg.payload);
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
- // The file watcher will catch this change and broadcast to all clients
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 (typeof baseRevision === 'string' && baseRevision !== currentRevision) {
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, findConfigFile, } = await import('@memoarchitect/tools');
885
- const { loadAndResolveConfig } = await import('./config-resolver.js');
886
- const configPath = findConfigFile(projectRoot);
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, config);
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, config, knownIds);
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, config } = await llmQueryContext();
1766
+ const { ctx, ontology } = await llmQueryContext();
990
1767
  const provider = createProvider(llmConfig);
991
1768
  const result = await runChatTurn({
992
- question, history, ctx, provider, config,
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
- // Generate SysML v2 from natural language (#54)
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 { resolveLLMConfig, createProvider, generateSysml, findConfigFile } = await import('@memoarchitect/tools');
1075
- const { loadAndResolveConfig } = await import('./config-resolver.js');
1076
- const llmConfig = resolveLLMConfig();
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
- broadcast(messages) {
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(...messages);
2074
+ initialMessages.push(...publication);
1299
2075
  for (const client of clients) {
1300
2076
  if (client.readyState === 1) { // WebSocket.OPEN
1301
- for (const msg of messages) {
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();