ui-design-workbench-cli 0.5.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 (73) hide show
  1. android_resource_resolver.py +370 -0
  2. android_xml_support.py +230 -0
  3. apple_resource_resolver.py +141 -0
  4. coverage_report.py +134 -0
  5. fidelity_adapter_api.py +89 -0
  6. fidelity_adapters.py +508 -0
  7. fidelity_core.py +241 -0
  8. fidelity_platform_adapters.py +902 -0
  9. ir_contracts.py +76 -0
  10. native_render_android.py +160 -0
  11. native_render_apple.py +165 -0
  12. native_render_contracts.py +148 -0
  13. native_render_registry.py +86 -0
  14. quality_common.py +217 -0
  15. render_preview.py +1797 -0
  16. scan_ui.py +1485 -0
  17. scoped_context.py +458 -0
  18. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/SKILL.md +52 -0
  19. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/adapter-api.md +20 -0
  20. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/agent-integrations.md +39 -0
  21. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/cache-protocol.md +61 -0
  22. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/design-modes.md +109 -0
  23. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/fidelity.md +53 -0
  24. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/ir-schema.md +412 -0
  25. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/native-rendering.md +40 -0
  26. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/platform-profiles.json +84 -0
  27. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/platform-standards.md +131 -0
  28. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/platforms.md +40 -0
  29. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/quality-automation.md +77 -0
  30. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/review-workflow.md +135 -0
  31. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/ui-guidance-mode.md +22 -0
  32. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/ui-policy-schema.md +83 -0
  33. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/ui-reviewer.md +175 -0
  34. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/validation.md +34 -0
  35. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/references/workbench-ui.md +108 -0
  36. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/schemas/native-render-state.schema.json +71 -0
  37. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/schemas/ui-agent-job.schema.json +36 -0
  38. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/schemas/ui-graph.schema.json +46 -0
  39. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/schemas/ui-ir.patch.schema.json +36 -0
  40. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/schemas/ui-ir.schema.json +296 -0
  41. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/schemas/uidw-config.schema.json +68 -0
  42. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/android_resource_resolver.py +370 -0
  43. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/android_xml_support.py +230 -0
  44. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/apple_resource_resolver.py +141 -0
  45. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/coverage_report.py +134 -0
  46. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/fidelity_adapter_api.py +89 -0
  47. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/fidelity_adapters.py +508 -0
  48. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/fidelity_core.py +241 -0
  49. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/fidelity_platform_adapters.py +902 -0
  50. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/generate_interaction_matrix.py +166 -0
  51. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/ir_contracts.py +76 -0
  52. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/merge_review_state.py +267 -0
  53. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/native_render_android.py +160 -0
  54. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/native_render_apple.py +165 -0
  55. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/native_render_contracts.py +148 -0
  56. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/native_render_registry.py +86 -0
  57. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/quality_common.py +217 -0
  58. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/render_preview.py +1797 -0
  59. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/scan_ui.py +1485 -0
  60. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/scoped_context.py +458 -0
  61. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/smoke_preview.js +978 -0
  62. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/uidw.py +2821 -0
  63. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/uidw_mcp.py +282 -0
  64. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/validate_platform_profiles.py +93 -0
  65. ui_design_workbench_cli-0.5.0.data/data/share/ui-design-workbench/scripts/visual_regression.py +141 -0
  66. ui_design_workbench_cli-0.5.0.dist-info/METADATA +133 -0
  67. ui_design_workbench_cli-0.5.0.dist-info/RECORD +73 -0
  68. ui_design_workbench_cli-0.5.0.dist-info/WHEEL +5 -0
  69. ui_design_workbench_cli-0.5.0.dist-info/entry_points.txt +3 -0
  70. ui_design_workbench_cli-0.5.0.dist-info/top_level.txt +20 -0
  71. uidw.py +2821 -0
  72. uidw_mcp.py +282 -0
  73. validate_platform_profiles.py +93 -0
