makeover-webview 0.60.0

The webview renderer for makeover-layout. Emits CSS, and is the one renderer that needs no palette: var() is the late binding, so resolution stays with the browser.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
#!/usr/bin/env python3
"""Do the tree's in-house `version` requirements still resolve?

DO NOT EDIT IN PLACE. The master is _private/infra/bootstrap/githooks/internal-deps.py.

Usage:
    internal-deps.py <tree-root> [repo-root] [pushed-sha]

With a repo root, only pairs that repo is on either side of can fail the run;
everything else is reported as a note. Without one, every pair is graded, which
is the whole-tree report:

    python3 internal-deps.py ~/Code

With a pushed sha as well, the run grades TWO views and fails on either:

  working copy   what this machine builds today. The historical check.
  as pushed      the same question asked of the repo's manifests AS THEY EXIST
                 AT THAT COMMIT, against the rest of the tree on disk.

WHY THE SECOND VIEW EXISTS. The first one reads manifests off the filesystem, so
an uncommitted forward-fix makes it grade text that git is not publishing. That
is not hypothetical: on 2026-08-24 mnw-cli's `synckit-client` requirement had
been advanced to "0.9" in the working copy and never committed, this gate printed
`internal deps coherent (42 requirements)`, the push went out, and Sando failed
to resolve `^0.8` against 0.9.0 minutes later. The gate was checking a tree that
was not the tree being published, and nothing distinguished that from real
coherence.

WHAT IT GRADES. Every dependency in the tree that carries both a `git` URL on one
of our forges and a `version` requirement, against the version in the working
copy of the crate that URL names. That is the pairing cargo enforces and the one
that broke: a requirement of "0.11" against a sibling that has moved to 0.14 is
not a warning, it is a graph that will not resolve on any machine.

WHY WORKING COPIES AND NOT REMOTES. `~/Code/.cargo/config.toml` patches every one
of these dependencies to the working copy in the tree, so what is on disk here is
what every local build reads. A bump that has not been pushed yet breaks its
consumers just as thoroughly, and finding that out at push time is the point.
That is why the as-pushed view ADDS a check rather than replacing this one:
grading only the commit would stop catching the unpushed bump that breaks every
build on this machine. The two views answer different questions and both matter.

The rest of the tree is read from disk in both views, deliberately. Reading other
repos' remotes would need a fetch per repo, and the same `[patch]` block means
disk is what a local build resolves against anyway.

WHAT IT DOES NOT GRADE, on purpose:

  crates.io deps      the makeover suite and friends resolve from the registry,
                      where working ahead of a release is normal and a tree
                      version above the published one is not a finding. The
                      sweep's `coherence` check grades those against the index.
  path deps           no version requirement to be wrong about.
  ranges and wildcards  `>=`, `<`, `*` and comma lists are deliberate statements
                      about a span, not a pin that drifts. Counted as unchecked.
"""

import os
import re
import subprocess
import sys
import tomllib

# The forges that make a git URL ours. A dependency on somebody else's git repo
# is not something this tree can forward-fix.
OURS = re.compile(r"(makenot\.work|git\.sr\.ht/~maxmj)", re.I)

# Directories that hold code we do not grade: retired, staged for deletion, or
# not ours. Mirrors the sweep's exclusions rather than inventing a second list.
SKIP_DIRS = {
    "target", ".git", "node_modules", "dist", "vendor",
    "_archive", "_scratch", "trash", "_meta", "vtebench",
}
MAX_DEPTH = 4

DEP_SECTIONS = ("dependencies", "dev-dependencies", "build-dependencies")


def manifests(root):
    """Every Cargo.toml in the tree, shallow-walked."""
    out = []
    stack = [(root, 0)]
    while stack:
        d, depth = stack.pop()
        try:
            entries = list(os.scandir(d))
        except OSError:
            continue
        for e in entries:
            if e.is_file() and e.name == "Cargo.toml":
                out.append(e.path)
            elif e.is_dir() and e.name not in SKIP_DIRS and depth < MAX_DEPTH:
                stack.append((e.path, depth + 1))
    return out


def load(path):
    try:
        with open(path, "rb") as fh:
            return tomllib.load(fh)
    except (OSError, tomllib.TOMLDecodeError):
        return None


def dep_tables(doc):
    """Every dependency table in a manifest, including per-target and workspace."""
    for section in DEP_SECTIONS:
        table = doc.get(section)
        if isinstance(table, dict):
            yield table
    for cfg in (doc.get("target") or {}).values():
        if not isinstance(cfg, dict):
            continue
        for section in DEP_SECTIONS:
            table = cfg.get(section)
            if isinstance(table, dict):
                yield table
    ws = doc.get("workspace") or {}
    table = ws.get("dependencies")
    if isinstance(table, dict):
        yield table


