@aryacode/dsh-engineering 0.4.7 → 0.4.21

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 (162) hide show
  1. package/CHANGELOG.md +202 -0
  2. package/README.md +40 -26
  3. package/cordis.patch.yml +7 -6
  4. package/entries/config.js +62 -0
  5. package/entries/ftview.js +5 -4
  6. package/entries/l5x.js +47 -15
  7. package/entries/mwi.js +26 -6
  8. package/entries/optix.js +105 -0
  9. package/lib/client.js +335 -6
  10. package/package.json +11 -10
  11. package/vendor/ftview/SKILL.md +17 -0
  12. package/vendor/l5x/SKILL.md +17 -0
  13. package/vendor/l5x/studio-bridge/NuGet.Config +1 -1
  14. package/vendor/mwi/README.md +41 -18
  15. package/vendor/mwi/SKILL.md +39 -20
  16. package/vendor/mwi/bridge/mw_bridge.ps1 +2768 -2763
  17. package/vendor/mwi/bridge/sdk-interop-importer.cs +30 -30
  18. package/vendor/mwi/bridge/sdk-variable-reader.cs +22 -22
  19. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
  20. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/errors.cpython-312.pyc +0 -0
  21. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/staging.cpython-312.pyc +0 -0
  22. package/vendor/mwi/code/engine/motionworks_iec_mcp/build.py +339 -339
  23. package/vendor/mwi/code/engine/motionworks_iec_mcp/cfb.py +765 -765
  24. package/vendor/mwi/code/engine/motionworks_iec_mcp/declarations.py +399 -399
  25. package/vendor/mwi/code/engine/motionworks_iec_mcp/ide.py +313 -313
  26. package/vendor/mwi/code/engine/motionworks_iec_mcp/manuals.py +520 -520
  27. package/vendor/mwi/code/engine/motionworks_iec_mcp/mwt_bind.py +133 -133
  28. package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/catalog.json +672 -672
  29. package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/patterns.json +86 -86
  30. package/vendor/mwi/code/engine/motionworks_iec_mcp/restore.py +208 -208
  31. package/vendor/mwi/code/engine/motionworks_iec_mcp/staging.py +134 -130
  32. package/vendor/mwi/code/engine/motionworks_iec_mcp/stlint.py +673 -673
  33. package/vendor/mwi/code/engine/motionworks_iec_mcp/tree_writer.py +790 -790
  34. package/vendor/mwi/code/engine/motionworks_iec_mcp/writer.py +642 -642
  35. package/vendor/mwi/lib/core/code-engine.d.ts +13 -0
  36. package/vendor/mwi/lib/core/code-engine.js +18 -1
  37. package/vendor/mwi/lib/edit.d.ts +1 -1
  38. package/vendor/mwi/lib/edit.js +1 -1
  39. package/vendor/mwi/lib/read/service.js +8 -1
  40. package/vendor/mwi/lib/session/backup.d.ts +72 -0
  41. package/vendor/mwi/lib/session/backup.js +257 -0
  42. package/vendor/mwi/lib/session/project.d.ts +4 -1
  43. package/vendor/mwi/lib/session/project.js +13 -2
  44. package/vendor/mwi/lib/session/service.d.ts +13 -6
  45. package/vendor/mwi/lib/session/service.js +127 -36
  46. package/vendor/mwi/lib/session/workspace.d.ts +10 -0
  47. package/vendor/mwi/lib/session/workspace.js +53 -3
  48. package/vendor/mwi/lib/skill.d.ts +2 -2
  49. package/vendor/mwi/lib/skill.js +2 -2
  50. package/vendor/mwi/lib/tools/edit.d.ts +3 -3
  51. package/vendor/mwi/lib/tools/edit.js +14 -15
  52. package/vendor/mwi/lib/tools/read.js +1 -1
  53. package/vendor/mwi/lib/tools/session.js +13 -9
  54. package/vendor/optix/templates/arya-optix-ipc/project/AryaNewHMIProject_IPC.optix +81 -81
  55. package/vendor/optix/templates/arya-optix-ipc/project/AryaNewHMIProject_IPC.optix.design +38 -38
  56. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Alarms/Alarms.yaml +3 -3
  57. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/AryaNewHMIProject_IPC.yaml +67 -67
  58. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/CommDrivers/CommDrivers.yaml +2 -2
  59. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Converters/Converters.yaml +2 -2
  60. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/DataStores/DataStores.yaml +2 -2
  61. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Loggers/Loggers.yaml +2 -2
  62. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/MQTT/MQTT.yaml +2 -2
  63. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Model/Model.yaml +2 -2
  64. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/NetLogic/NetLogic.yaml +26 -26
  65. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/OPC-UA/OPC-UA.yaml +2 -2
  66. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Recipes/Recipes.yaml +2 -2
  67. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Reports/Reports.yaml +2 -2
  68. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Retentivity/Retentivity.yaml +57 -57
  69. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Security/Groups/Groups.yaml +3 -3
  70. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Security/Roles/Roles.yaml +3 -3
  71. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Security/Security.yaml +7 -7
  72. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Security/Users/Users.yaml +3 -3
  73. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/System/System.yaml +2 -2
  74. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Translations/Translations.yaml +16 -16
  75. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/UI/Keyboards/Keyboards.yaml +2 -2
  76. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/UI/Screens/Screens.yaml +2 -2
  77. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/UI/UI.yaml +250 -250
  78. package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/.vscode/launch.json +18 -18
  79. package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/AryaNewHMIProject_IPC.code-workspace +9 -9
  80. package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/AryaNewHMIProject_IPC.csproj +19 -19
  81. package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/AryaNewHMIProject_IPC.references +172 -172
  82. package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/AryaNewHMIProject_IPC.sln +21 -21
  83. package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/Private/TypeConstants.cs +20 -20
  84. package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/Private/UITypeDefinitions.cs +11 -11
  85. package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/StudioMCPBridge.cs +4340 -4340
  86. package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/UserDefinedModule.xml +26 -26
  87. package/vendor/optix/templates/arya-optix-panel/project/AryaNewHMIProject_OptixPanel.optix +97 -97
  88. package/vendor/optix/templates/arya-optix-panel/project/AryaNewHMIProject_OptixPanel.optix.design +38 -38
  89. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Alarms/Alarms.yaml +3 -3
  90. package/vendor/optix/templates/arya-optix-panel/project/Nodes/AryaNewHMIProject_OptixPanel.yaml +67 -67
  91. package/vendor/optix/templates/arya-optix-panel/project/Nodes/CommDrivers/CommDrivers.yaml +2 -2
  92. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Converters/Converters.yaml +2 -2
  93. package/vendor/optix/templates/arya-optix-panel/project/Nodes/DataStores/DataStores.yaml +2 -2
  94. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Loggers/Loggers.yaml +2 -2
  95. package/vendor/optix/templates/arya-optix-panel/project/Nodes/MQTT/MQTT.yaml +2 -2
  96. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Model/Model.yaml +2 -2
  97. package/vendor/optix/templates/arya-optix-panel/project/Nodes/NetLogic/NetLogic.yaml +26 -26
  98. package/vendor/optix/templates/arya-optix-panel/project/Nodes/OPC-UA/OPC-UA.yaml +2 -2
  99. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Recipes/Recipes.yaml +2 -2
  100. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Reports/Reports.yaml +2 -2
  101. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Retentivity/Retentivity.yaml +57 -57
  102. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Security/Groups/Groups.yaml +3 -3
  103. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Security/Roles/Roles.yaml +3 -3
  104. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Security/Security.yaml +7 -7
  105. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Security/Users/Users.yaml +3 -3
  106. package/vendor/optix/templates/arya-optix-panel/project/Nodes/System/System.yaml +444 -444
  107. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Translations/Translations.yaml +81 -81
  108. package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/Keyboards/Keyboards.yaml +2 -2
  109. package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/Screens/Screens.yaml +2 -2
  110. package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/DeviceSettingsWidget/DeviceSettingsWidget.yaml +163 -163
  111. package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/DeviceSettingsWidget/DeviceSettingsWidgetComponents/DeviceSettingsWidgetComponents.yaml +1663 -1663
  112. package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/FactoryTalkRemoteAccessWidget/FTRemoteAccessWidgetComponents/FTRemoteAccessWidgetComponents.yaml +2471 -2471
  113. package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/FactoryTalkRemoteAccessWidget/FactoryTalkRemoteAccessWidget.yaml +218 -218
  114. package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/NetworkSettingsWidget/NetworkSettingsWidget.yaml +524 -524
  115. package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/NetworkSettingsWidget/NetworkSettingsWidgetComponents/NetworkSettingsWidgetComponents.yaml +690 -690
  116. package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/SettingsWidgets.yaml +1265 -1265
  117. package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/UI.yaml +252 -252
  118. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/.vscode/launch.json +18 -18
  119. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/ApplicationNameLogic.cs +19 -19
  120. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/AryaNewHMIProject_OptixPanel.code-workspace +9 -9
  121. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/AryaNewHMIProject_OptixPanel.csproj +19 -19
  122. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/AryaNewHMIProject_OptixPanel.references +172 -172
  123. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/AryaNewHMIProject_OptixPanel.sln +21 -21
  124. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/ConnectionSwitchLogic.cs +74 -74
  125. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/DateAndTimeWidgetLogic.cs +420 -420
  126. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/DeviceSettingsWidgetLogic.cs +34 -34
  127. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/FTOptixStudioVersionLogic.cs +47 -47
  128. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/FTRemoteAccessWidgetSetupLogic.cs +153 -153
  129. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/IncomingConnectionApprovalLogic.cs +118 -118
  130. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/IncomingConnectionReceiverLogic.cs +166 -166
  131. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/NetworkConfiguratorEditModelsLogic.cs +56 -56
  132. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/NetworkInterfaceLogic.cs +138 -138
  133. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/NetworkInterfacesEditModelReaderLogic.cs +87 -87
  134. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/NetworkInterfacesEditModelToSystemWriterLogic.cs +139 -139
  135. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/NetworkSettingsLogic.cs +168 -168
  136. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/Private/FTRemoteAccessWidgetDataObject.cs +106 -106
  137. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/Private/TypeConstants.cs +37 -37
  138. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/Private/UITypeDefinitions.cs +91 -91
  139. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/RequestAssistanceSwitchLogic.cs +93 -93
  140. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/StudioMCPBridge.cs +4340 -4340
  141. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/SystemToNetworkInterfacesEditModelWriterLogic.cs +77 -77
  142. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/UserDefinedModule.xml +111 -111
  143. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/WarningIcon.svg +4 -4
  144. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/block_interfaces.cpython-312.pyc +0 -0
  145. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/cfb.cpython-312.pyc +0 -0
  146. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/datatypes.cpython-312.pyc +0 -0
  147. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/declarations.cpython-312.pyc +0 -0
  148. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/graphical_listing.cpython-312.pyc +0 -0
  149. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/grid.cpython-312.pyc +0 -0
  150. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/knowledge.cpython-312.pyc +0 -0
  151. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/mwt_bind.cpython-312.pyc +0 -0
  152. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/navigation.cpython-312.pyc +0 -0
  153. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/program_checks.cpython-312.pyc +0 -0
  154. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/project.cpython-312.pyc +0 -0
  155. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/structure.cpython-312.pyc +0 -0
  156. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/task_bindings.cpython-312.pyc +0 -0
  157. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tasks.cpython-312.pyc +0 -0
  158. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tree.cpython-312.pyc +0 -0
  159. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/validation.cpython-312.pyc +0 -0
  160. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variable_edit.cpython-312.pyc +0 -0
  161. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variables.cpython-312.pyc +0 -0
  162. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/workflow.cpython-312.pyc +0 -0
