robotframework-harness 1.0.0__py3-none-any.whl

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 (82) hide show
  1. rf_harness/__init__.py +3 -0
  2. rf_harness/_assets/.claude-plugin/plugin.json +10 -0
  3. rf_harness/_assets/bin/rfh-launch +60 -0
  4. rf_harness/_assets/bin/rfh-launch.cmd +19 -0
  5. rf_harness/_assets/bin/rfh_launch.py +719 -0
  6. rf_harness/_assets/hooks/hooks.json +84 -0
  7. rf_harness/_assets/skills/rf-harness/SKILL.md +78 -0
  8. rf_harness/_assets/skills/rf-harness/references/browser.md +157 -0
  9. rf_harness/_assets/skills/rf-harness/references/debug.md +39 -0
  10. rf_harness/_assets/skills/rf-harness/references/explore.md +52 -0
  11. rf_harness/_assets/skills/rf-harness/references/requests.md +37 -0
  12. rf_harness/_assets/skills/rf-harness/references/results.md +34 -0
  13. rf_harness/_assets/skills/rf-harness/references/selenium.md +40 -0
  14. rf_harness/_assets/skills/rf-harness/references/waits.md +28 -0
  15. rf_harness/_assets/variants/codex/hooks.json +72 -0
  16. rf_harness/_assets/variants/copilot/hooks.json +53 -0
  17. rf_harness/_assets/variants/opencode/plugins/rf-harness.js +130 -0
  18. rf_harness/config.py +255 -0
  19. rf_harness/installer/__init__.py +1 -0
  20. rf_harness/installer/_assets.py +73 -0
  21. rf_harness/installer/adapters/__init__.py +37 -0
  22. rf_harness/installer/adapters/_base.py +189 -0
  23. rf_harness/installer/adapters/claude_code.py +88 -0
  24. rf_harness/installer/adapters/codex.py +108 -0
  25. rf_harness/installer/adapters/copilot_cli.py +89 -0
  26. rf_harness/installer/adapters/opencode.py +62 -0
  27. rf_harness/installer/adapters/vscode.py +56 -0
  28. rf_harness/installer/cli.py +490 -0
  29. rf_harness/installer/codex_trust.py +190 -0
  30. rf_harness/installer/coexist.py +211 -0
  31. rf_harness/installer/demo_site/index.html +28 -0
  32. rf_harness/installer/doctor.py +343 -0
  33. rf_harness/installer/envfind.py +354 -0
  34. rf_harness/installer/executor.py +288 -0
  35. rf_harness/installer/homes.py +102 -0
  36. rf_harness/installer/manifest.py +204 -0
  37. rf_harness/installer/prompt.py +58 -0
  38. rf_harness/installer/transforms.py +483 -0
  39. rf_harness/runtime/__init__.py +1 -0
  40. rf_harness/runtime/analyzer.py +132 -0
  41. rf_harness/runtime/data/failure_rules.json +77 -0
  42. rf_harness/runtime/data/kwnames_cache.json +1 -0
  43. rf_harness/runtime/data/repl_format_rules.json +455 -0
  44. rf_harness/runtime/data/robocop-agent.toml +58 -0
  45. rf_harness/runtime/desktop/__init__.py +87 -0
  46. rf_harness/runtime/desktop/_env.py +134 -0
  47. rf_harness/runtime/desktop/decision.py +616 -0
  48. rf_harness/runtime/desktop/focus.py +1094 -0
  49. rf_harness/runtime/desktop/guidance.py +91 -0
  50. rf_harness/runtime/desktop/launch_env.py +562 -0
  51. rf_harness/runtime/desktop/safety.py +509 -0
  52. rf_harness/runtime/desktop/signals.py +321 -0
  53. rf_harness/runtime/docs.py +190 -0
  54. rf_harness/runtime/docsgen.py +174 -0
  55. rf_harness/runtime/engine.py +1000 -0
  56. rf_harness/runtime/execd.py +1131 -0
  57. rf_harness/runtime/guards.py +294 -0
  58. rf_harness/runtime/hints.py +1523 -0
  59. rf_harness/runtime/hook.py +1897 -0
  60. rf_harness/runtime/hookcore.py +832 -0
  61. rf_harness/runtime/kwdoc.py +285 -0
  62. rf_harness/runtime/kwdocs.py +286 -0
  63. rf_harness/runtime/leanfmt.py +451 -0
  64. rf_harness/runtime/lintd.py +423 -0
  65. rf_harness/runtime/livetmo.py +385 -0
  66. rf_harness/runtime/pagestate.py +489 -0
  67. rf_harness/runtime/portable.py +191 -0
  68. rf_harness/runtime/procs.py +106 -0
  69. rf_harness/runtime/profiles.py +144 -0
  70. rf_harness/runtime/proto.py +120 -0
  71. rf_harness/runtime/pwlog.py +89 -0
  72. rf_harness/runtime/replfmt.py +556 -0
  73. rf_harness/runtime/rfh_repl_listener.py +244 -0
  74. rf_harness/runtime/richfmt.py +301 -0
  75. rf_harness/runtime/robotcode_adapter.py +157 -0
  76. rf_harness/runtime/rthooks.py +623 -0
  77. rf_harness/runtime/smoke.py +113 -0
  78. robotframework_harness-1.0.0.dist-info/METADATA +85 -0
  79. robotframework_harness-1.0.0.dist-info/RECORD +82 -0
  80. robotframework_harness-1.0.0.dist-info/WHEEL +4 -0
  81. robotframework_harness-1.0.0.dist-info/entry_points.txt +3 -0
  82. robotframework_harness-1.0.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,157 @@