def parse_version(v):
    """A version as a 3-tuple, prerelease dropped. Junk sorts as (0, 0, 0)."""
    core = str(v).split("+")[0].split("-")[0]
    parts = []
    for piece in core.split(".")[:3]:
        try:
            parts.append(int(piece))
        except ValueError:
            parts.append(0)
    while len(parts) < 3:
        parts.append(0)
    return tuple(parts)


def satisfies(req, version):
    """Cargo's default (caret) requirement semantics. None means 'not graded'.

    The rule that matters here is the 0.x one: under 0.1.0 and above, the MINOR
    is the compatibility boundary, which is why a 0.11 requirement rejects 0.14
    outright rather than treating it as a newer patch.
    """
    req = req.strip()
    if not req or any(c in req for c in "<>*,~"):
        return None
    # A prerelease satisfies nothing that does not ask for a prerelease of the
    # same version, so a plain requirement rejects it. This is the shape the
    # maturity ladder produces at beta entry: a sibling at 1.0.0-beta.1 does not
    # resolve for a consumer requiring "1.0", and cargo says so.
    if "-" in str(version).split("+")[0] and "-" not in req:
        return False
    exact = req.startswith("=")
    req = req.lstrip("^=").strip()
    if not req:
        return None
    given = req.split(".")
    try:
        r = [int(p) for p in given[:3]]
    except ValueError:
        return None
    v = parse_version(version)
    if exact:
        return tuple(v[: len(r)]) == tuple(r)
    if r[0] > 0:
        return v[0] == r[0] and v[1:] >= tuple(r[1:] + [0] * (2 - len(r[1:])))
    if len(r) == 1:
        return v[0] == 0
    if r[1] > 0:
        return v[0] == 0 and v[1] == r[1] and v[2] >= (r[2] if len(r) > 2 else 0)
    # 0.0.x: every patch is its own compatibility island.
    if len(r) > 2:
        return v[:3] == (0, 0, r[2])
    return v[0] == 0 and v[1] == 0


def git_lines(repo, *args):
    """Run git in `repo` and return stdout lines, or None if it failed."""
    try:
        out = subprocess.run(
            ["git", "-C", repo, *args],
            capture_output=True, text=True, check=True,
        )
    except (OSError, subprocess.CalledProcessError):
        return None
    return out.stdout.splitlines()


def git_manifests(repo, sha):
    """Repo-relative paths of every Cargo.toml at `sha`, or None if unreadable."""
    lines = git_lines(repo, "ls-tree", "-r", "--name-only", sha)
    if lines is None:
        return None
    out = []
    for rel in lines:
        if os.path.basename(rel) != "Cargo.toml":
            continue
        if any(part in SKIP_DIRS for part in rel.split("/")):
            continue
        out.append(rel)
    return out


def load_at(repo, sha, rel):
    """One manifest as it exists at `sha`. None if missing or unparseable."""
    lines = git_lines(repo, "show", f"{sha}:{rel}")
    if lines is None:
        return None
    try:
        return tomllib.loads("\n".join(lines))
    except tomllib.TOMLDecodeError:
        return None


def pushed_view(docs, repo, sha):
    """`docs` with everything under `repo` replaced by its content at `sha`.

    The rest of the tree stays as it is on disk, which is what a local build
    resolves against either way. Returns None if the commit cannot be read, so
    the caller can skip the view rather than invent a verdict about it.
    """
    rels = git_manifests(repo, sha)
    if rels is None:
        return None
    out = {k: v for k, v in docs.items() if not k.startswith(repo + os.sep)}
    for rel in rels:
        out[os.path.join(repo, rel)] = load_at(repo, sha, rel)
    return out


def analyze(docs, tree):
    """Grade every in-house git+version pair in `docs`.

    Returns (broken, unchecked, absent, graded), where a broken entry is
    (consumer manifest, crate, requirement, version found, provider manifest).
    """
    # Workspace versions first: a member saying `version.workspace = true` gets
    # its number from the root, and reporting it as 0.0.0 would be a false break.
    ws_version = {}
    for p, doc in docs.items():
        if not doc:
            continue
        v = ((doc.get("workspace") or {}).get("package") or {}).get("version")
        if isinstance(v, str):
            ws_version[os.path.dirname(p)] = v

    def resolve_version(manifest_path, pkg):
        v = pkg.get("version")
        if isinstance(v, str):
            return v
        d = os.path.dirname(manifest_path)
        while d.startswith(tree):
            if d in ws_version:
                return ws_version[d]
            d = os.path.dirname(d)
        return None

    # crate name -> (version, manifest path)
    versions = {}
    for p, doc in docs.items():
        if not doc:
            continue
        pkg = doc.get("package")
        if not isinstance(pkg, dict) or not isinstance(pkg.get("name"), str):
            continue
        v = resolve_version(p, pkg)
        if v:
            versions[pkg["name"]] = (v, p)

    broken, unchecked, absent, graded = [], 0, set(), 0
    for p, doc in docs.items():
        if not doc:
            continue
        for table in dep_tables(doc):
            for key, spec in table.items():
                if not isinstance(spec, dict):
                    continue
                git = spec.get("git")
                req = spec.get("version")
                if not isinstance(git, str) or not isinstance(req, str):
                    continue
                if not OURS.search(git):
                    continue
                name = spec.get("package") if isinstance(spec.get("package"), str) else key
                known = versions.get(name)
                if known is None:
                    # A repo that is not on this machine (ripgrow lives on mbp
                    # only). Not a finding: nothing here can be wrong about it.
                    absent.add(name)
                    continue
                verdict = satisfies(req, known[0])
                if verdict is None:
                    unchecked += 1
                    continue
                graded += 1
                if not verdict:
                    broken.append((p, name, req, known[0], known[1]))
    return broken, unchecked, absent, graded