@@ -1,125 +1,125 @@
1
- """Workspace-scoped project guards shared by the file engine.
2
-
3
- The host supplies the current session workspace for each child process. Missing scope
4
- fails closed. Staged copies live at <workspace>/.motionworks/stage and identities
5
- must match that workspace, source, wrapper and project directory.
6
- """
7
- from __future__ import annotations
8
-
9
- import os
10
- from pathlib import Path
11
-
12
- from .errors import MotionWorksError
13
-
14
- #: Optional consistency check; it cannot relocate the workspace stage.
15
- STAGE_ENV = "MOTIONWORKS_MCP_STAGE"
16
-
17
-
18
- class StagingRefused(MotionWorksError):
19
- """A project path outside the staging root."""
20
-
21
-
22
- def workspace_root() -> Path:
23
- value = os.environ.get("MOTIONWORKS_MCP_WORKSPACE")
24
- if not value or not Path(value).is_absolute():
25
- raise StagingRefused("REFUSED: no absolute session workspace was supplied to the engine.")
26
- return Path(value).resolve()
27
-
28
-
29
- def staging_root() -> Path:
30
- """A private stage within the calling workspace, never the shared plugin stage."""
31
- workspace = workspace_root()
32
- fixed = workspace / ".motionworks" / "stage"
33
- if not _inside(fixed, workspace):
34
- raise StagingRefused("REFUSED: workspace stage resolves outside the workspace.")
35
- override = os.environ.get(STAGE_ENV)
36
- if override and Path(override).resolve() != fixed.resolve():
37
- raise StagingRefused("REFUSED: stage override does not match the calling workspace stage.")
38
- return fixed.resolve()
39
-
40
-
41
- def _inside(child: Path, parent: Path) -> bool:
42
- try:
43
- child.resolve().relative_to(parent.resolve())
44
- return True
45
- except ValueError:
46
- return False
47
-
48
-
49
- def assert_staged(project_root: Path | str, what: str = "project") -> Path:
50
- """Return the resolved project root, or refuse if it is not a staged copy.
51
-
52
- Called by every write and every IDE open. The message names the path, the staging root and the
53
- way through - because the caller is usually an agent that has just been stopped mid-task and
54
- needs to know what to do instead, not merely that it may not.
55
- """
56
- given = Path(project_root).resolve()
57
- root = staging_root()
58
-
59
- if given == root or _inside(given, root):
60
- return given
61
-
62
- raise StagingRefused(
63
- f"REFUSED: {what} '{given}' is outside the staging root '{root}'. This engine only ever "
64
- f"reads or writes a STAGED COPY, never a real project tree - a real tree holds the owner's "
65
- f"work and MotionWorks rewrites whole files, so a mistake there is not recoverable from "
66
- f"here. Stage a copy first with mw_ide_stage, then point at the copy. "
67
- f"{STAGE_ENV} cannot aim this root at a real project."
68
- )
69
-
70
-
1
+ """Workspace-scoped project guards shared by the file engine.
2
+
3
+ The host supplies the current session workspace for each child process. Missing scope
4
+ fails closed. Staged copies live at <workspace>/.motionworks/stage and identities
5
+ must match that workspace, source, wrapper and project directory.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from pathlib import Path
11
+
12
+ from .errors import MotionWorksError
13
+
14
+ #: Optional consistency check; it cannot relocate the workspace stage.
15
+ STAGE_ENV = "MOTIONWORKS_MCP_STAGE"
16
+
17
+
18
+ class StagingRefused(MotionWorksError):
19
+ """A project path outside the staging root."""
20
+
21
+
22
+ def workspace_root() -> Path:
23
+ value = os.environ.get("MOTIONWORKS_MCP_WORKSPACE")
24
+ if not value or not Path(value).is_absolute():
25
+ raise StagingRefused("REFUSED: no absolute session workspace was supplied to the engine.")
26
+ return Path(value).resolve()
27
+
28
+
29
+ def staging_root() -> Path:
30
+ """A private stage within the calling workspace, never the shared plugin stage."""
31
+ workspace = workspace_root()
32
+ fixed = workspace / ".motionworks" / "stage"
33
+ if not _inside(fixed, workspace):
34
+ raise StagingRefused("REFUSED: workspace stage resolves outside the workspace.")
35
+ override = os.environ.get(STAGE_ENV)
36
+ if override and Path(override).resolve() != fixed.resolve():
37
+ raise StagingRefused("REFUSED: stage override does not match the calling workspace stage.")
38
+ return fixed.resolve()
39
+
40
+
41
+ def _inside(child: Path, parent: Path) -> bool:
42
+ try:
43
+ child.resolve().relative_to(parent.resolve())
44
+ return True
45
+ except ValueError:
46
+ return False
47
+
48
+
49
+ def assert_staged(project_root: Path | str, what: str = "project") -> Path:
50
+ """Return the resolved project root, or refuse if it is not a staged copy.
51
+
52
+ Called by every write and every IDE open. The message names the path, the staging root and the
53
+ way through - because the caller is usually an agent that has just been stopped mid-task and
54
+ needs to know what to do instead, not merely that it may not.
55
+ """
56
+ given = Path(project_root).resolve()
57
+ root = staging_root()
58
+
59
+ if given == root or _inside(given, root):
60
+ return given
61
+
62
+ raise StagingRefused(
63
+ f"REFUSED: {what} '{given}' is outside the staging root '{root}'. This engine only ever "
64
+ f"reads or writes a STAGED COPY, never a real project tree - a real tree holds the owner's "
65
+ f"work and MotionWorks rewrites whole files, so a mistake there is not recoverable from "
66
+ f"here. Stage a copy first with mw_ide_stage, then point at the copy. "
67
+ f"{STAGE_ENV} cannot aim this root at a real project."
68
+ )
69
+
70
+
71
71
  def assert_proven(project_root: Path | str) -> Path:
72
- """Refuse a staged copy that mw_ide_stage did not record a source for.
73
-
74
- A directory under stage/ is not provenance. Scripts that call this engine directly
75
- used to edit whichever copy was there, including one with no recorded workspace source.
76
- """
77
- import json
78
-
79
- given = assert_staged(project_root)
80
- root = staging_root()
81
- path = given
82
- if path.suffix.lower() == ".mwt":
83
- path = path.with_suffix("")
84
- try:
85
- rel = path.resolve().relative_to(root.resolve())
86
- except ValueError as exc:
87
- raise StagingRefused(
88
- f"REFUSED: '{given}' is not inside the staging root '{root}'."
89
- ) from exc
90
- if not rel.parts:
91
- raise StagingRefused(
92
- f"REFUSED: '{given}' is the staging root, not a project."
93
- )
94
- name = rel.parts[0]
95
- ident = root / f"{name}.identity.json"
96
- if not ident.is_file():
97
- raise StagingRefused(
98
- f"REFUSED: staged project '{name}' has no identity file. Stage it from the workspace "
99
- f"with mw_ide_stage. A folder copied into stage by hand is not edited."
100
- )
101
- try:
102
- data = json.loads(ident.read_text(encoding="utf-8"))
103
- except (OSError, json.JSONDecodeError) as exc:
104
- raise StagingRefused(f"REFUSED: could not read {ident.name}: {exc}") from exc
105
- if not data.get("source"):
106
- raise StagingRefused(
107
- f"REFUSED: staged project '{name}' has no recorded source. Stage it again from the "
108
- f"workspace project. A copy with no provenance is not edited."
109
- )
110
- workspace = workspace_root()
111
- if (not data.get("workspace")
112
- or Path(data["workspace"]).resolve() != workspace
113
- or not _inside(Path(data["source"]), workspace)
114
- or not _inside(Path(data.get("source_directory", data["source"])), workspace)
115
- or Path(data.get("staged_directory", "")).resolve() != (root / name).resolve()
116
- or Path(data.get("staged_mwt", "")).resolve() != (root / f"{name}.mwt").resolve()):
117
- raise StagingRefused("REFUSED: project identity does not belong to the calling workspace.")
118
- project = root / name
119
- if project.is_dir():
120
- for member in project.rglob("*"):
121
- if not _inside(member, project):
122
- raise StagingRefused(f"REFUSED: linked project member escapes the project: {member}")
72
+ """Refuse a staged copy that mw_ide_stage did not record a source for.
73
+
74
+ A directory under stage/ is not provenance. Scripts that call this engine directly
75
+ used to edit whichever copy was there, including one with no recorded workspace source.
76
+ """
77
+ import json
78
+
79
+ given = assert_staged(project_root)
80
+ root = staging_root()
81
+ path = given
82
+ if path.suffix.lower() == ".mwt":
83
+ path = path.with_suffix("")
84
+ try:
85
+ rel = path.resolve().relative_to(root.resolve())
86
+ except ValueError as exc:
87
+ raise StagingRefused(
88
+ f"REFUSED: '{given}' is not inside the staging root '{root}'."
89
+ ) from exc
90
+ if not rel.parts:
91
+ raise StagingRefused(
92
+ f"REFUSED: '{given}' is the staging root, not a project."
93
+ )
94
+ name = rel.parts[0]
95
+ ident = root / f"{name}.identity.json"
96
+ if not ident.is_file():
97
+ raise StagingRefused(
98
+ f"REFUSED: staged project '{name}' has no identity file. Stage it from the workspace "
99
+ f"with mw_ide_stage. A folder copied into stage by hand is not edited."
100
+ )
101
+ try:
102
+ data = json.loads(ident.read_text(encoding="utf-8"))
103
+ except (OSError, json.JSONDecodeError) as exc:
104
+ raise StagingRefused(f"REFUSED: could not read {ident.name}: {exc}") from exc
105
+ if not data.get("source"):
106
+ raise StagingRefused(
107
+ f"REFUSED: staged project '{name}' has no recorded source. Stage it again from the "
108
+ f"workspace project. A copy with no provenance is not edited."
109
+ )
110
+ workspace = workspace_root()
111
+ if (not data.get("workspace")
112
+ or Path(data["workspace"]).resolve() != workspace
113
+ or not _inside(Path(data["source"]), workspace)
114
+ or not _inside(Path(data.get("source_directory", data["source"])), workspace)
115
+ or Path(data.get("staged_directory", "")).resolve() != (root / name).resolve()
116
+ or Path(data.get("staged_mwt", "")).resolve() != (root / f"{name}.mwt").resolve()):
117
+ raise StagingRefused("REFUSED: project identity does not belong to the calling workspace.")
118
+ project = root / name
119
+ if project.is_dir():
120
+ for member in project.rglob("*"):
121
+ if not _inside(member, project):
122
+ raise StagingRefused(f"REFUSED: linked project member escapes the project: {member}")
123
123
  return given
124
124
 
125
125
 
@@ -149,9 +149,13 @@ def assert_direct_attached(project_root: Path | str) -> Path:
149
149
  continue
150
150
  backup = Path(record["backup"]).resolve()
151
151
  manifest = Path(record["backup_manifest"]).resolve()
152
- backup_root = workspace / ".motionworks" / "attached" / "backups"
152
+ # One workspace backup tree for every engineering product:
153
+ # <workspace>/backups/<product>/<subject>/<stamp>. Records written
154
+ # before 0.4.21 point at .motionworks/attached/backups and stay valid.
155
+ backup_root = workspace / "backups" / "motionworks"
156
+ legacy_root = workspace / ".motionworks" / "attached" / "backups"
153
157
  if (Path(record["workspace"]).resolve() != workspace
154
- or not _inside(backup, backup_root)
158
+ or not (_inside(backup, backup_root) or _inside(backup, legacy_root))
155
159
  or not _inside(manifest, backup)
156
160
  or not manifest.is_file()
157
161
  or hashlib.sha256(manifest.read_bytes()).hexdigest() != record["backup_manifest_sha256"]
@@ -170,10 +174,10 @@ def assert_direct_attached(project_root: Path | str) -> Path:
170
174
  except (OSError, ValueError, KeyError, TypeError):
171
175
  continue
172
176
  raise StagingRefused("REFUSED: direct project attachment or backup identity is invalid.")
173
-
174
-
175
- def is_staged(project_root: Path | str) -> bool:
176
- """Whether a path is inside the staging root, for a caller that wants to ask rather than try."""
177
- given = Path(project_root).resolve()
178
- root = staging_root()
179
- return given == root or _inside(given, root)
177
+
178
+
179
+ def is_staged(project_root: Path | str) -> bool:
180
+ """Whether a path is inside the staging root, for a caller that wants to ask rather than try."""
181
+ given = Path(project_root).resolve()
182
+ root = staging_root()
183
+ return given == root or _inside(given, root)