@aryacode/dsh-engineering 0.0.0-stage → 0.4.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 (284) hide show
  1. package/CHANGELOG.md +128 -0
  2. package/LICENSE +21 -0
  3. package/README.md +230 -2
  4. package/cordis.patch.yml +123 -0
  5. package/lib/client.js +3448 -0
  6. package/lib/index.d.ts +2 -0
  7. package/lib/index.js +9 -0
  8. package/locale/en.json +6 -0
  9. package/locale/zh.json +6 -0
  10. package/package.json +94 -3
  11. package/vendor/ftview/README.md +55 -0
  12. package/vendor/ftview/SKILL.md +74 -0
  13. package/vendor/ftview/lib/checks.d.ts +27 -0
  14. package/vendor/ftview/lib/checks.js +39 -0
  15. package/vendor/ftview/lib/core.d.ts +208 -0
  16. package/vendor/ftview/lib/core.js +160 -0
  17. package/vendor/ftview/lib/edit/drafts.d.ts +49 -0
  18. package/vendor/ftview/lib/edit/drafts.js +84 -0
  19. package/vendor/ftview/lib/edit/operations.d.ts +62 -0
  20. package/vendor/ftview/lib/edit/operations.js +126 -0
  21. package/vendor/ftview/lib/edit/prepare.d.ts +38 -0
  22. package/vendor/ftview/lib/edit/prepare.js +133 -0
  23. package/vendor/ftview/lib/edit/tools.d.ts +25 -0
  24. package/vendor/ftview/lib/edit/tools.js +198 -0
  25. package/vendor/ftview/lib/edit.d.ts +30 -0
  26. package/vendor/ftview/lib/edit.js +38 -0
  27. package/vendor/ftview/lib/index.d.ts +31 -0
  28. package/vendor/ftview/lib/index.js +36 -0
  29. package/vendor/ftview/lib/model.d.ts +80 -0
  30. package/vendor/ftview/lib/model.js +267 -0
  31. package/vendor/ftview/lib/session.d.ts +48 -0
  32. package/vendor/ftview/lib/session.js +98 -0
  33. package/vendor/ftview/lib/skill.d.ts +29 -0
  34. package/vendor/ftview/lib/skill.js +43 -0
  35. package/vendor/ftview/lib/studio/discover.d.ts +36 -0
  36. package/vendor/ftview/lib/studio/discover.js +87 -0
  37. package/vendor/ftview/lib/studio/preflight.d.ts +51 -0
  38. package/vendor/ftview/lib/studio/preflight.js +64 -0
  39. package/vendor/ftview/lib/studio/tools.d.ts +15 -0
  40. package/vendor/ftview/lib/studio/tools.js +83 -0
  41. package/vendor/ftview/lib/studio.d.ts +30 -0
  42. package/vendor/ftview/lib/studio.js +37 -0
  43. package/vendor/ftview/lib/tools.d.ts +14 -0
  44. package/vendor/ftview/lib/tools.js +202 -0
  45. package/vendor/l5x/README.md +62 -0
  46. package/vendor/l5x/SKILL.md +81 -0
  47. package/vendor/l5x/lib/approvals.d.ts +50 -0
  48. package/vendor/l5x/lib/approvals.js +93 -0
  49. package/vendor/l5x/lib/client.js +412 -0
  50. package/vendor/l5x/lib/client.js.map +1 -0
  51. package/vendor/l5x/lib/core/errors.d.ts +38 -0
  52. package/vendor/l5x/lib/core/errors.js +48 -0
  53. package/vendor/l5x/lib/core/params.d.ts +49 -0
  54. package/vendor/l5x/lib/core/params.js +39 -0
  55. package/vendor/l5x/lib/core/result.d.ts +46 -0
  56. package/vendor/l5x/lib/core/result.js +21 -0
  57. package/vendor/l5x/lib/core/tool.d.ts +102 -0
  58. package/vendor/l5x/lib/core/tool.js +19 -0
  59. package/vendor/l5x/lib/drafts.d.ts +66 -0
  60. package/vendor/l5x/lib/drafts.js +98 -0
  61. package/vendor/l5x/lib/editing.d.ts +30 -0
  62. package/vendor/l5x/lib/editing.js +42 -0
  63. package/vendor/l5x/lib/engine/acd-edit.d.ts +13 -0
  64. package/vendor/l5x/lib/engine/acd-edit.js +115 -0
  65. package/vendor/l5x/lib/engine/analysis.d.ts +22 -0
  66. package/vendor/l5x/lib/engine/analysis.js +258 -0
  67. package/vendor/l5x/lib/engine/capabilities.d.ts +9 -0
  68. package/vendor/l5x/lib/engine/capabilities.js +40 -0
  69. package/vendor/l5x/lib/engine/change-set.d.ts +122 -0
  70. package/vendor/l5x/lib/engine/change-set.js +446 -0
  71. package/vendor/l5x/lib/engine/configuration.d.ts +21 -0
  72. package/vendor/l5x/lib/engine/configuration.js +42 -0
  73. package/vendor/l5x/lib/engine/details.d.ts +47 -0
  74. package/vendor/l5x/lib/engine/details.js +173 -0
  75. package/vendor/l5x/lib/engine/direct-polling.d.ts +91 -0
  76. package/vendor/l5x/lib/engine/direct-polling.js +193 -0
  77. package/vendor/l5x/lib/engine/download-review.d.ts +15 -0
  78. package/vendor/l5x/lib/engine/download-review.js +63 -0
  79. package/vendor/l5x/lib/engine/logic-syntax.d.ts +60 -0
  80. package/vendor/l5x/lib/engine/logic-syntax.js +237 -0
  81. package/vendor/l5x/lib/engine/observations.d.ts +41 -0
  82. package/vendor/l5x/lib/engine/observations.js +67 -0
  83. package/vendor/l5x/lib/engine/project-analysis.d.ts +131 -0
  84. package/vendor/l5x/lib/engine/project-analysis.js +314 -0
  85. package/vendor/l5x/lib/engine/review-evidence.d.ts +8 -0
  86. package/vendor/l5x/lib/engine/review-evidence.js +49 -0
  87. package/vendor/l5x/lib/engine/review.d.ts +18 -0
  88. package/vendor/l5x/lib/engine/review.js +123 -0
  89. package/vendor/l5x/lib/engine/studio-sdk.d.ts +101 -0
  90. package/vendor/l5x/lib/engine/studio-sdk.js +571 -0
  91. package/vendor/l5x/lib/engine/tag-resolution.d.ts +20 -0
  92. package/vendor/l5x/lib/engine/tag-resolution.js +169 -0
  93. package/vendor/l5x/lib/engine/usage.d.ts +16 -0
  94. package/vendor/l5x/lib/engine/usage.js +92 -0
  95. package/vendor/l5x/lib/engine/xml-regions.d.ts +23 -0
  96. package/vendor/l5x/lib/engine/xml-regions.js +77 -0
  97. package/vendor/l5x/lib/fs.d.ts +17 -0
  98. package/vendor/l5x/lib/fs.js +36 -0
  99. package/vendor/l5x/lib/index.d.ts +36 -0
  100. package/vendor/l5x/lib/index.js +42 -0
  101. package/vendor/l5x/lib/live.d.ts +48 -0
  102. package/vendor/l5x/lib/live.js +48 -0
  103. package/vendor/l5x/lib/model/aois.d.ts +39 -0
  104. package/vendor/l5x/lib/model/aois.js +112 -0
  105. package/vendor/l5x/lib/model/errors.d.ts +15 -0
  106. package/vendor/l5x/lib/model/errors.js +18 -0
  107. package/vendor/l5x/lib/model/index.d.ts +20 -0
  108. package/vendor/l5x/lib/model/index.js +19 -0
  109. package/vendor/l5x/lib/model/module.d.ts +37 -0
  110. package/vendor/l5x/lib/model/module.js +54 -0
  111. package/vendor/l5x/lib/model/modules.d.ts +24 -0
  112. package/vendor/l5x/lib/model/modules.js +55 -0
  113. package/vendor/l5x/lib/model/parse.d.ts +32 -0
  114. package/vendor/l5x/lib/model/parse.js +116 -0
  115. package/vendor/l5x/lib/model/project.d.ts +15 -0
  116. package/vendor/l5x/lib/model/project.js +31 -0
  117. package/vendor/l5x/lib/model/routines.d.ts +71 -0
  118. package/vendor/l5x/lib/model/routines.js +183 -0
  119. package/vendor/l5x/lib/model/tags.d.ts +51 -0
  120. package/vendor/l5x/lib/model/tags.js +119 -0
  121. package/vendor/l5x/lib/model/types.d.ts +304 -0
  122. package/vendor/l5x/lib/model/types.js +12 -0
  123. package/vendor/l5x/lib/model/udts.d.ts +34 -0
  124. package/vendor/l5x/lib/model/udts.js +81 -0
  125. package/vendor/l5x/lib/model/xml.d.ts +139 -0
  126. package/vendor/l5x/lib/model/xml.js +291 -0
  127. package/vendor/l5x/lib/model/xref.d.ts +28 -0
  128. package/vendor/l5x/lib/model/xref.js +154 -0
  129. package/vendor/l5x/lib/session.d.ts +119 -0
  130. package/vendor/l5x/lib/session.js +236 -0
  131. package/vendor/l5x/lib/skill.d.ts +30 -0
  132. package/vendor/l5x/lib/skill.js +44 -0
  133. package/vendor/l5x/lib/studio-state.d.ts +77 -0
  134. package/vendor/l5x/lib/studio-state.js +79 -0
  135. package/vendor/l5x/lib/studio.d.ts +29 -0
  136. package/vendor/l5x/lib/studio.js +40 -0
  137. package/vendor/l5x/lib/tools/checks.d.ts +19 -0
  138. package/vendor/l5x/lib/tools/checks.js +140 -0
  139. package/vendor/l5x/lib/tools/common.d.ts +41 -0
  140. package/vendor/l5x/lib/tools/common.js +84 -0
  141. package/vendor/l5x/lib/tools/controller.d.ts +21 -0
  142. package/vendor/l5x/lib/tools/controller.js +170 -0
  143. package/vendor/l5x/lib/tools/drafts.d.ts +23 -0
  144. package/vendor/l5x/lib/tools/drafts.js +270 -0
  145. package/vendor/l5x/lib/tools/live.d.ts +21 -0
  146. package/vendor/l5x/lib/tools/live.js +175 -0
  147. package/vendor/l5x/lib/tools/read.d.ts +19 -0
  148. package/vendor/l5x/lib/tools/read.js +245 -0
  149. package/vendor/l5x/lib/tools/references.d.ts +20 -0
  150. package/vendor/l5x/lib/tools/references.js +269 -0
  151. package/vendor/l5x/lib/tools/studio.d.ts +21 -0
  152. package/vendor/l5x/lib/tools/studio.js +193 -0
  153. package/vendor/l5x/poll-worker.mjs +77 -0
  154. package/vendor/l5x/studio-bridge/NuGet.Config +1 -0
  155. package/vendor/l5x/studio-bridge/Program.cs +304 -0
  156. package/vendor/l5x/studio-bridge/StudioBridge.csproj +11 -0
  157. package/vendor/mwi/README.md +60 -0
  158. package/vendor/mwi/SKILL.md +87 -0
  159. package/vendor/mwi/bridge/README.md +8 -0
  160. package/vendor/mwi/bridge/mw_bridge.ps1 +2763 -0
  161. package/vendor/mwi/bridge/sdk-interop-importer.cs +30 -0
  162. package/vendor/mwi/bridge/sdk-variable-reader.cs +22 -0
  163. package/vendor/mwi/bridge/start_bridge.cmd +21 -0
  164. package/vendor/mwi/bridge/watch_dialogs.cmd +13 -0
  165. package/vendor/mwi/bridge/watch_dialogs.ps1 +153 -0
  166. package/vendor/mwi/code/engine/motionworks_iec_mcp/__init__.py +14 -0
  167. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
  168. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/block_interfaces.cpython-312.pyc +0 -0
  169. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/cfb.cpython-312.pyc +0 -0
  170. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/datatypes.cpython-312.pyc +0 -0
  171. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/declarations.cpython-312.pyc +0 -0
  172. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/errors.cpython-312.pyc +0 -0
  173. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/graphical_listing.cpython-312.pyc +0 -0
  174. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/grid.cpython-312.pyc +0 -0
  175. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/knowledge.cpython-312.pyc +0 -0
  176. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/mwt_bind.cpython-312.pyc +0 -0
  177. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/navigation.cpython-312.pyc +0 -0
  178. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/program_checks.cpython-312.pyc +0 -0
  179. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/project.cpython-312.pyc +0 -0
  180. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/staging.cpython-312.pyc +0 -0
  181. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/structure.cpython-312.pyc +0 -0
  182. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/task_bindings.cpython-312.pyc +0 -0
  183. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tasks.cpython-312.pyc +0 -0
  184. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tree.cpython-312.pyc +0 -0
  185. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/validation.cpython-312.pyc +0 -0
  186. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variable_edit.cpython-312.pyc +0 -0
  187. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variables.cpython-312.pyc +0 -0
  188. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/workflow.cpython-312.pyc +0 -0
  189. package/vendor/mwi/code/engine/motionworks_iec_mcp/block_interfaces.py +275 -0
  190. package/vendor/mwi/code/engine/motionworks_iec_mcp/build.py +339 -0
  191. package/vendor/mwi/code/engine/motionworks_iec_mcp/capture.py +448 -0
  192. package/vendor/mwi/code/engine/motionworks_iec_mcp/cfb.py +765 -0
  193. package/vendor/mwi/code/engine/motionworks_iec_mcp/com.py +268 -0
  194. package/vendor/mwi/code/engine/motionworks_iec_mcp/datatypes.py +204 -0
  195. package/vendor/mwi/code/engine/motionworks_iec_mcp/declarations.py +399 -0
  196. package/vendor/mwi/code/engine/motionworks_iec_mcp/descriptions.py +30 -0
  197. package/vendor/mwi/code/engine/motionworks_iec_mcp/eip.py +292 -0
  198. package/vendor/mwi/code/engine/motionworks_iec_mcp/errors.py +36 -0
  199. package/vendor/mwi/code/engine/motionworks_iec_mcp/graphical_listing.py +208 -0
  200. package/vendor/mwi/code/engine/motionworks_iec_mcp/grid.py +227 -0
  201. package/vendor/mwi/code/engine/motionworks_iec_mcp/ide.py +317 -0
  202. package/vendor/mwi/code/engine/motionworks_iec_mcp/iec.py +317 -0
  203. package/vendor/mwi/code/engine/motionworks_iec_mcp/installed_help.py +151 -0
  204. package/vendor/mwi/code/engine/motionworks_iec_mcp/knowledge.py +218 -0
  205. package/vendor/mwi/code/engine/motionworks_iec_mcp/libraries.py +250 -0
  206. package/vendor/mwi/code/engine/motionworks_iec_mcp/manuals.py +520 -0
  207. package/vendor/mwi/code/engine/motionworks_iec_mcp/mwt_bind.py +133 -0
  208. package/vendor/mwi/code/engine/motionworks_iec_mcp/navigation.py +44 -0
  209. package/vendor/mwi/code/engine/motionworks_iec_mcp/pou_writer.py +1547 -0
  210. package/vendor/mwi/code/engine/motionworks_iec_mcp/program_checks.py +360 -0
  211. package/vendor/mwi/code/engine/motionworks_iec_mcp/project.py +704 -0
  212. package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/catalog.json +672 -0
  213. package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/patterns.json +86 -0
  214. package/vendor/mwi/code/engine/motionworks_iec_mcp/restore.py +208 -0
  215. package/vendor/mwi/code/engine/motionworks_iec_mcp/server.py +1913 -0
  216. package/vendor/mwi/code/engine/motionworks_iec_mcp/snapshot.py +138 -0
  217. package/vendor/mwi/code/engine/motionworks_iec_mcp/st_comments.py +25 -0
  218. package/vendor/mwi/code/engine/motionworks_iec_mcp/staging.py +179 -0
  219. package/vendor/mwi/code/engine/motionworks_iec_mcp/stlint.py +676 -0
  220. package/vendor/mwi/code/engine/motionworks_iec_mcp/structure.py +98 -0
  221. package/vendor/mwi/code/engine/motionworks_iec_mcp/task_bindings.py +68 -0
  222. package/vendor/mwi/code/engine/motionworks_iec_mcp/tasks.py +162 -0
  223. package/vendor/mwi/code/engine/motionworks_iec_mcp/transaction.py +157 -0
  224. package/vendor/mwi/code/engine/motionworks_iec_mcp/tree.py +563 -0
  225. package/vendor/mwi/code/engine/motionworks_iec_mcp/tree_writer.py +790 -0
  226. package/vendor/mwi/code/engine/motionworks_iec_mcp/uia.py +465 -0
  227. package/vendor/mwi/code/engine/motionworks_iec_mcp/validation.py +41 -0
  228. package/vendor/mwi/code/engine/motionworks_iec_mcp/variable_edit.py +183 -0
  229. package/vendor/mwi/code/engine/motionworks_iec_mcp/variables.py +472 -0
  230. package/vendor/mwi/code/engine/motionworks_iec_mcp/workflow.py +130 -0
  231. package/vendor/mwi/code/engine/motionworks_iec_mcp/writer.py +666 -0
  232. package/vendor/mwi/code/mw_code.py +1258 -0
  233. package/vendor/mwi/legacy/edit-session.js +50 -0
  234. package/vendor/mwi/legacy/fb-insertion.js +74 -0
  235. package/vendor/mwi/legacy/native-code.js +83 -0
  236. package/vendor/mwi/legacy/native-groups.js +75 -0
  237. package/vendor/mwi/legacy/native-structure.js +143 -0
  238. package/vendor/mwi/legacy/native-variables.js +77 -0
  239. package/vendor/mwi/legacy/pou-conversion.js +47 -0
  240. package/vendor/mwi/legacy/pou-package.js +66 -0
  241. package/vendor/mwi/lib/core/bridge.d.ts +71 -0
  242. package/vendor/mwi/lib/core/bridge.js +53 -0
  243. package/vendor/mwi/lib/core/code-engine.d.ts +47 -0
  244. package/vendor/mwi/lib/core/code-engine.js +109 -0
  245. package/vendor/mwi/lib/core/errors.d.ts +37 -0
  246. package/vendor/mwi/lib/core/errors.js +47 -0
  247. package/vendor/mwi/lib/core/params.d.ts +52 -0
  248. package/vendor/mwi/lib/core/params.js +46 -0
  249. package/vendor/mwi/lib/core/ps-bridge.d.ts +62 -0
  250. package/vendor/mwi/lib/core/ps-bridge.js +127 -0
  251. package/vendor/mwi/lib/core/result.d.ts +45 -0
  252. package/vendor/mwi/lib/core/result.js +20 -0
  253. package/vendor/mwi/lib/core/tool.d.ts +80 -0
  254. package/vendor/mwi/lib/core/tool.js +55 -0
  255. package/vendor/mwi/lib/edit/compare.d.ts +33 -0
  256. package/vendor/mwi/lib/edit/compare.js +70 -0
  257. package/vendor/mwi/lib/edit.d.ts +20 -0
  258. package/vendor/mwi/lib/edit.js +32 -0
  259. package/vendor/mwi/lib/index.d.ts +21 -0
  260. package/vendor/mwi/lib/index.js +33 -0
  261. package/vendor/mwi/lib/read/service.d.ts +117 -0
  262. package/vendor/mwi/lib/read/service.js +229 -0
  263. package/vendor/mwi/lib/read.d.ts +21 -0
  264. package/vendor/mwi/lib/read.js +33 -0
  265. package/vendor/mwi/lib/session/project.d.ts +34 -0
  266. package/vendor/mwi/lib/session/project.js +54 -0
  267. package/vendor/mwi/lib/session/service.d.ts +167 -0
  268. package/vendor/mwi/lib/session/service.js +566 -0
  269. package/vendor/mwi/lib/session/stage.d.ts +84 -0
  270. package/vendor/mwi/lib/session/stage.js +187 -0
  271. package/vendor/mwi/lib/session/workspace.d.ts +52 -0
  272. package/vendor/mwi/lib/session/workspace.js +90 -0
  273. package/vendor/mwi/lib/skill.d.ts +36 -0
  274. package/vendor/mwi/lib/skill.js +56 -0
  275. package/vendor/mwi/lib/tools/edit.d.ts +20 -0
  276. package/vendor/mwi/lib/tools/edit.js +286 -0
  277. package/vendor/mwi/lib/tools/read.d.ts +19 -0
  278. package/vendor/mwi/lib/tools/read.js +161 -0
  279. package/vendor/mwi/lib/tools/session.d.ts +20 -0
  280. package/vendor/mwi/lib/tools/session.js +166 -0
  281. package/vendor/mwi/lib/tools/verify.d.ts +19 -0
  282. package/vendor/mwi/lib/tools/verify.js +85 -0
  283. package/vendor/mwi/lib/verify.d.ts +17 -0
  284. package/vendor/mwi/lib/verify.js +29 -0
