@miller-tech/uap 1.138.1 → 1.139.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.
Files changed (39) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/bin/cli.js +12 -0
  3. package/dist/bin/cli.js.map +1 -1
  4. package/dist/cli/fidelity.d.ts +5 -0
  5. package/dist/cli/fidelity.d.ts.map +1 -0
  6. package/dist/cli/fidelity.js +54 -0
  7. package/dist/cli/fidelity.js.map +1 -0
  8. package/dist/cli/verify.d.ts +5 -0
  9. package/dist/cli/verify.d.ts.map +1 -1
  10. package/dist/cli/verify.js +77 -12
  11. package/dist/cli/verify.js.map +1 -1
  12. package/dist/cli/wizard-config.d.ts +9 -0
  13. package/dist/cli/wizard-config.d.ts.map +1 -1
  14. package/dist/cli/wizard-config.js +21 -0
  15. package/dist/cli/wizard-config.js.map +1 -1
  16. package/dist/config/settings-registry.d.ts.map +1 -1
  17. package/dist/config/settings-registry.js +32 -0
  18. package/dist/config/settings-registry.js.map +1 -1
  19. package/dist/delivery/fidelity.d.ts +24 -0
  20. package/dist/delivery/fidelity.d.ts.map +1 -0
  21. package/dist/delivery/fidelity.js +58 -0
  22. package/dist/delivery/fidelity.js.map +1 -0
  23. package/dist/delivery/visual-baseline.d.ts +32 -0
  24. package/dist/delivery/visual-baseline.d.ts.map +1 -0
  25. package/dist/delivery/visual-baseline.js +219 -0
  26. package/dist/delivery/visual-baseline.js.map +1 -0
  27. package/dist/delivery/visual-gate.d.ts +7 -1
  28. package/dist/delivery/visual-gate.d.ts.map +1 -1
  29. package/dist/delivery/visual-gate.js +25 -5
  30. package/dist/delivery/visual-gate.js.map +1 -1
  31. package/dist/types/config.d.ts +60 -0
  32. package/dist/types/config.d.ts.map +1 -1
  33. package/dist/types/config.js +19 -0
  34. package/dist/types/config.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
  37. package/src/policies/enforcers/visual_verification.py +127 -0
  38. package/src/policies/schemas/policies/visual-verification.md +18 -0
  39. package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env python3
