@memoarchitect/tools 0.6.6 → 0.7.2

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