@aryacode/dsh-engineering 0.0.0-stage → 0.3.0

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 (281) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/LICENSE +21 -0
  3. package/README.md +228 -2
  4. package/cordis.patch.yml +122 -0
  5. package/locale/en.json +6 -0
  6. package/locale/zh.json +6 -0
  7. package/package.json +77 -3
  8. package/vendor/ftview/README.md +55 -0
  9. package/vendor/ftview/SKILL.md +74 -0
  10. package/vendor/ftview/lib/checks.d.ts +27 -0
  11. package/vendor/ftview/lib/checks.js +39 -0
  12. package/vendor/ftview/lib/core.d.ts +208 -0
  13. package/vendor/ftview/lib/core.js +160 -0
  14. package/vendor/ftview/lib/edit/drafts.d.ts +49 -0
  15. package/vendor/ftview/lib/edit/drafts.js +84 -0
  16. package/vendor/ftview/lib/edit/operations.d.ts +62 -0
  17. package/vendor/ftview/lib/edit/operations.js +126 -0
  18. package/vendor/ftview/lib/edit/prepare.d.ts +38 -0
  19. package/vendor/ftview/lib/edit/prepare.js +133 -0
  20. package/vendor/ftview/lib/edit/tools.d.ts +25 -0
  21. package/vendor/ftview/lib/edit/tools.js +198 -0
  22. package/vendor/ftview/lib/edit.d.ts +30 -0
  23. package/vendor/ftview/lib/edit.js +38 -0
  24. package/vendor/ftview/lib/index.d.ts +31 -0
  25. package/vendor/ftview/lib/index.js +36 -0
  26. package/vendor/ftview/lib/model.d.ts +80 -0
  27. package/vendor/ftview/lib/model.js +267 -0
  28. package/vendor/ftview/lib/session.d.ts +48 -0
  29. package/vendor/ftview/lib/session.js +98 -0
  30. package/vendor/ftview/lib/skill.d.ts +29 -0
  31. package/vendor/ftview/lib/skill.js +43 -0
  32. package/vendor/ftview/lib/studio/discover.d.ts +36 -0
  33. package/vendor/ftview/lib/studio/discover.js +87 -0
  34. package/vendor/ftview/lib/studio/preflight.d.ts +51 -0
  35. package/vendor/ftview/lib/studio/preflight.js +64 -0
  36. package/vendor/ftview/lib/studio/tools.d.ts +15 -0
  37. package/vendor/ftview/lib/studio/tools.js +83 -0
  38. package/vendor/ftview/lib/studio.d.ts +30 -0
  39. package/vendor/ftview/lib/studio.js +37 -0
  40. package/vendor/ftview/lib/tools.d.ts +14 -0
  41. package/vendor/ftview/lib/tools.js +202 -0
  42. package/vendor/l5x/README.md +62 -0
  43. package/vendor/l5x/SKILL.md +81 -0
  44. package/vendor/l5x/lib/approvals.d.ts +50 -0
  45. package/vendor/l5x/lib/approvals.js +93 -0
  46. package/vendor/l5x/lib/client.js +412 -0
  47. package/vendor/l5x/lib/client.js.map +1 -0
  48. package/vendor/l5x/lib/core/errors.d.ts +38 -0
  49. package/vendor/l5x/lib/core/errors.js +48 -0
  50. package/vendor/l5x/lib/core/params.d.ts +49 -0
  51. package/vendor/l5x/lib/core/params.js +39 -0
  52. package/vendor/l5x/lib/core/result.d.ts +46 -0
  53. package/vendor/l5x/lib/core/result.js +21 -0
  54. package/vendor/l5x/lib/core/tool.d.ts +102 -0
  55. package/vendor/l5x/lib/core/tool.js +19 -0
  56. package/vendor/l5x/lib/drafts.d.ts +66 -0
  57. package/vendor/l5x/lib/drafts.js +98 -0
  58. package/vendor/l5x/lib/editing.d.ts +30 -0
  59. package/vendor/l5x/lib/editing.js +42 -0
  60. package/vendor/l5x/lib/engine/acd-edit.d.ts +13 -0
  61. package/vendor/l5x/lib/engine/acd-edit.js +115 -0
  62. package/vendor/l5x/lib/engine/analysis.d.ts +22 -0
  63. package/vendor/l5x/lib/engine/analysis.js +258 -0
  64. package/vendor/l5x/lib/engine/capabilities.d.ts +9 -0
  65. package/vendor/l5x/lib/engine/capabilities.js +40 -0
  66. package/vendor/l5x/lib/engine/change-set.d.ts +122 -0
  67. package/vendor/l5x/lib/engine/change-set.js +446 -0
  68. package/vendor/l5x/lib/engine/configuration.d.ts +21 -0
  69. package/vendor/l5x/lib/engine/configuration.js +42 -0
  70. package/vendor/l5x/lib/engine/details.d.ts +47 -0
  71. package/vendor/l5x/lib/engine/details.js +173 -0
  72. package/vendor/l5x/lib/engine/direct-polling.d.ts +91 -0
  73. package/vendor/l5x/lib/engine/direct-polling.js +193 -0
  74. package/vendor/l5x/lib/engine/download-review.d.ts +15 -0
  75. package/vendor/l5x/lib/engine/download-review.js +63 -0
  76. package/vendor/l5x/lib/engine/logic-syntax.d.ts +60 -0
  77. package/vendor/l5x/lib/engine/logic-syntax.js +237 -0
  78. package/vendor/l5x/lib/engine/observations.d.ts +41 -0
  79. package/vendor/l5x/lib/engine/observations.js +67 -0
  80. package/vendor/l5x/lib/engine/project-analysis.d.ts +131 -0
  81. package/vendor/l5x/lib/engine/project-analysis.js +314 -0
  82. package/vendor/l5x/lib/engine/review-evidence.d.ts +8 -0
  83. package/vendor/l5x/lib/engine/review-evidence.js +49 -0
  84. package/vendor/l5x/lib/engine/review.d.ts +18 -0
  85. package/vendor/l5x/lib/engine/review.js +123 -0
  86. package/vendor/l5x/lib/engine/studio-sdk.d.ts +101 -0
  87. package/vendor/l5x/lib/engine/studio-sdk.js +571 -0
  88. package/vendor/l5x/lib/engine/tag-resolution.d.ts +20 -0
  89. package/vendor/l5x/lib/engine/tag-resolution.js +169 -0
  90. package/vendor/l5x/lib/engine/usage.d.ts +16 -0
  91. package/vendor/l5x/lib/engine/usage.js +92 -0
  92. package/vendor/l5x/lib/engine/xml-regions.d.ts +23 -0
  93. package/vendor/l5x/lib/engine/xml-regions.js +77 -0
  94. package/vendor/l5x/lib/fs.d.ts +17 -0
  95. package/vendor/l5x/lib/fs.js +36 -0
  96. package/vendor/l5x/lib/index.d.ts +36 -0
  97. package/vendor/l5x/lib/index.js +42 -0
  98. package/vendor/l5x/lib/live.d.ts +48 -0
  99. package/vendor/l5x/lib/live.js +48 -0
  100. package/vendor/l5x/lib/model/aois.d.ts +39 -0
  101. package/vendor/l5x/lib/model/aois.js +112 -0
  102. package/vendor/l5x/lib/model/errors.d.ts +15 -0
  103. package/vendor/l5x/lib/model/errors.js +18 -0
  104. package/vendor/l5x/lib/model/index.d.ts +20 -0
  105. package/vendor/l5x/lib/model/index.js +19 -0
  106. package/vendor/l5x/lib/model/module.d.ts +37 -0
  107. package/vendor/l5x/lib/model/module.js +54 -0
  108. package/vendor/l5x/lib/model/modules.d.ts +24 -0
  109. package/vendor/l5x/lib/model/modules.js +55 -0
  110. package/vendor/l5x/lib/model/parse.d.ts +32 -0
  111. package/vendor/l5x/lib/model/parse.js +116 -0
  112. package/vendor/l5x/lib/model/project.d.ts +15 -0
  113. package/vendor/l5x/lib/model/project.js +31 -0
  114. package/vendor/l5x/lib/model/routines.d.ts +71 -0
  115. package/vendor/l5x/lib/model/routines.js +183 -0
  116. package/vendor/l5x/lib/model/tags.d.ts +51 -0
  117. package/vendor/l5x/lib/model/tags.js +119 -0
  118. package/vendor/l5x/lib/model/types.d.ts +304 -0
  119. package/vendor/l5x/lib/model/types.js +12 -0
  120. package/vendor/l5x/lib/model/udts.d.ts +34 -0
  121. package/vendor/l5x/lib/model/udts.js +81 -0
  122. package/vendor/l5x/lib/model/xml.d.ts +139 -0
  123. package/vendor/l5x/lib/model/xml.js +291 -0
  124. package/vendor/l5x/lib/model/xref.d.ts +28 -0
  125. package/vendor/l5x/lib/model/xref.js +154 -0
  126. package/vendor/l5x/lib/session.d.ts +119 -0
  127. package/vendor/l5x/lib/session.js +236 -0
  128. package/vendor/l5x/lib/skill.d.ts +30 -0
  129. package/vendor/l5x/lib/skill.js +44 -0
  130. package/vendor/l5x/lib/studio-state.d.ts +77 -0
  131. package/vendor/l5x/lib/studio-state.js +79 -0
  132. package/vendor/l5x/lib/studio.d.ts +29 -0
  133. package/vendor/l5x/lib/studio.js +40 -0
  134. package/vendor/l5x/lib/tools/checks.d.ts +19 -0
  135. package/vendor/l5x/lib/tools/checks.js +140 -0
  136. package/vendor/l5x/lib/tools/common.d.ts +41 -0
  137. package/vendor/l5x/lib/tools/common.js +84 -0
  138. package/vendor/l5x/lib/tools/controller.d.ts +21 -0
  139. package/vendor/l5x/lib/tools/controller.js +170 -0
  140. package/vendor/l5x/lib/tools/drafts.d.ts +23 -0
  141. package/vendor/l5x/lib/tools/drafts.js +270 -0
  142. package/vendor/l5x/lib/tools/live.d.ts +21 -0
  143. package/vendor/l5x/lib/tools/live.js +175 -0
  144. package/vendor/l5x/lib/tools/read.d.ts +19 -0
  145. package/vendor/l5x/lib/tools/read.js +245 -0
  146. package/vendor/l5x/lib/tools/references.d.ts +20 -0
  147. package/vendor/l5x/lib/tools/references.js +269 -0
  148. package/vendor/l5x/lib/tools/studio.d.ts +21 -0
  149. package/vendor/l5x/lib/tools/studio.js +193 -0
  150. package/vendor/l5x/poll-worker.mjs +77 -0
  151. package/vendor/l5x/studio-bridge/NuGet.Config +1 -0
  152. package/vendor/l5x/studio-bridge/Program.cs +304 -0
  153. package/vendor/l5x/studio-bridge/StudioBridge.csproj +11 -0
  154. package/vendor/mwi/README.md +60 -0
  155. package/vendor/mwi/SKILL.md +87 -0
  156. package/vendor/mwi/bridge/README.md +8 -0
  157. package/vendor/mwi/bridge/mw_bridge.ps1 +2763 -0
  158. package/vendor/mwi/bridge/sdk-interop-importer.cs +30 -0
  159. package/vendor/mwi/bridge/sdk-variable-reader.cs +22 -0
  160. package/vendor/mwi/bridge/start_bridge.cmd +21 -0
  161. package/vendor/mwi/bridge/watch_dialogs.cmd +13 -0
  162. package/vendor/mwi/bridge/watch_dialogs.ps1 +153 -0
  163. package/vendor/mwi/code/engine/motionworks_iec_mcp/__init__.py +14 -0
  164. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
  165. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/block_interfaces.cpython-312.pyc +0 -0
  166. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/cfb.cpython-312.pyc +0 -0
  167. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/datatypes.cpython-312.pyc +0 -0
  168. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/declarations.cpython-312.pyc +0 -0
  169. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/errors.cpython-312.pyc +0 -0
  170. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/graphical_listing.cpython-312.pyc +0 -0
  171. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/grid.cpython-312.pyc +0 -0
  172. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/knowledge.cpython-312.pyc +0 -0
  173. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/mwt_bind.cpython-312.pyc +0 -0
  174. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/navigation.cpython-312.pyc +0 -0
  175. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/program_checks.cpython-312.pyc +0 -0
  176. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/project.cpython-312.pyc +0 -0
  177. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/staging.cpython-312.pyc +0 -0
  178. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/structure.cpython-312.pyc +0 -0
  179. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/task_bindings.cpython-312.pyc +0 -0
  180. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tasks.cpython-312.pyc +0 -0
  181. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tree.cpython-312.pyc +0 -0
  182. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/validation.cpython-312.pyc +0 -0
  183. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variable_edit.cpython-312.pyc +0 -0
  184. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variables.cpython-312.pyc +0 -0
  185. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/workflow.cpython-312.pyc +0 -0
  186. package/vendor/mwi/code/engine/motionworks_iec_mcp/block_interfaces.py +275 -0
  187. package/vendor/mwi/code/engine/motionworks_iec_mcp/build.py +339 -0
  188. package/vendor/mwi/code/engine/motionworks_iec_mcp/capture.py +448 -0
  189. package/vendor/mwi/code/engine/motionworks_iec_mcp/cfb.py +765 -0
  190. package/vendor/mwi/code/engine/motionworks_iec_mcp/com.py +268 -0
  191. package/vendor/mwi/code/engine/motionworks_iec_mcp/datatypes.py +204 -0
  192. package/vendor/mwi/code/engine/motionworks_iec_mcp/declarations.py +399 -0
  193. package/vendor/mwi/code/engine/motionworks_iec_mcp/descriptions.py +30 -0
  194. package/vendor/mwi/code/engine/motionworks_iec_mcp/eip.py +292 -0
  195. package/vendor/mwi/code/engine/motionworks_iec_mcp/errors.py +36 -0
  196. package/vendor/mwi/code/engine/motionworks_iec_mcp/graphical_listing.py +208 -0
  197. package/vendor/mwi/code/engine/motionworks_iec_mcp/grid.py +227 -0
  198. package/vendor/mwi/code/engine/motionworks_iec_mcp/ide.py +317 -0
  199. package/vendor/mwi/code/engine/motionworks_iec_mcp/iec.py +317 -0
  200. package/vendor/mwi/code/engine/motionworks_iec_mcp/installed_help.py +151 -0
  201. package/vendor/mwi/code/engine/motionworks_iec_mcp/knowledge.py +218 -0
  202. package/vendor/mwi/code/engine/motionworks_iec_mcp/libraries.py +250 -0
  203. package/vendor/mwi/code/engine/motionworks_iec_mcp/manuals.py +520 -0
  204. package/vendor/mwi/code/engine/motionworks_iec_mcp/mwt_bind.py +133 -0
  205. package/vendor/mwi/code/engine/motionworks_iec_mcp/navigation.py +44 -0
  206. package/vendor/mwi/code/engine/motionworks_iec_mcp/pou_writer.py +1547 -0
  207. package/vendor/mwi/code/engine/motionworks_iec_mcp/program_checks.py +360 -0
  208. package/vendor/mwi/code/engine/motionworks_iec_mcp/project.py +704 -0
  209. package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/catalog.json +672 -0
  210. package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/patterns.json +86 -0
  211. package/vendor/mwi/code/engine/motionworks_iec_mcp/restore.py +208 -0
  212. package/vendor/mwi/code/engine/motionworks_iec_mcp/server.py +1913 -0
  213. package/vendor/mwi/code/engine/motionworks_iec_mcp/snapshot.py +138 -0
  214. package/vendor/mwi/code/engine/motionworks_iec_mcp/st_comments.py +25 -0
  215. package/vendor/mwi/code/engine/motionworks_iec_mcp/staging.py +179 -0
  216. package/vendor/mwi/code/engine/motionworks_iec_mcp/stlint.py +676 -0
  217. package/vendor/mwi/code/engine/motionworks_iec_mcp/structure.py +98 -0
  218. package/vendor/mwi/code/engine/motionworks_iec_mcp/task_bindings.py +68 -0
  219. package/vendor/mwi/code/engine/motionworks_iec_mcp/tasks.py +162 -0
  220. package/vendor/mwi/code/engine/motionworks_iec_mcp/transaction.py +157 -0
  221. package/vendor/mwi/code/engine/motionworks_iec_mcp/tree.py +563 -0
  222. package/vendor/mwi/code/engine/motionworks_iec_mcp/tree_writer.py +790 -0
  223. package/vendor/mwi/code/engine/motionworks_iec_mcp/uia.py +465 -0
  224. package/vendor/mwi/code/engine/motionworks_iec_mcp/validation.py +41 -0
  225. package/vendor/mwi/code/engine/motionworks_iec_mcp/variable_edit.py +183 -0
  226. package/vendor/mwi/code/engine/motionworks_iec_mcp/variables.py +472 -0
  227. package/vendor/mwi/code/engine/motionworks_iec_mcp/workflow.py +130 -0
  228. package/vendor/mwi/code/engine/motionworks_iec_mcp/writer.py +666 -0
  229. package/vendor/mwi/code/mw_code.py +1258 -0
  230. package/vendor/mwi/legacy/edit-session.js +50 -0
  231. package/vendor/mwi/legacy/fb-insertion.js +74 -0
  232. package/vendor/mwi/legacy/native-code.js +83 -0
  233. package/vendor/mwi/legacy/native-groups.js +75 -0
  234. package/vendor/mwi/legacy/native-structure.js +143 -0
  235. package/vendor/mwi/legacy/native-variables.js +77 -0
  236. package/vendor/mwi/legacy/pou-conversion.js +47 -0
  237. package/vendor/mwi/legacy/pou-package.js +66 -0
  238. package/vendor/mwi/lib/core/bridge.d.ts +71 -0
  239. package/vendor/mwi/lib/core/bridge.js +53 -0
  240. package/vendor/mwi/lib/core/code-engine.d.ts +47 -0
  241. package/vendor/mwi/lib/core/code-engine.js +109 -0
  242. package/vendor/mwi/lib/core/errors.d.ts +37 -0
  243. package/vendor/mwi/lib/core/errors.js +47 -0
  244. package/vendor/mwi/lib/core/params.d.ts +52 -0
  245. package/vendor/mwi/lib/core/params.js +46 -0
  246. package/vendor/mwi/lib/core/ps-bridge.d.ts +62 -0
  247. package/vendor/mwi/lib/core/ps-bridge.js +127 -0
  248. package/vendor/mwi/lib/core/result.d.ts +45 -0
  249. package/vendor/mwi/lib/core/result.js +20 -0
  250. package/vendor/mwi/lib/core/tool.d.ts +80 -0
  251. package/vendor/mwi/lib/core/tool.js +55 -0
  252. package/vendor/mwi/lib/edit/compare.d.ts +33 -0
  253. package/vendor/mwi/lib/edit/compare.js +70 -0
  254. package/vendor/mwi/lib/edit.d.ts +20 -0
  255. package/vendor/mwi/lib/edit.js +32 -0
  256. package/vendor/mwi/lib/index.d.ts +21 -0
  257. package/vendor/mwi/lib/index.js +33 -0
  258. package/vendor/mwi/lib/read/service.d.ts +117 -0
  259. package/vendor/mwi/lib/read/service.js +229 -0
  260. package/vendor/mwi/lib/read.d.ts +21 -0
  261. package/vendor/mwi/lib/read.js +33 -0
  262. package/vendor/mwi/lib/session/project.d.ts +34 -0
  263. package/vendor/mwi/lib/session/project.js +54 -0
  264. package/vendor/mwi/lib/session/service.d.ts +167 -0
  265. package/vendor/mwi/lib/session/service.js +566 -0
  266. package/vendor/mwi/lib/session/stage.d.ts +84 -0
  267. package/vendor/mwi/lib/session/stage.js +187 -0
  268. package/vendor/mwi/lib/session/workspace.d.ts +52 -0
  269. package/vendor/mwi/lib/session/workspace.js +90 -0
  270. package/vendor/mwi/lib/skill.d.ts +36 -0
  271. package/vendor/mwi/lib/skill.js +56 -0
  272. package/vendor/mwi/lib/tools/edit.d.ts +20 -0
  273. package/vendor/mwi/lib/tools/edit.js +286 -0
  274. package/vendor/mwi/lib/tools/read.d.ts +19 -0
  275. package/vendor/mwi/lib/tools/read.js +161 -0
  276. package/vendor/mwi/lib/tools/session.d.ts +20 -0
  277. package/vendor/mwi/lib/tools/session.js +166 -0
  278. package/vendor/mwi/lib/tools/verify.d.ts +19 -0
  279. package/vendor/mwi/lib/tools/verify.js +85 -0
  280. package/vendor/mwi/lib/verify.d.ts +17 -0
  281. package/vendor/mwi/lib/verify.js +29 -0
