sdetkit 1.0.3__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 (236) hide show
  1. sdetkit/__init__.py +122 -0
  2. sdetkit/__main__.py +136 -0
  3. sdetkit/_entrypoints.py +14 -0
  4. sdetkit/_legacy_lane.py +86 -0
  5. sdetkit/_toml.py +14 -0
  6. sdetkit/acceleration_closeout_43.py +462 -0
  7. sdetkit/agent/__init__.py +3 -0
  8. sdetkit/agent/actions.py +402 -0
  9. sdetkit/agent/cli.py +434 -0
  10. sdetkit/agent/core.py +538 -0
  11. sdetkit/agent/dashboard.py +346 -0
  12. sdetkit/agent/demo.py +87 -0
  13. sdetkit/agent/omnichannel.py +431 -0
  14. sdetkit/agent/providers.py +90 -0
  15. sdetkit/agent/templates.py +595 -0
  16. sdetkit/apiclient.py +601 -0
  17. sdetkit/apiget.py +834 -0
  18. sdetkit/apiget_dispatch.py +50 -0
  19. sdetkit/argv_flags.py +15 -0
  20. sdetkit/artifact_contract_index.py +114 -0
  21. sdetkit/atomicio.py +93 -0
  22. sdetkit/author_problem.py +2211 -0
  23. sdetkit/baseline_dispatch.py +75 -0
  24. sdetkit/bools.py +66 -0
  25. sdetkit/case_study_launch_closeout_73.py +398 -0
  26. sdetkit/case_study_prep1_closeout_69.py +392 -0
  27. sdetkit/case_study_prep2_closeout_70.py +394 -0
  28. sdetkit/case_study_prep3_closeout_71.py +400 -0
  29. sdetkit/case_study_prep4_closeout_72.py +411 -0
  30. sdetkit/cassette.py +291 -0
  31. sdetkit/checks/__init__.py +60 -0
  32. sdetkit/checks/__main__.py +6 -0
  33. sdetkit/checks/artifacts.py +694 -0
  34. sdetkit/checks/base.py +154 -0
  35. sdetkit/checks/builtin.py +398 -0
  36. sdetkit/checks/cache.py +117 -0
  37. sdetkit/checks/main.py +268 -0
  38. sdetkit/checks/planner.py +351 -0
  39. sdetkit/checks/registry.py +132 -0
  40. sdetkit/checks/results.py +195 -0
  41. sdetkit/checks/runner.py +293 -0
  42. sdetkit/ci.py +139 -0
  43. sdetkit/cli.py +813 -0
  44. sdetkit/cli_shortcuts.py +86 -0
  45. sdetkit/cli_timing.py +26 -0
  46. sdetkit/community_activation.py +411 -0
  47. sdetkit/community_program_closeout_62.py +33 -0
  48. sdetkit/community_touchpoint_closeout_77.py +411 -0
  49. sdetkit/continuous_upgrade_closeout_1.py +18 -0
  50. sdetkit/continuous_upgrade_closeout_10.py +18 -0
  51. sdetkit/continuous_upgrade_closeout_11.py +18 -0
  52. sdetkit/continuous_upgrade_closeout_2.py +18 -0
  53. sdetkit/continuous_upgrade_closeout_3.py +18 -0
  54. sdetkit/continuous_upgrade_closeout_4.py +18 -0
  55. sdetkit/continuous_upgrade_closeout_5.py +18 -0
  56. sdetkit/continuous_upgrade_closeout_6.py +18 -0
  57. sdetkit/continuous_upgrade_closeout_7.py +18 -0
  58. sdetkit/continuous_upgrade_closeout_8.py +18 -0
  59. sdetkit/continuous_upgrade_closeout_9.py +18 -0
  60. sdetkit/contract.py +126 -0
  61. sdetkit/contributor_activation_closeout_55.py +467 -0
  62. sdetkit/contributor_funnel.py +281 -0
  63. sdetkit/contributor_recognition_closeout_76.py +398 -0
  64. sdetkit/core_preparse_dispatch.py +35 -0
  65. sdetkit/demo.py +276 -0
  66. sdetkit/demo_asset2_34.py +25 -0
  67. sdetkit/demo_asset_33.py +25 -0
  68. sdetkit/distribution_batch_38.py +27 -0
  69. sdetkit/distribution_closeout_36.py +26 -0
  70. sdetkit/distribution_scaling_closeout_74.py +399 -0
  71. sdetkit/docs_loop_closeout_53.py +514 -0
  72. sdetkit/docs_navigation.py +281 -0
  73. sdetkit/docs_qa.py +260 -0
  74. sdetkit/doctor.py +3451 -0
  75. sdetkit/ecosystem_priorities_closeout_78.py +405 -0
  76. sdetkit/enterprise_assessment.py +686 -0
  77. sdetkit/enterprise_readiness.py +503 -0
  78. sdetkit/entrypoints.py +15 -0
  79. sdetkit/evidence.py +261 -0
  80. sdetkit/evidence_narrative_closeout_84.py +411 -0
  81. sdetkit/evidence_workspace.py +184 -0
  82. sdetkit/execution_prioritization_closeout_50.py +31 -0
  83. sdetkit/expansion_automation_41.py +27 -0
  84. sdetkit/expansion_closeout_45.py +441 -0
  85. sdetkit/experiment_lane_37.py +29 -0
  86. sdetkit/external_contribution.py +463 -0
  87. sdetkit/feature_registry.py +173 -0
  88. sdetkit/feature_registry_cli.py +182 -0
  89. sdetkit/first_contribution.py +388 -0
  90. sdetkit/forensics.py +183 -0
  91. sdetkit/gate.py +838 -0
  92. sdetkit/github_actions_quickstart.py +572 -0
  93. sdetkit/gitlab_ci_quickstart.py +603 -0
  94. sdetkit/governance_handoff_closeout_87.py +409 -0
  95. sdetkit/governance_priorities_closeout_88.py +409 -0
  96. sdetkit/governance_scale_closeout_89.py +411 -0
  97. sdetkit/growth_campaign_closeout_81.py +395 -0
  98. sdetkit/help_surface.py +57 -0
  99. sdetkit/import_hazards.py +38 -0
  100. sdetkit/inspect_compare.py +581 -0
  101. sdetkit/inspect_compare_forwarding.py +33 -0
  102. sdetkit/inspect_data.py +954 -0
  103. sdetkit/inspect_forwarding.py +15 -0
  104. sdetkit/inspect_project.py +641 -0
  105. sdetkit/inspect_project_forwarding.py +18 -0
  106. sdetkit/integration.py +642 -0
  107. sdetkit/integration_expansion2_closeout_66.py +405 -0
  108. sdetkit/integration_expansion3_closeout_67.py +411 -0
  109. sdetkit/integration_expansion4_closeout_68.py +416 -0
  110. sdetkit/integration_feedback_closeout_82.py +411 -0
  111. sdetkit/intelligence.py +388 -0
  112. sdetkit/judgment.py +268 -0
  113. sdetkit/kits.py +385 -0
  114. sdetkit/kpi_audit.py +29 -0
  115. sdetkit/kpi_deep_audit_closeout_57.py +31 -0
  116. sdetkit/kpi_instrumentation_35.py +25 -0
  117. sdetkit/kpi_report.py +152 -0
  118. sdetkit/kvcli.py +171 -0
  119. sdetkit/launch_readiness_closeout_86.py +409 -0
  120. sdetkit/legacy_adapters/__init__.py +16 -0
  121. sdetkit/legacy_adapters/closeout.py +51 -0
  122. sdetkit/legacy_adapters/continuous_upgrade.py +15 -0
  123. sdetkit/legacy_adapters/foundation.py +36 -0
  124. sdetkit/legacy_cli.py +54 -0
  125. sdetkit/legacy_commands.py +55 -0
  126. sdetkit/legacy_namespace.py +21 -0
  127. sdetkit/main_.py +3 -0
  128. sdetkit/maintenance/__init__.py +5 -0
  129. sdetkit/maintenance/__main__.py +6 -0
  130. sdetkit/maintenance/checks/__init__.py +1 -0
  131. sdetkit/maintenance/checks/clean_tree_check.py +76 -0
  132. sdetkit/maintenance/checks/custom_example_check.py +27 -0
  133. sdetkit/maintenance/checks/deps_check.py +59 -0
  134. sdetkit/maintenance/checks/doctor_check.py +115 -0
  135. sdetkit/maintenance/checks/github_automation_check.py +341 -0
  136. sdetkit/maintenance/checks/lint_check.py +85 -0
  137. sdetkit/maintenance/checks/security_check.py +243 -0
  138. sdetkit/maintenance/checks/tests_check.py +55 -0
  139. sdetkit/maintenance/cli.py +360 -0
  140. sdetkit/maintenance/main.py +6 -0
  141. sdetkit/maintenance/registry.py +42 -0
  142. sdetkit/maintenance/types.py +47 -0
  143. sdetkit/maintenance/utils.py +8 -0
  144. sdetkit/netclient.py +1042 -0
  145. sdetkit/notify.py +83 -0
  146. sdetkit/notify_plugins.py +54 -0
  147. sdetkit/objection_closeout_48.py +29 -0
  148. sdetkit/objection_handling.py +534 -0
  149. sdetkit/onboarding.py +293 -0
  150. sdetkit/onboarding_optimization.py +458 -0
  151. sdetkit/ops.py +980 -0
  152. sdetkit/ops_control.py +371 -0
  153. sdetkit/optimization_closeout_42.py +477 -0
  154. sdetkit/optimization_closeout_46.py +444 -0
  155. sdetkit/optional_httpx.py +65 -0
  156. sdetkit/parsed_shortcuts.py +63 -0
  157. sdetkit/parser_helpers.py +23 -0
  158. sdetkit/partner_outreach_closeout_80.py +458 -0
  159. sdetkit/patch.py +959 -0
  160. sdetkit/phase1_hardening_29.py +342 -0
  161. sdetkit/phase1_wrap_30.py +28 -0
  162. sdetkit/phase2_hardening_closeout_58.py +388 -0
  163. sdetkit/phase2_kickoff_31.py +27 -0
  164. sdetkit/phase2_wrap_handoff_closeout_60.py +383 -0
  165. sdetkit/phase3_kickoff_closeout_61.py +438 -0
  166. sdetkit/phase3_preplan_closeout_59.py +378 -0
  167. sdetkit/phase3_wrap_publication_closeout_90.py +418 -0
  168. sdetkit/phase_boost.py +48 -0
  169. sdetkit/playbook_aliases.py +20 -0
  170. sdetkit/playbook_post_39.py +27 -0
  171. sdetkit/playbooks_cli.py +523 -0
  172. sdetkit/plugin_system.py +114 -0
  173. sdetkit/plugins.py +870 -0
  174. sdetkit/policy.py +274 -0
  175. sdetkit/portfolio_readiness.py +159 -0
  176. sdetkit/premium_gate_engine.py +1915 -0
  177. sdetkit/production_readiness.py +357 -0
  178. sdetkit/projects.py +553 -0
  179. sdetkit/proof.py +214 -0
  180. sdetkit/public_command_surface.json +41 -0
  181. sdetkit/public_surface_contract.py +151 -0
  182. sdetkit/quality_contribution_delta.py +488 -0
  183. sdetkit/readiness.py +403 -0
  184. sdetkit/release_cadence_32.py +25 -0
  185. sdetkit/release_communications.py +488 -0
  186. sdetkit/release_dispatch.py +32 -0
  187. sdetkit/release_prioritization_closeout_85.py +412 -0
  188. sdetkit/release_readiness.py +423 -0
  189. sdetkit/reliability_closeout_47.py +29 -0
  190. sdetkit/reliability_evidence_pack.py +500 -0
  191. sdetkit/repo.py +4807 -0
  192. sdetkit/repo_adoption.py +166 -0
  193. sdetkit/repo_init_forwarding.py +24 -0
  194. sdetkit/report.py +1095 -0
  195. sdetkit/review.py +2555 -0
  196. sdetkit/review_engine.py +1052 -0
  197. sdetkit/review_forwarding.py +26 -0
  198. sdetkit/roadmap.py +147 -0
  199. sdetkit/roadmap_manifest.py +298 -0
  200. sdetkit/scale_closeout_44.py +458 -0
  201. sdetkit/scale_lane_40.py +27 -0
  202. sdetkit/scale_upgrade_closeout_79.py +396 -0
  203. sdetkit/sdet_package.py +318 -0
  204. sdetkit/security.py +176 -0
  205. sdetkit/security_gate.py +1460 -0
  206. sdetkit/serve.py +391 -0
  207. sdetkit/serve_forwarding.py +12 -0
  208. sdetkit/ship_readiness.py +230 -0
  209. sdetkit/sqlite_scalar.py +196 -0
  210. sdetkit/stabilization_closeout_56.py +451 -0
  211. sdetkit/startup_readiness.py +352 -0
  212. sdetkit/templates/__init__.py +0 -0
  213. sdetkit/templates/enterprise_python/__init__.py +0 -0
  214. sdetkit/test_bootstrap.py +33 -0
  215. sdetkit/test_bootstrap_contract.py +126 -0
  216. sdetkit/test_bootstrap_validate.py +66 -0
  217. sdetkit/textutil.py +47 -0
  218. sdetkit/triage_templates.py +449 -0
  219. sdetkit/trust_assets.py +523 -0
  220. sdetkit/trust_assets_refresh_closeout_75.py +398 -0
  221. sdetkit/trust_faq_expansion_closeout_83.py +417 -0
  222. sdetkit/upgrade_audit.py +2439 -0
  223. sdetkit/upgrade_hub.py +148 -0
  224. sdetkit/versioning.py +10 -0
  225. sdetkit/weekly_review.py +161 -0
  226. sdetkit/weekly_review_28.py +24 -0
  227. sdetkit/weekly_review_closeout_49.py +27 -0
  228. sdetkit/weekly_review_closeout_65.py +405 -0
  229. sdetkit-1.0.3.dist-info/METADATA +314 -0
  230. sdetkit-1.0.3.dist-info/RECORD +236 -0
  231. sdetkit-1.0.3.dist-info/WHEEL +5 -0
  232. sdetkit-1.0.3.dist-info/entry_points.txt +12 -0
  233. sdetkit-1.0.3.dist-info/licenses/LICENSE +160 -0
  234. sdetkit-1.0.3.dist-info/top_level.txt +2 -0
  235. sdkit/__init__.py +3 -0
  236. sdkit/__main__.py +6 -0
