@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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/bin/cli.js +12 -0
- package/dist/bin/cli.js.map +1 -1
- package/dist/cli/fidelity.d.ts +5 -0
- package/dist/cli/fidelity.d.ts.map +1 -0
- package/dist/cli/fidelity.js +54 -0
- package/dist/cli/fidelity.js.map +1 -0
- package/dist/cli/verify.d.ts +5 -0
- package/dist/cli/verify.d.ts.map +1 -1
- package/dist/cli/verify.js +77 -12
- package/dist/cli/verify.js.map +1 -1
- package/dist/cli/wizard-config.d.ts +9 -0
- package/dist/cli/wizard-config.d.ts.map +1 -1
- package/dist/cli/wizard-config.js +21 -0
- package/dist/cli/wizard-config.js.map +1 -1
- package/dist/config/settings-registry.d.ts.map +1 -1
- package/dist/config/settings-registry.js +32 -0
- package/dist/config/settings-registry.js.map +1 -1
- package/dist/delivery/fidelity.d.ts +24 -0
- package/dist/delivery/fidelity.d.ts.map +1 -0
- package/dist/delivery/fidelity.js +58 -0
- package/dist/delivery/fidelity.js.map +1 -0
- package/dist/delivery/visual-baseline.d.ts +32 -0
- package/dist/delivery/visual-baseline.d.ts.map +1 -0
- package/dist/delivery/visual-baseline.js +219 -0
- package/dist/delivery/visual-baseline.js.map +1 -0
- package/dist/delivery/visual-gate.d.ts +7 -1
- package/dist/delivery/visual-gate.d.ts.map +1 -1
- package/dist/delivery/visual-gate.js +25 -5
- package/dist/delivery/visual-gate.js.map +1 -1
- package/dist/types/config.d.ts +60 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +19 -0
- package/dist/types/config.js.map +1 -1
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/src/policies/enforcers/visual_verification.py +127 -0
- package/src/policies/schemas/policies/visual-verification.md +18 -0
- 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`.
|