def split_blame(broken, repo):
    """Breaks this push owns, and breaks that were already there."""
    ours, theirs = [], []
    for item in broken:
        consumer_manifest, _name, _req, _have, provider_manifest = item
        mine = repo is not None and (
            consumer_manifest.startswith(repo + os.sep)
            or provider_manifest.startswith(repo + os.sep)
        )
        (ours if mine else theirs).append(item)
    return ours, theirs


def report(broken, repo, tree, label):
    """Print one view's breaks. Returns True if this push has to be refused."""
    ours, theirs = split_blame(broken, repo)

    def rel(path):
        return os.path.relpath(path, tree)

    for consumer_manifest, name, req, have, provider_manifest in ours + theirs:
        print(
            f"  [{label}] {rel(consumer_manifest)}: requires {name} \"{req}\", "
            f"the tree has {have} ({rel(provider_manifest)})",
            file=sys.stderr,
        )
    if repo is None:
        return bool(broken)
    if not ours:
        # Somebody else's skew. Worth seeing, never worth blocking this push on:
        # a gate that fails for a reason the pusher cannot fix is a gate that
        # gets bypassed by reflex, and then it is not a gate.
        if theirs:
            print(
                f"pre-push: [{label}] {len(theirs)} unresolvable requirements "
                "elsewhere in the tree (listed above, not this push's).",
            )
        return False
    return True


def main():
    if len(sys.argv) < 2:
        print(__doc__.strip(), file=sys.stderr)
        return 2
    tree = os.path.realpath(sys.argv[1])
    repo = os.path.realpath(sys.argv[2]) if len(sys.argv) > 2 else None
    sha = sys.argv[3] if len(sys.argv) > 3 else None

    docs = {p: load(p) for p in manifests(tree)}

    views = [("working copy", docs)]
    skipped_push_view = False
    if repo and sha:
        pushed = pushed_view(docs, repo, sha)
        if pushed is None:
            skipped_push_view = True
        else:
            views.append(("as pushed", pushed))

    refuse = False
    summaries = []
    for label, view in views:
        broken, unchecked, absent, graded = analyze(view, tree)
        summaries.append((label, graded, unchecked, absent, bool(broken)))
        if broken and report(broken, repo, tree, label):
            refuse = True

    if refuse:
        print("", file=sys.stderr)
        print(
            "pre-push: this push leaves a dependency that cannot resolve.\n"
            "  A version requirement states which major a consumer was written against,\n"
            "  so bumping a library and fixing its consumers is one pass (CLAUDE.md,\n"
            "  \"a breaking bump of an in-house crate is forward-fixed, in the same pass\").\n"
            "  Fix: bump the requirement in the manifests above, make the consumers\n"
            "  compile, and push them with this one.",
            file=sys.stderr,
        )
        clean = [lbl for lbl, _g, _u, _a, bad in summaries if not bad]
        if clean:
            # The whole point of the second view. Saying which one passed is what
            # turns "it worked on my machine" into a diagnosis.
            print(
                f"  Note: the {clean[0]} view is clean, so the difference is what is\n"
                "  committed. An uncommitted manifest edit is the usual cause.",
                file=sys.stderr,
            )
        return 1

    for label, graded, unchecked, absent, _bad in summaries:
        print(
            f"pre-push: internal deps coherent [{label}] ({graded} requirements"
            + (f", {unchecked} unchecked" if unchecked else "")
            + (f", {len(absent)} crates not in this tree" if absent else "")
            + ")."
        )
    if skipped_push_view:
        # Never silently: a view that did not run must not read as one that passed.
        print("pre-push: could not read the pushed commit; graded the working copy only.")
    return 0


if __name__ == "__main__":
    sys.exit(main())