2
+ """visual-verification gate: under MAX fidelity, a commit that changes UI files
3
+ must be visually verified first.
4
+
5
+ The visual gate (`uap verify`) renders every entry page in a real browser,
6
+ checks it is not blank/static/erroring, scores its appearance, and — when it
7
+ runs — writes `.uap/visual/last-verdict.json` = {passed, mode, at}. This enforcer
8
+ fires on `git commit`: if maximum fidelity is active and the staged change set
9
+ touches UI files, it BLOCKS unless a *passing* verdict exists that is newer than
10
+ every staged UI file (i.e. the UI on disk has actually been observed since it
11
+ last changed).
12
+
13
+ This is the non-bypassable backstop for agentic/opencode sessions that edit
14
+ files directly and never go through `uap deliver` or the Stop-hook `uap verify`.
15
+
16
+ Active only when fidelity is `max` (`.uap.json` `fidelity.mode` or `UAP_FIDELITY`).
17
+ Escape hatch (justify in the commit message): UAP_VISUAL_GATE_OFF=1.
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ import os
23
+ import sys
24
+ from pathlib import Path
25
+
26
+ sys.path.insert(0, str(Path(__file__).parent))
27
+ from _common import emit, parse_cli, run, worktree_root # noqa: E402
28
+
29
+ UI_EXT = {".css", ".scss", ".sass", ".less", ".tsx", ".jsx", ".vue", ".svelte", ".html", ".astro"}
30
+ UI_DIR_PREFIXES = ("web/", "src/dashboard/", "public/")
31
+ MARKER = ".uap/visual/last-verdict.json"
32
+
33
+
34
+ def _is_commit(cmd_lower: str) -> bool:
35
+ return "git commit" in cmd_lower and " --amend" not in cmd_lower
36
+
37
+
38
+ def _fidelity_max(root: Path) -> bool:
39
+ env = (os.environ.get("UAP_FIDELITY") or "").strip().lower()
40
+ if env in ("max", "maximum", "strict", "high"):
41
+ return True
42
+ if env in ("standard", "std", "normal", "off"):
43
+ return False
44
+ for cfg in (root / ".uap.json", root.parent / ".uap.json"):
45
+ if cfg.exists():
46
+ try:
47
+ mode = (json.loads(cfg.read_text()).get("fidelity", {}).get("mode") or "").lower()
48
+ return mode in ("max", "maximum")
49
+ except Exception: # noqa: BLE001
50
+ return False
51
+ return False
52
+
53
+
54
+ def _staged_ui_files(root: Path) -> list[str]:
55
+ rc, out, _ = run(["git", "diff", "--cached", "--name-only"], cwd=root)
56
+ if rc != 0:
57
+ return []
58
+ files = [f for f in out.splitlines() if f]
59
+ return [
60
+ f
61
+ for f in files
62
+ if Path(f).suffix.lower() in UI_EXT or any(f.startswith(p) for p in UI_DIR_PREFIXES)
63
+ ]
64
+
65
+
66
+ def main() -> None:
67
+ op, args = parse_cli()
68
+ cmd = (args.get("command") or args.get("cmd") or "").strip()
69
+ if not cmd or op.lower() != "bash":
70
+ emit(True, "not a Bash command")
71
+ if not _is_commit(cmd.lower()):
72
+ emit(True, "not a git commit")
73
+ if os.environ.get("UAP_VISUAL_GATE_OFF") == "1":
74
+ emit(True, "UAP_VISUAL_GATE_OFF=1 override (justify in commit msg)")
75
+
76
+ root = worktree_root()
77
+ if not _fidelity_max(root):
78
+ emit(True, "fidelity is not max — visual gate advisory only")
79
+
80
+ ui = _staged_ui_files(root)
81
+ if not ui:
82
+ emit(True, "no UI files staged — visual verification not required")
83
+
84
+ marker = root / MARKER
85
+ if not marker.exists():
86
+ emit(
87
+ False,
88
+ "visual-verification: UI files are staged under max fidelity but they have "
89
+ "never been visually verified.\n"
90
+ f" staged UI: {', '.join(sorted(ui)[:6])}{' …' if len(ui) > 6 else ''}\n"
91
+ " -> run `uap verify` (renders the UI, checks it looks right), then commit.",
92
+ )
93
+
94
+ try:
95
+ verdict = json.loads(marker.read_text())
96
+ except Exception: # noqa: BLE001
97
+ verdict = {}
98
+ if not verdict.get("passed"):
99
+ emit(
100
+ False,
101
+ "visual-verification: the last visual verification did NOT pass.\n"
102
+ " -> fix the rendered UI and re-run `uap verify` until it passes, then commit.",
103
+ )
104
+
105
+ verified_at = float(verdict.get("at") or 0)
106
+ stale = []
107
+ for f in ui:
108
+ p = root / f
109
+ try:
110
+ if p.exists() and p.stat().st_mtime > verified_at + 1: # 1s grace
111
+ stale.append(f)
112
+ except OSError:
113
+ continue
114
+ if stale:
115
+ emit(
116
+ False,
117
+ "visual-verification: these UI files changed AFTER the last passing visual "
118
+ "verification — the current look is unverified.\n"
119
+ f" stale: {', '.join(sorted(stale)[:6])}{' …' if len(stale) > 6 else ''}\n"
120
+ " -> re-run `uap verify` to observe the new UI, then commit.",
121
+ )
122
+
123
+ emit(True, f"visual verification current ({len(ui)} UI file(s) verified)")
124
+
125
+
126
+ if __name__ == "__main__":
127
+ main()
@@ -50,3 +50,21 @@ After any delivery with a rendered artifact: run `uap verify --dir <project>`,
50
50
  Read the `.uap/visual/*.png` screenshots, apply design/aesthetic judgment
51
51
  (spacing, palette, hierarchy, motion feel), fix what looks wrong, and re-verify.
52
52
  "It passed the gates" is not "it looks right".
53
+
54
+ ## Commit-time enforcer (max fidelity)
55
+
56
+ Under maximum fidelity (`.uap.json` `fidelity.mode: max` or `UAP_FIDELITY=max`),
57
+ the Python enforcer `visual_verification.py` fires on `git commit` and **blocks**
58
+ the commit when UI files are staged but have not been visually verified since
59
+ they last changed:
60
+
61
+ - `uap verify` writes `.uap/visual/last-verdict.json` (`{passed, mode, at}`)
62
+ whenever the visual gate actually renders.
63
+ - The enforcer blocks if that marker is missing, records a non-passing verdict,
64
+ or is older than any staged UI file's on-disk mtime (the look changed after it
65
+ was last observed).
66
+
67
+ This is the **non-bypassable backstop** for agentic / opencode / direct-edit
68
+ sessions that never run `uap deliver` or the Stop-hook `uap verify`. It is
69
+ INACTIVE (fails open) when fidelity is `standard`. Escape hatch (justify in the
70
+ commit message): `UAP_VISUAL_GATE_OFF=1`.