@@ -0,0 +1,314 @@
1
+ import { resolveTag } from "./tag-resolution.js";
2
+ import { addressAt, lexLogic, parseLadder } from "./logic-syntax.js";
3
+ /** Registry version changes when operand roles change. It does not certify controller availability. */
4
+ export const SEMANTICS_VERSION = 1;
5
+ const roles = {
6
+ XIC: ['read'], XIO: ['read'], OTE: ['write'], OTL: ['write'], OTU: ['write'], CLR: ['write'],
7
+ MOV: ['read', 'write'], MOVE: ['read', 'write'], COP: ['read', 'write', 'read'], CPS: ['read', 'write', 'read'], FLL: ['read', 'write', 'read'],
8
+ ADD: ['read', 'read', 'write'], SUB: ['read', 'read', 'write'], MUL: ['read', 'read', 'write'], DIV: ['read', 'read', 'write'],
9
+ EQU: ['read', 'read'], EQ: ['read', 'read'], NEQ: ['read', 'read'], NE: ['read', 'read'], GRT: ['read', 'read'], GT: ['read', 'read'],
10
+ GEQ: ['read', 'read'], GE: ['read', 'read'], LES: ['read', 'read'], LT: ['read', 'read'], LEQ: ['read', 'read'], LE: ['read', 'read'],
11
+ LIM: ['read', 'read', 'read'], LIMIT: ['read', 'read', 'read'], TON: ['read_write', 'read', 'read'], TOF: ['read_write', 'read', 'read'], RTO: ['read_write', 'read', 'read'],
12
+ CTU: ['read_write', 'read', 'read'], CTD: ['read_write', 'read', 'read'], RES: ['read_write'], ONS: ['read_write'], NOP: [], AFI: [],
13
+ };
14
+ const keywords = new Set('IF THEN ELSE ELSIF END_IF FOR TO BY DO END_FOR WHILE END_WHILE REPEAT UNTIL END_REPEAT CASE OF END_CASE RETURN EXIT TRUE FALSE NOT AND OR XOR MOD'.split(' '));
15
+ const cache = new WeakMap();
16
+ /** Build source-positioned references and conservative local dependency edges once per snapshot.
17
+ * @param project - Immutable export snapshot.
18
+ * @param bounds - Inclusive reference and edge limits; truncation makes coverage incomplete.
19
+ * @returns Shared analysis with located limitations and source identity.
20
+ */
21
+ export function analyzeProject(project, bounds) {
22
+ const cacheKey = `${bounds.references}:${bounds.edges}`;
23
+ const cached = cache.get(project)?.get(cacheKey);
24
+ if (cached !== undefined)
25
+ return cached;
26
+ const references = [];
27
+ const dependencies = [];
28
+ const limitations = [];
29
+ const truncated = new Set();
30
+ const location = (routine, token, rung) => ({ scope: routine.program, routine: routine.name, language: routine.type, ...(rung === undefined ? { line: token.line } : { rung }), start: token.start, end: token.end });
31
+ const limit = (routine, reason, rung, offset) => {
32
+ if (limitations.length < bounds.references)
33
+ limitations.push({ scope: routine.program, routine: routine.name, ...(rung === undefined ? {} : { rung }), ...(offset === undefined ? {} : { offset }), reason });
34
+ else
35
+ truncated.add('Limitation report limit reached');
36
+ };
37
+ const addAddresses = (routine, tokens, access, source, rung, instruction) => {
38
+ const indices = [];
39
+ for (let cursor = 0; cursor < tokens.length;) {
40
+ if (references.length >= bounds.references) {
41
+ truncated.add('Reference analysis limit reached');
42
+ break;
43
+ }
44
+ const token = tokens[cursor];
45
+ if (token.kind !== 'identifier' || keywords.has(token.text.toUpperCase())) {
46
+ cursor++;
47
+ continue;
48
+ }
49
+ const found = addressAt(tokens, cursor);
50
+ if (found === undefined) {
51
+ limit(routine, 'Malformed symbol address', rung, token.start);
52
+ cursor++;
53
+ continue;
54
+ }
55
+ cursor = found.next;
56
+ const call = tokens[cursor]?.text === '(';
57
+ const resolution = resolveTag(project.document, routine.program, found.address.text);
58
+ const role = call ? 'unknown' : access;
59
+ const index = references.length;
60
+ references.push({ ...location(routine, token, rung), end: found.address.end, symbol: found.address.text,
61
+ ...(resolution.canonical === undefined ? {} : { canonical: resolution.canonical }), access: role,
62
+ ...(instruction === undefined ? {} : { instruction }), indices_verified: resolution.indices_verified,
63
+ context: rung === undefined ? source.split('\n')[token.line] ?? '' : source });
64
+ indices.push(index);
65
+ if (!resolution.resolved)
66
+ limit(routine, `${found.address.text}: ${resolution.reason}`, rung, token.start);
67
+ if (!resolution.indices_verified)
68
+ limit(routine, `Dynamic subscript in ${found.address.text}`, rung, token.start);
69
+ if (role === 'unknown')
70
+ limit(routine, `Unresolved operand effects for ${found.address.text}`, rung, token.start);
71
+ if (found.address.indexTokens.length > 0)
72
+ indices.push(...addAddresses(routine, found.address.indexTokens, 'read', source, rung, instruction));
73
+ }
74
+ return indices;
75
+ };
76
+ const connect = (indices, kind) => {
77
+ const reads = indices.filter(index => ['read', 'read_write'].includes(references[index].access));
78
+ const writes = indices.filter(index => ['write', 'read_write'].includes(references[index].access));
79
+ for (const read of reads)
80
+ for (const write of writes) {
81
+ const from = references[read].canonical;
82
+ const to = references[write].canonical;
83
+ if (from !== undefined && to !== undefined) {
84
+ if (dependencies.length >= bounds.edges) {
85
+ truncated.add('Dependency analysis limit reached');
86
+ return;
87
+ }
88
+ dependencies.push({ from, to, kind, evidence: [read, write] });
89
+ }
90
+ }
91
+ };
92
+ const analyzeLadder = (routine, source, rung) => {
93
+ const parsed = parseLadder(source);
94
+ for (const issue of parsed.issues)
95
+ limit(routine, issue.message, rung, issue.offset);
96
+ const rungIndices = [];
97
+ const visit = (nodes) => {
98
+ for (const node of nodes) {
99
+ if (references.length >= bounds.references) {
100
+ truncated.add('Reference analysis limit reached');
101
+ break;
102
+ }
103
+ if (node.kind === 'branch') {
104
+ for (const path of node.paths)
105
+ visit(path);
106
+ continue;
107
+ }
108
+ const name = node.name.toUpperCase();
109
+ if (name === 'JSR') {
110
+ const target = node.operands[0]?.map(token => token.text).join('') ?? '';
111
+ const called = routines.find(entry => entry.program.toLowerCase() === routine.program.toLowerCase() && entry.name.toLowerCase() === target.toLowerCase());
112
+ references.push({ scope: routine.program, routine: routine.name, language: routine.type, rung, start: node.start, end: node.end, symbol: target, ...(called === undefined ? {} : { canonical: `routine::${called.program}::${called.name}` }), access: 'call', instruction: node.name, indices_verified: true, context: source });
113
+ limit(routine, 'JSR parameter transfer and called-routine effects require interprocedural analysis', rung, node.start);
114
+ for (const operand of node.operands.slice(1))
115
+ rungIndices.push(...addAddresses(routine, operand, 'unknown', source, rung, node.name));
116
+ continue;
117
+ }
118
+ const aoi = project.document.aois.find(entry => entry.name.toUpperCase() === name);
119
+ let operandRoles = roles[name];
120
+ if (aoi !== undefined) {
121
+ references.push({ scope: routine.program, routine: routine.name, language: routine.type, rung, start: node.start, end: node.end, symbol: aoi.name, canonical: `AOI::${aoi.name}`, access: 'call', instruction: node.name, indices_verified: true, context: source });
122
+ const parameters = aoi.parameters.filter(parameter => parameter.visible !== false && !['EnableIn', 'EnableOut'].includes(parameter.name));
123
+ operandRoles = ['read_write', ...parameters.map(parameter => parameter.usage === 'Input' ? 'read' : parameter.usage === 'Output' ? 'write' : parameter.usage === 'InOut' ? 'read_write' : 'unknown')];
124
+ limit(routine, 'AOI parameter usage is known; instance execution effects remain partial', rung, node.start);
125
+ }
126
+ if (operandRoles === undefined || operandRoles.length !== node.operands.length) {
127
+ limit(routine, `Unsupported instruction or operand count: ${node.name}`, rung, node.start);
128
+ operandRoles = node.operands.map(() => 'unknown');
129
+ }
130
+ const instructionIndices = node.operands.flatMap((operand, index) => addAddresses(routine, operand, operandRoles[index], source, rung, node.name));
131
+ rungIndices.push(...instructionIndices);
132
+ connect(instructionIndices, 'operand');
133
+ }
134
+ };
135
+ visit(parsed.nodes);
136
+ connect(rungIndices, 'condition_candidate');
137
+ };
138
+ const analyzeSt = (routine, source) => {
139
+ const parsed = lexLogic(source);
140
+ for (const issue of parsed.issues)
141
+ limit(routine, issue.message, undefined, issue.offset);
142
+ let statement = [];
143
+ const visit = () => {
144
+ if (statement.length === 0)
145
+ return;
146
+ const assignment = statement.findIndex(token => token.text === ':=');
147
+ const hasControl = statement.some(token => keywords.has(token.text.toUpperCase()) && !['TRUE', 'FALSE', 'AND', 'OR', 'XOR', 'NOT', 'MOD'].includes(token.text.toUpperCase()));
148
+ if (hasControl)
149
+ limit(routine, 'ST control-flow dependencies are partial', undefined, statement[0].start);
150
+ if (assignment < 0) {
151
+ addAddresses(routine, statement, 'read', source);
152
+ return;
153
+ }
154
+ const prefix = statement.slice(0, assignment);
155
+ let targetOffset = -1;
156
+ for (let cursor = 0; cursor < prefix.length; cursor++) {
157
+ const target = addressAt(prefix, cursor);
158
+ if (target?.next === prefix.length && !keywords.has(prefix[cursor].text.toUpperCase())) {
159
+ targetOffset = cursor;
160
+ break;
161
+ }
162
+ }
163
+ if (targetOffset < 0 || statement.slice(assignment + 1).some(token => token.text === ':=')) {
164
+ addAddresses(routine, statement, 'unknown', source);
165
+ limit(routine, 'Unsupported assignment target or chained assignment', undefined, statement[0].start);
166
+ return;
167
+ }
168
+ const inputs = addAddresses(routine, prefix.slice(0, targetOffset), 'read', source);
169
+ const outputs = addAddresses(routine, prefix.slice(targetOffset), 'write', source);
170
+ const sources = addAddresses(routine, statement.slice(assignment + 1), 'read', source);
171
+ connect([...inputs, ...outputs, ...sources], 'operand');
172
+ };
173
+ for (const token of parsed.tokens) {
174
+ if (token.text === ';') {
175
+ visit();
176
+ statement = [];
177
+ }
178
+ else
179
+ statement.push(token);
180
+ }
181
+ if (statement.length > 0) {
182
+ visit();
183
+ limit(routine, 'Source ends without a statement terminator', undefined, statement[0].start);
184
+ }
185
+ };
186
+ const routines = [...project.document.routines, ...project.document.aois.flatMap(aoi => aoi.routines.map(routine => ({ ...routine, program: `AOI::${aoi.name}` })))];
187
+ for (const routine of routines) {
188
+ if (routine.body.type === 'RLL')
189
+ for (const rung of routine.body.rungs)
190
+ analyzeLadder(routine, rung.text, rung.number);
191
+ else if (routine.body.type === 'ST')
192
+ analyzeSt(routine, routine.body.code);
193
+ else
194
+ limit(routine, `${routine.type} execution analysis is unsupported`);
195
+ }
196
+ if (project.features.protectedContent === 'unsupported')
197
+ limitations.push({ scope: 'controller', routine: '', reason: 'Protected content is not analyzed' });
198
+ for (const reason of truncated)
199
+ limitations.push({ scope: 'controller', routine: '', reason });
200
+ const result = { project_id: project.id, source_sha256: project.sha256, references, dependencies, limitations, static_complete: limitations.length === 0, runtime_causality_verified: false };
201
+ const ownedCache = cache.get(project) ?? new Map();
202
+ ownedCache.set(cacheKey, result);
203
+ cache.set(project, ownedCache);
204
+ return result;
205
+ }
206
+ /** Read exported task scheduling, program entry routines and parsed JSR/AOI call locations.
207
+ * @param project - Immutable selected export.
208
+ * @param limits - Deployment bounds for graph size and source analysis.
209
+ * @returns A bounded execution graph; declared scheduling is not observed scan execution.
210
+ */
211
+ export function executionGraph(project, limits) {
212
+ const analysis = analyzeProject(project, limits.analysis);
213
+ const nodes = new Map();
214
+ const edges = [];
215
+ let truncated = false;
216
+ const add = (identity, kind, declared) => {
217
+ if (nodes.has(identity))
218
+ return true;
219
+ if (nodes.size >= limits.nodes) {
220
+ truncated = true;
221
+ return false;
222
+ }
223
+ nodes.set(identity, { identity, kind, declared });
224
+ return true;
225
+ };
226
+ const edge = (from, to, kind, evidence) => {
227
+ if (!nodes.has(from) || !nodes.has(to) || edges.length >= limits.edges) {
228
+ truncated = true;
229
+ return;
230
+ }
231
+ edges.push({ from, to, kind, ...(evidence === undefined ? {} : { evidence }) });
232
+ };
233
+ for (const program of project.document.programs) {
234
+ const id = `program::${program.name}`;
235
+ add(id, 'program', true);
236
+ if (program.mainRoutine !== '') {
237
+ const entry = project.document.routines.find(routine => routine.program === program.name && routine.name.toLowerCase() === program.mainRoutine.toLowerCase());
238
+ const target = `routine::${program.name}::${entry?.name ?? program.mainRoutine}`;
239
+ add(target, 'routine', entry !== undefined);
240
+ edge(id, target, 'entry');
241
+ }
242
+ }
243
+ for (const task of project.document.tasks) {
244
+ const id = `task::${task.name}`;
245
+ add(id, 'task', true);
246
+ for (const scheduled of task.programs) {
247
+ const program = project.document.programs.find(program => program.name.toLowerCase() === scheduled.toLowerCase());
248
+ const target = `program::${program?.name ?? scheduled}`;
249
+ add(target, 'program', program !== undefined);
250
+ edge(id, target, 'schedules');
251
+ }
252
+ }
253
+ for (const reference of analysis.references)
254
+ if (reference.access === 'call') {
255
+ const caller = `routine::${reference.scope}::${reference.routine}`;
256
+ add(caller, 'routine', true);
257
+ const target = reference.canonical ?? `routine::${reference.scope}::${reference.symbol}`;
258
+ add(target, target.startsWith('AOI::') ? 'aoi' : 'routine', reference.canonical !== undefined);
259
+ edge(caller, target, 'call', reference);
260
+ }
261
+ return { project_id: project.id, source_sha256: project.sha256, nodes: [...nodes.values()], edges, truncated,
262
+ unscheduled_programs: project.document.programs.filter(program => !project.document.tasks.some(task => task.programs.some(name => name.toLowerCase() === program.name.toLowerCase()))).slice(0, limits.nodes).map(program => program.name),
263
+ static_complete: analysis.static_complete && !truncated && [...nodes.values()].every(node => node.declared), runtime_execution_verified: false };
264
+ }
265
+ /** Trace dependencies or dependents, returning every stopping reason.
266
+ * @param project - Immutable selected source.
267
+ * @param symbol - Selected tag address.
268
+ * @param scope - Its resolution scope.
269
+ * @param direction - Upstream sources or downstream impact.
270
+ * @param limits - Validated deployment bounds.
271
+ * @returns Bounded graph with source-linked edges, coverage and truncation indicators.
272
+ */
273
+ export function traceDependencies(project, symbol, scope, direction, limits) {
274
+ const analysis = analyzeProject(project, limits.analysis);
275
+ const resolution = resolveTag(project.document, scope, symbol);
276
+ const root = resolution.canonical;
277
+ const coverage = { limitations: analysis.limitations.slice(0, limits.edges), limitations_total: analysis.limitations.length, limitations_truncated: analysis.limitations.length > limits.edges };
278
+ if (root === undefined)
279
+ return { project_id: project.id, source_sha256: project.sha256, resolution, nodes: [], edges: [], ...coverage, truncated: false, static_complete: false, runtime_causality_verified: false };
280
+ const nodes = new Map([[root, 0]]);
281
+ const queue = [root];
282
+ const edges = [];
283
+ const seenEdges = new Set();
284
+ const adjacency = new Map();
285
+ for (const edge of analysis.dependencies) {
286
+ const key = direction === 'upstream' ? edge.to : edge.from;
287
+ const existing = adjacency.get(key);
288
+ if (existing === undefined)
289
+ adjacency.set(key, [edge]);
290
+ else
291
+ existing.push(edge);
292
+ }
293
+ let truncated = false;
294
+ for (let cursor = 0; cursor < queue.length; cursor++) {
295
+ const current = queue[cursor];
296
+ const depth = nodes.get(current);
297
+ for (const edge of adjacency.get(current) ?? []) {
298
+ const next = direction === 'upstream' ? edge.from : edge.to;
299
+ if (seenEdges.has(edge))
300
+ continue;
301
+ if (depth >= limits.depth || edges.length >= limits.edges || (!nodes.has(next) && nodes.size >= limits.nodes)) {
302
+ truncated = true;
303
+ continue;
304
+ }
305
+ seenEdges.add(edge);
306
+ edges.push(edge);
307
+ if (!nodes.has(next)) {
308
+ nodes.set(next, depth + 1);
309
+ queue.push(next);
310
+ }
311
+ }
312
+ }
313
+ return { project_id: project.id, source_sha256: project.sha256, resolution, direction, nodes: [...nodes].map(([symbol, depth]) => ({ symbol, depth })), edges: edges.map(edge => ({ ...edge, evidence: edge.evidence.map(index => analysis.references[index]) })), ...coverage, truncated, static_complete: analysis.static_complete && !truncated, runtime_causality_verified: false };
314
+ }
@@ -0,0 +1,8 @@
1
+ /** Exact source-citation checks for model-authored engineering findings. */
2
+ import { type ProjectSnapshot } from '../session.ts';
3
+ /** Verify exact names, locations and quotes without certifying their interpretation.
4
+ * @param project - hash-checked export snapshot.
5
+ * @param request - JSON array of program/routine/quote citations with rung or zero-based ST line; AOI programs use AOI::Name.
6
+ * @returns source identity and per-citation results; no controller or network contact.
7
+ */
8
+ export declare function verifyReviewEvidence(project: ProjectSnapshot, request: string): object;
@@ -0,0 +1,49 @@
1
+ /** Exact source-citation checks for model-authored engineering findings. */
2
+ import { EngineeringError } from "../session.js";
3
+ function citation(value) {
4
+ if (typeof value !== 'object' || value === null || !('program' in value) || typeof value.program !== 'string'
5
+ || !('routine' in value) || typeof value.routine !== 'string' || !('quote' in value) || typeof value.quote !== 'string'
6
+ || value.program.trim() === '' || value.routine.trim() === '' || value.quote.trim() === '') {
7
+ throw new EngineeringError('INVALID_CITATION', 'Each citation requires program, routine and a nonempty verbatim quote');
8
+ }
9
+ const rung = 'rung' in value ? value.rung : undefined;
10
+ const line = 'line' in value ? value.line : undefined;
11
+ if ((rung === undefined) === (line === undefined)
12
+ || (rung !== undefined && (typeof rung !== 'number' || !Number.isInteger(rung) || rung < 0))
13
+ || (line !== undefined && (typeof line !== 'number' || !Number.isInteger(line) || line < 0))) {
14
+ throw new EngineeringError('INVALID_CITATION', 'Specify exactly one nonnegative integer rung or zero-based ST line');
15
+ }
16
+ return { program: value.program, routine: value.routine, quote: value.quote,
17
+ ...(typeof rung === 'number' ? { rung } : {}), ...(typeof line === 'number' ? { line } : {}) };
18
+ }
19
+ /** Verify exact names, locations and quotes without certifying their interpretation.
20
+ * @param project - hash-checked export snapshot.
21
+ * @param request - JSON array of program/routine/quote citations with rung or zero-based ST line; AOI programs use AOI::Name.
22
+ * @returns source identity and per-citation results; no controller or network contact.
23
+ */
24
+ export function verifyReviewEvidence(project, request) {
25
+ let parsed;
26
+ try {
27
+ parsed = JSON.parse(request);
28
+ }
29
+ catch (error) {
30
+ throw new EngineeringError('INVALID_CITATION', `Citations must be JSON: ${error instanceof Error ? error.message : String(error)}`);
31
+ }
32
+ if (!Array.isArray(parsed) || parsed.length === 0)
33
+ throw new EngineeringError('INVALID_CITATION', 'Citations must be a nonempty JSON array');
34
+ const citations = parsed.map(citation);
35
+ const results = citations.map(item => {
36
+ const routine = item.program.startsWith('AOI::')
37
+ ? project.document.aois.find(aoi => `AOI::${aoi.name}` === item.program)?.routines.find(r => r.name === item.routine)
38
+ : project.document.routines.find(r => r.program === item.program && r.name === item.routine);
39
+ const text = routine?.body.type === 'RLL' && item.rung !== undefined
40
+ ? routine.body.rungs.find(r => r.number === item.rung)?.text
41
+ : routine?.body.type === 'ST' && item.line !== undefined ? routine.body.code.split('\n')[item.line] : undefined;
42
+ const reason = routine === undefined ? 'routine_not_found' : text === undefined ? 'location_not_found'
43
+ : !text.includes(item.quote) ? 'quote_mismatch' : 'matched';
44
+ return { ...item, verified: reason === 'matched', reason, ...(text === undefined ? {} : { source_text: text }) };
45
+ });
46
+ return { project_id: project.id, sha256: project.sha256, results, all_citations_verified: results.every(result => result.verified),
47
+ interpretation_verified: false, controller_contacted: false,
48
+ note: 'Matching text verifies a citation only. It does not establish runtime behavior, absence of other references, instruction semantics or whole-machine safety.' };
49
+ }
@@ -0,0 +1,18 @@
1
+ import type { ProjectSnapshot } from '../session.ts';
2
+ export interface Change {
3
+ readonly kind: 'added' | 'removed' | 'changed';
4
+ readonly component: string;
5
+ readonly key: string;
6
+ readonly before?: unknown;
7
+ readonly after?: unknown;
8
+ }
9
+ /** Compare parsed engineering content; timestamps and XML presentation never enter the comparison. */
10
+ export declare function compareProjects(before: ProjectSnapshot, after: ProjectSnapshot): object;
11
+ export interface Finding {
12
+ readonly rule: string;
13
+ readonly severity: 'info' | 'warning' | 'critical';
14
+ readonly message: string;
15
+ readonly evidence: unknown;
16
+ readonly confidence: 'high' | 'medium' | 'low';
17
+ }
18
+ export declare function runChecks(project: ProjectSnapshot, baseline?: ProjectSnapshot): object;
@@ -0,0 +1,123 @@
1
+ import { evidenceOf } from "./analysis.js";
2
+ import { engineeringTags, moduleConfigurations } from "./configuration.js";
3
+ function changes(component, before, after, key) {
4
+ const old = new Map(before.map(value => [key(value), value]));
5
+ const next = new Map(after.map(value => [key(value), value]));
6
+ const result = [];
7
+ for (const [name, value] of old) {
8
+ const other = next.get(name);
9
+ if (other === undefined)
10
+ result.push({ kind: 'removed', component, key: name, before: value });
11
+ else if (JSON.stringify(value) !== JSON.stringify(other))
12
+ result.push({ kind: 'changed', component, key: name, before: value, after: other });
13
+ }
14
+ for (const [name, value] of next)
15
+ if (!old.has(name))
16
+ result.push({ kind: 'added', component, key: name, after: value });
17
+ return result;
18
+ }
19
+ function allRoutines(document) {
20
+ return [...document.routines, ...document.aois.flatMap(aoi => aoi.routines.map(routine => ({ ...routine, program: `AOI::${aoi.name}` })))];
21
+ }
22
+ /** Compare parsed engineering content; timestamps and XML presentation never enter the comparison. */
23
+ export function compareProjects(before, after) {
24
+ const a = before.document;
25
+ const b = after.document;
26
+ const oldRoutines = allRoutines(a);
27
+ const newRoutines = allRoutines(b);
28
+ const result = [
29
+ ...changes('program', a.programs, b.programs, v => v.name),
30
+ ...changes('task', a.tasks, b.tasks, v => v.name),
31
+ ...changes('tag', engineeringTags(a), engineeringTags(b), v => `${v.scope}::${v.name}`),
32
+ ...changes('udt', a.udts, b.udts, v => v.name),
33
+ ...changes('aoi', a.aois.map(({ routines: _routines, ...rest }) => rest), b.aois.map(({ routines: _routines, ...rest }) => rest), v => v.name),
34
+ ...changes('module', moduleConfigurations(a), moduleConfigurations(b), v => v.name),
35
+ ...changes('alarm', a.alarms, b.alarms, v => v.name),
36
+ ...changes('routine', oldRoutines.map(({ body: _body, ...rest }) => rest), newRoutines.map(({ body: _body, ...rest }) => rest), v => `${v.program}::${v.name}`),
37
+ ];
38
+ const oldRungs = oldRoutines.flatMap(r => r.body.type === 'RLL' ? r.body.rungs.map(v => ({ program: r.program, routine: r.name, ...v })) : []);
39
+ const newRungs = newRoutines.flatMap(r => r.body.type === 'RLL' ? r.body.rungs.map(v => ({ program: r.program, routine: r.name, ...v })) : []);
40
+ result.push(...changes('rung', oldRungs, newRungs, v => `${v.program}::${v.routine}::${v.number}`));
41
+ const oldSt = oldRoutines.filter(r => r.body.type === 'ST').map(r => ({ program: r.program, routine: r.name, code: r.body.type === 'ST' ? r.body.code : '' }));
42
+ const newSt = newRoutines.filter(r => r.body.type === 'ST').map(r => ({ program: r.program, routine: r.name, code: r.body.type === 'ST' ? r.body.code : '' }));
43
+ result.push(...changes('structured_text', oldSt, newSt, v => `${v.program}::${v.routine}`));
44
+ const oldDiagrams = oldRoutines.filter(r => r.body.type === 'FBD' || r.body.type === 'SFC').map(r => ({ program: r.program, routine: r.name, type: r.type, nodes: r.body.type === 'FBD' || r.body.type === 'SFC' ? r.body.nodes : [] }));
45
+ const newDiagrams = newRoutines.filter(r => r.body.type === 'FBD' || r.body.type === 'SFC').map(r => ({ program: r.program, routine: r.name, type: r.type, nodes: r.body.type === 'FBD' || r.body.type === 'SFC' ? r.body.nodes : [] }));
46
+ result.push(...changes('diagram', oldDiagrams, newDiagrams, v => `${v.program}::${v.routine}`));
47
+ return { before: before.sha256, after: after.sha256, changes: result, count: result.length, coverage: { before: before.features, after: after.features } };
48
+ }
49
+ export function runChecks(project, baseline) {
50
+ const doc = project.document;
51
+ const references = evidenceOf(project);
52
+ const findings = [];
53
+ const report = (rule, severity, message, evidence, confidence = 'high') => { findings.push({ rule, severity, message, evidence, confidence }); };
54
+ const used = new Set();
55
+ for (const ref of references) {
56
+ if (ref.resolved !== undefined) {
57
+ const resolved = ref.resolved.toLowerCase();
58
+ used.add(resolved);
59
+ used.add(resolved.split('.')[0] ?? resolved);
60
+ }
61
+ const rawBase = ref.symbol.split('.')[0] ?? ref.symbol;
62
+ used.add(`${ref.scope}::${rawBase}`.toLowerCase());
63
+ used.add(`controller::${rawBase}`.toLowerCase());
64
+ }
65
+ for (const tag of doc.tags) {
66
+ const key = `${tag.scope}::${tag.name}`.toLowerCase();
67
+ if (!used.has(key))
68
+ report('UNUSED_TAG', 'info', `No indexed logic reference to ${tag.scope}::${tag.name}`, { scope: tag.scope, tag: tag.name }, 'low');
69
+ }
70
+ const unresolved = new Map();
71
+ for (const ref of references) {
72
+ if (ref.resolved !== undefined || ref.access === 'call' || ref.access === 'unknown')
73
+ continue;
74
+ const key = `${ref.scope}::${ref.symbol}`;
75
+ unresolved.set(key, [...unresolved.get(key) ?? [], ref]);
76
+ }
77
+ for (const [symbol, locations] of unresolved)
78
+ report('UNRESOLVED_REFERENCE', 'info', `Symbol ${symbol} could not be resolved in this export`, { occurrences: locations.length, examples: locations.slice(0, 3) }, 'low');
79
+ const scheduled = new Map();
80
+ for (const task of doc.tasks)
81
+ for (const program of task.programs)
82
+ scheduled.set(program, [...scheduled.get(program) ?? [], task.name]);
83
+ for (const program of doc.programs) {
84
+ const tasks = scheduled.get(program.name) ?? [];
85
+ if (tasks.length === 0)
86
+ report('UNSCHEDULED_PROGRAM', 'warning', `Program ${program.name} is not scheduled`, { program: program.name });
87
+ if (tasks.length > 1)
88
+ report('MULTIPLE_SCHEDULES', 'warning', `Program ${program.name} is scheduled more than once`, { program: program.name, tasks });
89
+ if (program.description.trim() === '')
90
+ report('UNDOCUMENTED_PROGRAM', 'info', `Program ${program.name} has no description`, { program: program.name });
91
+ }
92
+ for (const routine of doc.routines) {
93
+ if (routine.description.trim() === '')
94
+ report('UNDOCUMENTED_ROUTINE', 'info', `Routine ${routine.name} has no description`, { program: routine.program, routine: routine.name });
95
+ if (routine.body.type === 'RLL') {
96
+ // One finding per routine, not per rung: a large export otherwise buries
97
+ // every substantive finding under thousands of documentation rows.
98
+ const undocumented = routine.body.rungs.filter(rung => (rung.comment ?? '').trim() === '');
99
+ if (undocumented.length > 0)
100
+ report('UNDOCUMENTED_RUNG', 'info', `${undocumented.length} rung(s) in ${routine.program}::${routine.name} have no comment`, { program: routine.program, routine: routine.name, count: undocumented.length, rungs: undocumented.slice(0, 20).map(rung => rung.number), truncated: undocumented.length > 20 }, 'high');
101
+ }
102
+ }
103
+ const writers = new Map();
104
+ for (const ref of references)
105
+ if (ref.resolved !== undefined && (ref.access === 'write' || ref.access === 'read_write'))
106
+ writers.set(ref.resolved, [...writers.get(ref.resolved) ?? [], ref]);
107
+ for (const [symbol, locations] of writers)
108
+ if (locations.length > 1 && /(?:^|::|\.)[^.]*:(?:O|Output)|_Output|\.O\./iu.test(symbol))
109
+ report('MULTIPLE_OUTPUT_WRITES', 'warning', `Multiple indexed writes to output-like symbol ${symbol}`, locations, 'medium');
110
+ if (baseline !== undefined) {
111
+ const difference = compareProjects(baseline, project);
112
+ for (const change of difference.changes)
113
+ if (change.component === 'module')
114
+ report('IO_MODULE_CHANGED', 'warning', `I/O module ${change.key} ${change.kind}`, change);
115
+ }
116
+ const byRule = {};
117
+ const bySeverity = {};
118
+ for (const finding of findings) {
119
+ byRule[finding.rule] = (byRule[finding.rule] ?? 0) + 1;
120
+ bySeverity[finding.severity] = (bySeverity[finding.severity] ?? 0) + 1;
121
+ }
122
+ return { project_id: project.id, baseline_id: baseline?.id, findings, count: findings.length, summary: { byRule, bySeverity }, coverage: project.features, note: 'Heuristic findings require engineering review; absence of findings does not prove controller safety.' };
123
+ }
@@ -0,0 +1,101 @@
1
+ import { type ProjectSnapshot } from '../session.ts';
2
+ import type { LogicSample } from './observations.ts';
3
+ export interface SdkStatus {
4
+ available: boolean;
5
+ reason: string;
6
+ sdk_service_status?: string;
7
+ activation_service_status?: string;
8
+ communication_services?: {
9
+ readonly factorytalk_linx: string;
10
+ readonly factorytalk_linx_02: string;
11
+ readonly rslinx_classic: string;
12
+ };
13
+ live_communications_available?: boolean;
14
+ sdk_root: string;
15
+ client_package?: string;
16
+ bridge_built: boolean;
17
+ /** Minimum project revision the SDK can open. */
18
+ open_supported_project_revisions: '31+';
19
+ /** Minimum project revision whose Logix Designer engine supports Build. */
20
+ build_supported_project_revisions: '37+';
21
+ /** Highest installed Logix Designer release, which is the SDK release axis. */
22
+ sdk_release: number | null;
23
+ installed_studio_versions: readonly number[];
24
+ }
25
+ export declare function parseSdkServiceState(output: string): string;
26
+ export declare function hasLiveCommunicationsService(states: readonly string[]): boolean;
27
+ export declare function sdkDiagnosticLines(output: string): string[];
28
+ export declare function studioSdkStatus(): Promise<SdkStatus>;
29
+ /** Export one existing ACD as a new full L5X; this never connects to a controller. */
30
+ export declare function exportAcdToL5x(acdPath: string, outputPath: string, signal?: AbortSignal): Promise<object>;
31
+ export declare function verifyOfflineL5x(project: ProjectSnapshot, outputPath: string, signal?: AbortSignal, options?: {
32
+ readonly probeBuild?: boolean;
33
+ }): Promise<object>;
34
+ /** Work only on a fresh ACD copy; this bridge command has no controller route. */
35
+ export declare function editOfflineAcd(acdPath: string, draft: ProjectSnapshot, program: string, routine: string, outputPath: string, exportPath: string, signal?: AbortSignal): Promise<{
36
+ offline_build: string;
37
+ source_acd_sha256: string;
38
+ output_path: string | null;
39
+ output_sha256: string | null;
40
+ result_export_path: string | null;
41
+ log: string;
42
+ partial_output_path: string | null;
43
+ failure_stage: string | null;
44
+ import_errors: number | null;
45
+ }>;
46
+ export declare function readControllerMode(project: ProjectSnapshot, commPath: string, signal?: AbortSignal, snapshotPath?: string): Promise<object>;
47
+ /** Read the exported Controller attribute, never an instruction string or a guessed route. */
48
+ export declare function savedL5xCommPath(xml: string): string;
49
+ /** Inspect the path recorded in one project, without browsing drivers or going online. */
50
+ export declare function readProjectCommPath(inputPath: string, signal?: AbortSignal): Promise<object>;
51
+ export declare function rememberControllerRoute(ip: string, projectPath: string, route: string): Promise<void>;
52
+ export declare function inheritControllerRoute(sourcePath: string, targetPath: string): Promise<void>;
53
+ export declare function validateControllerIp(ip: string): string;
54
+ /** A saved Linx route is usable only when its final IP hop is the user's exact target. */
55
+ export declare function validateControllerRoute(ip: string, route: string): string;
56
+ /** Resolve a route from the named project and cache it only while that exact file hash is unchanged. */
57
+ export declare function resolveControllerRoute(ip: string, projectPath: string, signal?: AbortSignal): Promise<string>;
58
+ export type LiveScalarType = 'BOOL' | 'INT' | 'DINT' | 'REAL';
59
+ export interface LiveWatchTag {
60
+ readonly name: string;
61
+ readonly data_type: LiveScalarType;
62
+ }
63
+ /** Wire result from the bounded read-only SDK sampler. */
64
+ export interface LiveSamplingResult {
65
+ snapshot_path: string;
66
+ snapshot_sha256: string;
67
+ communications_path: string;
68
+ requested_interval_ms: number;
69
+ sample_count: number;
70
+ tags: readonly LiveWatchTag[];
71
+ samples: LogicSample[];
72
+ timing: {
73
+ average_sweep_ms: number;
74
+ max_sweep_ms: number;
75
+ average_observed_interval_ms: number | null;
76
+ };
77
+ operation: 'read_only';
78
+ note: string;
79
+ }
80
+ /** Validate SDK sample wire data, including identity, per-tag type, quality and timestamps.
81
+ * @param text - JSON emitted by the owned bridge.
82
+ * @param tags - exact requested watch list.
83
+ * @returns a validated sweep.
84
+ */
85
+ export declare function decodeLogicSample(text: string, tags: readonly LiveWatchTag[]): LogicSample;
86
+ export declare function validateLiveSampling(tags: readonly LiveWatchTag[], sampleCount: number, intervalMs: number): void;
87
+ /** Read repeated sweeps through one open SDK project. Actual sweep timing is returned; the requested interval is not guaranteed. */
88
+ export declare function sampleLiveTags(snapshotPath: string, commPath: string, tags: readonly LiveWatchTag[], sampleCount: number, intervalMs: number, signal?: AbortSignal): Promise<LiveSamplingResult>;
89
+ export declare function readLiveTag(snapshotPath: string, commPath: string, tag: string, dataType: LiveScalarType, signal?: AbortSignal): Promise<object>;
90
+ export declare function readLiveDint(snapshotPath: string, commPath: string, tag: string, signal?: AbortSignal): Promise<object>;
91
+ export declare function captureControllerSnapshot(commPath: string, outputPath: string, signal?: AbortSignal): Promise<object>;
92
+ export declare function controllerUploadPreflight(commPath: string, outputPath: string): Promise<string>;
93
+ export interface ControllerActionFile {
94
+ path: string;
95
+ sha256: string;
96
+ }
97
+ export declare function controllerActionPreflight(acdPath: string, commPath: string): Promise<ControllerActionFile>;
98
+ export declare function goOnline(acdPath: string, commPath: string, signal?: AbortSignal): Promise<object>;
99
+ export declare function readControllerModeForAction(file: ControllerActionFile, commPath: string, signal?: AbortSignal): Promise<string>;
100
+ export declare function changeControllerMode(file: ControllerActionFile, commPath: string, mode: string, expectedMode: string, signal?: AbortSignal): Promise<object>;
101
+ export declare function downloadProject(file: ControllerActionFile, commPath: string, signal?: AbortSignal): Promise<object>;