1
+ """The only module that touches robotcode internals (pinned to robotcode >=2.7,<2.8; contract tests in
2
+ tests/runtime/test_robotcode_adapter.py).
3
+
4
+ robotcode has no stable Python API for what the harness needs, so every internal name the runtime uses is listed here
5
+ once and re-exported lazily (PEP 562): `from .robotcode_adapter import BaseInterpreter` imports only
6
+ `robotcode.repl.base_interpreter`, so the live-session daemon does not pay for the analyzer and vice versa.
7
+
8
+ Used for:
9
+ * the live session: `Application`, `BaseInterpreter` (subclassed by engine.Helper) and `run_repl` (one RF suite
10
+ whose body is fed from the helper's request queue);
11
+ * the static gate: `CodeAnalyzer` + `AnalyzeConfig`/`ModifiersConfig` + robot.toml loading
12
+ (`get_config_files`, `load_robot_config_from_path`) + LSP `Uri`/`FileEvent`/`FileChangeType` (analyzer.WarmAnalyzer,
13
+ lintd);
14
+ * keyword documentation: the `robotcode libdoc` command line (docsgen), run in a subprocess.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import importlib
20
+ import os
21
+ import subprocess
22
+ import sys
23
+ from collections.abc import Iterable, Sequence
24
+ from pathlib import Path
25
+ from typing import TYPE_CHECKING, Any
26
+
27
+ SUPPORTED_RANGE = ((2, 7), (2, 8)) # >=2.7,<2.8, same as the `runtime` extra
28
+
29
+ _NAMES = {
30
+ # live session (REPL embedding)
31
+ "Application": "robotcode.plugin",
32
+ "BaseInterpreter": "robotcode.repl.base_interpreter",
33
+ "run_repl": "robotcode.repl.run",
34
+ # static analysis
35
+ "CodeAnalyzer": "robotcode.analyze.code.code_analyzer",
36
+ "AnalyzeConfig": "robotcode.analyze.config",
37
+ "ModifiersConfig": "robotcode.analyze.config",
38
+ "FileChangeType": "robotcode.core.lsp.types",
39
+ "FileEvent": "robotcode.core.lsp.types",
40
+ "Uri": "robotcode.core.uri",
41
+ "load_robot_config_from_path": "robotcode.robot.config.loader",
42
+ "get_config_files": "robotcode.robot.config.utils",
43
+ }
44
+
45
+ __all__ = [
46
+ "AnalyzeConfig",
47
+ "Application",
48
+ "BaseInterpreter",
49
+ "CodeAnalyzer",
50
+ "FileChangeType",
51
+ "FileEvent",
52
+ "ModifiersConfig",
53
+ "Uri",
54
+ "get_config_files",
55
+ "load_robot_config_from_path",
56
+ "run_repl",
57
+ "SUPPORTED_RANGE",
58
+ "changed_file_events",
59
+ "document_uri",
60
+ "is_supported",
61
+ "libdoc",
62
+ "new_application",
63
+ "robotcode_command",
64
+ "robotcode_version",
65
+ ]
66
+
67
+ if TYPE_CHECKING: # pragma: no cover - static names for type checkers only
68
+ from robotcode.analyze.code.code_analyzer import CodeAnalyzer
69
+ from robotcode.analyze.config import AnalyzeConfig, ModifiersConfig
70
+ from robotcode.core.lsp.types import FileChangeType, FileEvent
71
+ from robotcode.core.uri import Uri
72
+ from robotcode.plugin import Application
73
+ from robotcode.repl.base_interpreter import BaseInterpreter
74
+ from robotcode.repl.run import run_repl
75
+ from robotcode.robot.config.loader import load_robot_config_from_path
76
+ from robotcode.robot.config.utils import get_config_files
77
+
78
+
79
+ def __getattr__(name: str) -> Any:
80
+ module = _NAMES.get(name)
81
+ if module is None:
82
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
83
+ value = getattr(importlib.import_module(module), name)
84
+ globals()[name] = value # cache: next access skips __getattr__
85
+ return value
86
+
87
+
88
+ def __dir__() -> list[str]:
89
+ return sorted(set(globals()) | set(__all__))
90
+
91
+
92
+ # ------------------------------------------------------------------------------------------------ version
93
+ def robotcode_version() -> str:
94
+ """Installed robotcode version ("" when robotcode is missing)."""
95
+ try:
96
+ from importlib.metadata import PackageNotFoundError, version
97
+ except ImportError: # pragma: no cover
98
+ return ""
99
+ try:
100
+ return version("robotcode")
101
+ except PackageNotFoundError:
102
+ return ""
103
+
104
+
105
+ def is_supported(ver: str | None = None) -> bool:
106
+ """True when `ver` (default: the installed robotcode) lies in SUPPORTED_RANGE."""
107
+ ver = robotcode_version() if ver is None else ver
108
+ try:
109
+ major_minor = tuple(int(x) for x in ver.split(".")[:2])
110
+ except ValueError:
111
+ return False
112
+ lo, hi = SUPPORTED_RANGE
113
+ return len(major_minor) == 2 and lo <= major_minor < hi
114
+
115
+
116
+ # ------------------------------------------------------------------------------------------------ live session
117
+ def new_application(root: Path | None = None) -> Any:
118
+ """A robotcode `Application` rooted at `root` (default: cwd), as `robotcode repl` builds it."""
119
+ app = __getattr__("Application")()
120
+ app.config.root = Path(root or os.getcwd())
121
+ return app
122
+
123
+
124
+ # ------------------------------------------------------------------------------------------------ static analysis
125
+ def document_uri(path: Path) -> Any:
126
+ """Normalized LSP document URI of a file (key of the analyzer's document store)."""
127
+ return __getattr__("Uri").from_path(path).normalized()
128
+
129
+
130
+ def changed_file_events(paths: Iterable[str]) -> list[Any]:
131
+ """`did_change_watched_files` events for files edited on disk."""
132
+ uri, event, kind = __getattr__("Uri"), __getattr__("FileEvent"), __getattr__("FileChangeType")
133
+ return [event(str(uri.from_path(p)), kind.CHANGED) for p in paths]
134
+
135
+
136
+ # ------------------------------------------------------------------------------------------------ libdoc
137
+ def robotcode_command(python: str | None = None) -> list[str]:
138
+ """argv prefix of the `robotcode` CLI next to `python` (default: this interpreter)."""
139
+ py = Path(python or sys.executable)
140
+ exe = py.parent / ("robotcode.exe" if os.name == "nt" else "robotcode")
141
+ return [str(exe)] if exe.is_file() else [str(py), "-m", "robotcode"]
142
+
143
+
144
+ def libdoc(args: Sequence[str], timeout: float = 60, python: str | None = None,
145
+ root: str | os.PathLike | None = None) -> subprocess.CompletedProcess:
146
+ """`robotcode libdoc <args>` in a subprocess without PYTHONPATH (docs come from the project environment as a plain
147
+ run sees it), e.g. `libdoc(["--specdocformat", "raw", "Browser", "spec.json"])` or
148
+ `libdoc(["BuiltIn", "show", "Log"])`.
149
+
150
+ The project root is always passed explicitly (`--root`, default: the current directory): robotcode otherwise
151
+ derives it from the common base of its path arguments, which fails when they sit on different Windows drives and
152
+ can pick the wrong directory elsewhere (research trap G3)."""
153
+ env = dict(os.environ)
154
+ env.pop("PYTHONPATH", None)
155
+ base = str(Path(root) if root is not None else Path.cwd())
156
+ return subprocess.run([*robotcode_command(python), "--root", base, "libdoc", *args], capture_output=True,
157
+ text=True, timeout=timeout, env=env)