@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,160 @@
1
+ /**
2
+ * Shared result, error and parameter conventions for the engineering bundle.
3
+ *
4
+ * Every tool answers with `summary`, `data`, `next` and `coverage`, and every
5
+ * parameter is described at the point of declaration. Failures carry a stable
6
+ * code, a message in the reader's terms, and the fix that unblocks the caller.
7
+ *
8
+ * @module dsh-engineering-ftview/core
9
+ */
10
+ /** A failure the caller can usually repair. */
11
+ export class EngineeringError extends Error {
12
+ code;
13
+ fix;
14
+ candidates;
15
+ /**
16
+ * @param code - Stable identifier for tests and logs.
17
+ * @param message - What failed, in the reader's terms.
18
+ * @param fix - The next call or check.
19
+ * @param candidates - Known names the caller probably meant.
20
+ */
21
+ constructor(code, message, fix, candidates = []) {
22
+ super(message);
23
+ this.name = 'EngineeringError';
24
+ this.code = code;
25
+ this.fix = fix;
26
+ this.candidates = candidates;
27
+ }
28
+ }
29
+ /**
30
+ * Convert any thrown value into the error envelope.
31
+ *
32
+ * @param error - The thrown value.
33
+ * @returns The envelope the tool registry reports.
34
+ */
35
+ export function errorEnvelope(error) {
36
+ if (error instanceof EngineeringError) {
37
+ return { error: { code: error.code, message: error.message, fix: error.fix, candidates: error.candidates } };
38
+ }
39
+ return {
40
+ error: {
41
+ code: 'INTERNAL',
42
+ message: error instanceof Error ? error.message : String(error),
43
+ fix: 'Retry the call; if it fails the same way, report the message and the arguments used.',
44
+ candidates: [],
45
+ },
46
+ };
47
+ }
48
+ /** Build one success document. */
49
+ export function success(summary, data, next, coverage) {
50
+ return { summary, data, next, coverage };
51
+ }
52
+ /** A described string parameter. */
53
+ export function string(description, extra = {}) {
54
+ return { type: 'string', description, ...extra };
55
+ }
56
+ /** A described integer parameter with an optional range. */
57
+ export function integer(description, extra = {}) {
58
+ return { type: 'integer', description, ...extra };
59
+ }
60
+ /** A described enum parameter. */
61
+ export function choice(description, values, extra = {}) {
62
+ return { type: 'string', description, enum: values, ...extra };
63
+ }
64
+ /** One object element of an array parameter. */
65
+ export function objectItem(description, properties, required = []) {
66
+ return { type: 'object', description, properties, required, additionalProperties: false };
67
+ }
68
+ /** An array of one parameter type. */
69
+ export function list(description, items, extra = {}) {
70
+ return { type: 'array', description, items, ...extra };
71
+ }
72
+ /**
73
+ * Build a parameter object.
74
+ *
75
+ * @param properties - Named parameters, each already described.
76
+ * @param required - Names the caller must supply.
77
+ * @returns The schema a tool definition declares.
78
+ */
79
+ export function object(properties, required = []) {
80
+ return { type: 'object', properties, required, additionalProperties: false };
81
+ }
82
+ /**
83
+ * Present the harness filesystem with string paths.
84
+ *
85
+ * The model, edits and discovery compose paths (`${root}/${file}`); the harness
86
+ * service takes opaque targets. This adapter resolves each path before calling
87
+ * the service and returns display strings, so path composition stays readable.
88
+ * A provider that already returns strings, such as a test double, passes through.
89
+ *
90
+ * @param service - The harness filesystem service.
91
+ * @returns The same operations addressed by path strings.
92
+ */
93
+ export function stringFs(service) {
94
+ const targetOf = async (path, signal) => await service.resolve(path, signal === undefined ? {} : { signal });
95
+ const fs = {
96
+ async resolve(path, options) {
97
+ const value = await service.resolve(path, options);
98
+ return typeof value === 'string' ? value : value.displayPath;
99
+ },
100
+ async stat(path, signal) {
101
+ return await service.stat(await targetOf(path, signal), signal);
102
+ },
103
+ async readBytes(path, signal, maxBytes) {
104
+ return await service.readBytes(await targetOf(path, signal), signal, maxBytes);
105
+ },
106
+ };
107
+ if (service.listDir !== undefined) {
108
+ fs.listDir = async (path, signal) => await service.listDir(await targetOf(path, signal), signal);
109
+ }
110
+ if (service.writeText !== undefined) {
111
+ fs.writeText = async (path, text, options, signal) => {
112
+ await service.writeText(await targetOf(path, signal), text, options, signal);
113
+ };
114
+ }
115
+ return fs;
116
+ }
117
+ /** Render one tool answer as JSON text for the transcript. */
118
+ function jsonText(_args, value) {
119
+ return [{ type: 'text', text: JSON.stringify(value, null, 2) }];
120
+ }
121
+ /**
122
+ * Wrap one tool body in the shared envelope and error handling.
123
+ *
124
+ * @param spec - Tool identity and body.
125
+ * @returns The definition the registry accepts.
126
+ */
127
+ export function tool(spec) {
128
+ return {
129
+ name: spec.name,
130
+ description: spec.description,
131
+ parameters: spec.parameters,
132
+ output: { schema: { type: 'object', additionalProperties: true }, render: jsonText },
133
+ async execute(args, exec) {
134
+ try {
135
+ return await spec.run(args, exec);
136
+ }
137
+ catch (error) {
138
+ return errorEnvelope(error);
139
+ }
140
+ },
141
+ };
142
+ }
143
+ /** A required string argument. */
144
+ export function requiredString(args, name) {
145
+ const value = args[name];
146
+ if (typeof value !== 'string' || value === '') {
147
+ throw new EngineeringError('INVALID_ARGUMENT', `${name} is required and must be a non-empty string.`, `Pass ${name}, for example "${name}".`);
148
+ }
149
+ return value;
150
+ }
151
+ /** An optional string argument. */
152
+ export function optionalString(args, name) {
153
+ const value = args[name];
154
+ return typeof value === 'string' && value !== '' ? value : undefined;
155
+ }
156
+ /** An optional integer argument. */
157
+ export function optionalInteger(args, name) {
158
+ const value = args[name];
159
+ return typeof value === 'number' && Number.isInteger(value) ? value : undefined;
160
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Drafts for staged ME edits.
3
+ *
4
+ * One draft per opened export per chat. Operations accumulate until validate or
5
+ * apply; nothing here reads or writes export files.
6
+ *
7
+ * @module dsh-engineering-ftview/edit/drafts
8
+ */
9
+ import { type ExecContext } from '../core.ts';
10
+ import type { EditOperation } from './operations.ts';
11
+ /** One accumulated edit list. */
12
+ export interface EditDraft {
13
+ readonly id: string;
14
+ readonly viewId: string;
15
+ readonly operations: readonly EditOperation[];
16
+ readonly createdAt: string;
17
+ }
18
+ /** Drafts, scoped to the chat that created them. */
19
+ export declare class EditDrafts {
20
+ private readonly chats;
21
+ private readonly detached;
22
+ /**
23
+ * Append one operation to the draft for a view, creating it on first use.
24
+ *
25
+ * @param exec - Calling context.
26
+ * @param viewId - View the draft edits.
27
+ * @param operation - Operation to append.
28
+ * @param maxOperations - Inclusive cap for one draft.
29
+ * @returns The updated draft.
30
+ */
31
+ append(exec: ExecContext | undefined, viewId: string, operation: EditOperation, maxOperations: number): EditDraft;
32
+ /**
33
+ * Resolve one draft by handle.
34
+ *
35
+ * @param exec - Calling context.
36
+ * @param id - Draft handle; empty selects the chat's only draft.
37
+ * @returns The draft.
38
+ */
39
+ get(exec: ExecContext | undefined, id: string | undefined): EditDraft;
40
+ /**
41
+ * Drop one draft after it is applied.
42
+ *
43
+ * @param exec - Calling context.
44
+ * @param id - Draft handle.
45
+ */
46
+ remove(exec: ExecContext | undefined, id: string): void;
47
+ /** The draft map for one calling chat. */
48
+ private draftsOf;
49
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Drafts for staged ME edits.
3
+ *
4
+ * One draft per opened export per chat. Operations accumulate until validate or
5
+ * apply; nothing here reads or writes export files.
6
+ *
7
+ * @module dsh-engineering-ftview/edit/drafts
8
+ */
9
+ import { randomUUID } from 'node:crypto';
10
+ import { EngineeringError } from "../core.js";
11
+ /** Drafts, scoped to the chat that created them. */
12
+ export class EditDrafts {
13
+ chats = new WeakMap();
14
+ detached = new Map();
15
+ /**
16
+ * Append one operation to the draft for a view, creating it on first use.
17
+ *
18
+ * @param exec - Calling context.
19
+ * @param viewId - View the draft edits.
20
+ * @param operation - Operation to append.
21
+ * @param maxOperations - Inclusive cap for one draft.
22
+ * @returns The updated draft.
23
+ */
24
+ append(exec, viewId, operation, maxOperations) {
25
+ const drafts = this.draftsOf(exec);
26
+ const current = [...drafts.values()].find(draft => draft.viewId === viewId);
27
+ if (current === undefined) {
28
+ const created = { id: randomUUID(), viewId, operations: [operation], createdAt: new Date().toISOString() };
29
+ drafts.set(created.id, created);
30
+ return created;
31
+ }
32
+ if (current.operations.length >= maxOperations) {
33
+ throw new EngineeringError('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.');
34
+ }
35
+ const updated = { ...current, operations: [...current.operations, operation] };
36
+ drafts.set(updated.id, updated);
37
+ return updated;
38
+ }
39
+ /**
40
+ * Resolve one draft by handle.
41
+ *
42
+ * @param exec - Calling context.
43
+ * @param id - Draft handle; empty selects the chat's only draft.
44
+ * @returns The draft.
45
+ */
46
+ get(exec, id) {
47
+ const drafts = this.draftsOf(exec);
48
+ const ids = [...drafts.keys()];
49
+ if (id === undefined || id === '') {
50
+ const only = ids[0];
51
+ if (only === undefined)
52
+ throw new EngineeringError('NO_DRAFT', 'No edit draft exists in this chat.', 'Add an operation with an ftview_draft_* tool first.');
53
+ if (ids.length > 1)
54
+ throw new EngineeringError('AMBIGUOUS_DRAFT', `${ids.length} drafts exist in this chat, so draft_id is required.`, `Pass one of: ${ids.join(', ')}.`, ids);
55
+ return drafts.get(only);
56
+ }
57
+ const draft = drafts.get(id);
58
+ if (draft === undefined) {
59
+ throw new EngineeringError('UNKNOWN_DRAFT', `No draft exists with draft_id ${JSON.stringify(id)} in this chat.`, ids.length === 0 ? 'Add an operation with an ftview_draft_* tool first.' : `Pass one of this chat's draft ids: ${ids.join(', ')}.`, ids);
60
+ }
61
+ return draft;
62
+ }
63
+ /**
64
+ * Drop one draft after it is applied.
65
+ *
66
+ * @param exec - Calling context.
67
+ * @param id - Draft handle.
68
+ */
69
+ remove(exec, id) {
70
+ this.draftsOf(exec).delete(id);
71
+ }
72
+ /** The draft map for one calling chat. */
73
+ draftsOf(exec) {
74
+ const chat = exec?.chat;
75
+ if (chat === undefined)
76
+ return this.detached;
77
+ const existing = this.chats.get(chat);
78
+ if (existing !== undefined)
79
+ return existing;
80
+ const created = new Map();
81
+ this.chats.set(chat, created);
82
+ return created;
83
+ }
84
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Text-level edits over an ME export.
3
+ *
4
+ * Every transform changes only the targeted bytes: an attribute on one tag, a
5
+ * display's name attribute, or one tag CSV row. Nothing is re-serialized, so
6
+ * unknown elements, comments and formatting survive.
7
+ *
8
+ * @module dsh-engineering-ftview/edit/operations
9
+ */
10
+ /** Set one attribute on a display or one of its named objects. */
11
+ export interface DisplaySetOperation {
12
+ readonly kind: 'display_set';
13
+ readonly file: string;
14
+ readonly object?: string;
15
+ readonly attribute: string;
16
+ readonly value: string;
17
+ }
18
+ /** Add a display by copying an existing display file under a new name. */
19
+ export interface DisplayAddOperation {
20
+ readonly kind: 'display_add';
21
+ readonly copyFrom: string;
22
+ readonly name: string;
23
+ }
24
+ /** Insert or update one HMI tag row. */
25
+ export interface TagUpsertOperation {
26
+ readonly kind: 'tag_upsert';
27
+ readonly name: string;
28
+ readonly description?: string;
29
+ readonly type?: string;
30
+ }
31
+ /** One staged edit. */
32
+ export type EditOperation = DisplaySetOperation | DisplayAddOperation | TagUpsertOperation;
33
+ /** Escape one XML attribute value. */
34
+ export declare function escapeAttribute(value: string): string;
35
+ /** Escape one CSV cell when it needs quoting. */
36
+ export declare function encodeCell(value: string): string;
37
+ /**
38
+ * Set one attribute on a display root or object, preserving the rest byte for byte.
39
+ *
40
+ * @param xml - Display XML.
41
+ * @param object - Object name, or undefined for the display root.
42
+ * @param attribute - Attribute to set; a plain XML name.
43
+ * @param value - New value.
44
+ * @returns The modified XML.
45
+ */
46
+ export declare function setAttribute(xml: string, object: string | undefined, attribute: string, value: string): string;
47
+ /**
48
+ * Rename a display root.
49
+ *
50
+ * @param xml - Display XML.
51
+ * @param name - New display name.
52
+ * @returns The modified XML.
53
+ */
54
+ export declare function renameDisplay(xml: string, name: string): string;
55
+ /**
56
+ * Insert or update one tag row in the CSV.
57
+ *
58
+ * @param csv - Whole tag CSV.
59
+ * @param tag - Tag name and the columns to set.
60
+ * @returns The modified CSV.
61
+ */
62
+ export declare function upsertTag(csv: string, tag: TagUpsertOperation): string;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Text-level edits over an ME export.
3
+ *
4
+ * Every transform changes only the targeted bytes: an attribute on one tag, a
5
+ * display's name attribute, or one tag CSV row. Nothing is re-serialized, so
6
+ * unknown elements, comments and formatting survive.
7
+ *
8
+ * @module dsh-engineering-ftview/edit/operations
9
+ */
10
+ import { EngineeringError } from "../core.js";
11
+ import { splitCsv } from "../model.js";
12
+ /** Escape one XML attribute value. */
13
+ export function escapeAttribute(value) {
14
+ return value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;');
15
+ }
16
+ /** Escape one CSV cell when it needs quoting. */
17
+ export function encodeCell(value) {
18
+ return /[";\r\n]/u.test(value) ? `"${value.replaceAll('"', '""')}"` : value;
19
+ }
20
+ /** Read one attribute from an attribute string. */
21
+ function attributeValue(attributes, name) {
22
+ const match = new RegExp(`\\b${name}\\s*=\\s*("([^"]*)"|'([^']*)')`, 'u').exec(attributes);
23
+ return match?.[2] ?? match?.[3];
24
+ }
25
+ /** Find the root tag or the tag whose name attribute equals `object`. */
26
+ function findTag(xml, object) {
27
+ if (object === undefined) {
28
+ const root = /<([A-Za-z_][\w.:-]*)((?:[^<>"']|"[^"]*"|'[^']*')*?)(\/?)>/u.exec(xml);
29
+ if (root === null)
30
+ throw new EngineeringError('INVALID_DISPLAY', 'The display has no root element.', 'Open the display with ftview_get first; only display XML can be edited.');
31
+ return { start: root.index, end: root.index + root[0].length, attributes: root[2] ?? '', selfClosing: root[3] === '/' };
32
+ }
33
+ const pattern = /<([A-Za-z_][\w.:-]*)((?:[^<>"']|"[^"]*"|'[^']*')*?)(\/?)>/gu;
34
+ let match;
35
+ while ((match = pattern.exec(xml)) !== null) {
36
+ if (attributeValue(match[2] ?? '', 'name') === object) {
37
+ return { start: match.index, end: match.index + match[0].length, attributes: match[2] ?? '', selfClosing: match[3] === '/' };
38
+ }
39
+ }
40
+ throw new EngineeringError('COMPONENT_NOT_FOUND', `No object in this display is named ${JSON.stringify(object)}.`, 'Call ftview_get with kind "display" to see the object names, then set the attribute on one of them.');
41
+ }
42
+ /**
43
+ * Set one attribute on a display root or object, preserving the rest byte for byte.
44
+ *
45
+ * @param xml - Display XML.
46
+ * @param object - Object name, or undefined for the display root.
47
+ * @param attribute - Attribute to set; a plain XML name.
48
+ * @param value - New value.
49
+ * @returns The modified XML.
50
+ */
51
+ export function setAttribute(xml, object, attribute, value) {
52
+ if (!/^[A-Za-z_][\w.:-]*$/u.test(attribute)) {
53
+ throw new EngineeringError('INVALID_ATTRIBUTE', `${JSON.stringify(attribute)} is not an XML attribute name.`, 'Pass a plain attribute name such as "visible" or "caption".');
54
+ }
55
+ const span = findTag(xml, object);
56
+ const existing = new RegExp(`\\b${attribute}\\s*=\\s*("[^"]*"|'[^']*')`, 'u');
57
+ let attributes;
58
+ if (existing.test(span.attributes)) {
59
+ attributes = span.attributes.replace(existing, `${attribute}="${escapeAttribute(value)}"`);
60
+ }
61
+ else {
62
+ attributes = `${span.attributes} ${attribute}="${escapeAttribute(value)}"`;
63
+ }
64
+ const tagName = /^<([A-Za-z_][\w.:-]*)/u.exec(xml.slice(span.start, span.end))?.[1] ?? '';
65
+ const rebuilt = `<${tagName}${attributes}${span.selfClosing ? '/' : ''}>`;
66
+ return xml.slice(0, span.start) + rebuilt + xml.slice(span.end);
67
+ }
68
+ /**
69
+ * Rename a display root.
70
+ *
71
+ * @param xml - Display XML.
72
+ * @param name - New display name.
73
+ * @returns The modified XML.
74
+ */
75
+ export function renameDisplay(xml, name) {
76
+ return setAttribute(xml, undefined, 'name', name);
77
+ }
78
+ /**
79
+ * Insert or update one tag row in the CSV.
80
+ *
81
+ * @param csv - Whole tag CSV.
82
+ * @param tag - Tag name and the columns to set.
83
+ * @returns The modified CSV.
84
+ */
85
+ export function upsertTag(csv, tag) {
86
+ const eol = csv.includes('\r\n') ? '\r\n' : '\n';
87
+ const lines = csv.split(/\r?\n/);
88
+ const versionIndex = lines.findIndex(line => line.includes('###002'));
89
+ if (versionIndex < 0) {
90
+ throw new EngineeringError('TAG_CSV_MISSING', 'The tag CSV has no `###002` version line.', 'Pass an export whose tag file is the documented CSV export.');
91
+ }
92
+ const headerIndex = lines.findIndex((line, index) => index > versionIndex && line.trim() !== '');
93
+ if (headerIndex < 0)
94
+ throw new EngineeringError('TAG_CSV_MISSING', 'The tag CSV has no header row.', 'Re-export the HMI tag database as a CSV.');
95
+ const header = splitCsv(lines[headerIndex]).map(cell => cell.trim());
96
+ const nameIndex = header.indexOf('Tag Name');
97
+ if (nameIndex < 0)
98
+ throw new EngineeringError('TAG_CSV_MISSING', 'The tag CSV header has no `Tag Name` column.', 'Re-export the HMI tag database as a CSV.');
99
+ const descriptionIndex = header.indexOf('Tag Description');
100
+ const typeIndex = header.indexOf('Tag Type');
101
+ if (tag.description !== undefined && descriptionIndex < 0)
102
+ throw new EngineeringError('TAG_CSV_UNSUPPORTED', 'This tag CSV has no `Tag Description` column.', 'Set only the type, or re-export with descriptions enabled.');
103
+ if (tag.type !== undefined && typeIndex < 0)
104
+ throw new EngineeringError('TAG_CSV_UNSUPPORTED', 'This tag CSV has no `Tag Type` column.', 'Set only the description, or re-export with tag types.');
105
+ const rowIndex = lines.findIndex((line, index) => index > headerIndex && line.trim() !== '' && (splitCsv(line)[nameIndex] ?? '').trim() === tag.name);
106
+ if (rowIndex >= 0) {
107
+ const cells = splitCsv(lines[rowIndex]);
108
+ if (tag.description !== undefined)
109
+ cells[descriptionIndex] = tag.description;
110
+ if (tag.type !== undefined)
111
+ cells[typeIndex] = tag.type;
112
+ lines[rowIndex] = cells.map(encodeCell).join(';');
113
+ return lines.join(eol);
114
+ }
115
+ const cells = new Array(header.length).fill('');
116
+ cells[nameIndex] = tag.name;
117
+ if (tag.description !== undefined)
118
+ cells[descriptionIndex] = tag.description;
119
+ if (tag.type !== undefined)
120
+ cells[typeIndex] = tag.type;
121
+ if (lines.length > 0 && lines[lines.length - 1] === '')
122
+ lines.splice(lines.length - 1, 0, cells.map(encodeCell).join(';'));
123
+ else
124
+ lines.push(cells.map(encodeCell).join(';'));
125
+ return lines.join(eol);
126
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Prepare staged edits against one opened export.
3
+ *
4
+ * Preparation reads the source files, applies every operation to the text, and
5
+ * re-parses the result. It writes nothing: apply is the only writer, and it
6
+ * writes to a new folder.
7
+ *
8
+ * @module dsh-engineering-ftview/edit/prepare
9
+ */
10
+ import { type Coverage, type FsService } from '../core.ts';
11
+ import { type Finding } from '../checks.ts';
12
+ import { type Application } from '../model.ts';
13
+ import { type EditOperation } from './operations.ts';
14
+ /** One prepared edit set. */
15
+ export interface PreparedEdit {
16
+ /** Files whose text changed, keyed by export-relative path. */
17
+ readonly texts: ReadonlyMap<string, string>;
18
+ /** Files the edit adds. */
19
+ readonly added: readonly string[];
20
+ /** Every file written by apply, including unchanged copies. */
21
+ readonly changed: readonly string[];
22
+ /** Findings after the edit. */
23
+ readonly findings: readonly Finding[];
24
+ /** Stable hash over the source hashes and the operations. */
25
+ readonly hash: string;
26
+ }
27
+ /** Coverage for anything derived from an export. */
28
+ export declare const EDIT_COVERAGE: Coverage;
29
+ /**
30
+ * Apply every operation to text and re-parse the result.
31
+ *
32
+ * @param fs - Filesystem provider.
33
+ * @param app - The opened export.
34
+ * @param operations - Staged operations.
35
+ * @param signal - Cancellation.
36
+ * @returns Prepared texts, findings and a hash.
37
+ */
38
+ export declare function prepareEdit(fs: FsService, app: Application, operations: readonly EditOperation[], signal: AbortSignal | undefined): Promise<PreparedEdit>;
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Prepare staged edits against one opened export.
3
+ *
4
+ * Preparation reads the source files, applies every operation to the text, and
5
+ * re-parses the result. It writes nothing: apply is the only writer, and it
6
+ * writes to a new folder.
7
+ *
8
+ * @module dsh-engineering-ftview/edit/prepare
9
+ */
10
+ import { createHash } from 'node:crypto';
11
+ import { EngineeringError } from "../core.js";
12
+ import { findingsOf } from "../checks.js";
13
+ import { parseDisplay, parseTagCsv } from "../model.js";
14
+ import { renameDisplay, setAttribute, upsertTag } from "./operations.js";
15
+ /** Coverage for anything derived from an export. */
16
+ export const EDIT_COVERAGE = {
17
+ complete: false,
18
+ unverified: [
19
+ 'vendor XSD schema validation',
20
+ 'Studio import and runtime behavior',
21
+ 'native binary files are not copied by apply',
22
+ 'unchanged text exports are copied verbatim',
23
+ ],
24
+ };
25
+ /** Decode UTF-8 file bytes. */
26
+ function decode(bytes) {
27
+ return new TextDecoder('utf-8').decode(bytes);
28
+ }
29
+ /** Read one source file as text. */
30
+ async function readText(fs, root, file, signal) {
31
+ return decode(await fs.readBytes(`${root}/${file}`, signal, 64 * 1024 * 1024));
32
+ }
33
+ /** The tag CSV the edit targets, refusing an ambiguous export. */
34
+ function tagFileOf(app) {
35
+ if (app.tagFiles.length === 0) {
36
+ throw new EngineeringError('TAG_CSV_MISSING', 'This export has no tag CSV, so tags cannot be edited.', 'Export the HMI tag database as a CSV into the same folder, then reopen the export.');
37
+ }
38
+ if (app.tagFiles.length > 1) {
39
+ throw new EngineeringError('AMBIGUOUS_TAG_CSV', `This export has ${app.tagFiles.length} tag CSV files, so the edit target is ambiguous.`, 'Keep one tag CSV per export folder, then reopen and retry.', app.tagFiles);
40
+ }
41
+ return app.tagFiles[0];
42
+ }
43
+ /**
44
+ * Apply every operation to text and re-parse the result.
45
+ *
46
+ * @param fs - Filesystem provider.
47
+ * @param app - The opened export.
48
+ * @param operations - Staged operations.
49
+ * @param signal - Cancellation.
50
+ * @returns Prepared texts, findings and a hash.
51
+ */
52
+ export async function prepareEdit(fs, app, operations, signal) {
53
+ const texts = new Map();
54
+ const added = [];
55
+ const displayByFile = new Map(app.displays.map(display => [display.file, display]));
56
+ const displayNames = new Set(app.displays.map(display => display.name));
57
+ for (const operation of operations) {
58
+ if (operation.kind === 'display_set') {
59
+ if (!displayByFile.has(operation.file)) {
60
+ throw new EngineeringError('COMPONENT_NOT_FOUND', `The draft targets ${operation.file}, which this export no longer contains.`, 'Reopen the export and stage the edit again.');
61
+ }
62
+ const current = texts.get(operation.file) ?? await readText(fs, app.root, operation.file, signal);
63
+ texts.set(operation.file, setAttribute(current, operation.object, operation.attribute, operation.value));
64
+ continue;
65
+ }
66
+ if (operation.kind === 'display_add') {
67
+ const source = app.displays.find(display => display.name === operation.copyFrom);
68
+ if (source === undefined) {
69
+ throw new EngineeringError('COMPONENT_NOT_FOUND', `No display is named ${JSON.stringify(operation.copyFrom)} to copy.`, 'Call ftview_list with kind "display" to see the display names.', [...displayNames].slice(0, 5));
70
+ }
71
+ if (displayNames.has(operation.name) || added.includes(operation.name)) {
72
+ throw new EngineeringError('COMPONENT_EXISTS', `A display named ${JSON.stringify(operation.name)} already exists in this export or draft.`, 'Pass a new display name, for example "LineOverview2".');
73
+ }
74
+ const text = renameDisplay(await readText(fs, app.root, source.file, signal), operation.name);
75
+ const directory = source.file.includes('/') ? source.file.slice(0, source.file.lastIndexOf('/') + 1) : '';
76
+ const target = `${directory}${operation.name}.xml`;
77
+ if (app.files.some(file => file.file.toLowerCase() === target.toLowerCase())) {
78
+ throw new EngineeringError('COMPONENT_EXISTS', `The export already has a file ${target}.`, 'Pass a different new display name.');
79
+ }
80
+ texts.set(target, text);
81
+ added.push(target);
82
+ displayNames.add(operation.name);
83
+ continue;
84
+ }
85
+ const tagFile = tagFileOf(app);
86
+ const current = texts.get(tagFile) ?? await readText(fs, app.root, tagFile, signal);
87
+ texts.set(tagFile, upsertTag(current, operation));
88
+ }
89
+ const displays = [];
90
+ for (const display of app.displays) {
91
+ const text = texts.get(display.file);
92
+ if (text === undefined) {
93
+ displays.push(display);
94
+ continue;
95
+ }
96
+ const parsed = parseDisplay(display.file, text);
97
+ if (parsed.parseError !== undefined) {
98
+ throw new EngineeringError('INVALID_DISPLAY', `The edited ${display.file} no longer parses: ${parsed.parseError}`, 'Undo the last operation and check the display file, then stage it again.');
99
+ }
100
+ displays.push(parsed);
101
+ }
102
+ for (const file of added) {
103
+ const text = texts.get(file);
104
+ const parsed = parseDisplay(file, text);
105
+ if (parsed.parseError !== undefined) {
106
+ throw new EngineeringError('INVALID_DISPLAY', `The added ${file} does not parse: ${parsed.parseError}`, 'Copy a different display or pass another name.');
107
+ }
108
+ displays.push(parsed);
109
+ }
110
+ let tags = app.tags;
111
+ let tagErrors = app.tagErrors;
112
+ for (const [file, text] of texts) {
113
+ if (!app.tagFiles.includes(file))
114
+ continue;
115
+ const parsed = parseTagCsv(text);
116
+ tags = parsed.rows;
117
+ tagErrors = parsed.errors;
118
+ }
119
+ const changedFiles = new Set([...texts.keys()]);
120
+ const diagnostics = app.diagnostics.filter(diagnostic => {
121
+ const file = diagnostic.split(':')[0] ?? '';
122
+ return !changedFiles.has(file);
123
+ });
124
+ const findings = findingsOf({ displays, tags, tagErrors, diagnostics });
125
+ const hashInput = app.files.map(file => `${file.file}:${file.sha256}`).join('\n') + '\n' + JSON.stringify(operations);
126
+ return {
127
+ texts,
128
+ added,
129
+ changed: [...app.files.map(file => file.file), ...added],
130
+ findings,
131
+ hash: createHash('sha256').update(hashInput).digest('hex'),
132
+ };
133
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The FTView editing tools: stage, validate, apply.
3
+ *
4
+ * One operation per call, accumulated per chat and export, validated against a
5
+ * re-parsed copy, then written to a new export folder. The source is never
6
+ * modified.
7
+ *
8
+ * @module dsh-engineering-ftview/edit/tools
9
+ */
10
+ import { type FsService, type ToolDefinition } from '../core.ts';
11
+ import type { ViewSession } from '../session.ts';
12
+ import type { EditDrafts } from './drafts.ts';
13
+ import type { EditOperation } from './operations.ts';
14
+ /**
15
+ * The editing tools.
16
+ *
17
+ * @param session - View handles from the read entry.
18
+ * @param drafts - Draft store for the calling chat.
19
+ * @param fs - Filesystem provider used to read sources and write the result.
20
+ * @param maxOperations - Inclusive operations per draft.
21
+ * @returns Tool definitions in registration order.
22
+ */
23
+ export declare function editTools(session: ViewSession, drafts: EditDrafts, fs: FsService, maxOperations: number): readonly ToolDefinition[];
24
+ /** A helper kept for callers that want the operation count type. */
25
+ export type { EditOperation };