super-ux 0.43.0 → 0.44.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/CHANGELOG.md +40 -0
- package/package.json +1 -1
- package/plugins/super-ux/scripts/ux_lint.py +54 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.44.0 — 2026-08-17
|
|
4
|
+
|
|
5
|
+
**Three lint codes for the two layers whose claims nothing could check.** `U055`, `U056` and
|
|
6
|
+
`U057`; the linter goes 43 → **54** fixtures, each code carrying the defect it must catch and
|
|
7
|
+
the shapes it must **not**.
|
|
8
|
+
|
|
9
|
+
### A `Coverage` claim is a claim about code (`U055`, `U056`) — closes #6
|
|
10
|
+
|
|
11
|
+
Measured in a real project: five screens carried `partial` in the index while their entries
|
|
12
|
+
named no file, and one said `none — no per-account memberships route exists` about a route a
|
|
13
|
+
task had built the day before. **Two fields of one record contradicted each other for a day,
|
|
14
|
+
and neither was checked against the other.**
|
|
15
|
+
|
|
16
|
+
- `U055` (warn) — a `Coverage` value other than `none` that names no file. A claim about code
|
|
17
|
+
citing no code cannot be checked by a script *or* by a reader, who has nowhere to go to
|
|
18
|
+
disagree.
|
|
19
|
+
- `U056` (error) — a cited path that does not exist. A stale citation is the same defect with
|
|
20
|
+
the means to notice.
|
|
21
|
+
|
|
22
|
+
**The path pattern is deliberately narrow and that was the design decision, not a detail.** It
|
|
23
|
+
requires a slash **and** an extension, so `src/routes/x.tsx:12` matches while *"partial —
|
|
24
|
+
client/server split"* and *"the route is built"* do not. A wider pattern was tried first and
|
|
25
|
+
flagged three correct prose entries — the false positive that gets a rule switched off inside a
|
|
26
|
+
day, taking the real check with it.
|
|
27
|
+
|
|
28
|
+
### A flow's verdict must be measurable, not inherited (`U057`) — closes #7
|
|
29
|
+
|
|
30
|
+
The chain is foundation → flows → screens → scenarios, and audits in practice attach to the two
|
|
31
|
+
**ends**. Flows sit between and are the only layer with no artefact of their own: a flow is a
|
|
32
|
+
path across screens, so the cheap thing is to derive its verdict from theirs — and a derived
|
|
33
|
+
verdict presented as a measured one is how one project's `flows.md` carried **no code verdict
|
|
34
|
+
for 42 flows across three weeks**, its header delegating to an audit that had itself derived
|
|
35
|
+
them, and a later scenario walk refuting that audit on every clause without touching flows.
|
|
36
|
+
|
|
37
|
+
`U057` does not verdict a flow. It reports the flows for which **no verdict can be measured at
|
|
38
|
+
all** — the state that was invisible. A flow naming no screen stays `U010`'s subject, not this
|
|
39
|
+
one.
|
|
40
|
+
|
|
41
|
+
Run against this repository's own chain: clean.
|
|
42
|
+
|
|
3
43
|
## 0.43.0 — 2026-08-17
|
|
4
44
|
|
|
5
45
|
**`ux-flows` has told agents to sweep real products before inventing a flow since 0.35.0,
|
package/package.json
CHANGED
|
@@ -102,6 +102,14 @@ def entry_blocks(text: str, prefix: str) -> dict[str, str]:
|
|
|
102
102
|
return out
|
|
103
103
|
|
|
104
104
|
|
|
105
|
+
# A cited path, deliberately narrow: it must carry a slash AND an extension, so
|
|
106
|
+
# `src/routes/x.tsx:12` matches and prose like "partial — client/server split"
|
|
107
|
+
# or "the route is built" does not. Widening this to any slash-bearing token was
|
|
108
|
+
# tried and flagged three correct prose entries, which is the false positive that
|
|
109
|
+
# gets a rule switched off.
|
|
110
|
+
CITED_PATH = re.compile(r"\b([\w.-]+(?:/[\w.-]+)+\.[A-Za-z][\w]{0,4}(?::\d+)?)")
|
|
111
|
+
|
|
112
|
+
|
|
105
113
|
def screen_blocks(text: str) -> dict[str, str]:
|
|
106
114
|
"""Map SCR-id -> its section body."""
|
|
107
115
|
return entry_blocks(text, "SCR")
|
|
@@ -289,6 +297,33 @@ def main() -> int:
|
|
|
289
297
|
for orphan in sorted(screen_ids - used):
|
|
290
298
|
warn(f"[U011] screens.md: {orphan} is used by no flow (orphan)")
|
|
291
299
|
|
|
300
|
+
# --- A flow's verdict must be measurable, not inherited -------------
|
|
301
|
+
#
|
|
302
|
+
# The layer order is foundation → flows → screens → scenarios, and audits
|
|
303
|
+
# in practice attach to the two ENDS. Flows sit between and are the only
|
|
304
|
+
# layer with no artefact of their own to measure: a flow is a path across
|
|
305
|
+
# screens, so the cheap thing is to derive its verdict from theirs — and a
|
|
306
|
+
# derived verdict presented as a measured one is what let one project's
|
|
307
|
+
# `flows.md` carry no code verdict for 42 flows across three weeks, its
|
|
308
|
+
# header delegating to an audit that had itself derived them.
|
|
309
|
+
#
|
|
310
|
+
# This does not verdict a flow. It reports the flows for which no verdict
|
|
311
|
+
# can be measured at all, which is the state that was invisible.
|
|
312
|
+
for fid, fbody in sorted(entry_blocks(flows, "FLW").items()):
|
|
313
|
+
mine = [b for b in screen_blocks(screens).values()
|
|
314
|
+
if re.search(r"\*\*Used by:\*\*[^\n]*\b" + re.escape(fid) + r"\b", b)]
|
|
315
|
+
if not mine:
|
|
316
|
+
continue # a flow naming no screen is U010's subject, not this one
|
|
317
|
+
cited_anywhere = False
|
|
318
|
+
for b in mine:
|
|
319
|
+
m = re.search(r"\*\*Coverage:\*\*\s*(.+)", b)
|
|
320
|
+
if m and CITED_PATH.search(m.group(1)):
|
|
321
|
+
cited_anywhere = True
|
|
322
|
+
break
|
|
323
|
+
if not cited_anywhere:
|
|
324
|
+
warn(f"[U057] flows.md: {fid} has no screen naming an implementing "
|
|
325
|
+
f"file, so its coverage cannot be measured — only inherited")
|
|
326
|
+
|
|
292
327
|
# --- Scenario traces resolve ---
|
|
293
328
|
if ids(scenarios, "SCN"):
|
|
294
329
|
story_ids = set(ids(foundation, "ST"))
|
|
@@ -317,6 +352,9 @@ def main() -> int:
|
|
|
317
352
|
# --- Screen-level: Figma frames, coverage, drift status ---
|
|
318
353
|
if has_screens:
|
|
319
354
|
fig = figma_enabled(foundation)
|
|
355
|
+
# Cited paths are project-relative, so they resolve against the tree the
|
|
356
|
+
# ux directory sits in — the same derivation `check_links` already uses.
|
|
357
|
+
screens_root = ux.parent.parent if ux.name == "ux" else ux.parent
|
|
320
358
|
for sid, body in screen_blocks(screens).items():
|
|
321
359
|
status_m = re.search(r"\*\*Status:\*\*\s*(designed|built|drifted|retired)", body)
|
|
322
360
|
status = status_m.group(1) if status_m else None
|
|
@@ -335,6 +373,22 @@ def main() -> int:
|
|
|
335
373
|
cov = cov_m.group(1).strip() if cov_m else ""
|
|
336
374
|
if status == "built" and (not cov or cov.lower().startswith("none")):
|
|
337
375
|
warn(f"[U021] screens.md: {sid} is 'built' but has no Coverage")
|
|
376
|
+
# A Coverage value other than `none` is a CLAIM ABOUT CODE, and a claim
|
|
377
|
+
# about code that names no code is unfalsifiable — not by a script and
|
|
378
|
+
# not by a reader, who has nowhere to go to disagree. Measured in a real
|
|
379
|
+
# project: five screens carried `partial` in the index while their
|
|
380
|
+
# entries named no file, and one of them said `none — no route exists`
|
|
381
|
+
# about a route a task had built the day before. Two fields of one
|
|
382
|
+
# record contradicting each other, neither checked against the other.
|
|
383
|
+
if cov and not cov.lower().startswith("none"):
|
|
384
|
+
cited = CITED_PATH.findall(cov)
|
|
385
|
+
if not cited:
|
|
386
|
+
warn(f"[U055] screens.md: {sid} claims Coverage '{cov}' and names no file")
|
|
387
|
+
for rel in cited:
|
|
388
|
+
# The line suffix is part of a citation, not of the path.
|
|
389
|
+
target = (screens_root / rel.split(":", 1)[0])
|
|
390
|
+
if not target.exists():
|
|
391
|
+
err(f"[U056] screens.md: {sid} cites '{rel}', which does not exist")
|
|
338
392
|
|
|
339
393
|
check_vision(ux, vision)
|
|
340
394
|
check_web_surface(screens, flows)
|