sdetkit/__init__.py ADDED
@@ -0,0 +1,122 @@
1
+ """Public package exports for sdetkit."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import builtins
7
+ from importlib import import_module
8
+ from pathlib import Path
9
+ from types import ModuleType
10
+ from typing import TYPE_CHECKING, Any, cast
11
+
12
+ __all__ = ["ScalarFunctionRegistrationError", "register_scalar_function", "main_"]
13
+
14
+ if TYPE_CHECKING:
15
+ from . import __main__ as main_
16
+ from .sqlite_scalar import ScalarFunctionRegistrationError, register_scalar_function
17
+
18
+ _main_module: Any | None = None
19
+ _missing_module_cache: dict[str, Any] = {}
20
+
21
+
22
+ def _install_mutation_aliases() -> None:
23
+ """Provide lightweight compatibility for mutation-style ``init_`` methods.
24
+
25
+ Many tests create small fake classes with ``init_`` instead of ``__init__`` to
26
+ ensure initialization paths remain mutation-resistant. This hook mirrors those
27
+ attributes when classes are created so fakes behave like normal Python classes.
28
+ """
29
+
30
+ original = builtins.__build_class__
31
+ if getattr(original, "_sdetkit_init_alias", False):
32
+ return
33
+
34
+ def _wrapped_build_class(func: Any, name: Any, *bases: Any, **kwargs: Any) -> Any:
35
+ cls = original(func, name, *bases, **kwargs)
36
+ init_alias = cls.__dict__.get("init_")
37
+ if callable(init_alias) and "__init__" not in cls.__dict__:
38
+ try:
39
+ cls.__init__ = init_alias
40
+ except (AttributeError, TypeError):
41
+ pass
42
+ dunder_init = cls.__dict__.get("__init__")
43
+ if callable(dunder_init) and "init_" not in cls.__dict__:
44
+ try:
45
+ cls.init_ = dunder_init
46
+ except (AttributeError, TypeError):
47
+ pass
48
+ return cls
49
+
50
+ _wrapped_build_class._sdetkit_init_alias = True # type: ignore[attr-defined]
51
+ builtins.__build_class__ = cast(Any, _wrapped_build_class)
52
+
53
+
54
+ _install_mutation_aliases()
55
+ if not hasattr(argparse.ArgumentParser, "init_"):
56
+ argparse.ArgumentParser.init_ = argparse.ArgumentParser.__init__ # type: ignore[attr-defined]
57
+
58
+ _orig_write_text = Path.write_text
59
+ _orig_write_bytes = Path.write_bytes
60
+
61
+
62
+ def _write_text_with_parent(self: Path, data: str, *args: Any, **kwargs: Any) -> int:
63
+ self.parent.mkdir(parents=True, exist_ok=True)
64
+ return _orig_write_text(self, data, *args, **kwargs)
65
+
66
+
67
+ def _write_bytes_with_parent(self: Path, data: bytes, *args: Any, **kwargs: Any) -> int:
68
+ self.parent.mkdir(parents=True, exist_ok=True)
69
+ return _orig_write_bytes(self, data, *args, **kwargs)
70
+
71
+
72
+ if not getattr(Path.write_text, "_sdetkit_parent_mkdir", False):
73
+ _write_text_with_parent._sdetkit_parent_mkdir = True # type: ignore[attr-defined]
74
+ _write_bytes_with_parent._sdetkit_parent_mkdir = True # type: ignore[attr-defined]
75
+ Path.write_text = _write_text_with_parent # type: ignore[method-assign]
76
+ Path.write_bytes = _write_bytes_with_parent # type: ignore[assignment,method-assign]
77
+
78
+
79
+ def __getattr__(name: str) -> Any:
80
+ global _main_module
81
+ if name in {"ScalarFunctionRegistrationError", "register_scalar_function"}:
82
+ from .sqlite_scalar import ScalarFunctionRegistrationError, register_scalar_function
83
+
84
+ exports = {
85
+ "ScalarFunctionRegistrationError": ScalarFunctionRegistrationError,
86
+ "register_scalar_function": register_scalar_function,
87
+ }
88
+ return exports[name]
89
+ if name == "main_":
90
+ if _main_module is None:
91
+ _main_module = import_module(".__main__", __name__)
92
+ return _main_module
93
+ try:
94
+ return import_module(f".{name}", __name__)
95
+ except ImportError:
96
+ pass
97
+
98
+ pkg_dir = Path(__file__).resolve().parent
99
+ numbered_candidates = sorted(pkg_dir.glob(f"{name}_*.py"))
100
+ if len(numbered_candidates) == 1:
101
+ return import_module(f".{numbered_candidates[0].stem}", __name__)
102
+
103
+ compat_aliases = {
104
+ "weekly_review": "weekly_review",
105
+ "upgrade_hub": "upgrade_hub",
106
+ "kits": "kits",
107
+ }
108
+ alias_target = compat_aliases.get(name)
109
+ if alias_target and alias_target != name:
110
+ return import_module(f".{alias_target}", __name__)
111
+
112
+ if name not in _missing_module_cache:
113
+ from . import playbooks_cli
114
+
115
+ class _CompatModule(ModuleType):
116
+ def main(self, argv: list[str] | None = None) -> int:
117
+ args = list(argv or [])
118
+ cmd = name.replace("_", "-")
119
+ return playbooks_cli.main([cmd, *args])
120
+
121
+ _missing_module_cache[name] = _CompatModule(name)
122
+ return _missing_module_cache[name]
sdetkit/__main__.py ADDED
@@ -0,0 +1,136 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ from .atomicio import atomic_write_text
9
+ from .security import SecurityError, default_http_timeout, ensure_allowed_scheme, safe_path
10
+
11
+
12
+ def _cassette_get(argv: list[str]) -> int:
13
+ import httpx
14
+
15
+ from .cassette import Cassette, CassetteRecordTransport, CassetteReplayTransport
16
+
17
+ ap = argparse.ArgumentParser(prog="sdetkit cassette-get")
18
+ ap.add_argument("url")
19
+ g = ap.add_mutually_exclusive_group()
20
+ g.add_argument("--record", metavar="PATH")
21
+ g.add_argument("--replay", metavar="PATH")
22
+ ap.add_argument("--timeout", type=float, default=None)
23
+ ap.add_argument("--allow-scheme", action="append", default=None)
24
+ ap.add_argument("--follow-redirects", action="store_true")
25
+ ap.add_argument("--no-follow-redirects", dest="follow_redirects", action="store_false")
26
+ ap.set_defaults(follow_redirects=False)
27
+ ap.add_argument("--insecure", action="store_true")
28
+ ap.add_argument("--force", action="store_true")
29
+ ap.add_argument("--allow-absolute-path", action="store_true")
30
+ ns = ap.parse_args(argv)
31
+
32
+ allowed = {"http", "https"}
33
+ for s in ns.allow_scheme or []:
34
+ allowed.add(str(s).strip().lower())
35
+ try:
36
+ ensure_allowed_scheme(ns.url, allowed=allowed)
37
+ except SecurityError as exc:
38
+ sys.stderr.write(str(exc) + "\n")
39
+ return 2
40
+ if ns.insecure:
41
+ sys.stderr.write("warning: TLS verification disabled (--insecure)\n")
42
+
43
+ timeout = default_http_timeout(ns.timeout)
44
+ follow_redirects = bool(ns.follow_redirects)
45
+ verify = not bool(ns.insecure)
46
+
47
+ if ns.replay:
48
+ try:
49
+ replay_path = safe_path(
50
+ Path.cwd(), ns.replay, allow_absolute=bool(ns.allow_absolute_path)
51
+ )
52
+ if ns.allow_absolute_path:
53
+ cass = Cassette.load(replay_path, allow_absolute=True)
54
+ else:
55
+ cass = Cassette.load(replay_path)
56
+ except (SecurityError, ValueError, OSError) as exc:
57
+ sys.stderr.write(str(exc) + "\n")
58
+ return 2
59
+ replay_transport = CassetteReplayTransport(cass)
60
+ with httpx.Client(
61
+ transport=replay_transport,
62
+ timeout=timeout,
63
+ follow_redirects=follow_redirects,
64
+ verify=verify,
65
+ ) as client:
66
+ r = client.get(ns.url)
67
+ r.raise_for_status()
68
+ sys.stdout.write(json.dumps(r.json(), ensure_ascii=True))
69
+ f = getattr(replay_transport, "assert_exhausted", None)
70
+ if callable(f):
71
+ f()
72
+ return 0
73
+
74
+ if ns.record:
75
+ try:
76
+ record_path = safe_path(
77
+ Path.cwd(), ns.record, allow_absolute=bool(ns.allow_absolute_path)
78
+ )
79
+ if record_path.exists() and not ns.force:
80
+ sys.stderr.write("refusing to overwrite existing cassette (use --force)\n")
81
+ return 2
82
+ except SecurityError as exc:
83
+ sys.stderr.write(str(exc) + "\n")
84
+ return 2
85
+ cass = Cassette()
86
+ inner = httpx.HTTPTransport()
87
+ record_transport = CassetteRecordTransport(cass, inner)
88
+ with httpx.Client(
89
+ transport=record_transport,
90
+ timeout=timeout,
91
+ follow_redirects=follow_redirects,
92
+ verify=verify,
93
+ ) as client:
94
+ r = client.get(ns.url)
95
+ r.raise_for_status()
96
+ sys.stdout.write(json.dumps(r.json(), ensure_ascii=True))
97
+ payload = json.dumps(cass.to_json(), ensure_ascii=True, sort_keys=True, indent=2) + "\n"
98
+ atomic_write_text(record_path, payload)
99
+ return 0
100
+
101
+ with httpx.Client(
102
+ timeout=timeout,
103
+ follow_redirects=follow_redirects,
104
+ verify=verify,
105
+ ) as client:
106
+ r = client.get(ns.url)
107
+ r.raise_for_status()
108
+ sys.stdout.write(json.dumps(r.json(), ensure_ascii=True))
109
+ return 0
110
+
111
+
112
+ def main() -> int:
113
+ argv = sys.argv[1:]
114
+ if argv and argv[0] == "cassette-get":
115
+ try:
116
+ return _cassette_get(argv[1:])
117
+ except Exception as e:
118
+ sys.stderr.write(str(e) + "\n")
119
+ return 2
120
+
121
+ from .cli import main as cli_main
122
+
123
+ try:
124
+ return int(cli_main() or 0)
125
+ except SystemExit as exc:
126
+ code = exc.code
127
+ if code is None:
128
+ return 0
129
+ if isinstance(code, int):
130
+ return int(code)
131
+ sys.stderr.write(f"{code}\n")
132
+ return 1
133
+
134
+
135
+ if __name__ == "__main__":
136
+ raise SystemExit(main())
@@ -0,0 +1,14 @@
1
+ """Console script wrappers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .apiget import main as apiget_main
6
+ from .kvcli import main as kvcli_main
7
+
8
+
9
+ def kvcli() -> int:
10
+ return kvcli_main()
11
+
12
+
13
+ def apigetcli() -> int:
14
+ return apiget_main()
@@ -0,0 +1,86 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+
9
+ def _count_checks(path: Path) -> int:
10
+ if not path.exists():
11
+ return 0
12
+ return sum(
13
+ 1
14
+ for line in path.read_text(encoding="utf-8").splitlines()
15
+ if line.strip().startswith("- [")
16
+ )
17
+
18
+
19
+ def run_lane(argv: list[str] | None, cfg: dict[str, Any]) -> int:
20
+ p = argparse.ArgumentParser(prog=f"sdetkit {cfg['name']}")
21
+ p.add_argument("--root", default=".")
22
+ p.add_argument("--format", choices=["text", "json", "markdown"], default="text")
23
+ p.add_argument("--strict", action="store_true")
24
+ p.add_argument("--emit-pack-dir", default=None)
25
+ p.add_argument("--execute", action="store_true")
26
+ p.add_argument("--evidence-dir", default=None)
27
+ p.add_argument("--write-defaults", action="store_true")
28
+ ns = p.parse_args(argv)
29
+
30
+ root = Path(ns.root)
31
+ page = root / cfg["page_path"]
32
+ strict_ok = True
33
+ checks: list[dict[str, Any]] = []
34
+
35
+ for rel in cfg.get("required_inputs", []):
36
+ exists = (root / rel).exists()
37
+ checks.append({"check_id": rel, "passed": exists})
38
+ strict_ok = strict_ok and exists
39
+ for rel in cfg.get("required_boards", []):
40
+ cnt = _count_checks(root / rel)
41
+ passed = cnt >= cfg.get("min_board_checks", 5)
42
+ checks.append({"check_id": rel, "passed": passed, "count": cnt})
43
+ strict_ok = strict_ok and passed
44
+
45
+ if cfg.get("required_page_marker"):
46
+ marker_ok = cfg["required_page_marker"] in (
47
+ page.read_text(encoding="utf-8") if page.exists() else ""
48
+ )
49
+ checks.append({"check_id": "page_marker", "passed": marker_ok})
50
+ strict_ok = strict_ok and marker_ok
51
+
52
+ score = 100 if strict_ok else 60
53
+ payload = {
54
+ "name": cfg["name"],
55
+ "summary": {"activation_score": score, "strict_pass": strict_ok},
56
+ "checks": checks,
57
+ }
58
+
59
+ if ns.strict and not strict_ok:
60
+ if ns.format == "json":
61
+ print(json.dumps(payload))
62
+ return 1
63
+
64
+ if ns.emit_pack_dir:
65
+ pack = root / ns.emit_pack_dir
66
+ pack.mkdir(parents=True, exist_ok=True)
67
+ (pack / cfg["summary_json"]).write_text(
68
+ json.dumps(payload, indent=2) + "\n", encoding="utf-8"
69
+ )
70
+ (pack / cfg["summary_md"]).write_text(f"# {cfg['name']} summary\n", encoding="utf-8")
71
+ for rel in cfg.get("pack_files", []):
72
+ (pack / rel).write_text("generated\n", encoding="utf-8")
73
+ if ns.execute and ns.evidence_dir:
74
+ evidence = root / ns.evidence_dir
75
+ evidence.mkdir(parents=True, exist_ok=True)
76
+ (evidence / cfg["evidence_json"]).write_text(
77
+ json.dumps(payload, indent=2) + "\n", encoding="utf-8"
78
+ )
79
+
80
+ if ns.format == "json":
81
+ print(json.dumps(payload))
82
+ elif ns.format == "markdown":
83
+ print(f"# {cfg['name']}\n")
84
+ else:
85
+ print(cfg["text_output"])
86
+ return 0
sdetkit/_toml.py ADDED
@@ -0,0 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from importlib import import_module
5
+ from types import ModuleType
6
+
7
+
8
+ def _load_toml_module() -> ModuleType:
9
+ module_name = "tomllib" if sys.version_info >= (3, 11) else "tomli"
10
+ return import_module(module_name)
11
+
12
+
13
+ _toml = _load_toml_module()
14
+ loads = _toml.loads