@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.
- package/CHANGELOG.md +128 -0
- package/LICENSE +21 -0
- package/README.md +230 -2
- package/cordis.patch.yml +123 -0
- package/lib/client.js +3448 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +9 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +94 -3
- package/vendor/ftview/README.md +55 -0
- package/vendor/ftview/SKILL.md +74 -0
- package/vendor/ftview/lib/checks.d.ts +27 -0
- package/vendor/ftview/lib/checks.js +39 -0
- package/vendor/ftview/lib/core.d.ts +208 -0
- package/vendor/ftview/lib/core.js +160 -0
- package/vendor/ftview/lib/edit/drafts.d.ts +49 -0
- package/vendor/ftview/lib/edit/drafts.js +84 -0
- package/vendor/ftview/lib/edit/operations.d.ts +62 -0
- package/vendor/ftview/lib/edit/operations.js +126 -0
- package/vendor/ftview/lib/edit/prepare.d.ts +38 -0
- package/vendor/ftview/lib/edit/prepare.js +133 -0
- package/vendor/ftview/lib/edit/tools.d.ts +25 -0
- package/vendor/ftview/lib/edit/tools.js +198 -0
- package/vendor/ftview/lib/edit.d.ts +30 -0
- package/vendor/ftview/lib/edit.js +38 -0
- package/vendor/ftview/lib/index.d.ts +31 -0
- package/vendor/ftview/lib/index.js +36 -0
- package/vendor/ftview/lib/model.d.ts +80 -0
- package/vendor/ftview/lib/model.js +267 -0
- package/vendor/ftview/lib/session.d.ts +48 -0
- package/vendor/ftview/lib/session.js +98 -0
- package/vendor/ftview/lib/skill.d.ts +29 -0
- package/vendor/ftview/lib/skill.js +43 -0
- package/vendor/ftview/lib/studio/discover.d.ts +36 -0
- package/vendor/ftview/lib/studio/discover.js +87 -0
- package/vendor/ftview/lib/studio/preflight.d.ts +51 -0
- package/vendor/ftview/lib/studio/preflight.js +64 -0
- package/vendor/ftview/lib/studio/tools.d.ts +15 -0
- package/vendor/ftview/lib/studio/tools.js +83 -0
- package/vendor/ftview/lib/studio.d.ts +30 -0
- package/vendor/ftview/lib/studio.js +37 -0
- package/vendor/ftview/lib/tools.d.ts +14 -0
- package/vendor/ftview/lib/tools.js +202 -0
- package/vendor/l5x/README.md +62 -0
- package/vendor/l5x/SKILL.md +81 -0
- package/vendor/l5x/lib/approvals.d.ts +50 -0
- package/vendor/l5x/lib/approvals.js +93 -0
- package/vendor/l5x/lib/client.js +412 -0
- package/vendor/l5x/lib/client.js.map +1 -0
- package/vendor/l5x/lib/core/errors.d.ts +38 -0
- package/vendor/l5x/lib/core/errors.js +48 -0
- package/vendor/l5x/lib/core/params.d.ts +49 -0
- package/vendor/l5x/lib/core/params.js +39 -0
- package/vendor/l5x/lib/core/result.d.ts +46 -0
- package/vendor/l5x/lib/core/result.js +21 -0
- package/vendor/l5x/lib/core/tool.d.ts +102 -0
- package/vendor/l5x/lib/core/tool.js +19 -0
- package/vendor/l5x/lib/drafts.d.ts +66 -0
- package/vendor/l5x/lib/drafts.js +98 -0
- package/vendor/l5x/lib/editing.d.ts +30 -0
- package/vendor/l5x/lib/editing.js +42 -0
- package/vendor/l5x/lib/engine/acd-edit.d.ts +13 -0
- package/vendor/l5x/lib/engine/acd-edit.js +115 -0
- package/vendor/l5x/lib/engine/analysis.d.ts +22 -0
- package/vendor/l5x/lib/engine/analysis.js +258 -0
- package/vendor/l5x/lib/engine/capabilities.d.ts +9 -0
- package/vendor/l5x/lib/engine/capabilities.js +40 -0
- package/vendor/l5x/lib/engine/change-set.d.ts +122 -0
- package/vendor/l5x/lib/engine/change-set.js +446 -0
- package/vendor/l5x/lib/engine/configuration.d.ts +21 -0
- package/vendor/l5x/lib/engine/configuration.js +42 -0
- package/vendor/l5x/lib/engine/details.d.ts +47 -0
- package/vendor/l5x/lib/engine/details.js +173 -0
- package/vendor/l5x/lib/engine/direct-polling.d.ts +91 -0
- package/vendor/l5x/lib/engine/direct-polling.js +193 -0
- package/vendor/l5x/lib/engine/download-review.d.ts +15 -0
- package/vendor/l5x/lib/engine/download-review.js +63 -0
- package/vendor/l5x/lib/engine/logic-syntax.d.ts +60 -0
- package/vendor/l5x/lib/engine/logic-syntax.js +237 -0
- package/vendor/l5x/lib/engine/observations.d.ts +41 -0
- package/vendor/l5x/lib/engine/observations.js +67 -0
- package/vendor/l5x/lib/engine/project-analysis.d.ts +131 -0
- package/vendor/l5x/lib/engine/project-analysis.js +314 -0
- package/vendor/l5x/lib/engine/review-evidence.d.ts +8 -0
- package/vendor/l5x/lib/engine/review-evidence.js +49 -0
- package/vendor/l5x/lib/engine/review.d.ts +18 -0
- package/vendor/l5x/lib/engine/review.js +123 -0
- package/vendor/l5x/lib/engine/studio-sdk.d.ts +101 -0
- package/vendor/l5x/lib/engine/studio-sdk.js +571 -0
- package/vendor/l5x/lib/engine/tag-resolution.d.ts +20 -0
- package/vendor/l5x/lib/engine/tag-resolution.js +169 -0
- package/vendor/l5x/lib/engine/usage.d.ts +16 -0
- package/vendor/l5x/lib/engine/usage.js +92 -0
- package/vendor/l5x/lib/engine/xml-regions.d.ts +23 -0
- package/vendor/l5x/lib/engine/xml-regions.js +77 -0
- package/vendor/l5x/lib/fs.d.ts +17 -0
- package/vendor/l5x/lib/fs.js +36 -0
- package/vendor/l5x/lib/index.d.ts +36 -0
- package/vendor/l5x/lib/index.js +42 -0
- package/vendor/l5x/lib/live.d.ts +48 -0
- package/vendor/l5x/lib/live.js +48 -0
- package/vendor/l5x/lib/model/aois.d.ts +39 -0
- package/vendor/l5x/lib/model/aois.js +112 -0
- package/vendor/l5x/lib/model/errors.d.ts +15 -0
- package/vendor/l5x/lib/model/errors.js +18 -0
- package/vendor/l5x/lib/model/index.d.ts +20 -0
- package/vendor/l5x/lib/model/index.js +19 -0
- package/vendor/l5x/lib/model/module.d.ts +37 -0
- package/vendor/l5x/lib/model/module.js +54 -0
- package/vendor/l5x/lib/model/modules.d.ts +24 -0
- package/vendor/l5x/lib/model/modules.js +55 -0
- package/vendor/l5x/lib/model/parse.d.ts +32 -0
- package/vendor/l5x/lib/model/parse.js +116 -0
- package/vendor/l5x/lib/model/project.d.ts +15 -0
- package/vendor/l5x/lib/model/project.js +31 -0
- package/vendor/l5x/lib/model/routines.d.ts +71 -0
- package/vendor/l5x/lib/model/routines.js +183 -0
- package/vendor/l5x/lib/model/tags.d.ts +51 -0
- package/vendor/l5x/lib/model/tags.js +119 -0
- package/vendor/l5x/lib/model/types.d.ts +304 -0
- package/vendor/l5x/lib/model/types.js +12 -0
- package/vendor/l5x/lib/model/udts.d.ts +34 -0
- package/vendor/l5x/lib/model/udts.js +81 -0
- package/vendor/l5x/lib/model/xml.d.ts +139 -0
- package/vendor/l5x/lib/model/xml.js +291 -0
- package/vendor/l5x/lib/model/xref.d.ts +28 -0
- package/vendor/l5x/lib/model/xref.js +154 -0
- package/vendor/l5x/lib/session.d.ts +119 -0
- package/vendor/l5x/lib/session.js +236 -0
- package/vendor/l5x/lib/skill.d.ts +30 -0
- package/vendor/l5x/lib/skill.js +44 -0
- package/vendor/l5x/lib/studio-state.d.ts +77 -0
- package/vendor/l5x/lib/studio-state.js +79 -0
- package/vendor/l5x/lib/studio.d.ts +29 -0
- package/vendor/l5x/lib/studio.js +40 -0
- package/vendor/l5x/lib/tools/checks.d.ts +19 -0
- package/vendor/l5x/lib/tools/checks.js +140 -0
- package/vendor/l5x/lib/tools/common.d.ts +41 -0
- package/vendor/l5x/lib/tools/common.js +84 -0
- package/vendor/l5x/lib/tools/controller.d.ts +21 -0
- package/vendor/l5x/lib/tools/controller.js +170 -0
- package/vendor/l5x/lib/tools/drafts.d.ts +23 -0
- package/vendor/l5x/lib/tools/drafts.js +270 -0
- package/vendor/l5x/lib/tools/live.d.ts +21 -0
- package/vendor/l5x/lib/tools/live.js +175 -0
- package/vendor/l5x/lib/tools/read.d.ts +19 -0
- package/vendor/l5x/lib/tools/read.js +245 -0
- package/vendor/l5x/lib/tools/references.d.ts +20 -0
- package/vendor/l5x/lib/tools/references.js +269 -0
- package/vendor/l5x/lib/tools/studio.d.ts +21 -0
- package/vendor/l5x/lib/tools/studio.js +193 -0
- package/vendor/l5x/poll-worker.mjs +77 -0
- package/vendor/l5x/studio-bridge/NuGet.Config +1 -0
- package/vendor/l5x/studio-bridge/Program.cs +304 -0
- package/vendor/l5x/studio-bridge/StudioBridge.csproj +11 -0
- package/vendor/mwi/README.md +60 -0
- package/vendor/mwi/SKILL.md +87 -0
- package/vendor/mwi/bridge/README.md +8 -0
- package/vendor/mwi/bridge/mw_bridge.ps1 +2763 -0
- package/vendor/mwi/bridge/sdk-interop-importer.cs +30 -0
- package/vendor/mwi/bridge/sdk-variable-reader.cs +22 -0
- package/vendor/mwi/bridge/start_bridge.cmd +21 -0
- package/vendor/mwi/bridge/watch_dialogs.cmd +13 -0
- package/vendor/mwi/bridge/watch_dialogs.ps1 +153 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__init__.py +14 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/block_interfaces.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/cfb.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/datatypes.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/declarations.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/errors.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/graphical_listing.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/grid.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/knowledge.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/mwt_bind.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/navigation.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/program_checks.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/project.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/staging.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/structure.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/task_bindings.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tasks.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tree.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/validation.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variable_edit.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variables.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/workflow.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/block_interfaces.py +275 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/build.py +339 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/capture.py +448 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/cfb.py +765 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/com.py +268 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/datatypes.py +204 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/declarations.py +399 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/descriptions.py +30 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/eip.py +292 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/errors.py +36 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/graphical_listing.py +208 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/grid.py +227 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/ide.py +317 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/iec.py +317 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/installed_help.py +151 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/knowledge.py +218 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/libraries.py +250 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/manuals.py +520 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/mwt_bind.py +133 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/navigation.py +44 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/pou_writer.py +1547 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/program_checks.py +360 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/project.py +704 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/catalog.json +672 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/patterns.json +86 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/restore.py +208 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/server.py +1913 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/snapshot.py +138 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/st_comments.py +25 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/staging.py +179 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/stlint.py +676 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/structure.py +98 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/task_bindings.py +68 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tasks.py +162 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/transaction.py +157 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tree.py +563 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tree_writer.py +790 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/uia.py +465 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/validation.py +41 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/variable_edit.py +183 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/variables.py +472 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/workflow.py +130 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/writer.py +666 -0
- package/vendor/mwi/code/mw_code.py +1258 -0
- package/vendor/mwi/legacy/edit-session.js +50 -0
- package/vendor/mwi/legacy/fb-insertion.js +74 -0
- package/vendor/mwi/legacy/native-code.js +83 -0
- package/vendor/mwi/legacy/native-groups.js +75 -0
- package/vendor/mwi/legacy/native-structure.js +143 -0
- package/vendor/mwi/legacy/native-variables.js +77 -0
- package/vendor/mwi/legacy/pou-conversion.js +47 -0
- package/vendor/mwi/legacy/pou-package.js +66 -0
- package/vendor/mwi/lib/core/bridge.d.ts +71 -0
- package/vendor/mwi/lib/core/bridge.js +53 -0
- package/vendor/mwi/lib/core/code-engine.d.ts +47 -0
- package/vendor/mwi/lib/core/code-engine.js +109 -0
- package/vendor/mwi/lib/core/errors.d.ts +37 -0
- package/vendor/mwi/lib/core/errors.js +47 -0
- package/vendor/mwi/lib/core/params.d.ts +52 -0
- package/vendor/mwi/lib/core/params.js +46 -0
- package/vendor/mwi/lib/core/ps-bridge.d.ts +62 -0
- package/vendor/mwi/lib/core/ps-bridge.js +127 -0
- package/vendor/mwi/lib/core/result.d.ts +45 -0
- package/vendor/mwi/lib/core/result.js +20 -0
- package/vendor/mwi/lib/core/tool.d.ts +80 -0
- package/vendor/mwi/lib/core/tool.js +55 -0
- package/vendor/mwi/lib/edit/compare.d.ts +33 -0
- package/vendor/mwi/lib/edit/compare.js +70 -0
- package/vendor/mwi/lib/edit.d.ts +20 -0
- package/vendor/mwi/lib/edit.js +32 -0
- package/vendor/mwi/lib/index.d.ts +21 -0
- package/vendor/mwi/lib/index.js +33 -0
- package/vendor/mwi/lib/read/service.d.ts +117 -0
- package/vendor/mwi/lib/read/service.js +229 -0
- package/vendor/mwi/lib/read.d.ts +21 -0
- package/vendor/mwi/lib/read.js +33 -0
- package/vendor/mwi/lib/session/project.d.ts +34 -0
- package/vendor/mwi/lib/session/project.js +54 -0
- package/vendor/mwi/lib/session/service.d.ts +167 -0
- package/vendor/mwi/lib/session/service.js +566 -0
- package/vendor/mwi/lib/session/stage.d.ts +84 -0
- package/vendor/mwi/lib/session/stage.js +187 -0
- package/vendor/mwi/lib/session/workspace.d.ts +52 -0
- package/vendor/mwi/lib/session/workspace.js +90 -0
- package/vendor/mwi/lib/skill.d.ts +36 -0
- package/vendor/mwi/lib/skill.js +56 -0
- package/vendor/mwi/lib/tools/edit.d.ts +20 -0
- package/vendor/mwi/lib/tools/edit.js +286 -0
- package/vendor/mwi/lib/tools/read.d.ts +19 -0
- package/vendor/mwi/lib/tools/read.js +161 -0
- package/vendor/mwi/lib/tools/session.d.ts +20 -0
- package/vendor/mwi/lib/tools/session.js +166 -0
- package/vendor/mwi/lib/tools/verify.d.ts +19 -0
- package/vendor/mwi/lib/tools/verify.js +85 -0
- package/vendor/mwi/lib/verify.d.ts +17 -0
- package/vendor/mwi/lib/verify.js +29 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""SHA-256 tree snapshots, used as the safety spine around every write.
|
|
2
|
+
|
|
3
|
+
A snapshot is a JSON manifest of relative path -> SHA-256 for every file in a
|
|
4
|
+
project. Taking one before a change and verifying after gives a precise answer
|
|
5
|
+
to "what exactly did this touch?", which matters because MotionWorks will
|
|
6
|
+
rewrite unrelated state on the next save.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from .errors import VerificationFailed
|
|
18
|
+
|
|
19
|
+
MANIFEST_FORMAT = "motionworks-sha256-v1"
|
|
20
|
+
|
|
21
|
+
#: Directory names never included in a snapshot; they are build output.
|
|
22
|
+
_SKIP_DIRS = {"__pycache__", ".git"}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def sha256_file(path: Path, chunk: int = 1 << 20) -> str:
|
|
26
|
+
digest = hashlib.sha256()
|
|
27
|
+
with path.open("rb") as handle:
|
|
28
|
+
for block in iter(lambda: handle.read(chunk), b""):
|
|
29
|
+
digest.update(block)
|
|
30
|
+
return digest.hexdigest()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def hash_tree(
|
|
34
|
+
base: Path,
|
|
35
|
+
exclude: Path | None = None,
|
|
36
|
+
include_generated: bool = True,
|
|
37
|
+
) -> dict[str, str]:
|
|
38
|
+
"""Hash every file under ``base``, keyed by POSIX-style relative path.
|
|
39
|
+
|
|
40
|
+
``tmp.sto`` build caches are included by default because a change there is
|
|
41
|
+
still a change worth seeing, but ``include_generated=False`` filters them
|
|
42
|
+
out when only source differences matter.
|
|
43
|
+
"""
|
|
44
|
+
base = base.resolve()
|
|
45
|
+
exclude_resolved = exclude.resolve() if exclude else None
|
|
46
|
+
files: dict[str, str] = {}
|
|
47
|
+
for path in sorted(base.rglob("*")):
|
|
48
|
+
if not path.is_file():
|
|
49
|
+
continue
|
|
50
|
+
if any(part in _SKIP_DIRS for part in path.parts):
|
|
51
|
+
continue
|
|
52
|
+
if exclude_resolved is not None and path.resolve() == exclude_resolved:
|
|
53
|
+
continue
|
|
54
|
+
if not include_generated and path.suffix.lower() in {".sto", ".sn"}:
|
|
55
|
+
continue
|
|
56
|
+
files[path.relative_to(base).as_posix()] = sha256_file(path)
|
|
57
|
+
return files
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def make_manifest(
|
|
61
|
+
base: Path,
|
|
62
|
+
exclude: Path | None = None,
|
|
63
|
+
include_generated: bool = True,
|
|
64
|
+
note: str | None = None,
|
|
65
|
+
) -> dict[str, object]:
|
|
66
|
+
return {
|
|
67
|
+
"format": MANIFEST_FORMAT,
|
|
68
|
+
"base": str(Path(base).resolve()),
|
|
69
|
+
"created": datetime.now().isoformat(timespec="seconds"),
|
|
70
|
+
"note": note,
|
|
71
|
+
"files": hash_tree(base, exclude, include_generated),
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def write_manifest(manifest: dict[str, object], output: Path) -> Path:
|
|
76
|
+
output = Path(output)
|
|
77
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
78
|
+
output.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
|
|
79
|
+
return output
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def load_manifest(path: Path) -> dict[str, object]:
|
|
83
|
+
data = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
84
|
+
if data.get("format") != MANIFEST_FORMAT:
|
|
85
|
+
raise VerificationFailed(
|
|
86
|
+
f"Unsupported manifest format {data.get('format')!r}; "
|
|
87
|
+
f"expected {MANIFEST_FORMAT!r}"
|
|
88
|
+
)
|
|
89
|
+
return data
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def diff_manifest(
|
|
93
|
+
manifest: dict[str, object],
|
|
94
|
+
base: Path | None = None,
|
|
95
|
+
include_generated: bool = True,
|
|
96
|
+
) -> dict[str, list[str]]:
|
|
97
|
+
"""Compare a manifest against the current tree.
|
|
98
|
+
|
|
99
|
+
Returns lists of ``missing``, ``added``, and ``changed`` paths. An empty
|
|
100
|
+
result across all three means the tree is byte-for-byte identical.
|
|
101
|
+
"""
|
|
102
|
+
target = Path(base) if base else Path(str(manifest["base"]))
|
|
103
|
+
expected: dict[str, str] = manifest["files"] # type: ignore[assignment]
|
|
104
|
+
actual = hash_tree(target, include_generated=include_generated)
|
|
105
|
+
return {
|
|
106
|
+
"missing": sorted(set(expected) - set(actual)),
|
|
107
|
+
"added": sorted(set(actual) - set(expected)),
|
|
108
|
+
"changed": sorted(
|
|
109
|
+
name for name in set(actual) & set(expected) if actual[name] != expected[name]
|
|
110
|
+
),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def verify(
|
|
115
|
+
manifest: dict[str, object],
|
|
116
|
+
base: Path | None = None,
|
|
117
|
+
include_generated: bool = True,
|
|
118
|
+
raise_on_difference: bool = True,
|
|
119
|
+
) -> dict[str, list[str]]:
|
|
120
|
+
result = diff_manifest(manifest, base, include_generated)
|
|
121
|
+
if raise_on_difference and any(result.values()):
|
|
122
|
+
summary = ", ".join(f"{k}={len(v)}" for k, v in result.items() if v)
|
|
123
|
+
raise VerificationFailed(f"Snapshot verification failed: {summary}")
|
|
124
|
+
return result
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def default_backup_dir(project_root: Path) -> Path:
|
|
128
|
+
"""Where to place backups for a project.
|
|
129
|
+
|
|
130
|
+
Deliberately outside the project directory: a backup inside the tree would
|
|
131
|
+
be picked up by MotionWorks and by later snapshots.
|
|
132
|
+
"""
|
|
133
|
+
from .staging import workspace_root, StagingRefused
|
|
134
|
+
workspace = workspace_root()
|
|
135
|
+
base = Path(os.environ.get("MOTIONWORKS_MCP_BACKUP_DIR", workspace / '.motionworks')).resolve()
|
|
136
|
+
if not base.is_relative_to(workspace):
|
|
137
|
+
raise StagingRefused('REFUSED: backup directory must remain inside the workspace')
|
|
138
|
+
return base / 'backups' / project_root.name
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Resolve native ST comment references using the worksheet translation file."""
|
|
2
|
+
import re
|
|
3
|
+
import xml.etree.ElementTree as ET
|
|
4
|
+
|
|
5
|
+
MARKER = re.compile(r'\x07(\d+),(\d+)\x07')
|
|
6
|
+
|
|
7
|
+
def resolve_comments(body, translation_path):
|
|
8
|
+
if '\x07' not in body:
|
|
9
|
+
return body
|
|
10
|
+
root = ET.parse(translation_path).getroot()
|
|
11
|
+
comments = {}
|
|
12
|
+
for item in root.findall('./ItemList/item'):
|
|
13
|
+
values = [t for t in item.findall('translation')
|
|
14
|
+
if t.get('{http://www.w3.org/XML/1998/namespace}lang') == 'undefined']
|
|
15
|
+
if len(values) != 1 or item.get('id') in comments:
|
|
16
|
+
raise ValueError('Ambiguous native comment translation')
|
|
17
|
+
comments[item.get('id')] = ''.join(values[0].itertext())
|
|
18
|
+
def replace(match):
|
|
19
|
+
if match[1] != '1' or match[2] not in comments:
|
|
20
|
+
raise ValueError('Unresolved native ST comment reference')
|
|
21
|
+
return comments[match[2]]
|
|
22
|
+
result = MARKER.sub(replace, body)
|
|
23
|
+
if '\x07' in result:
|
|
24
|
+
raise ValueError('Unsupported native ST comment reference')
|
|
25
|
+
return result
|
|
@@ -0,0 +1,179 @@
|
|
|
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
|
+
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}")
|
|
123
|
+
return given
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def assert_direct_attached(project_root: Path | str) -> Path:
|
|
127
|
+
"""Authorize read-only inspection of one backed-up, directly attached workspace project.
|
|
128
|
+
|
|
129
|
+
Offline writers still call assert_proven/assert_staged and remain stage-only.
|
|
130
|
+
"""
|
|
131
|
+
import hashlib
|
|
132
|
+
import json
|
|
133
|
+
|
|
134
|
+
given = Path(project_root).resolve()
|
|
135
|
+
directory = given.with_suffix("") if given.suffix.lower() == ".mwt" else given
|
|
136
|
+
wrapper = Path(str(directory) + ".mwt")
|
|
137
|
+
workspace = workspace_root()
|
|
138
|
+
if not _inside(directory, workspace) or _inside(directory, workspace / ".motionworks"):
|
|
139
|
+
raise StagingRefused("REFUSED: direct project is outside the writable workspace area.")
|
|
140
|
+
records = workspace / ".motionworks" / "attached" / "records"
|
|
141
|
+
if not records.is_dir():
|
|
142
|
+
raise StagingRefused("REFUSED: direct project has no verified attachment backup.")
|
|
143
|
+
for record_file in records.glob("*.json"):
|
|
144
|
+
try:
|
|
145
|
+
record = json.loads(record_file.read_text(encoding="utf-8"))
|
|
146
|
+
if record.get("mode") != "direct":
|
|
147
|
+
continue
|
|
148
|
+
if Path(record["mwt"]).resolve() != wrapper or Path(record["directory"]).resolve() != directory:
|
|
149
|
+
continue
|
|
150
|
+
backup = Path(record["backup"]).resolve()
|
|
151
|
+
manifest = Path(record["backup_manifest"]).resolve()
|
|
152
|
+
backup_root = workspace / ".motionworks" / "attached" / "backups"
|
|
153
|
+
if (Path(record["workspace"]).resolve() != workspace
|
|
154
|
+
or not _inside(backup, backup_root)
|
|
155
|
+
or not _inside(manifest, backup)
|
|
156
|
+
or not manifest.is_file()
|
|
157
|
+
or hashlib.sha256(manifest.read_bytes()).hexdigest() != record["backup_manifest_sha256"]
|
|
158
|
+
or not wrapper.is_file() or not directory.is_dir()):
|
|
159
|
+
break
|
|
160
|
+
manifest_data = json.loads(manifest.read_text(encoding="utf-8"))
|
|
161
|
+
if not isinstance(manifest_data.get("files"), list) or any(
|
|
162
|
+
not _inside(backup / item["path"], backup)
|
|
163
|
+
or not (backup / item["path"]).is_file()
|
|
164
|
+
for item in manifest_data["files"]):
|
|
165
|
+
break
|
|
166
|
+
for member in directory.rglob("*"):
|
|
167
|
+
if not _inside(member, directory):
|
|
168
|
+
raise StagingRefused(f"REFUSED: linked project member escapes attachment: {member}")
|
|
169
|
+
return given
|
|
170
|
+
except (OSError, ValueError, KeyError, TypeError):
|
|
171
|
+
continue
|
|
172
|
+
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)
|