uidw_mcp.py ADDED
@@ -0,0 +1,282 @@
1
+ #!/usr/bin/env python3
2
+ """Optional local stdio MCP facade over the deterministic UIDW CLI core."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ import uidw
12
+ from native_render_registry import native_render_status
13
+ from scoped_context import apply_patch_file, build_scoped_context
14
+
15
+
16
+ SERVER_INSTRUCTIONS = (
17
+ "Start with ui_project, then call ui_scope for one screen or a small finding set. "
18
+ "If ui_project returns setupRequired, ask the user only for low, medium, or high, then call "
19
+ "ui_configure with that choice before continuing. "
20
+ "Do not request or embed the complete UI IR when bounded context is sufficient. "
21
+ "Use ui_prepare_job for review, proposal, or implementation handoffs. Review and proposal work "
22
+ "must preserve immutable baseline UI and return sparse ui-ir.patch.json operations; application "
23
+ "source changes require an explicit implementation request. Use ui_build_preview for projection "
24
+ "checks only; do not infer UI/UX findings unless the user explicitly requests a review. "
25
+ "For Android or Apple visual accuracy, call ui_native_status and never describe source projection as a native render. "
26
+ "Native builds, simulators, and captures are explicit-only operations."
27
+ )
28
+
29
+
30
+ def _root(value: str | None, default: Path) -> Path:
31
+ path = Path(value).expanduser().resolve() if value else default.resolve()
32
+ if not path.is_dir():
33
+ raise ValueError(f"Repository directory does not exist: {path}")
34
+ return path
35
+
36
+
37
+ def _ir(root: Path, value: str | None) -> tuple[Path, dict[str, Any]]:
38
+ return uidw.load_ir_argument(uidw.state_paths(root), Path(value).expanduser() if value else None)
39
+
40
+
41
+ def project_summary(repo: str | None, default_repo: Path) -> dict[str, Any]:
42
+ root = _root(repo, default_repo)
43
+ initialization, paths, config = uidw.ensure_initialized(root)
44
+ ir = uidw.load_project_ir(paths) or {}
45
+ configuration = uidw.configuration_context(config)
46
+ setup_required = bool(configuration.get("setupRequired"))
47
+ return {
48
+ "status": initialization.get("status"),
49
+ "cache": initialization.get("initialization", {}).get("status"),
50
+ "repoRoot": str(root),
51
+ "uiIrFile": str(paths["ir"]),
52
+ "contextFile": str(paths["context"]),
53
+ "screens": [{"id": item.get("id"), "name": item.get("name"), "platform": item.get("platform")} for item in ir.get("screens", [])],
54
+ "findingCount": len(ir.get("review", {}).get("audit", {}).get("findings", [])),
55
+ "setupRequired": setup_required,
56
+ "detailLevel": configuration.get("detailLevel"),
57
+ "detailChoices": ["low", "medium", "high"] if setup_required else [],
58
+ "questionsForUser": configuration.get("questionsForUser", []),
59
+ "uiModeEnabled": bool(config.get(uidw.UI_MODE_KEY, {}).get("enabled")),
60
+ "next": (
61
+ "Ask the user to choose low, medium, or high, then call ui_configure."
62
+ if setup_required
63
+ else "Use ui_scope for one screen or a small finding set; do not read the full IR."
64
+ ),
65
+ }
66
+
67
+
68
+ def scoped_payload(
69
+ repo: str | None,
70
+ default_repo: Path,
71
+ ui_ir_file: str | None,
72
+ screen_ids: list[str],
73
+ finding_ids: list[str],
74
+ max_tokens: int,
75
+ ) -> dict[str, Any]:
76
+ root = _root(repo, default_repo)
77
+ ir_path, ir = _ir(root, ui_ir_file)
78
+ return build_scoped_context(
79
+ ir,
80
+ screen_ids=screen_ids,
81
+ finding_ids=finding_ids,
82
+ token_budget=max_tokens,
83
+ ui_ir_file=str(ir_path),
84
+ )
85
+
86
+
87
+ def compact_workbench_result(result: dict[str, Any]) -> dict[str, Any]:
88
+ return {
89
+ key: result.get(key)
90
+ for key in ("version", "status", "previewFile", "url", "workflow", "initialization")
91
+ if result.get(key) is not None
92
+ }
93
+
94
+
95
+ def configure_detail(repo: str | None, default_repo: Path, detail: str) -> dict[str, Any]:
96
+ root = _root(repo, default_repo)
97
+ normalized = detail.strip().lower()
98
+ if normalized not in {"low", "medium", "high"}:
99
+ return {
100
+ "status": "blocked",
101
+ "error": "detail must be low, medium, or high",
102
+ "detailChoices": ["low", "medium", "high"],
103
+ }
104
+ result = uidw.configure_project(root, "set", "detail", normalized)
105
+ configuration = result.get("configuration", {})
106
+ return {
107
+ "status": result.get("status"),
108
+ "repoRoot": result.get("repoRoot"),
109
+ "setupRequired": bool(configuration.get("setupRequired")),
110
+ "detailLevel": configuration.get("detailLevel"),
111
+ "mockData": configuration.get(uidw.MOCK_DATA_KEY),
112
+ "contextFile": result.get("contextFile"),
113
+ "next": "Use ui_scope, then ui_build_preview when the requested screens are ready.",
114
+ }
115
+
116
+
117
+ def build_preview(
118
+ repo: str | None,
119
+ default_repo: Path,
120
+ ui_ir_file: str | None,
121
+ output_dir: str | None,
122
+ level: str,
123
+ ) -> dict[str, Any]:
124
+ root = _root(repo, default_repo)
125
+ paths = uidw.state_paths(root)
126
+ configuration = uidw.configuration_context(uidw.load_config(paths["config"]))
127
+ if configuration.get("setupRequired"):
128
+ return {
129
+ "status": "needs-setup",
130
+ "setupRequired": True,
131
+ "detailChoices": ["low", "medium", "high"],
132
+ "questionsForUser": configuration.get("questionsForUser", []),
133
+ "next": "Ask the user to choose low, medium, or high, then call ui_configure.",
134
+ }
135
+
136
+ ir_path, _ = _ir(root, ui_ir_file)
137
+ destination = Path(output_dir).expanduser().resolve() if output_dir else ir_path.parent / "workbench"
138
+ cached_ir = paths["ir"].resolve()
139
+ allow_draft = ir_path.resolve() == cached_ir
140
+ try:
141
+ result = uidw.build_workbench(
142
+ root,
143
+ ir_path,
144
+ destination,
145
+ level,
146
+ allow_draft,
147
+ "generic",
148
+ False,
149
+ None,
150
+ None,
151
+ None,
152
+ None,
153
+ None,
154
+ )
155
+ except (RuntimeError, ValueError, OSError) as exc:
156
+ return {
157
+ "status": "blocked",
158
+ "error": str(exc),
159
+ "uiIrFile": str(ir_path),
160
+ "next": "Resolve the reported IR or fidelity problem, then call ui_build_preview again.",
161
+ }
162
+ return compact_workbench_result(result)
163
+
164
+
165
+ def create_server(default_repo: Path, name: str = "UI Design Workbench") -> Any:
166
+ try:
167
+ from mcp.server import MCPServer
168
+ except ImportError as exc:
169
+ raise RuntimeError(
170
+ "MCP support is optional. Install it with `pip install \"ui-design-workbench-cli[mcp]\"` "
171
+ "or `pip install \"mcp>=2,<3\"`. The regular uidw CLI does not require it."
172
+ ) from exc
173
+
174
+ server = MCPServer(
175
+ name,
176
+ version=uidw.CLI_VERSION,
177
+ instructions=SERVER_INSTRUCTIONS,
178
+ )
179
+
180
+ @server.tool()
181
+ def ui_project(repo: str = "") -> dict[str, Any]:
182
+ """Get a compact cached UI catalog and the next bounded action."""
183
+ return project_summary(repo or None, default_repo)
184
+
185
+ @server.tool()
186
+ def ui_configure(detail: str, repo: str = "") -> dict[str, Any]:
187
+ """Set the required low, medium, or high preview detail after the user chooses it."""
188
+ return configure_detail(repo or None, default_repo, detail)
189
+
190
+ @server.tool()
191
+ def ui_scope(
192
+ screen_ids: list[str],
193
+ finding_ids: list[str],
194
+ repo: str = "",
195
+ ui_ir_file: str = "",
196
+ max_tokens: int = 4000,
197
+ ) -> dict[str, Any]:
198
+ """Read complete UI evidence only for named screens/findings."""
199
+ return scoped_payload(repo or None, default_repo, ui_ir_file or None, screen_ids, finding_ids, max_tokens)
200
+
201
+ @server.tool()
202
+ def ui_prepare_job(
203
+ kind: str,
204
+ finding_ids: list[str],
205
+ repo: str = "",
206
+ ui_ir_file: str = "",
207
+ output_file: str = "",
208
+ scope: str = "current",
209
+ direct: bool = False,
210
+ ) -> dict[str, Any]:
211
+ """Prepare a portable bounded job plus context and ui-ir.patch.json."""
212
+ root = _root(repo or None, default_repo)
213
+ ir_path, ir = _ir(root, ui_ir_file or None)
214
+ if kind not in {"expert", "proposal", "implementation"}:
215
+ raise ValueError("kind must be expert, proposal, or implementation")
216
+ output = Path(output_file).expanduser().resolve() if output_file else ir_path.parent / f"ui-agent-job-{kind}.json"
217
+ return uidw.prepare_agent_job(root, ir_path, ir, kind, output, identifiers=finding_ids, scope=scope, direct=direct)
218
+
219
+ @server.tool()
220
+ def ui_apply_patch(
221
+ patch_file: str,
222
+ repo: str = "",
223
+ ui_ir_file: str = "",
224
+ output_file: str = "",
225
+ ) -> dict[str, Any]:
226
+ """Validate and apply sparse review/proposal operations without touching baseline UI."""
227
+ root = _root(repo or None, default_repo)
228
+ ir_path, _ = _ir(root, ui_ir_file or None)
229
+ output = Path(output_file).expanduser().resolve() if output_file else ir_path.with_name("ui-ir.patched.json")
230
+ return apply_patch_file(ir_path, Path(patch_file).expanduser(), output)
231
+
232
+ @server.tool()
233
+ def ui_build_preview(
234
+ repo: str = "",
235
+ ui_ir_file: str = "",
236
+ output_dir: str = "",
237
+ level: str = "quick",
238
+ ) -> dict[str, Any]:
239
+ """Render and projection-check the HTML workbench; never auto-review UX."""
240
+ return build_preview(repo or None, default_repo, ui_ir_file or None, output_dir or None, level)
241
+
242
+ @server.tool()
243
+ def ui_native_status(repo: str = "", platform: str = "all") -> dict[str, Any]:
244
+ """Discover native Android/Apple render providers without executing builds or simulators."""
245
+ if platform not in {"all", "android", "apple"}:
246
+ raise ValueError("platform must be all, android, or apple")
247
+ root = _root(repo or None, default_repo)
248
+ return native_render_status(root, uidw.state_paths(root)["native"], platform)
249
+
250
+ @server.tool()
251
+ def ui_fidelity(identifier: str, repo: str = "", ui_ir_file: str = "") -> dict[str, Any]:
252
+ """Explain one node/evidence id without returning the complete IR."""
253
+ root = _root(repo or None, default_repo)
254
+ _, ir = _ir(root, ui_ir_file or None)
255
+ return uidw.fidelity_command(ir, "explain", identifier, None, "json")
256
+
257
+ return server
258
+
259
+
260
+ def run_server(default_repo: Path, name: str = "UI Design Workbench") -> None:
261
+ create_server(default_repo, name).run()
262
+
263
+
264
+ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
265
+ parser = argparse.ArgumentParser(description="Local stdio MCP server for UI Design Workbench")
266
+ parser.add_argument("--repo", type=Path, default=Path.cwd())
267
+ parser.add_argument("--name", default="UI Design Workbench")
268
+ return parser.parse_args(argv)
269
+
270
+
271
+ def main() -> int:
272
+ args = parse_args()
273
+ try:
274
+ run_server(args.repo.resolve(), args.name)
275
+ except (RuntimeError, ValueError, OSError) as exc:
276
+ print(f"uidw mcp: {exc}", file=sys.stderr)
277
+ return 3
278
+ return 0
279
+
280
+
281
+ if __name__ == "__main__":
282
+ raise SystemExit(main())
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env python3
2
+ """Validate platform-profile selection and node standard references in UI IR."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from quality_common import framework_adapters, node_screen_map, platform_family, profile_catalog, read_json, target_families, write_json
12
+
13
+
14
+ def validate_profiles(ir: dict[str, Any]) -> dict[str, Any]:
15
+ catalog = profile_catalog()
16
+ profiles = catalog.get("profiles", {})
17
+ adapters = catalog.get("frameworkAdapters", {})
18
+ families = target_families(ir)
19
+ selected = ir.get("design", {}).get("standardProfiles", {})
20
+ issues: list[dict[str, Any]] = []
21
+ resolved: dict[str, Any] = {}
22
+
23
+ if not families:
24
+ supported = ", ".join(sorted({str(item.get("family")) for item in profiles.values() if isinstance(item, dict) and item.get("family")}))
25
+ issues.append({"code": "target-platform-missing", "severity": "error", "message": f"design.targetPlatforms must resolve to a supported family: {supported}."})
26
+
27
+ for adapter_id in sorted(framework_adapters(ir)):
28
+ adapter = adapters[adapter_id]
29
+ if adapter.get("requiresExplicitTarget") and not ir.get("design", {}).get("targetPlatforms"):
30
+ issues.append({"code": "framework-target-ambiguous", "severity": "error", "adapter": adapter_id, "message": f"{adapter_id} requires explicit design.targetPlatforms."})
31
+
32
+ for family in sorted(families):
33
+ selection = selected.get(family)
34
+ if not isinstance(selection, dict):
35
+ issues.append({"code": "profile-missing", "severity": "error", "family": family, "message": f"Missing design.standardProfiles.{family}."})
36
+ continue
37
+ profile_id = selection.get("id")
38
+ if profile_id == "project":
39
+ if not selection.get("reason"):
40
+ issues.append({"code": "project-profile-unreasoned", "severity": "error", "family": family, "message": "A project profile needs a reason and evidence."})
41
+ else:
42
+ resolved[family] = {**selection, "family": family, "standardRefPrefixes": ["project."]}
43
+ continue
44
+ profile = profiles.get(profile_id)
45
+ if not profile or profile.get("family") != family:
46
+ issues.append({"code": "profile-invalid", "severity": "error", "family": family, "profile": profile_id, "message": f"Profile {profile_id!r} does not belong to {family}."})
47
+ continue
48
+ resolved[family] = {**profile, "id": profile_id, "source": selection.get("source")}
49
+
50
+ decisions = {item.get("id") for item in ir.get("design", {}).get("decisions", []) if isinstance(item, dict)}
51
+ screen_families = {str(screen.get("id")): platform_family(screen.get("platform")) for screen in ir.get("screens", [])}
52
+ node_screens = node_screen_map(ir)
53
+ for node_id, node in ir.get("nodes", {}).items():
54
+ if node.get("type") == "spacer" or node.get("decisionId") in decisions:
55
+ continue
56
+ shared = str(node.get("standardRef", ""))
57
+ per_platform = node.get("standardRefs", {}) if isinstance(node.get("standardRefs"), dict) else {}
58
+ node_families = {screen_families.get(screen_id) for screen_id in node_screens.get(str(node_id), set())}
59
+ node_families.discard(None)
60
+ node_families = node_families or families
61
+ for family in sorted(node_families):
62
+ profile = resolved.get(family)
63
+ if not profile:
64
+ continue
65
+ ref = str(per_platform.get(family, shared))
66
+ prefixes = tuple(profile.get("standardRefPrefixes", []))
67
+ if not ref.startswith(prefixes):
68
+ issues.append({"code": "standard-ref-mismatch", "severity": "error", "family": family, "node": node_id, "ref": ref or None, "allowedPrefixes": list(prefixes), "message": f"Node {node_id} lacks a {family} standard reference or decision."})
69
+
70
+ return {
71
+ "version": 1,
72
+ "status": "blocked" if any(item["severity"] == "error" for item in issues) else "pass",
73
+ "targetFamilies": sorted(families),
74
+ "frameworkAdapters": sorted(framework_adapters(ir)),
75
+ "resolvedProfiles": resolved,
76
+ "issues": issues,
77
+ }
78
+
79
+
80
+ def main() -> int:
81
+ parser = argparse.ArgumentParser(description=__doc__)
82
+ parser.add_argument("ir", help="Path to ui-ir.json")
83
+ parser.add_argument("--output", required=True, help="Path for platform-profile-report.json")
84
+ parser.add_argument("--strict", action="store_true", help="Exit non-zero when the report is blocked")
85
+ args = parser.parse_args()
86
+ report = validate_profiles(read_json(args.ir))
87
+ write_json(args.output, report)
88
+ print(Path(args.output).resolve())
89
+ return 2 if args.strict and report["status"] != "pass" else 0
90
+
91
+
92
+ if __name__ == "__main__":
93
+ sys.exit(main())