@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,154 @@
1
+ /**
2
+ * Cross-reference search — which symbols a project's logic references, and where.
3
+ *
4
+ * The index is built from decoded ladder NeutralText and Structured Text lines,
5
+ * not from the raw XML, so a symbol is reported the way a reader of the logic
6
+ * would write it: `Motor_1.Running` is one symbol and `Motor_1` is also its own
7
+ * entry, because a caller asking "where is Motor_1 used" expects the dotted
8
+ * references too.
9
+ *
10
+ * Instruction mnemonics and language keywords are excluded — `XIC`, `OTE`, `IF`,
11
+ * and `AND` are not tags, and reporting them would bury every real answer. The
12
+ * index is cached per parsed document, so asking one project several questions
13
+ * scans its logic once.
14
+ */
15
+ import { splitLines } from "./routines.js";
16
+ /** Ladder instruction mnemonics, which are operators rather than tags. */
17
+ const INSTRUCTION_MNEMONICS = new Set([
18
+ 'XIC', 'XIO', 'OTE', 'OTL', 'OTU', 'TON', 'TOF', 'RTO', 'CTU', 'CTD',
19
+ 'RES', 'MOV', 'ADD', 'SUB', 'MUL', 'DIV', 'NEG', 'CMP', 'EQU', 'NEQ',
20
+ 'GRT', 'GEQ', 'LES', 'LEQ', 'JSR', 'RET', 'SBR', 'AFI', 'NOP', 'MSG',
21
+ 'GSV', 'SSV', 'COP', 'FLL', 'CLR', 'BTD', 'SWPB', 'SIZE',
22
+ ]);
23
+ /** Structured Text keywords, which are syntax rather than tags. */
24
+ const STRUCTURED_TEXT_KEYWORDS = new Set([
25
+ 'IF', 'THEN', 'ELSE', 'ELSIF', 'END_IF', 'FOR', 'TO', 'DO', 'END_FOR',
26
+ 'WHILE', 'END_WHILE', 'REPEAT', 'UNTIL', 'END_REPEAT', 'CASE', 'OF',
27
+ 'END_CASE', 'RETURN', 'EXIT', 'NOT', 'AND', 'OR', 'XOR', 'MOD', 'TRUE', 'FALSE',
28
+ ]);
29
+ /** Identifiers, including their dotted member paths. */
30
+ const IDENTIFIER = /[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*/g;
31
+ /** Indices already built, keyed by the document they describe. */
32
+ const indices = new WeakMap();
33
+ /**
34
+ * Search the project's logic for symbols matching a pattern.
35
+ *
36
+ * The pattern is a regular expression, evaluated case-insensitively. A pattern
37
+ * that is not a valid expression is searched for literally instead, so a tag
38
+ * whose name contains regex punctuation still finds its uses.
39
+ *
40
+ * @param document - The parsed project to search.
41
+ * @param pattern - Regular expression, or literal text when it does not compile.
42
+ * @returns One entry per matching symbol, in the order the symbols were first
43
+ * seen, each with every reference to it and an empty result when nothing matched.
44
+ */
45
+ export function searchLogic(document, pattern) {
46
+ const matcher = compile(pattern);
47
+ const results = [];
48
+ for (const [symbol, references] of indexOf(document)) {
49
+ if (matcher.test(symbol))
50
+ results.push({ symbol, references });
51
+ }
52
+ return results;
53
+ }
54
+ /** Compile the pattern, falling back to a literal search when it is invalid. */
55
+ function compile(pattern) {
56
+ try {
57
+ return new RegExp(pattern, 'i');
58
+ }
59
+ catch {
60
+ return new RegExp(escapeRegExp(pattern), 'i');
61
+ }
62
+ }
63
+ /** Escape a string so a regular expression matches it literally. */
64
+ function escapeRegExp(text) {
65
+ return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
66
+ }
67
+ /** The index for a document, building and caching it on first use. */
68
+ function indexOf(document) {
69
+ const cached = indices.get(document);
70
+ if (cached !== undefined)
71
+ return cached;
72
+ const built = build(document);
73
+ indices.set(document, built);
74
+ return built;
75
+ }
76
+ /** Scan every routine of every program, and each add-on instruction's ladder, for referenced symbols. */
77
+ function build(document) {
78
+ const index = new Map();
79
+ for (const routine of document.routines) {
80
+ if (routine.body.type === 'RLL') {
81
+ for (const rung of routine.body.rungs) {
82
+ if (rung.text === '')
83
+ continue;
84
+ for (const symbol of symbolsOf(rung.text, INSTRUCTION_MNEMONICS)) {
85
+ add(index, symbol, {
86
+ program: routine.program,
87
+ routine: routine.name,
88
+ rung: rung.number,
89
+ context: rung.text,
90
+ });
91
+ }
92
+ }
93
+ continue;
94
+ }
95
+ if (routine.body.type !== 'ST')
96
+ continue;
97
+ for (const line of splitLines(routine.body.code)) {
98
+ const text = line.text.trim();
99
+ if (text === '' || text.startsWith('//'))
100
+ continue;
101
+ for (const symbol of symbolsOf(line.text, STRUCTURED_TEXT_KEYWORDS)) {
102
+ add(index, symbol, { program: routine.program, routine: routine.name, line: line.number, context: text });
103
+ }
104
+ }
105
+ }
106
+ // An add-on instruction's ladder is logic a program runs, so its references
107
+ // belong in the same index. Its Structured Text is not indexed, matching the
108
+ // reference engine, which scans only RLL inside an AOI.
109
+ for (const aoi of document.aois) {
110
+ for (const routine of aoi.routines) {
111
+ if (routine.body.type !== 'RLL')
112
+ continue;
113
+ for (const rung of routine.body.rungs) {
114
+ if (rung.text === '')
115
+ continue;
116
+ for (const symbol of symbolsOf(rung.text, INSTRUCTION_MNEMONICS)) {
117
+ add(index, symbol, { aoi: aoi.name, routine: routine.name, rung: rung.number, context: rung.text });
118
+ }
119
+ }
120
+ }
121
+ }
122
+ return index;
123
+ }
124
+ /** Record one reference under its symbol. */
125
+ function add(index, symbol, reference) {
126
+ const references = index.get(symbol);
127
+ if (references === undefined)
128
+ index.set(symbol, [reference]);
129
+ else
130
+ references.push(reference);
131
+ }
132
+ /**
133
+ * The symbols one piece of logic references.
134
+ *
135
+ * A dotted reference contributes both the full path and its base tag, so
136
+ * searching either finds the use.
137
+ *
138
+ * @param text - Ladder NeutralText or one Structured Text line.
139
+ * @param excluded - Mnemonics or keywords that are not tags.
140
+ * @returns The symbols, without duplicates.
141
+ */
142
+ function symbolsOf(text, excluded) {
143
+ const symbols = new Set();
144
+ for (const match of text.matchAll(IDENTIFIER)) {
145
+ const token = match[0];
146
+ const base = token.split('.')[0];
147
+ if (base === undefined || excluded.has(base.toUpperCase()))
148
+ continue;
149
+ symbols.add(token);
150
+ if (token.includes('.'))
151
+ symbols.add(base);
152
+ }
153
+ return symbols;
154
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Open L5X exports for one chat.
3
+ *
4
+ * A snapshot names the parsed document, the XML it came from, and the hash of
5
+ * the bytes. Reads are pure over the document, so a project is parsed once;
6
+ * every later call re-reads the file only to prove the document is still the
7
+ * file's content.
8
+ *
9
+ * Handles belong to the calling chat. A second chat starts with none open,
10
+ * which is why an unknown handle names the handles that chat does have.
11
+ *
12
+ * @module dsh-engineering-l5x/session
13
+ */
14
+ import { type L5xDocument } from './model/index.ts';
15
+ import type { ExecContext } from './core/tool.ts';
16
+ import { L5xError } from './core/errors.ts';
17
+ import type { FsService, FsTarget } from './core/tool.ts';
18
+ /** How completely one export feature can be analyzed. */
19
+ export type FeatureLevel = 'analyzed' | 'partial' | 'unsupported' | 'not_present';
20
+ /** One parsed export, identified by the handle the reader passes back. */
21
+ export interface ProjectSnapshot {
22
+ /** Handle returned by `open_project`. */
23
+ readonly id: string;
24
+ /** Path as the caller supplied it. */
25
+ readonly requestedPath: string;
26
+ /** Resolved path the filesystem provider returned, for model- and engine-facing output. */
27
+ readonly path: string;
28
+ /** Filesystem target used to re-read the export. */
29
+ readonly target: FsTarget;
30
+ /** SHA-256 of the bytes this document was parsed from. */
31
+ readonly sha256: string;
32
+ /** Size of the export in bytes. */
33
+ readonly bytes: number;
34
+ /** Decoded XML text. */
35
+ readonly xml: string;
36
+ /** The parsed document. */
37
+ readonly document: L5xDocument;
38
+ /** Studio software revision the export declares. */
39
+ readonly softwareRevision: string;
40
+ /** Whether this revision is in the parser's observed test matrix. */
41
+ readonly verification: 'observed_parse' | 'untested_revision';
42
+ /** Export target type, `Controller` for a full project. */
43
+ readonly targetType: string;
44
+ /** Whether the export is a whole controller or one component. */
45
+ readonly exportKind: 'controller' | 'component';
46
+ /** Feature analysis level for this export. */
47
+ readonly features: Readonly<Record<string, FeatureLevel>>;
48
+ /** ISO timestamp of the open. */
49
+ readonly openedAt: string;
50
+ }
51
+ /** A failure raised by an analysis engine. */
52
+ export declare class EngineeringError extends L5xError {
53
+ /**
54
+ * @param code - Stable identifier for tests and logs.
55
+ * @param message - What failed, in the reader's terms.
56
+ * @param fix - The next call or check; defaults to re-reading the project state.
57
+ */
58
+ constructor(code: string, message: string, fix?: string);
59
+ }
60
+ /** The parsed-document store, keyed by chat. */
61
+ export declare class ProjectSession {
62
+ private readonly fs;
63
+ private readonly maxFileBytes;
64
+ /** Projects per chat; the key is the calling session object. */
65
+ private readonly chats;
66
+ /** Projects for a call without a chat identity, which happens only in tests. */
67
+ private readonly detached;
68
+ /**
69
+ * @param fs - Filesystem provider for the profile.
70
+ * @param maxFileBytes - Largest export one open will read.
71
+ */
72
+ constructor(fs: FsService, maxFileBytes: number);
73
+ /**
74
+ * Handles open in the calling chat, in open order.
75
+ *
76
+ * @param exec - Calling context; its chat selects the handle set.
77
+ * @returns The handles that chat can pass to a read.
78
+ */
79
+ ids(exec?: ExecContext): readonly string[];
80
+ /**
81
+ * Read, parse and retain one export for the calling chat.
82
+ *
83
+ * @param requestedPath - Path as the caller supplied it, absolute or relative to the session workspace.
84
+ * @param exec - Calling context: cancellation, workspace root, and chat identity.
85
+ * @returns The retained project, including its handle.
86
+ */
87
+ open(requestedPath: string, exec?: ExecContext): Promise<ProjectSnapshot>;
88
+ /**
89
+ * Resolve the project a read applies to and prove it is still current.
90
+ *
91
+ * @param projectId - Explicit handle, or undefined to use the single open project in this chat.
92
+ * @param exec - Calling context: cancellation and chat identity.
93
+ * @returns The open project.
94
+ */
95
+ require(projectId: string | undefined, exec?: ExecContext): Promise<ProjectSnapshot>;
96
+ /**
97
+ * Resolve one path through the filesystem provider without reading it.
98
+ *
99
+ * @param requestedPath - Path as the caller supplied it.
100
+ * @param exec - Calling context: cancellation and workspace root.
101
+ * @returns The resolved path.
102
+ */
103
+ resolvePath(requestedPath: string, exec?: ExecContext): Promise<string>;
104
+ /**
105
+ * Write one prepared draft to a new path, never overwriting.
106
+ *
107
+ * @param outputPath - Destination as the caller supplied it.
108
+ * @param text - The prepared L5X document.
109
+ * @param exec - Calling context: cancellation and workspace root.
110
+ * @returns The resolved destination.
111
+ */
112
+ writeNew(outputPath: string, text: string, exec?: ExecContext): Promise<string>;
113
+ /** The handle set for one calling chat. */
114
+ private projectsOf;
115
+ /** The single open project in one chat, or the failure that explains how to choose one. */
116
+ private single;
117
+ /** Resolve, check and read one path through the filesystem provider. */
118
+ private readBytes;
119
+ }
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Open L5X exports for one chat.
3
+ *
4
+ * A snapshot names the parsed document, the XML it came from, and the hash of
5
+ * the bytes. Reads are pure over the document, so a project is parsed once;
6
+ * every later call re-reads the file only to prove the document is still the
7
+ * file's content.
8
+ *
9
+ * Handles belong to the calling chat. A second chat starts with none open,
10
+ * which is why an unknown handle names the handles that chat does have.
11
+ *
12
+ * @module dsh-engineering-l5x/session
13
+ */
14
+ import { createHash, randomUUID } from 'node:crypto';
15
+ import { L5xParseError, loadL5x } from "./model/index.js";
16
+ import { L5xError } from "./core/errors.js";
17
+ import { decodeExport } from "./fs.js";
18
+ /** A failure raised by an analysis engine. */
19
+ export class EngineeringError extends L5xError {
20
+ /**
21
+ * @param code - Stable identifier for tests and logs.
22
+ * @param message - What failed, in the reader's terms.
23
+ * @param fix - The next call or check; defaults to re-reading the project state.
24
+ */
25
+ constructor(code, message, fix = 'Call project_overview to confirm the project state, then retry.') {
26
+ super(code, message, fix);
27
+ }
28
+ }
29
+ /** Most projects one chat retains before the oldest is dropped. */
30
+ const MAX_PROJECTS = 4;
31
+ /** The parsed-document store, keyed by chat. */
32
+ export class ProjectSession {
33
+ fs;
34
+ maxFileBytes;
35
+ /** Projects per chat; the key is the calling session object. */
36
+ chats = new WeakMap();
37
+ /** Projects for a call without a chat identity, which happens only in tests. */
38
+ detached = new Map();
39
+ /**
40
+ * @param fs - Filesystem provider for the profile.
41
+ * @param maxFileBytes - Largest export one open will read.
42
+ */
43
+ constructor(fs, maxFileBytes) {
44
+ this.fs = fs;
45
+ this.maxFileBytes = maxFileBytes;
46
+ }
47
+ /**
48
+ * Handles open in the calling chat, in open order.
49
+ *
50
+ * @param exec - Calling context; its chat selects the handle set.
51
+ * @returns The handles that chat can pass to a read.
52
+ */
53
+ ids(exec) {
54
+ return [...this.projectsOf(exec).keys()];
55
+ }
56
+ /**
57
+ * Read, parse and retain one export for the calling chat.
58
+ *
59
+ * @param requestedPath - Path as the caller supplied it, absolute or relative to the session workspace.
60
+ * @param exec - Calling context: cancellation, workspace root, and chat identity.
61
+ * @returns The retained project, including its handle.
62
+ */
63
+ async open(requestedPath, exec) {
64
+ const { bytes, target, displayPath } = await this.readBytes(requestedPath, exec);
65
+ const xml = decodeExport(bytes);
66
+ if (/<!DOCTYPE\b|<!ENTITY\b/iu.test(xml)) {
67
+ throw new L5xError('UNSUPPORTED_XML', 'The export declares a DTD or an entity, which this plugin does not accept.', 'Re-export the project from Studio 5000 without a custom DTD, then reopen it.');
68
+ }
69
+ let document;
70
+ try {
71
+ document = loadL5x(xml);
72
+ }
73
+ catch (error) {
74
+ throw new L5xError('INVALID_L5X', error instanceof L5xParseError ? error.message : `The file is not a readable L5X export: ${String(error)}`, 'Open a full L5X export from Studio 5000 (File > Save As > L5X), then retry.');
75
+ }
76
+ const sha256 = createHash('sha256').update(bytes).digest('hex');
77
+ const targetType = attribute(xml, 'TargetType');
78
+ const softwareRevision = attribute(xml, 'SoftwareRevision');
79
+ const major = Number.parseInt(softwareRevision, 10);
80
+ if (Number.isFinite(major) && major < 19) {
81
+ throw new L5xError('UNSUPPORTED_REVISION', `Software revision ${softwareRevision} predates the revision-19 target this parser supports.`, 'Re-export the project from Studio 5000 revision 19 or later, then open that L5X.');
82
+ }
83
+ const project = {
84
+ id: randomUUID(),
85
+ requestedPath,
86
+ path: displayPath,
87
+ target,
88
+ sha256,
89
+ bytes: bytes.length,
90
+ xml,
91
+ document,
92
+ softwareRevision,
93
+ verification: verificationOf(major),
94
+ targetType,
95
+ exportKind: targetType === 'Controller' ? 'controller' : 'component',
96
+ features: featureLevels(xml),
97
+ openedAt: new Date().toISOString(),
98
+ };
99
+ const projects = this.projectsOf(exec);
100
+ projects.set(project.id, project);
101
+ while (projects.size > MAX_PROJECTS) {
102
+ const oldest = projects.keys().next().value;
103
+ if (oldest !== undefined)
104
+ projects.delete(oldest);
105
+ }
106
+ return project;
107
+ }
108
+ /**
109
+ * Resolve the project a read applies to and prove it is still current.
110
+ *
111
+ * @param projectId - Explicit handle, or undefined to use the single open project in this chat.
112
+ * @param exec - Calling context: cancellation and chat identity.
113
+ * @returns The open project.
114
+ */
115
+ async require(projectId, exec) {
116
+ const projects = this.projectsOf(exec);
117
+ const ids = [...projects.keys()];
118
+ const project = projectId === undefined || projectId === ''
119
+ ? this.single(ids, projects)
120
+ : projects.get(projectId);
121
+ if (project === undefined) {
122
+ throw new L5xError('UNKNOWN_PROJECT', `No project is open with project_id ${JSON.stringify(projectId)} in this chat.`, ids.length === 0
123
+ ? 'Call open_project with the L5X path, then retry.'
124
+ : `Pass one of this chat's open project ids: ${ids.join(', ')}.`, ids);
125
+ }
126
+ const bytes = await this.fs.readBytes(project.target, exec?.signal, this.maxFileBytes);
127
+ const sha256 = createHash('sha256').update(bytes).digest('hex');
128
+ if (sha256 !== project.sha256) {
129
+ throw new L5xError('STALE_EXPORT', `The export changed since open_project: ${project.requestedPath}`, 'Call open_project again on the same path, then repeat the read.');
130
+ }
131
+ return project;
132
+ }
133
+ /**
134
+ * Resolve one path through the filesystem provider without reading it.
135
+ *
136
+ * @param requestedPath - Path as the caller supplied it.
137
+ * @param exec - Calling context: cancellation and workspace root.
138
+ * @returns The resolved path.
139
+ */
140
+ async resolvePath(requestedPath, exec) {
141
+ const target = await this.fs.resolve(requestedPath, {
142
+ ...(exec?.cwd === undefined ? {} : { cwd: exec.cwd }),
143
+ ...(exec?.signal === undefined ? {} : { signal: exec.signal }),
144
+ });
145
+ return target.displayPath;
146
+ }
147
+ /**
148
+ * Write one prepared draft to a new path, never overwriting.
149
+ *
150
+ * @param outputPath - Destination as the caller supplied it.
151
+ * @param text - The prepared L5X document.
152
+ * @param exec - Calling context: cancellation and workspace root.
153
+ * @returns The resolved destination.
154
+ */
155
+ async writeNew(outputPath, text, exec) {
156
+ if (!/\.l5x$/iu.test(outputPath)) {
157
+ throw new L5xError('OUTPUT_EXTENSION', 'The draft must be written to an .L5X file.', 'Pass output_path ending in .L5X.');
158
+ }
159
+ const target = await this.fs.resolve(outputPath, {
160
+ ...(exec?.cwd === undefined ? {} : { cwd: exec.cwd }),
161
+ ...(exec?.signal === undefined ? {} : { signal: exec.signal }),
162
+ });
163
+ const info = await this.fs.stat(target, exec?.signal);
164
+ if (info !== undefined) {
165
+ throw new L5xError('OUTPUT_EXISTS', `A file already exists at ${outputPath}.`, 'Pass a new output_path; this tool never overwrites an existing file.');
166
+ }
167
+ if (this.fs.writeText === undefined) {
168
+ throw new L5xError('OUTPUT_UNAVAILABLE', 'The filesystem provider in this profile cannot write files.', 'Use a profile whose filesystem provider allows writes, or copy the draft text into Studio manually.');
169
+ }
170
+ await this.fs.writeText(target, text, { kind: 'createIfAbsent' }, exec?.signal);
171
+ return target.displayPath;
172
+ }
173
+ /** The handle set for one calling chat. */
174
+ projectsOf(exec) {
175
+ const chat = exec?.chat;
176
+ if (chat === undefined)
177
+ return this.detached;
178
+ const existing = this.chats.get(chat);
179
+ if (existing !== undefined)
180
+ return existing;
181
+ const created = new Map();
182
+ this.chats.set(chat, created);
183
+ return created;
184
+ }
185
+ /** The single open project in one chat, or the failure that explains how to choose one. */
186
+ single(ids, projects) {
187
+ const only = ids[0];
188
+ if (only === undefined) {
189
+ throw new L5xError('NO_PROJECT', 'No project is open in this chat.', 'Call open_project with the L5X path, then retry.');
190
+ }
191
+ if (ids.length > 1) {
192
+ throw new L5xError('AMBIGUOUS_PROJECT', `${ids.length} projects are open in this chat, so project_id is required.`, `Pass one of this chat's open project ids: ${ids.join(', ')}.`, ids);
193
+ }
194
+ return projects.get(only);
195
+ }
196
+ /** Resolve, check and read one path through the filesystem provider. */
197
+ async readBytes(requestedPath, exec) {
198
+ if (requestedPath.trim() === '') {
199
+ throw new L5xError('INVALID_ARGUMENT', 'path is required.', 'Pass the L5X file path, for example "C:/Projects/Line/Line.L5X".');
200
+ }
201
+ const target = await this.fs.resolve(requestedPath, {
202
+ ...(exec?.cwd === undefined ? {} : { cwd: exec.cwd }),
203
+ ...(exec?.signal === undefined ? {} : { signal: exec.signal }),
204
+ });
205
+ const info = await this.fs.stat(target, exec?.signal);
206
+ if (info === undefined) {
207
+ throw new L5xError('FILE_NOT_FOUND', `No file exists at ${requestedPath}.`, 'Check the path. Use an absolute path or a path inside this workspace, and the .L5X extension.');
208
+ }
209
+ if (info.type !== 'file') {
210
+ throw new L5xError('INVALID_PATH', `${requestedPath} is not a file.`, 'Pass the path of one .L5X export, not a directory.');
211
+ }
212
+ return { bytes: await this.fs.readBytes(target, exec?.signal, this.maxFileBytes), target, displayPath: target.displayPath };
213
+ }
214
+ }
215
+ /** Read one attribute from the export root element. */
216
+ function attribute(xml, name) {
217
+ const root = /<RSLogix5000Content\b([^>]*)>/u.exec(xml)?.[1] ?? '';
218
+ return new RegExp(`\\b${name}=["']([^"']*)["']`, 'u').exec(root)?.[1] ?? '';
219
+ }
220
+ /** Revisions the parser has been exercised against; anything else is still read, but flagged. */
221
+ const OBSERVED_REVISIONS = new Set([19, 33, 34, 36, 37]);
222
+ /** Whether a revision is in the observed test matrix. */
223
+ function verificationOf(major) {
224
+ return OBSERVED_REVISIONS.has(major) ? 'observed_parse' : 'untested_revision';
225
+ }
226
+ /** Feature analysis level for one export. */
227
+ function featureLevels(xml) {
228
+ return {
229
+ ladder: 'analyzed',
230
+ structuredText: 'analyzed',
231
+ functionBlock: xml.includes('<FBDContent') ? 'partial' : 'not_present',
232
+ sequentialFunctionChart: xml.includes('<SFCContent') ? 'partial' : 'not_present',
233
+ alarms: /<Alarm\b|<TagBasedAlarm\b/u.test(xml) ? 'partial' : 'not_present',
234
+ protectedContent: /<EncodedData\b|Encrypted="true"|SourceProtected="true"/u.test(xml) ? 'unsupported' : 'not_present',
235
+ };
236
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The guidance half of the L5X port.
3
+ *
4
+ * `src/index.ts` registers the read tools; this module registers the
5
+ * `rockwell-l5x` skill that tells a model how to use them. They are separate
6
+ * loader entries, so a profile can run the tools without the guidance or the
7
+ * guidance without the tools.
8
+ *
9
+ * @module dsh-engineering-l5x/skill
10
+ */
11
+ /** Plugin name in a composition. */
12
+ export declare const name = "l5x-skill";
13
+ /** Services this plugin cannot register without. */
14
+ export declare const inject: readonly ["skills"];
15
+ /** The plugin context this module needs. */
16
+ export interface SkillContext {
17
+ get(name: string): unknown;
18
+ }
19
+ /** Registration metadata, kept here so a test can assert it without the file. */
20
+ export declare const SKILL: {
21
+ readonly name: "rockwell-l5x";
22
+ readonly description: "Read and review Studio 5000 L5X exports: open a project, list and read components, find references, trace dependencies, resolve addresses, map bit usage, run checks, and compare exports.";
23
+ readonly whenToUse: "Use when the user asks about a Studio 5000 L5X export, Logix tags, routines, UDTs, AOIs, I/O modules, references, dependency tracing, checks, or comparison between exports.";
24
+ };
25
+ /**
26
+ * Register the Rockwell L5X skill.
27
+ *
28
+ * @param ctx - Plugin context whose skill registry receives the registration.
29
+ */
30
+ export declare function apply(ctx: SkillContext): void;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The guidance half of the L5X port.
3
+ *
4
+ * `src/index.ts` registers the read tools; this module registers the
5
+ * `rockwell-l5x` skill that tells a model how to use them. They are separate
6
+ * loader entries, so a profile can run the tools without the guidance or the
7
+ * guidance without the tools.
8
+ *
9
+ * @module dsh-engineering-l5x/skill
10
+ */
11
+ import { readFileSync } from 'node:fs';
12
+ import { fileURLToPath } from 'node:url';
13
+ /** Plugin name in a composition. */
14
+ export const name = 'l5x-skill';
15
+ /** Services this plugin cannot register without. */
16
+ export const inject = ['skills'];
17
+ /** Registration metadata, kept here so a test can assert it without the file. */
18
+ export const SKILL = {
19
+ name: 'rockwell-l5x',
20
+ description: 'Read and review Studio 5000 L5X exports: open a project, list and read components, find references, trace dependencies, resolve addresses, map bit usage, run checks, and compare exports.',
21
+ whenToUse: 'Use when the user asks about a Studio 5000 L5X export, Logix tags, routines, UDTs, AOIs, I/O modules, references, dependency tracing, checks, or comparison between exports.',
22
+ };
23
+ /**
24
+ * Register the Rockwell L5X skill.
25
+ *
26
+ * @param ctx - Plugin context whose skill registry receives the registration.
27
+ */
28
+ export function apply(ctx) {
29
+ const skills = ctx.get('skills');
30
+ if (skills === undefined || typeof skills.register !== 'function') {
31
+ throw new Error('l5x-skill: the skills service is unavailable, so the rockwell-l5x guidance cannot register');
32
+ }
33
+ const skillPath = fileURLToPath(new URL('../SKILL.md', import.meta.url));
34
+ const raw = readFileSync(skillPath, 'utf8');
35
+ const content = raw.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/u, '');
36
+ skills.register({
37
+ ...SKILL,
38
+ content,
39
+ path: skillPath,
40
+ source: 'custom',
41
+ provider: '@aryacode/dsh-engineering-l5x',
42
+ invocation: { modelInvocable: true, userInvocable: true },
43
+ });
44
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Provenance for ACD exports and verified builds, scoped to one chat.
3
+ *
4
+ * An ACD export is only usable as a baseline for a later build when this chat
5
+ * exported that exact L5X from that exact ACD: both hashes must still match.
6
+ * The record is what makes that check possible without trusting a path.
7
+ *
8
+ * @module dsh-engineering-l5x/studio-state
9
+ */
10
+ import type { ExecContext } from './core/tool.ts';
11
+ import type { DownloadReview } from './engine/download-review.ts';
12
+ /** One SDK build that passed and may be downloaded with its verification id. */
13
+ export interface VerifiedBuild {
14
+ /** Built ACD path. */
15
+ readonly path: string;
16
+ /** SHA-256 of the built ACD at verification time. */
17
+ readonly sha256: string;
18
+ /** Baseline L5X the build was reviewed against. */
19
+ readonly baselinePath: string;
20
+ /** Edited L5X the build imported. */
21
+ readonly sourcePath: string;
22
+ /** Review recorded with the build, including both L5X hashes. */
23
+ readonly review: Pick<DownloadReview, 'baseline_sha256' | 'source_sha256'>;
24
+ }
25
+ /** One L5X produced by `export_acd_to_l5x`, with the hashes that bind it. */
26
+ export interface AcdExportRecord {
27
+ /** Resolved ACD that was exported. */
28
+ readonly acdPath: string;
29
+ /** Resolved L5X that was written. */
30
+ readonly l5xPath: string;
31
+ /** SHA-256 of the ACD bytes at export time. */
32
+ readonly acdSha256: string;
33
+ /** SHA-256 of the L5X bytes at export time. */
34
+ readonly l5xSha256: string;
35
+ }
36
+ /** Exports and verified builds, scoped to the chat that ran them. */
37
+ export declare class StudioState {
38
+ private readonly chats;
39
+ private readonly detached;
40
+ private readonly buildChats;
41
+ private readonly detachedBuilds;
42
+ /**
43
+ * Record one export.
44
+ *
45
+ * @param exec - Calling context; its chat owns the record.
46
+ * @param record - Export binding to remember.
47
+ */
48
+ record(exec: ExecContext | undefined, record: AcdExportRecord): void;
49
+ /**
50
+ * The export that produced one L5X, if this chat produced it.
51
+ *
52
+ * @param exec - Calling context.
53
+ * @param l5xPath - Resolved L5X path.
54
+ * @returns The export record, or undefined.
55
+ */
56
+ forL5x(exec: ExecContext | undefined, l5xPath: string): AcdExportRecord | undefined;
57
+ /**
58
+ * Remember one build that passed and may later be downloaded.
59
+ *
60
+ * @param exec - Calling context.
61
+ * @param id - Verification id returned to the caller.
62
+ * @param build - Build binding and its review.
63
+ */
64
+ addVerified(exec: ExecContext | undefined, id: string, build: VerifiedBuild): void;
65
+ /**
66
+ * The build behind one verification id, if this chat produced it.
67
+ *
68
+ * @param exec - Calling context.
69
+ * @param id - Verification id.
70
+ * @returns The verified build, or undefined.
71
+ */
72
+ verified(exec: ExecContext | undefined, id: string): VerifiedBuild | undefined;
73
+ /** The record map for one calling chat. */
74
+ private chatsOf;
75
+ /** The verified-build map for one calling chat. */
76
+ private buildsOf;
77
+ }