@@ -0,0 +1,218 @@
1
+ """Versioned vendor references and bounded local PDF search. No IDE operations."""
2
+ from __future__ import annotations
3
+ import hashlib
4
+ import json
5
+ import os
6
+ import re
7
+ from pathlib import Path
8
+ from urllib.parse import urlsplit
9
+ from urllib.request import Request, urlopen
10
+ from uuid import uuid4
11
+
12
+ DATA = Path(__file__).with_name('reference_data')
13
+ MAX_DOWNLOAD = 64 * 1024 * 1024
14
+
15
+
16
+ def catalog():
17
+ return json.loads((DATA / 'catalog.json').read_text(encoding='utf-8'))
18
+
19
+
20
+ def citation(source_id, pages=None, section=None):
21
+ source = next(s for s in catalog()['sources'] if s['id'] == source_id)
22
+ return {**source, 'pdf_pages': pages or [], 'section': section,
23
+ 'page_url': source['url'] + (f'#page={pages[0]}' if pages else ''),
24
+ 'version_match': 'not_verified_against_installed_library'}
25
+
26
+
27
+ def reference(topic_id):
28
+ topic = next(t for t in catalog()['topics'] if t['id'] == topic_id)
29
+ return {**topic, 'citation': citation(topic['source_id'], topic['pdf_pages'], topic['section'])}
30
+
31
+
32
+ def tokens(text):
33
+ return re.findall(r'[a-z0-9_]+', text.casefold())
34
+
35
+
36
+ def _score(query, text):
37
+ wanted = set(tokens(query))
38
+ actual = set(tokens(text))
39
+ # Full-phrase matching also helps PDFs whose glyph extraction omits spaces.
40
+ compact = re.sub(r'\s+', '', text.casefold())
41
+ phrase = re.sub(r'\s+', '', query.casefold())
42
+ return len(wanted & actual) + (4 if phrase and phrase in compact else 0)
43
+
44
+
45
+ def cache_root():
46
+ from .staging import workspace_root
47
+ workspace = workspace_root()
48
+ root = (workspace / '.motionworks' / 'references').resolve()
49
+ if not root.is_relative_to(workspace):
50
+ raise ValueError('Reference cache escapes the workspace')
51
+ return root
52
+
53
+
54
+ def _safe_child(root, name):
55
+ path = root / name
56
+ if not path.resolve().is_relative_to(root):
57
+ raise ValueError('Linked reference cache entry escapes its directory')
58
+ return path
59
+
60
+
61
+ def search(query='', *, source_id=None, limit=5):
62
+ limit = max(1, min(int(limit), 10))
63
+ data = catalog()
64
+ if source_id and source_id not in {s['id'] for s in data['sources']}:
65
+ raise ValueError('Unknown source_id')
66
+ if not query.strip():
67
+ return {'sources': [s for s in data['sources'] if not source_id or s['id'] == source_id], 'topics': [{'id': t['id'], 'title': t['title']} for t in data['topics'] if not source_id or t['source_id'] == source_id],
68
+ 'signatures': sorted(data['signatures']), 'note': 'Curated notes work offline; sync PDFs for page search.'}
69
+ if len(query) > 256: raise ValueError('Reference query must be at most 256 characters')
70
+ scored = []
71
+ for topic in data['topics']:
72
+ if source_id and topic['source_id'] != source_id: continue
73
+ score = _score(query, ' '.join([topic['title'], topic['summary'], *topic['tags']]))
74
+ if score: scored.append((score, topic['id']))
75
+ curated = [reference(id) for _, id in sorted(scored, key=lambda x: (-x[0], x[1]))[:limit]]
76
+ pages, unavailable = [], []
77
+ exact_signature = signature(query.strip())
78
+ root = cache_root()
79
+ for source in data['sources']:
80
+ if source['kind'] != 'pdf' or (source_id and source['id'] != source_id): continue
81
+ path = _safe_child(root, source['id'] + '.json')
82
+ if not path.exists():
83
+ unavailable.append(source['id']); continue
84
+ try:
85
+ cached = json.loads(path.read_text(encoding='utf-8'))
86
+ pdf = _safe_child(root, source['id'] + '.pdf')
87
+ if cached['sha256'] != source['sha256'] or hashlib.sha256(pdf.read_bytes()).hexdigest() != source['sha256']:
88
+ raise ValueError('Cached PDF version/hash does not match reviewed source')
89
+ if len(cached['pages']) != source['pages'] or not all(isinstance(p, str) for p in cached['pages']):
90
+ raise ValueError('Cached page index is incomplete')
91
+ if cached.get('text_sha256') != hashlib.sha256(json.dumps(cached['pages'], ensure_ascii=False).encode('utf-8')).hexdigest():
92
+ raise ValueError('Cached page index failed integrity check; synchronize again')
93
+ best = []
94
+ for i, text in enumerate(cached['pages'], 1):
95
+ score = _score(query, text)
96
+ if score:
97
+ score += min(3, text.casefold().count(query.strip().casefold()))
98
+ if _score(query, text[:180]) >= 4: score += 2
99
+ if any(t['source_id'] == source['id'] and i in t['pdf_pages'] for t in curated): score += 8
100
+ if exact_signature and exact_signature['source_id'] == source['id'] and i in exact_signature['pdf_pages']: score += 20
101
+ if score: best.append((score, i, text))
102
+ # At most one short excerpt per source; other matches remain page links.
103
+ for rank, (_, page, text) in enumerate(sorted(best, key=lambda x: (-x[0], x[1]))[:limit]):
104
+ hit = re.search(re.escape(query.strip()), text, re.I)
105
+ start = hit.start() if hit else 0
106
+ excerpt = ' '.join(text[start:].split()[:20])[:150] if rank == 0 else None
107
+ pages.append({'source_id': source['id'], 'pdf_page': page, 'excerpt': excerpt,
108
+ 'citation': citation(source['id'], [page], 'PDF search result')})
109
+ except (OSError, ValueError, KeyError) as exc:
110
+ unavailable.append({'source_id': source['id'], 'reason': str(exc)})
111
+ return {'query': query, 'topics': curated, 'pages': pages, 'unavailable_indexes': unavailable,
112
+ 'note': 'PDF pages are physical 1-based pages, which can differ from printed labels. Historical references do not establish installed-version compatibility.'}
113
+
114
+
115
+ def signature(name):
116
+ match = next((v for k, v in catalog()['signatures'].items() if k.casefold() == name.casefold()), None)
117
+ if match is None: return None
118
+ return {**match, 'citation': citation(match['source_id'], match['pdf_pages'], match['section'])}
119
+
120
+
121
+ def _atomic(path, data):
122
+ temp = path.with_name(path.name + '.' + uuid4().hex + '.tmp')
123
+ try:
124
+ temp.write_bytes(data)
125
+ os.replace(temp, path)
126
+ finally:
127
+ temp.unlink(missing_ok=True)
128
+
129
+
130
+ def _fetch(url):
131
+ # Only catalog URLs are passed here. Never send project data or credentials.
132
+ with urlopen(Request(url, headers={'User-Agent': 'MotionWorksReference/0.3'}), timeout=45) as response:
133
+ final = urlsplit(response.url)
134
+ if final.scheme != 'https' or final.hostname != 'www.yaskawa.com':
135
+ raise ValueError('Reference download redirected outside the official host')
136
+ raw = response.read(MAX_DOWNLOAD + 1)
137
+ if len(raw) > MAX_DOWNLOAD or not raw.startswith(b'%PDF'):
138
+ raise ValueError('Official download did not return a supported PDF; it may require login')
139
+ return raw
140
+
141
+
142
+ def sync(source_ids=None):
143
+ data = catalog()
144
+ sources = {s['id']: s for s in data['sources'] if s['kind'] == 'pdf'}
145
+ ids = list(dict.fromkeys(sources if source_ids is None else source_ids))
146
+ if any(id not in sources for id in ids): raise ValueError('Only catalog PDF source IDs can be synchronized')
147
+ try:
148
+ from pypdf import PdfReader
149
+ except ImportError:
150
+ return {'ok': False, 'error': 'PDF indexing requires pypdf in the selected plugin Python runtime. Curated search remains available.', 'sources': []}
151
+ root = cache_root(); root.mkdir(parents=True, exist_ok=True)
152
+ results = []
153
+ for id in ids:
154
+ source = sources[id]
155
+ try:
156
+ pdf = _safe_child(root, id + '.pdf')
157
+ index = _safe_child(root, id + '.json')
158
+ raw = pdf.read_bytes() if pdf.exists() else _fetch(source['url'])
159
+ digest = hashlib.sha256(raw).hexdigest()
160
+ if digest != source['sha256']:
161
+ raise ValueError('Vendor PDF differs from the reviewed revision. Catalog review is required; no old page citations were applied.')
162
+ import io
163
+ reader = PdfReader(io.BytesIO(raw))
164
+ if len(reader.pages) != source['pages']: raise ValueError('PDF page count differs from catalog')
165
+ pages = [page.extract_text() or '' for page in reader.pages]
166
+ _atomic(pdf, raw)
167
+ text_digest = hashlib.sha256(json.dumps(pages, ensure_ascii=False).encode('utf-8')).hexdigest()
168
+ _atomic(index, json.dumps({'sha256': digest, 'text_sha256': text_digest, 'pages': pages}, ensure_ascii=False).encode('utf-8'))
169
+ results.append({'source_id': id, 'status': 'indexed', 'pages': len(pages), 'path': str(pdf), 'revision': source['revision']})
170
+ except Exception as exc:
171
+ results.append({'source_id': id, 'status': 'unavailable', 'error': str(exc)})
172
+ return {'ok': all(r['status'] == 'indexed' for r in results), 'sources': results}
173
+
174
+
175
+ def diagnose(message):
176
+ if not message.strip(): raise ValueError('Supply the compiler/runtime diagnostic text')
177
+ patterns = [
178
+ (r'file error.*(?:\.vbc|src\.st1)|MSILv2ResManager|variable.*not found', 'diagnostics', 'compiler_or_native_format', ['Preserve the exact project, error text and transaction journal.', 'Compare paired VB/VGR declarations and source stream hashes with the last verified snapshot.', 'Do not patch generated cache files, replace a binary container as text, or restore underneath an open IDE.']),
179
+ (r'division by zero|divide.by.zero', 'diagnostics', 'runtime', ['Inspect denominators and cam-generation segment lengths at the faulting operation.', 'Zero compiler errors do not exclude runtime arithmetic faults. Capture runtime values before proposing a change.']),
180
+ (r'identity.*workspace|outside.*stag|ENOTDIR', 'diagnostics', 'workspace_state', ['Read the calling session workspace and mw_workflow_check for the explicit stage.', 'Check relocated identity paths and wrappers. Do not weaken guards or restage over unsynced source edits.']),
181
+ (r'error\s+in\s+native\s+code\s+generation', 'native-code-error', 'compiler', ['Read the first underlying compiler diagnostic.', 'Compare IDE, controller target and library versions before changing declarations.']),
182
+ (r'operand\s+not\s+implemented|area\s+exceeded', 'address-range', 'runtime_or_debug', ['Inspect IO_Configuration address ranges.', 'Compare the located variable address with the configured driver.']),
183
+ (r'no\s+matching\s+global|global-not-declared-external|external.*(?:missing|mismatch)', 'scope', 'compiler', ['Compare the resource global with the POU external name and type.', 'Inspect both VB and VGR consistency; do not convert an external to a local just to hide the error.']),
184
+ (r'type.*(?:invalid|mismatch)|incompatible.*type|cannot.*convert', 'strict-types', 'compiler', ['Read operand declarations and FB formal types.', 'Use an explicit, range-appropriate conversion where intended.']),
185
+ (r'\b4370\b|motionprohibited', 'stop', 'runtime', ['Inspect axis state, active commands and the MC_Stop instance.', 'Do not automatically clear interlocks or reset the axis.']),
186
+ (r'\b4625\b|axis.*(?:id|number).*not', 'axis-id', 'runtime', ['Compare the logical axis reference with Hardware Configuration.']),
187
+ (r'watchdog|task.*overrun', 'task-priority', 'runtime', ['Inspect task timing, execution load and priority.', 'Do not mask an overrun merely by extending its watchdog.']),
188
+ (r'completion unverified|stalled|empty.*errors', 'diagnostics', 'ide_state', ['Inspect modal dialogs and current compiler state.', 'An empty error pane is not a success verdict; never repair disk files under an open IDE.']),
189
+ ]
190
+ matches = [{'category': kind, 'evidence': reference(topic), 'suggested_checks': checks,
191
+ 'confidence': 'candidate_not_confirmed', 'mapping': 'plugin_authored_from_cited_guidance'}
192
+ for pattern, topic, kind, checks in patterns if re.search(pattern, message, re.I)]
193
+ if re.search(r'''illegal\s+IEC\s+syntax\s+at\s+or\s+before\s+['"]>['"]''',message,re.I):
194
+ matches.append({
195
+ 'category':'compiler',
196
+ 'confidence':'candidate_not_confirmed',
197
+ 'mapping':'plugin_authored_from_live_observation_and_installed_help',
198
+ 'evidence':{'kind':'installed_help_and_live_compile',
199
+ 'observed_ide':'MotionWorks IEC 3 Pro, automation version 1.19',
200
+ 'installed_help':{'tool':'mw_code_installed_help','module':'ST001','topic':'callingfunctionblocksinst.htm'}},
201
+ 'suggested_checks':[
202
+ 'Read the exact offending ST line before diagnosing the token.',
203
+ 'If the line uses => for an FB output, read the installed ST FB-call topic: the tested IDE rejected that form and accepted assignments from instance output fields after the call.',
204
+ 'Resolve actual pin directions/types with mw_code_block_interface. Keep in-out bindings in the call; capture output fields afterward.',
205
+ 'A greater-than token can fail for other reasons. Confirm the source context and installed version, then use guarded code editing and fresh Build/Make; do not rewrite automatically.'
206
+ ]})
207
+ return {'matches': matches, 'matched': bool(matches), 'automatic_changes': False,
208
+ 'next': 'Confirm candidates against the exact diagnostic, project and installed versions.' if matches else 'No documented mapping matched. Preserve the exact message and inspect its source location; do not invent a root cause.'}
209
+
210
+
211
+ def patterns(name=None):
212
+ data = json.loads((DATA / 'patterns.json').read_text(encoding='utf-8'))
213
+ if name is None: return {'patterns': [{'id': p['id'], 'title': p['title']} for p in data]}
214
+ found = next((p for p in data if p['id'] == name), None)
215
+ if found is None: raise ValueError('Unknown pattern ID')
216
+ return {**found, 'references': [reference(id) for id in found['topic_ids']],
217
+ 'provenance': 'Original illustrative plugin pattern; not copied vendor code',
218
+ 'validation': 'Requires project-specific adaptation and native IDE compile; no hardware action is performed'}
@@ -0,0 +1,250 @@
1
+ """Read the library side of a MotionWorks project: which function blocks it uses, and what
2
+ those blocks offer.
3
+
4
+ Two sources, and they answer different questions.
5
+
6
+ **`eCLRPouDependencies.dat` is the map.** It is plain text at the top of the resource
7
+ directory and names every POU and library block in the project with its kind and what it
8
+ depends on::
9
+
10
+ PouDep.Cache;schema 1
11
+ 0,CalcBezier,3,FB
12
+ 1,CalcSplineMatrix,3,FB
13
+ 2,CalcSpline,3,FB; 1,CalcSplineMatrix
14
+ 4,CamGenerator,3,FB; 2,CalcSpline; 0,CalcBezier; 3,MasterIndex_Lookup
15
+ 5,TopCutterCamSetup,2,PG; 4,CamGenerator
16
+
17
+ so ``2,PG`` is a program and ``3,FB`` a function block, and the trailing entries are the
18
+ blocks it calls. That is enough to answer "what does this POU use" without reading any
19
+ binary.
20
+
21
+ **The block's own members come from its compiled assembly.** A library block in this project
22
+ is compiled to ``<name>.DLL`` beside the resource, and it is a real .NET assembly - the
23
+ signature is ``BSJB``, runtime ``v4.0.30319`` - so its identifiers sit in the metadata
24
+ ``#Strings`` heap as plain UTF-8. Measured on CamGenerator.DLL, that heap begins with the
25
+ names that matter::
26
+
27
+ CamGenerator, ProConOS_eCLR, Iec61131Standard, MasterIndex_Lookup, CalcSpline,
28
+ CalcBezier, FUNCTION_BLOCK, CamSegmentStruct, Y_MS_CAM_STRUCT, ...
29
+ Execute, TableSize, Done, Busy, Error, ErrorID, iActive, CamData, CamTable
30
+
31
+ WHAT THIS CANNOT TELL YOU, and it matters: **the heap does not record input/output
32
+ direction.** ``Execute`` and ``Done`` are distinguishable by convention, not by evidence
33
+ here, and this module does not guess. It reports the names the assembly defines and says
34
+ plainly that direction is not among them. The block's own declarations/native parameter
35
+ table (``mw_code_block_interface``) establish direction; caller instance declarations
36
+ do not establish a block's pin directions.
37
+
38
+ The heap also carries compiler temporaries - ``__temp_1`` through ``__temp_50``, ``s1``
39
+ through ``s7`` - which are noise here, so they are filtered out rather than reported as if
40
+ they were part of the block's interface.
41
+ """
42
+ from __future__ import annotations
43
+
44
+ import re
45
+ import struct
46
+ from dataclasses import dataclass, field
47
+ from pathlib import Path
48
+
49
+ from .errors import NotFound
50
+
51
+ DEPENDENCY_FILE = "C/Configuration/R/Resource/eCLRPouDependencies.dat"
52
+ RESOURCE_DIR = "C/Configuration/R/Resource"
53
+
54
+ KINDS = {"1": "FUNCTION", "2": "PROGRAM", "3": "FUNCTION_BLOCK", "4": "FUNCTION_BLOCK"}
55
+
56
+ # Names the compiler generates rather than the author writing: temporaries, scratch
57
+ # registers and one/two-letter locals. Reporting these as part of a block's interface would
58
+ # be worse than reporting nothing.
59
+ _NOISE = re.compile(
60
+ r"^(__temp_\d+|__\w+|_\w+|[a-z]\d*|[A-Z]\d*|[a-z]{2}\d*|k\d+|sPtr|amm|amp|C0|d|p)$"
61
+ )
62
+
63
+
64
+ @dataclass
65
+ class Block:
66
+ """One POU or library block named in the dependency manifest."""
67
+
68
+ index: int
69
+ name: str
70
+ kind: str
71
+ depends_on: list[str] = field(default_factory=list)
72
+
73
+ @property
74
+ def is_library(self) -> bool:
75
+ return self.kind == "FUNCTION_BLOCK" or self.kind == "FUNCTION"
76
+
77
+
78
+ @dataclass
79
+ class BlockMembers:
80
+ """The identifiers an assembly defines, in heap order."""
81
+
82
+ name: str
83
+ assembly: str
84
+ runtime: str
85
+ identifiers: list[str] = field(default_factory=list)
86
+ filtered_out: int = 0
87
+
88
+
89
+ def _resource_dir(project_root: Path) -> Path:
90
+ candidate = Path(project_root) / RESOURCE_DIR
91
+ if not candidate.is_dir():
92
+ raise NotFound(f"no {RESOURCE_DIR} in {project_root}")
93
+ return candidate
94
+
95
+
96
+ def read_dependencies(project_root: Path) -> list[Block]:
97
+ """Parse ``eCLRPouDependencies.dat`` into blocks, in file order."""
98
+ path = Path(project_root) / DEPENDENCY_FILE
99
+ if not path.is_file():
100
+ raise NotFound(
101
+ f"no {DEPENDENCY_FILE} in {project_root}; the project has not been built, so it "
102
+ f"has no dependency manifest yet"
103
+ )
104
+ blocks: list[Block] = []
105
+ by_index: dict[int, Block] = {}
106
+ for raw in path.read_text(encoding="latin1", errors="replace").splitlines():
107
+ line = raw.strip()
108
+ if not line or line.startswith("PouDep"):
109
+ continue
110
+ parts = [p.strip() for p in line.split(";")]
111
+ head = parts[0].split(",")
112
+ if len(head) < 4:
113
+ continue
114
+ try:
115
+ index = int(head[0])
116
+ except ValueError:
117
+ continue
118
+ name, code = head[1], head[2]
119
+ block = Block(index=index, name=name, kind=KINDS.get(code, f"KIND_{code}"))
120
+ blocks.append(block)
121
+ by_index[index] = block
122
+
123
+ # second pass: the trailing entries are indices, resolved once every block is known
124
+ for raw in path.read_text(encoding="latin1", errors="replace").splitlines():
125
+ line = raw.strip()
126
+ if not line or line.startswith("PouDep"):
127
+ continue
128
+ parts = [p.strip() for p in line.split(";")]
129
+ head = parts[0].split(",")
130
+ if len(head) < 4:
131
+ continue
132
+ try:
133
+ index = int(head[0])
134
+ except ValueError:
135
+ continue
136
+ block = by_index.get(index)
137
+ if block is None:
138
+ continue
139
+ for dep in parts[1:]:
140
+ if not dep:
141
+ continue
142
+ dep_index = dep.split(",")[0].strip()
143
+ try:
144
+ target = by_index.get(int(dep_index))
145
+ except ValueError:
146
+ target = None
147
+ block.depends_on.append(target.name if target else dep)
148
+ return blocks
149
+
150
+
151
+ # ── .NET metadata ────────────────────────────────────────────────────────────────
152
+
153
+ def _metadata_streams(data: bytes):
154
+ """Locate the .NET metadata streams, or None if this is not a managed assembly."""
155
+ at = data.find(b"BSJB")
156
+ if at < 0:
157
+ return None
158
+ off = at + 4
159
+ try:
160
+ _major, _minor, _reserved, version_len = struct.unpack_from("<HHII", data, off)
161
+ except struct.error:
162
+ return None
163
+ off += 12
164
+ version = data[off:off + version_len].split(b"\x00")[0].decode("ascii", "replace")
165
+ off += version_len + 2
166
+ try:
167
+ count = struct.unpack_from("<H", data, off)[0]
168
+ except struct.error:
169
+ return None
170
+ off += 2
171
+ heaps: dict[str, tuple[int, int]] = {}
172
+ for _ in range(count):
173
+ try:
174
+ stream_off, stream_size = struct.unpack_from("<II", data, off)
175
+ except struct.error:
176
+ return None
177
+ off += 8
178
+ end = data.find(b"\x00", off)
179
+ if end < 0:
180
+ return None
181
+ name = data[off:end].decode("ascii", "replace")
182
+ off = (end + 1 + 3) & ~3
183
+ heaps[name] = (at + stream_off, stream_size)
184
+ return version, heaps
185
+
186
+
187
+ def _strings(data: bytes, base: int, size: int) -> list[str]:
188
+ raw = data[base:base + size]
189
+ out: list[str] = []
190
+ current = bytearray()
191
+ for byte in raw:
192
+ if byte == 0:
193
+ if current:
194
+ out.append(current.decode("utf-8", "replace"))
195
+ current = bytearray()
196
+ else:
197
+ current.append(byte)
198
+ if current:
199
+ out.append(current.decode("utf-8", "replace"))
200
+ return out
201
+
202
+
203
+ def read_block_members(project_root: Path, name: str) -> BlockMembers:
204
+ """Identifiers defined by a block's compiled assembly.
205
+
206
+ Direction is NOT among them - see the module docstring - so this reports names and says
207
+ so rather than presenting a guess as an interface.
208
+ """
209
+ directory = _resource_dir(project_root)
210
+ for candidate in (directory / f"{name}.DLL", directory / f"{name}.dll"):
211
+ if candidate.is_file():
212
+ assembly = candidate
213
+ break
214
+ else:
215
+ blocks = {b.name.lower(): b for b in read_dependencies(project_root)}
216
+ known = ", ".join(sorted(b.name for b in blocks.values())[:25])
217
+ raise NotFound(
218
+ f"no compiled assembly for {name!r} in {RESOURCE_DIR}. Blocks with an assembly "
219
+ f"here: {known}. A block with no assembly may be defined in the project's own "
220
+ f"source rather than a library - list those with mw_code_pous."
221
+ )
222
+
223
+ data = assembly.read_bytes()
224
+ streams = _metadata_streams(data)
225
+ if not streams:
226
+ raise NotFound(
227
+ f"{assembly.name} is not a managed assembly, so it has no readable identifier "
228
+ f"table. It may be legacy native code."
229
+ )
230
+ version, heaps = streams
231
+ if "#Strings" not in heaps:
232
+ raise NotFound(f"{assembly.name} has .NET metadata but no #Strings heap")
233
+
234
+ base, size = heaps["#Strings"]
235
+ everything = _strings(data, base, size)
236
+ seen: set[str] = set()
237
+ kept: list[str] = []
238
+ dropped = 0
239
+ for item in everything:
240
+ if not item or item in seen:
241
+ continue
242
+ seen.add(item)
243
+ if _NOISE.match(item):
244
+ dropped += 1
245
+ continue
246
+ kept.append(item)
247
+ return BlockMembers(
248
+ name=name, assembly=assembly.name, runtime=version,
249
+ identifiers=kept, filtered_out=dropped,
250
+ )