@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,49 @@
1
+ /**
2
+ * Parameter schemas that always describe themselves.
3
+ *
4
+ * The port's first measured failure was 132 parameters with no description.
5
+ * These helpers make an undescribed parameter impossible to write: the
6
+ * description is a required argument, not an optional field.
7
+ *
8
+ * @module dsh-engineering-l5x/core/params
9
+ */
10
+ /** One JSON Schema property, with the description that is mandatory here. */
11
+ export interface ParameterSchema {
12
+ readonly type: 'string' | 'integer' | 'number' | 'boolean' | 'array' | 'object';
13
+ readonly description: string;
14
+ readonly enum?: readonly string[];
15
+ readonly items?: ParameterSchema;
16
+ readonly default?: unknown;
17
+ readonly minimum?: number;
18
+ readonly maximum?: number;
19
+ readonly minItems?: number;
20
+ readonly maxItems?: number;
21
+ readonly properties?: Readonly<Record<string, ParameterSchema>>;
22
+ readonly required?: readonly string[];
23
+ readonly additionalProperties?: boolean;
24
+ }
25
+ /** A string parameter. */
26
+ export declare function string(description: string, extra?: Partial<ParameterSchema>): ParameterSchema;
27
+ /** An integer parameter with an optional range. */
28
+ export declare function integer(description: string, extra?: Partial<ParameterSchema>): ParameterSchema;
29
+ /** A string parameter restricted to a known set. */
30
+ export declare function choice(description: string, values: readonly string[], extra?: Partial<ParameterSchema>): ParameterSchema;
31
+ /** An array of one parameter type. */
32
+ export declare function list(description: string, items: ParameterSchema, extra?: Partial<ParameterSchema>): ParameterSchema;
33
+ /** One object element of an array parameter. */
34
+ export declare function objectItem(description: string, properties: Readonly<Record<string, ParameterSchema>>, required?: readonly string[]): ParameterSchema;
35
+ /** The `parameters` object of one tool. */
36
+ export interface Parameters {
37
+ readonly type: 'object';
38
+ readonly properties: Readonly<Record<string, ParameterSchema>>;
39
+ readonly required: readonly string[];
40
+ readonly additionalProperties: false;
41
+ }
42
+ /**
43
+ * Build a tool's parameter object.
44
+ *
45
+ * @param properties - Named parameters, every one already described.
46
+ * @param required - Names the caller must supply.
47
+ * @returns The schema object a tool definition declares.
48
+ */
49
+ export declare function object(properties: Readonly<Record<string, ParameterSchema>>, required?: readonly string[]): Parameters;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Parameter schemas that always describe themselves.
3
+ *
4
+ * The port's first measured failure was 132 parameters with no description.
5
+ * These helpers make an undescribed parameter impossible to write: the
6
+ * description is a required argument, not an optional field.
7
+ *
8
+ * @module dsh-engineering-l5x/core/params
9
+ */
10
+ /** A string parameter. */
11
+ export function string(description, extra = {}) {
12
+ return { type: 'string', description, ...extra };
13
+ }
14
+ /** An integer parameter with an optional range. */
15
+ export function integer(description, extra = {}) {
16
+ return { type: 'integer', description, ...extra };
17
+ }
18
+ /** A string parameter restricted to a known set. */
19
+ export function choice(description, values, extra = {}) {
20
+ return { type: 'string', description, enum: values, ...extra };
21
+ }
22
+ /** An array of one parameter type. */
23
+ export function list(description, items, extra = {}) {
24
+ return { type: 'array', description, items, ...extra };
25
+ }
26
+ /** One object element of an array parameter. */
27
+ export function objectItem(description, properties, required = []) {
28
+ return { type: 'object', description, properties, required, additionalProperties: false };
29
+ }
30
+ /**
31
+ * Build a tool's parameter object.
32
+ *
33
+ * @param properties - Named parameters, every one already described.
34
+ * @param required - Names the caller must supply.
35
+ * @returns The schema object a tool definition declares.
36
+ */
37
+ export function object(properties, required = []) {
38
+ return { type: 'object', properties, required, additionalProperties: false };
39
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The model-facing success document.
3
+ *
4
+ * Every read answers with a summary first and concrete next calls last. The
5
+ * summary lets a small model decide whether it got what it asked for without
6
+ * reading the whole payload; `next` is the recovery path when its plan is thin.
7
+ *
8
+ * @module dsh-engineering-l5x/core/result
9
+ */
10
+ /** One call the reader can make next, with the reason it is useful. */
11
+ export interface NextStep {
12
+ /** Tool name registered by this plugin. */
13
+ readonly tool: string;
14
+ /** Why this call answers the current question. */
15
+ readonly why: string;
16
+ /** Arguments worth passing; omitted when the tool needs none. */
17
+ readonly args?: Readonly<Record<string, unknown>>;
18
+ }
19
+ /** How much of the requested question the answer actually covers. */
20
+ export interface Coverage {
21
+ /** Whether the requested scope was answered in full. */
22
+ readonly complete: boolean;
23
+ /** What this answer cannot establish, named rather than implied. */
24
+ readonly unverified: readonly string[];
25
+ }
26
+ /** A successful tool answer. */
27
+ export interface Success<T> {
28
+ /** One sentence the reader can act on. */
29
+ readonly summary: string;
30
+ /** Tool-specific payload. */
31
+ readonly data: T;
32
+ /** Present when the answer has limits worth stating. */
33
+ readonly coverage?: Coverage;
34
+ /** Concrete next calls, most useful first. */
35
+ readonly next: readonly NextStep[];
36
+ }
37
+ /**
38
+ * Build a success document.
39
+ *
40
+ * @param summary - One sentence naming what was read.
41
+ * @param data - Tool-specific payload.
42
+ * @param next - Concrete next calls; may be empty when the answer is terminal.
43
+ * @param coverage - Limits of the answer, when it has any.
44
+ * @returns The document the tool returns.
45
+ */
46
+ export declare function success<T>(summary: string, data: T, next?: readonly NextStep[], coverage?: Coverage): Success<T>;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The model-facing success document.
3
+ *
4
+ * Every read answers with a summary first and concrete next calls last. The
5
+ * summary lets a small model decide whether it got what it asked for without
6
+ * reading the whole payload; `next` is the recovery path when its plan is thin.
7
+ *
8
+ * @module dsh-engineering-l5x/core/result
9
+ */
10
+ /**
11
+ * Build a success document.
12
+ *
13
+ * @param summary - One sentence naming what was read.
14
+ * @param data - Tool-specific payload.
15
+ * @param next - Concrete next calls; may be empty when the answer is terminal.
16
+ * @param coverage - Limits of the answer, when it has any.
17
+ * @returns The document the tool returns.
18
+ */
19
+ export function success(summary, data, next = [], coverage) {
20
+ return coverage === undefined ? { summary, data, next } : { summary, data, coverage, next };
21
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * The tool and service surfaces this package registers against.
3
+ *
4
+ * Declared locally, and structurally, so the package compiles without the host
5
+ * packages at build time while still matching what the Cordis registry and the
6
+ * filesystem provider expose at runtime.
7
+ *
8
+ * @module dsh-engineering-l5x/core/tool
9
+ */
10
+ import type { Parameters } from './params.ts';
11
+ /** One content block the registry renders for the model. */
12
+ export interface TextBlock {
13
+ readonly type: 'text';
14
+ readonly text: string;
15
+ }
16
+ /** The canonical output declaration every tool must carry. */
17
+ export interface ToolOutput {
18
+ readonly schema: {
19
+ readonly type: 'object';
20
+ readonly additionalProperties: boolean;
21
+ };
22
+ render(args: unknown, value: unknown): readonly TextBlock[];
23
+ }
24
+ /** The execution context a tool receives from the registry. */
25
+ export interface ToolExec {
26
+ readonly signal?: AbortSignal;
27
+ readonly callId?: unknown;
28
+ readonly sessionId?: string;
29
+ readonly agent?: {
30
+ readonly session?: {
31
+ readonly header?: {
32
+ readonly cwd?: string;
33
+ };
34
+ };
35
+ };
36
+ }
37
+ /** The context a tool body receives: cancellation, workspace, chat identity and approval fields. */
38
+ export interface ExecContext {
39
+ readonly signal?: AbortSignal;
40
+ readonly cwd?: string;
41
+ /** The calling chat; handles are scoped to it. */
42
+ readonly chat?: object;
43
+ /** Registry call id, forwarded to the approval request. */
44
+ readonly callId?: unknown;
45
+ /** Owning session id, used by session-owned jobs. */
46
+ readonly sessionId?: string;
47
+ /** The calling agent, forwarded to the approval request. */
48
+ readonly agent?: unknown;
49
+ }
50
+ /** A registered tool. */
51
+ export interface ToolDefinition {
52
+ readonly name: string;
53
+ readonly description: string;
54
+ readonly parameters: Parameters;
55
+ readonly output: ToolOutput;
56
+ execute(args: Readonly<Record<string, unknown>>, exec: ToolExec): Promise<unknown>;
57
+ }
58
+ /** The registry service. */
59
+ export interface ToolRegistry {
60
+ register(definition: ToolDefinition): unknown;
61
+ }
62
+ /** A path resolved by the filesystem provider into its backend identity. */
63
+ export interface FsTarget {
64
+ /** Opaque key for stale guards and target lookup. */
65
+ readonly targetKey: unknown;
66
+ /** Path for model- and UI-facing output. */
67
+ readonly displayPath: string;
68
+ }
69
+ /** The filesystem service operations this package uses. */
70
+ export interface FsService {
71
+ resolve(path: string, options?: {
72
+ readonly signal?: AbortSignal;
73
+ readonly cwd?: string;
74
+ }): Promise<FsTarget>;
75
+ stat(target: FsTarget, signal?: AbortSignal): Promise<{
76
+ readonly type: string;
77
+ } | undefined>;
78
+ readBytes(target: FsTarget, signal: AbortSignal | undefined, maxBytes?: number): Promise<Uint8Array>;
79
+ writeText?(target: FsTarget, text: string, options?: {
80
+ readonly kind?: 'createIfAbsent';
81
+ }, signal?: AbortSignal): Promise<void>;
82
+ }
83
+ /** The services a composition must provide before this plugin loads. */
84
+ export interface PluginContext {
85
+ readonly tools: ToolRegistry;
86
+ readonly fs: FsService;
87
+ readonly logger?: {
88
+ warn(message: string): void;
89
+ };
90
+ /** Register a service other entries in the same composition can consume. */
91
+ provide?(name: string, value: unknown): void;
92
+ /** Read a service another entry in the same composition provided. */
93
+ get?(name: string): unknown;
94
+ }
95
+ /**
96
+ * Render one tool answer as pretty JSON.
97
+ *
98
+ * @param _args - Accepted arguments; unused by this renderer.
99
+ * @param value - The validated canonical value.
100
+ * @returns One text block.
101
+ */
102
+ export declare function jsonText(_args: unknown, value: unknown): readonly TextBlock[];
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The tool and service surfaces this package registers against.
3
+ *
4
+ * Declared locally, and structurally, so the package compiles without the host
5
+ * packages at build time while still matching what the Cordis registry and the
6
+ * filesystem provider expose at runtime.
7
+ *
8
+ * @module dsh-engineering-l5x/core/tool
9
+ */
10
+ /**
11
+ * Render one tool answer as pretty JSON.
12
+ *
13
+ * @param _args - Accepted arguments; unused by this renderer.
14
+ * @param value - The validated canonical value.
15
+ * @returns One text block.
16
+ */
17
+ export function jsonText(_args, value) {
18
+ return [{ type: 'text', text: JSON.stringify(value, undefined, 2) }];
19
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Drafts: one accumulated edit list per project per chat.
3
+ *
4
+ * The model adds one operation at a time, sees the count grow, then validates
5
+ * and applies. Nothing here writes a file; preparation and application belong
6
+ * to the tools, so an abandoned draft leaves no residue.
7
+ *
8
+ * @module dsh-engineering-l5x/drafts
9
+ */
10
+ import type { ExecContext } from './core/tool.ts';
11
+ /** One version-1 change-set operation, as the engine accepts it. */
12
+ export interface DraftOperation {
13
+ readonly operation: string;
14
+ readonly [field: string]: unknown;
15
+ }
16
+ /** One accumulated draft. */
17
+ export interface Draft {
18
+ /** Stable handle returned to the caller. */
19
+ readonly id: string;
20
+ /** Project handle this draft applies to. */
21
+ readonly projectId: string;
22
+ /** Operations in the order they were added. */
23
+ readonly operations: readonly DraftOperation[];
24
+ /** ISO timestamp of the first operation. */
25
+ readonly createdAt: string;
26
+ }
27
+ /** Drafts, scoped to the chat that created them. */
28
+ export declare class DraftStore {
29
+ private readonly chats;
30
+ private readonly detached;
31
+ /**
32
+ * The draft for one project in this chat, created on first use.
33
+ *
34
+ * @param exec - Calling context; its chat selects the draft set.
35
+ * @param projectId - Project handle the draft edits.
36
+ * @returns The accumulated draft.
37
+ */
38
+ forProject(exec: ExecContext | undefined, projectId: string): Draft;
39
+ /**
40
+ * Append one operation to a project's draft.
41
+ *
42
+ * @param exec - Calling context.
43
+ * @param projectId - Project handle the draft edits.
44
+ * @param operation - Operation to append.
45
+ * @param maxOperations - Inclusive operation cap for one draft.
46
+ * @returns The updated draft.
47
+ */
48
+ append(exec: ExecContext | undefined, projectId: string, operation: DraftOperation, maxOperations: number): Draft;
49
+ /**
50
+ * Resolve one draft by handle.
51
+ *
52
+ * @param exec - Calling context.
53
+ * @param id - Draft handle; empty selects the chat's only draft.
54
+ * @returns The draft.
55
+ */
56
+ get(exec: ExecContext | undefined, id: string | undefined): Draft;
57
+ /**
58
+ * Remove one draft after it is applied.
59
+ *
60
+ * @param exec - Calling context.
61
+ * @param id - Draft handle.
62
+ */
63
+ remove(exec: ExecContext | undefined, id: string): void;
64
+ /** The draft map for one calling chat. */
65
+ private chatsOf;
66
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Drafts: one accumulated edit list per project per chat.
3
+ *
4
+ * The model adds one operation at a time, sees the count grow, then validates
5
+ * and applies. Nothing here writes a file; preparation and application belong
6
+ * to the tools, so an abandoned draft leaves no residue.
7
+ *
8
+ * @module dsh-engineering-l5x/drafts
9
+ */
10
+ import { randomUUID } from 'node:crypto';
11
+ import { L5xError } from "./core/errors.js";
12
+ /** Drafts, scoped to the chat that created them. */
13
+ export class DraftStore {
14
+ chats = new WeakMap();
15
+ detached = new Map();
16
+ /**
17
+ * The draft for one project in this chat, created on first use.
18
+ *
19
+ * @param exec - Calling context; its chat selects the draft set.
20
+ * @param projectId - Project handle the draft edits.
21
+ * @returns The accumulated draft.
22
+ */
23
+ forProject(exec, projectId) {
24
+ const drafts = this.chatsOf(exec);
25
+ for (const draft of drafts.values()) {
26
+ if (draft.projectId === projectId)
27
+ return draft;
28
+ }
29
+ const created = { id: randomUUID(), projectId, operations: [], createdAt: new Date().toISOString() };
30
+ drafts.set(created.id, created);
31
+ return created;
32
+ }
33
+ /**
34
+ * Append one operation to a project's draft.
35
+ *
36
+ * @param exec - Calling context.
37
+ * @param projectId - Project handle the draft edits.
38
+ * @param operation - Operation to append.
39
+ * @param maxOperations - Inclusive operation cap for one draft.
40
+ * @returns The updated draft.
41
+ */
42
+ append(exec, projectId, operation, maxOperations) {
43
+ const current = this.forProject(exec, projectId);
44
+ if (current.operations.length >= maxOperations) {
45
+ throw new L5xError('DRAFT_TOO_LARGE', `This draft already holds ${current.operations.length} operations, the configured maximum.`, 'Validate and apply this draft, then start another for the remaining edits.');
46
+ }
47
+ const updated = { ...current, operations: [...current.operations, operation] };
48
+ this.chatsOf(exec).set(updated.id, updated);
49
+ return updated;
50
+ }
51
+ /**
52
+ * Resolve one draft by handle.
53
+ *
54
+ * @param exec - Calling context.
55
+ * @param id - Draft handle; empty selects the chat's only draft.
56
+ * @returns The draft.
57
+ */
58
+ get(exec, id) {
59
+ const drafts = this.chatsOf(exec);
60
+ const ids = [...drafts.keys()];
61
+ if (id === undefined || id === '') {
62
+ const only = ids[0];
63
+ if (only === undefined) {
64
+ throw new L5xError('NO_DRAFT', 'No draft exists in this chat.', 'Add an operation with a draft_* tool first.');
65
+ }
66
+ if (ids.length > 1) {
67
+ throw new L5xError('AMBIGUOUS_DRAFT', `${ids.length} drafts exist in this chat, so draft_id is required.`, `Pass one of: ${ids.join(', ')}.`, ids);
68
+ }
69
+ return drafts.get(only);
70
+ }
71
+ const draft = drafts.get(id);
72
+ if (draft === undefined) {
73
+ throw new L5xError('UNKNOWN_DRAFT', `No draft exists with draft_id ${JSON.stringify(id)} in this chat.`, ids.length === 0 ? 'Add an operation with a draft_* tool first.' : `Pass one of this chat's draft ids: ${ids.join(', ')}.`, ids);
74
+ }
75
+ return draft;
76
+ }
77
+ /**
78
+ * Remove one draft after it is applied.
79
+ *
80
+ * @param exec - Calling context.
81
+ * @param id - Draft handle.
82
+ */
83
+ remove(exec, id) {
84
+ this.chatsOf(exec).delete(id);
85
+ }
86
+ /** The draft map for one calling chat. */
87
+ chatsOf(exec) {
88
+ const chat = exec?.chat;
89
+ if (chat === undefined)
90
+ return this.detached;
91
+ const existing = this.chats.get(chat);
92
+ if (existing !== undefined)
93
+ return existing;
94
+ const created = new Map();
95
+ this.chats.set(chat, created);
96
+ return created;
97
+ }
98
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The L5X editing entry: drafts over the read entry's project handles.
3
+ *
4
+ * Kept separate so a profile that only reads, checks and compares does not pay
5
+ * for nine draft schemas. It consumes the `l5xProjects` service the read entry
6
+ * provides, so a project is opened once and both entries see the same handle.
7
+ *
8
+ * @module dsh-engineering-l5x/editing
9
+ */
10
+ import type { PluginContext } from './core/tool.ts';
11
+ /** Plugin name in a composition. */
12
+ export declare const name = "l5x-editing";
13
+ /** The read entry's project service must already be mounted. */
14
+ export declare const inject: readonly ["tools", "l5xProjects"];
15
+ /** Configuration for the editing entry. */
16
+ export interface Config {
17
+ /** Largest export, in bytes, one preparation may read. Defaults to 64 MiB. */
18
+ maxFileBytes?: number;
19
+ /** Most operations one draft may accumulate. Defaults to 200. */
20
+ maxDraftOperations?: number;
21
+ }
22
+ /**
23
+ * Register the draft tools.
24
+ *
25
+ * @param ctx - Plugin context carrying the read entry's project service.
26
+ * @param config - Deployment limits.
27
+ */
28
+ export declare function apply(ctx: PluginContext, config?: Config): void;
29
+ export { DraftStore } from './drafts.ts';
30
+ export { draftTools } from './tools/drafts.ts';
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The L5X editing entry: drafts over the read entry's project handles.
3
+ *
4
+ * Kept separate so a profile that only reads, checks and compares does not pay
5
+ * for nine draft schemas. It consumes the `l5xProjects` service the read entry
6
+ * provides, so a project is opened once and both entries see the same handle.
7
+ *
8
+ * @module dsh-engineering-l5x/editing
9
+ */
10
+ import { DraftStore } from "./drafts.js";
11
+ import { draftTools } from "./tools/drafts.js";
12
+ import { PROJECTS_SERVICE } from "./index.js";
13
+ /** Plugin name in a composition. */
14
+ export const name = 'l5x-editing';
15
+ /** The read entry's project service must already be mounted. */
16
+ export const inject = ['tools', PROJECTS_SERVICE];
17
+ /**
18
+ * Register the draft tools.
19
+ *
20
+ * @param ctx - Plugin context carrying the read entry's project service.
21
+ * @param config - Deployment limits.
22
+ */
23
+ export function apply(ctx, config = {}) {
24
+ const session = ctx.get?.(PROJECTS_SERVICE);
25
+ if (session === undefined) {
26
+ throw new Error('l5x-editing: the l5xProjects service is unavailable; mount the L5X read entry before this one');
27
+ }
28
+ const maxFileBytes = config.maxFileBytes ?? 64 * 1024 * 1024;
29
+ if (!Number.isInteger(maxFileBytes) || maxFileBytes <= 0) {
30
+ throw new Error('l5x-editing: maxFileBytes must be a positive integer');
31
+ }
32
+ const maxDraftOperations = config.maxDraftOperations ?? 200;
33
+ if (!Number.isInteger(maxDraftOperations) || maxDraftOperations <= 0) {
34
+ throw new Error('l5x-editing: maxDraftOperations must be a positive integer');
35
+ }
36
+ const drafts = new DraftStore();
37
+ for (const definition of draftTools(session, drafts, maxDraftOperations, maxFileBytes)) {
38
+ ctx.tools.register(definition);
39
+ }
40
+ }
41
+ export { DraftStore } from "./drafts.js";
42
+ export { draftTools } from "./tools/drafts.js";
@@ -0,0 +1,13 @@
1
+ import { type ProjectSnapshot } from '../session.ts';
2
+ export declare function controllerContent(project: ProjectSnapshot, ignoreModifiedDate?: boolean, ignoreEncodedPayload?: boolean): string;
3
+ /** Validate one edited routine component for review in the user's IDE.
4
+ * @param baseline - original routine export from Studio.
5
+ * @param draft - unchanged-on-disk proposal derived from that export.
6
+ * @param program - intended containing program.
7
+ * @param routine - intended existing routine.
8
+ * @returns import target and source identity; no Studio import or controller operation occurs.
9
+ */
10
+ export declare function studioRoutineImportPlan(baseline: ProjectSnapshot, draft: ProjectSnapshot, program: string, routine: string): object;
11
+ export declare function routineEditPlan(baseline: ProjectSnapshot, draft: ProjectSnapshot, program: string, routine: string): {
12
+ xpath: string;
13
+ };
@@ -0,0 +1,115 @@
1
+ import { documentRoot, elementChildren, firstChild, parseXml } from "../model/xml.js";
2
+ import { EngineeringError } from "../session.js";
3
+ import { reviewDownloadSource } from "./download-review.js";
4
+ function controller(project) {
5
+ const node = firstChild(documentRoot(parseXml(project.xml)), 'Controller');
6
+ if (project.exportKind !== 'controller' || node === undefined)
7
+ throw new EngineeringError('CONTROLLER_EXPORT_REQUIRED', 'A full controller export is required');
8
+ return node;
9
+ }
10
+ function named(parent, kind, name) {
11
+ const matches = parent === undefined ? [] : elementChildren(parent, kind).filter(node => node.attrs.Name === name);
12
+ if (matches.length !== 1)
13
+ throw new EngineeringError('AMBIGUOUS_TARGET', `Expected one ${kind} named ${name}`);
14
+ return matches[0];
15
+ }
16
+ /** Compare every controller XML structure, including fields not modeled by the index. */
17
+ function canonical(node, omit, ignoreModifiedDate = false, ignoreEncodedPayload = false) {
18
+ if (node === omit)
19
+ return '#target-routine';
20
+ if (!('children' in node))
21
+ return node.name === '#comment' || node.text.trim() === '' ? null : ['text', node.text];
22
+ const attrs = Object.entries(node.attrs).filter(([key]) => !(ignoreModifiedDate && node.name === 'Controller' && key === 'LastModifiedDate'));
23
+ const children = node.children.filter(child => !(ignoreEncodedPayload && node.name === 'EncodedData' && !('children' in child)));
24
+ return [node.name, attrs.sort(([a], [b]) => a.localeCompare(b)), children.map(child => canonical(child, omit, ignoreModifiedDate, ignoreEncodedPayload)).filter(value => value !== null)];
25
+ }
26
+ export function controllerContent(project, ignoreModifiedDate = false, ignoreEncodedPayload = false) {
27
+ return JSON.stringify(canonical(controller(project), undefined, ignoreModifiedDate, ignoreEncodedPayload));
28
+ }
29
+ /** Validate one edited routine component for review in the user's IDE.
30
+ * @param baseline - original routine export from Studio.
31
+ * @param draft - unchanged-on-disk proposal derived from that export.
32
+ * @param program - intended containing program.
33
+ * @param routine - intended existing routine.
34
+ * @returns import target and source identity; no Studio import or controller operation occurs.
35
+ */
36
+ export function studioRoutineImportPlan(baseline, draft, program, routine) {
37
+ for (const value of [program, routine])
38
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(value))
39
+ throw new EngineeringError('INVALID_TARGET', 'Program and routine must be Logix identifiers');
40
+ const before = documentRoot(parseXml(baseline.xml));
41
+ const after = documentRoot(parseXml(draft.xml));
42
+ if (before.attrs.TargetType !== 'Routine' || before.attrs.TargetName !== routine
43
+ || after.attrs.TargetType !== 'Routine' || after.attrs.TargetName !== routine) {
44
+ throw new EngineeringError('ROUTINE_COMPONENT_REQUIRED', 'Export the selected routine from the visible IDE first; full controller exports cannot be used as routine import files');
45
+ }
46
+ const beforeController = firstChild(before, 'Controller');
47
+ const afterController = firstChild(after, 'Controller');
48
+ if (beforeController === undefined || afterController === undefined)
49
+ throw new EngineeringError('INVALID_COMPONENT', 'Routine export requires controller context');
50
+ const oldProgram = named(firstChild(beforeController, 'Programs'), 'Program', program);
51
+ const newProgram = named(firstChild(afterController, 'Programs'), 'Program', program);
52
+ const oldRoutine = named(firstChild(oldProgram, 'Routines'), 'Routine', routine);
53
+ const newRoutine = named(firstChild(newProgram, 'Routines'), 'Routine', routine);
54
+ const protectedNode = (node) => 'children' in node && (node.name === 'EncodedData'
55
+ || /safety/iu.test(node.attrs.Class ?? '')
56
+ || Object.entries(node.attrs).some(([key, value]) => /Encrypted|SourceProtected/iu.test(key) && value !== 'false')
57
+ || node.children.some(protectedNode));
58
+ if (protectedNode(before) || protectedNode(after) || /safety/iu.test(program))
59
+ throw new EngineeringError('PROTECTED_TARGET', 'Safety or protected components require the engineer workflow');
60
+ if (!['RLL', 'ST'].includes(oldRoutine.attrs.Type ?? '') || oldRoutine.attrs.Type !== newRoutine.attrs.Type
61
+ || oldRoutine.attrs.Use !== 'Target' || newRoutine.attrs.Use !== 'Target')
62
+ throw new EngineeringError('UNSUPPORTED_TARGET', 'Use an existing unprotected RLL or ST routine exported as Target');
63
+ if (JSON.stringify(canonical(before, oldRoutine)) !== JSON.stringify(canonical(after, newRoutine)))
64
+ throw new EngineeringError('UNRELATED_CHANGE', 'Only the selected routine may change; preserve dependency and export context');
65
+ if (JSON.stringify(canonical(oldRoutine)) === JSON.stringify(canonical(newRoutine)))
66
+ throw new EngineeringError('NO_CHANGE', 'Routine is unchanged');
67
+ return {
68
+ operation: 'studio_ide_routine_import_plan', program, routine, routine_type: newRoutine.attrs.Type,
69
+ controller_name: beforeController?.attrs.Name ?? null, source_path: baseline.path, source_sha256: baseline.sha256,
70
+ component_path: draft.path, component_sha256: draft.sha256, software_revision: baseline.softwareRevision,
71
+ validation: { xml: 'passed', source_and_draft_hashes: 'checked_by_calling_tool', changes_confined_to_routine: true, studio_import: 'not_run', studio_verify: 'not_run', machine_behavior: 'not_verified' },
72
+ controller_contacted: false, ide_modified: false,
73
+ steps: ['Observe the same Studio IDE and confirm the saved project, program, routine and offline state.',
74
+ 'Back up the saved project before replacement. Revalidate the proposal immediately before import.',
75
+ 'Navigate to the intended program routine import command using fresh observed UI controls; choose component_path.',
76
+ 'Review the collision preview: replace only this routine, retain existing dependencies, and stop for unexpected changes.',
77
+ 'After import, run Verify in Studio and inspect its diagnostics; unresolved errors block handoff.',
78
+ 'Present the result for engineering review. The engineer performs download; do not accept online edits or change controller mode through UI automation.'],
79
+ };
80
+ }
81
+ export function routineEditPlan(baseline, draft, program, routine) {
82
+ for (const value of [program, routine])
83
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(value))
84
+ throw new EngineeringError('INVALID_TARGET', 'Program and routine must be Logix identifiers');
85
+ const before = controller(baseline);
86
+ const after = controller(draft);
87
+ const oldProgram = named(firstChild(before, 'Programs'), 'Program', program);
88
+ const newProgram = named(firstChild(after, 'Programs'), 'Program', program);
89
+ const tasks = firstChild(before, 'Tasks');
90
+ const safetyScheduled = tasks !== undefined && elementChildren(tasks, 'Task').some(task => /safety/iu.test(task.attrs.Class ?? '')
91
+ && firstChild(task, 'ScheduledPrograms')?.children.some(child => 'attrs' in child && child.attrs.Name === program));
92
+ if (/safety/iu.test(oldProgram.attrs.Class ?? '') || /safety/iu.test(program)
93
+ || safetyScheduled
94
+ || baseline.document.tasks.some(task => /safety/iu.test(task.name) && task.programs.includes(program)))
95
+ throw new EngineeringError('PROTECTED_TARGET', 'Safety programs cannot be imported through the ACD edit tool');
96
+ const oldRoutine = named(firstChild(oldProgram, 'Routines'), 'Routine', routine);
97
+ const newRoutine = named(firstChild(newProgram, 'Routines'), 'Routine', routine);
98
+ if (/safety/iu.test(oldRoutine.attrs.Class ?? '') || /safety/iu.test(newRoutine.attrs.Class ?? ''))
99
+ throw new EngineeringError('PROTECTED_TARGET', 'Safety routines cannot be edited');
100
+ if (!['RLL', 'ST'].includes(oldRoutine.attrs.Type ?? '') || oldRoutine.attrs.Type !== newRoutine.attrs.Type)
101
+ throw new EngineeringError('UNSUPPORTED_TARGET', 'Only existing RLL and ST routines of the same type are supported');
102
+ const protectedRoutine = (node) => node.name === 'EncodedData'
103
+ || Object.entries(node.attrs).some(([key, value]) => /Encrypted|SourceProtected/iu.test(key) && value !== 'false')
104
+ || node.children.some(child => 'children' in child && protectedRoutine(child));
105
+ if (protectedRoutine(oldRoutine) || protectedRoutine(newRoutine))
106
+ throw new EngineeringError('PROTECTED_TARGET', 'Encoded or protected routines cannot be edited');
107
+ const review = reviewDownloadSource(baseline, draft);
108
+ if (!review.eligible)
109
+ throw new EngineeringError('EDIT_BLOCKED', review.blockers.join('; '));
110
+ if (JSON.stringify(canonical(before, oldRoutine)) !== JSON.stringify(canonical(after, newRoutine)))
111
+ throw new EngineeringError('UNRELATED_CHANGE', 'The draft must change only the selected routine; tags, modules, other logic and controller metadata must remain intact');
112
+ if (JSON.stringify(canonical(oldRoutine)) === JSON.stringify(canonical(newRoutine)))
113
+ throw new EngineeringError('NO_CHANGE', 'Selected routine is unchanged');
114
+ return { xpath: `Controller/Programs/Program[@Name='${program}']/Routines/Routine[@Name='${routine}']` };
115
+ }