super-ux 0.30.2 → 0.32.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 +115 -0
- package/README.md +36 -16
- package/bin/super-ux.js +23 -7
- package/cursor/rules/copywriting.mdc +65 -0
- package/cursor/rules/vision.mdc +67 -0
- package/package.json +1 -1
- package/plugins/super-ux/scripts/brand_lint.py +101 -17
- package/plugins/super-ux/scripts/ux_doctor.py +1 -1
- package/plugins/super-ux/scripts/ux_lint.py +57 -1
- package/templates/README.md +22 -5
- package/templates/claude-rule.md +5 -3
- package/templates/vision-rule.md +17 -0
- package/templates/vision.md +74 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,120 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.32.0 — 2026-08-10
|
|
4
|
+
|
|
5
|
+
A structural audit of 0.31.0 found twenty-two defects that 3427 green checks
|
|
6
|
+
could not see, because the suite verified shape and never once verified
|
|
7
|
+
composition. This release closes them and turns each finding class into a
|
|
8
|
+
check that fails.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **The system map linked `brand-contract.md`, and every skill links the
|
|
13
|
+
map.** `test/sync_references.py` copies the transitive closure of a skill's
|
|
14
|
+
links, so one pointer put all nine brand contracts inside every UX skill —
|
|
15
|
+
App Store guidance riding along in `ux-foundation`. The map now names
|
|
16
|
+
contracts and links none. `ux-flows`, `ux-foundation` and `ux-scenarios`
|
|
17
|
+
drop from 19 shipped contracts to 10, `brand-voice` from 19 to 10,
|
|
18
|
+
`copywriting` from 20 to 11; `ux-audit` keeps both shelves and now links
|
|
19
|
+
them deliberately, because its `copy` scope reads them.
|
|
20
|
+
- **`vision` shipped in 0.31.0 connected to nothing.** It is now in the
|
|
21
|
+
system map's pipeline, file table, skill list and command list; it points
|
|
22
|
+
back at the map; `ux_lint.py` checks its nine sections, its anti-vision and
|
|
23
|
+
whether its alignment rule was ever installed; `ux_doctor.py` reads its
|
|
24
|
+
contract marker; `templates/vision.md` seeds it; `cursor/rules/vision.mdc`
|
|
25
|
+
carries it to Cursor; `/ux-init` offers it as step 0.
|
|
26
|
+
- **`/ux` called itself the only command a user needs and could not reach
|
|
27
|
+
three of seven skills.** `vision`, `brand-voice` and `copywriting` now have
|
|
28
|
+
routing rows, status rows and menu entries. The action menu is renumbered
|
|
29
|
+
1..15.
|
|
30
|
+
- **The vision hard rule had no single source.** It now lives in
|
|
31
|
+
`templates/vision-rule.md`, and `validate_hard_rule_copies` is driven by a
|
|
32
|
+
pair list covering both rules instead of one.
|
|
33
|
+
- **Three commands told the reader to run a script nothing installed.**
|
|
34
|
+
`/ux-rule` seeds `doctor.py` beside `lint.py`, `/brand-init` seeds
|
|
35
|
+
`docs/brand/lint.py`, and `/ux-doctor` no longer claims `/ux-rule` "seeds
|
|
36
|
+
both scripts" when it seeded one.
|
|
37
|
+
- **Numbers that had agreed with themselves for months.** 181 practices
|
|
38
|
+
against a catalog of 206; "31 deterministic checks" against a linter
|
|
39
|
+
emitting 33; the heuristic range written as `PRN-01..10`, `..16`, `..21`
|
|
40
|
+
and `..24` in six files; "four skills" and "four agent-requested rules"
|
|
41
|
+
against seven of each; `plugin.json` and `marketplace.json` naming five of
|
|
42
|
+
seven skills; a documented update command that omitted two skills and
|
|
43
|
+
recommended the bare `skills update` that shadows the plugin.
|
|
44
|
+
- **`bin/super-ux.js`** — a Russian-only fallback message in an otherwise
|
|
45
|
+
English CLI; the routing-block offer firing on the `--cursor` path and not
|
|
46
|
+
from the menu; `--help` describing a skeleton three categories smaller than
|
|
47
|
+
the one it writes; and a `done:` line that counted rules and called it the
|
|
48
|
+
whole install.
|
|
49
|
+
- **`brand_lint.py`, four defects found by pointing it at real prose.**
|
|
50
|
+
`B030` read `BP-079..090`, `NIST SP 800-63B` and `Apple HIG 2025` as
|
|
51
|
+
unsourced claims. The literal extractor ignored which quote opened a string
|
|
52
|
+
and read the gaps between template interpolations as copy — 598 fragments
|
|
53
|
+
from one file. `B022` rescanned a file once per registry row. `B024` had no
|
|
54
|
+
allowance for declared proper nouns, acronyms, sentence boundaries or
|
|
55
|
+
escape sequences. The registry parser could not hold a string containing a
|
|
56
|
+
pipe, or one with leading or trailing space.
|
|
57
|
+
|
|
58
|
+
### Added
|
|
59
|
+
|
|
60
|
+
- **Three gates that ask for composition, not shape.**
|
|
61
|
+
`validate_stated_numbers` recomputes every count written in prose against
|
|
62
|
+
the artifact it counts. `validate_skill_parity` asks for each skill by name
|
|
63
|
+
in five places — its directory, a Cursor rule, the system map, both
|
|
64
|
+
manifest descriptions, and `commands/ux.md`. `validate_seeded_scripts`
|
|
65
|
+
proves every script an instruction names is copied there by some command.
|
|
66
|
+
Each was verified against a planted defect before being trusted.
|
|
67
|
+
- `validate_commands` now requires all fifteen commands, not eight;
|
|
68
|
+
`validate_templates` requires the vision skeleton and both rule snippets.
|
|
69
|
+
- `cursor/rules/copywriting.mdc` — the Cursor channel shipped four of seven
|
|
70
|
+
domains.
|
|
71
|
+
- **super-ux now runs its own chain.** `CLAUDE.md` with all three hard rules,
|
|
72
|
+
`docs/ux/` (vision, foundation, flows, screens, 15 scenarios covering the
|
|
73
|
+
installer TUI) and `docs/brand/` (peer-builder pack, terminology, a facts
|
|
74
|
+
table where every row names the command that recomputes it, channels, and a
|
|
75
|
+
string registry generated from the source). Both linters exit clean. Six of
|
|
76
|
+
the defects above were found by writing them.
|
|
77
|
+
- `docs/superpowers/backlog.md` and `verification.md` — the board and the
|
|
78
|
+
ledger the pipeline reads at stage 0 and writes at stage 8.
|
|
79
|
+
- Four `B030` regression fixtures, so the identifier, standard, year and
|
|
80
|
+
real-figure cases each have a test that has been watched fail.
|
|
81
|
+
|
|
82
|
+
### Changed
|
|
83
|
+
|
|
84
|
+
- `practice-selection.md`'s `ALWAYS` set is `PRN-01..24`. PRN-22..24 are the
|
|
85
|
+
verbal heuristics; they read as brand rules and were therefore filed as
|
|
86
|
+
optional, but any product with text has them.
|
|
87
|
+
- `templates/README.md` — the map seeded into every project now carries the
|
|
88
|
+
vision row, the `docs/brand/` sibling and `doctor.py`.
|
|
89
|
+
- The `ux-contract` gains an optional `vision.md` section. Still v4: the file
|
|
90
|
+
is additive and optional.
|
|
91
|
+
|
|
92
|
+
## 0.31.0 — 2026-08-06
|
|
93
|
+
|
|
94
|
+
### Added
|
|
95
|
+
- **`vision` skill + `/vision`** — the layer above `ux-foundation`. Foundation
|
|
96
|
+
answers who uses the product and why; vision answers what the product **is**
|
|
97
|
+
and what it refuses to become. Writes `docs/ux/vision.md` in nine layers —
|
|
98
|
+
essence, core idea, system behaviour, the user's role, principles that each
|
|
99
|
+
name a rejected alternative, the **anti-vision**, horizon, the one sentence,
|
|
100
|
+
and an alignment test — then installs that test as a rule so later features
|
|
101
|
+
are checked against it.
|
|
102
|
+
|
|
103
|
+
The chain now reads **vision -> foundation -> flows -> scenarios -> audit**.
|
|
104
|
+
|
|
105
|
+
### Notes
|
|
106
|
+
- Ported from a Cursor-only `vision-generator`. Two things changed on the way in.
|
|
107
|
+
It wrote its guardian rule to a **hardcoded `.cursor/skills/` path** (three
|
|
108
|
+
occurrences); it now installs into whichever instruction file the project
|
|
109
|
+
actually uses — `CLAUDE.md`, `AGENTS.md` or `GEMINI.md` — because a rule
|
|
110
|
+
installed where the running agent cannot see it is worse than no rule: absence
|
|
111
|
+
looks identical to compliance. And the document moved to `docs/ux/vision.md`,
|
|
112
|
+
into the chain the rest of this plugin maintains, rather than beside it in the
|
|
113
|
+
project root.
|
|
114
|
+
- The skill raises a contradiction with `foundation.md` as a finding rather than
|
|
115
|
+
smoothing it over. Two documents that disagree are worse than one that is
|
|
116
|
+
wrong, because teams follow whichever they read last.
|
|
117
|
+
|
|
3
118
|
## 0.30.2 — 2026-08-05
|
|
4
119
|
|
|
5
120
|
### Added
|
package/README.md
CHANGED
|
@@ -18,7 +18,8 @@ used as the checklist for evidence-backed audits of the code.
|
|
|
18
18
|
|
|
19
19
|
```mermaid
|
|
20
20
|
flowchart LR
|
|
21
|
-
|
|
21
|
+
V["Vision<br/>essence · principles<br/>anti-vision"] --> F["Foundation<br/>personas · JTBD<br/>journeys · stories"]
|
|
22
|
+
F --> L["Flows<br/>task analysis<br/>+ branches"]
|
|
22
23
|
L --> S["Screens<br/>states · elements<br/>Figma frames"]
|
|
23
24
|
S --> C["Scenarios<br/>action → response<br/>alt + error paths"]
|
|
24
25
|
C --> B["Build UI<br/>only now"]
|
|
@@ -26,6 +27,7 @@ flowchart LR
|
|
|
26
27
|
A --> P["Fix plan<br/>Freq × Severity<br/>× Solvability"]
|
|
27
28
|
P --> B
|
|
28
29
|
B -.->|same change| C
|
|
30
|
+
V -.->|alignment check| B
|
|
29
31
|
```
|
|
30
32
|
|
|
31
33
|
Every layer traces to the one above it. New product? Build it forward. Existing
|
|
@@ -69,15 +71,15 @@ routing is the agent's job.
|
|
|
69
71
|
npx super-ux --cursor /path/to/your/project
|
|
70
72
|
```
|
|
71
73
|
|
|
72
|
-
Copies the rules into `.cursor/rules/` (one always-on hard rule +
|
|
73
|
-
agent-requested rules), seeds `docs/ux/`, and installs the
|
|
74
|
+
Copies the rules into `.cursor/rules/` (one always-on hard rule + seven
|
|
75
|
+
agent-requested rules), seeds `docs/ux/`, and installs the linters. An existing
|
|
74
76
|
scenario base is never overwritten; re-run with `--force` after a release to
|
|
75
77
|
refresh rules and linter only.
|
|
76
78
|
|
|
77
79
|
### Any agent (70+, via the skills CLI)
|
|
78
80
|
|
|
79
81
|
```sh
|
|
80
|
-
npx skills add ssheleg/super-ux # all
|
|
82
|
+
npx skills add ssheleg/super-ux # all seven skills, current project
|
|
81
83
|
npx skills add ssheleg/super-ux -g # user-global
|
|
82
84
|
npx skills add ssheleg/super-ux --skill ux-audit # one skill
|
|
83
85
|
```
|
|
@@ -127,7 +129,7 @@ Commands: `/brand` (status → one recommended action), `/brand-init`,
|
|
|
127
129
|
python3 docs/brand/lint.py
|
|
128
130
|
```
|
|
129
131
|
|
|
130
|
-
|
|
132
|
+
33 deterministic checks (`B001`..`B073`) — banned words, one action under two names, a figure
|
|
131
133
|
with no sourced fact, a field over its limit with the locale coefficient
|
|
132
134
|
applied, blocked AI crawlers, keyword stuffing, humor on a billing screen,
|
|
133
135
|
a locale that lags without saying so. Exit 0 clean, 1 warnings, 2 errors.
|
|
@@ -200,19 +202,24 @@ compliance table. Full protocol:
|
|
|
200
202
|
|
|
201
203
|
## What's inside
|
|
202
204
|
|
|
203
|
-
|
|
205
|
+
Seven skills, one entry point, and a set of contracts they all obey. Every
|
|
206
|
+
one of them is reachable from `/ux` — a skill the entry point cannot route
|
|
207
|
+
to is a skill nobody runs.
|
|
204
208
|
|
|
205
209
|
| Piece | Purpose |
|
|
206
210
|
|---|---|
|
|
211
|
+
| skill `vision` | What the product **is** (`docs/ux/vision.md`) — the layer above the chain, never to be confused with `scenarios.md`, which says what it **does**: essence, core idea, system behaviour, the user's role, principles with a rejected side, the **anti-vision**, horizon, one sentence, and an alignment test later features are checked against. Installs that check into the project's own instruction file |
|
|
207
212
|
| skill `ux-foundation` | The WHY layer (`docs/ux/foundation.md`): personas, jobs to be done with forces, customer journey maps, user stories with Given/When/Then acceptance criteria, the monetization model |
|
|
208
213
|
| skill `ux-flows` | The HOW layer + the UI map: `docs/ux/flows.md` (task analysis, mermaid flows referencing screens by ID) and `docs/ux/screens.md` — every screen and state with its Figma frame, wireframe, code coverage, scenarios and resources. Also heuristic evaluation and traced redesign proposals |
|
|
209
214
|
| skill `ux-scenarios` | `docs/ux/scenarios.md`: use-case scenarios (action → observable response, alt and error paths) covering every flow node and edge, `Traces:` to stories and flows, validated for conflicts, coverage and traceability |
|
|
210
215
|
| skill `ux-audit` | Batched audit with full context: code vs every scenario plus its story's acceptance criteria; verdicts PASS / PARTIAL / FAIL / BLOCKED with `file:line` evidence; depths `quick` / `standard` / `deep`; a `coverage` scope that audits the chain itself |
|
|
216
|
+
| skill `brand-voice` | `docs/brand/`: the pack and its five axes, the words the product owns and bans, canonical facts, the per-surface register, locales — six shipped voice packs, each declaring the degeneration it collapses into when overdone |
|
|
217
|
+
| skill `copywriting` | Writes in that voice and never writes *to* it: interface strings, errors, empty states, landing and pricing pages, posts, changelogs, store listings, ads, lifecycle email. A missing term or an unsourced number is reported, never invented |
|
|
211
218
|
| `/ux` | **The one command**: sets up whatever is missing, reports status across every layer, then offers only the applicable actions with one marked recommended. Idempotent |
|
|
212
|
-
| `/ux-init` `/ux-foundation` `/ux-flows` `/ux-update` `/ux-audit` `/ux-rule` `/ux-lint` | Direct controls for when you know exactly what you want; `/ux-rule` installs
|
|
219
|
+
| `/vision` `/ux-init` `/ux-foundation` `/ux-flows` `/ux-update` `/ux-audit` `/ux-rule` `/ux-lint` `/ux-doctor` · `/brand` `/brand-init` `/brand-update` `/brand-lint` `/copy` | Direct controls for when you know exactly what you want; `/ux-rule` installs both hard rules and seeds `lint.py` + `doctor.py`; `/brand-init` seeds `docs/brand/` and its linter |
|
|
213
220
|
| `docs/ux/lint.py` + `/ux-lint` | The deterministic half: missing Figma frames, unresolved SCR/story traces, orphans, built screens without coverage, index desync, ID gaps, broken links. Stdlib-only, exit 1 on problems — wire it into CI so drift can't merge |
|
|
214
|
-
| `cursor/rules/*.mdc` | The same methodology for Cursor: one always-on hard rule +
|
|
215
|
-
| `templates/` | Seeds for `docs/ux/`: foundation, flows, screens, scenario base, the folder README, the audit-report skeleton
|
|
221
|
+
| `cursor/rules/*.mdc` | The same methodology for Cursor: one always-on hard rule + seven agent-requested rules (vision, foundation, flows, scenarios, audit, brand voice, copywriting) |
|
|
222
|
+
| `templates/` | Seeds for `docs/ux/`: the vision skeleton, foundation, flows, screens, scenario base, the folder README, and the audit-report skeleton. Both hard-rule snippets live here as their single source — `claude-rule.md` (scenario-first) and `vision-rule.md` (vision alignment) — and the validator fails if a command's embedded copy drifts from them. Seeds for `docs/brand/`: voice, terminology, facts, channels, the string registry, a locale delta, and its folder README |
|
|
216
223
|
|
|
217
224
|
The contracts every skill reads:
|
|
218
225
|
|
|
@@ -222,8 +229,8 @@ The contracts every skill reads:
|
|
|
222
229
|
| [system-map.md](plugins/super-ux/skills/references/system-map.md) | The whole system on one page — pipeline, files, skills, companions, and the four sync rules; every skill points here |
|
|
223
230
|
| [ux_doctor.py](plugins/super-ux/scripts/ux_doctor.py) | Contract doctor — reports mixed or stale contract versions across a project's artifacts, files the tooling cannot find under their contract names, and audits produced against a base that is not there. `/ux-lint` checks a chain against itself; this checks it against the contract. Installed as `docs/ux/doctor.py`, read-only unless `--fix` |
|
|
224
231
|
| [best-practices-index.md](plugins/super-ux/skills/references/best-practices-index.md) | Generated tag index over the catalog — tag → ids, id → title. Read it to decide which entries to open; regenerated by `plugins/super-ux/scripts/bp_index.py` and checked for drift by the validator |
|
|
225
|
-
| [ux-design-principles.md](plugins/super-ux/skills/references/ux-design-principles.md) | How the agent thinks: the design pipeline (forward and backwards), task analysis, flow rules, heuristics PRN-01..
|
|
226
|
-
| [best-practices.md](plugins/super-ux/skills/references/best-practices.md) | Living, tag-indexed catalog of
|
|
232
|
+
| [ux-design-principles.md](plugins/super-ux/skills/references/ux-design-principles.md) | How the agent thinks: the design pipeline (forward and backwards), task analysis, flow rules, heuristics PRN-01..24, the improvement procedure, anti-patterns |
|
|
233
|
+
| [best-practices.md](plugins/super-ux/skills/references/best-practices.md) | Living, tag-indexed catalog of 206 proven practices — subscription-app laws, mobile/web/voice guidance (Apple HIG 2025, M3 Expressive, NN/g, Baymard, WCAG 2.2), monetization economics (RevenueCat/PLG 2025 benchmarks, ASO, freemium boundaries), web funnels end to end (landing, pricing, checkout, dunning, cancel) and web2app (paid handoff, deferred deep links, storefront rules), motion and page weight (HTTP Archive field data, W3C sustainability), accessibility as it actually fails (WebAIM Million, EAA/ADA exposure), frustration telemetry, gamification and trend governance, growth loops and referral mechanics, empty states, authentication (NIST SP 800-63B rev 4) and form recovery, motion craft and perceived quality, the defaults that make an interface read as generated, interface state, locale and platform surfaces (Web Interface Guidelines), visual craft, Figma structure |
|
|
227
234
|
| [practice-selection.md](plugins/super-ux/skills/references/practice-selection.md) | The deterministic bridge: product profile → mandatory consideration sets → per-artifact checklists → a compliance table where every pulled practice gets a verdict. No silent skips, no cargo cult |
|
|
228
235
|
| [component-guidelines.md](plugins/super-ux/skills/references/component-guidelines.md) | Which control for which job (radios/select/switch, sheet/alert, modal/disclosure, combobox, nav bar/rail, FAB, dates, toasts) and the platform rules — Apple HIG, Material 3, W3C ARIA APG, GOV.UK |
|
|
229
236
|
| [visual-identity.md](plugins/super-ux/skills/references/visual-identity.md) | The visual layer and its owner: one style pack for the whole product, where it's recorded, how it meets Figma and code, and the division of labor with the craft floors |
|
|
@@ -231,15 +238,28 @@ The contracts every skill reads:
|
|
|
231
238
|
|
|
232
239
|
## Keeping installs current
|
|
233
240
|
|
|
234
|
-
|
|
235
|
-
the plugin reloads):
|
|
241
|
+
One command, every channel (run after a release, then restart the Claude Code
|
|
242
|
+
session so the plugin reloads):
|
|
236
243
|
|
|
237
244
|
```sh
|
|
238
|
-
|
|
239
|
-
claude plugin update super-ux@super-ux && \
|
|
240
|
-
npx --yes skills update ux-audit ux-flows ux-foundation ux-scenarios --global --yes
|
|
245
|
+
npx --yes sshlg-skills@latest update
|
|
241
246
|
```
|
|
242
247
|
|
|
248
|
+
It updates the Claude Code plugin **and** the agent copies, and clears any
|
|
249
|
+
plain copy under `~/.claude/skills/` that would shadow the plugin.
|
|
250
|
+
|
|
251
|
+
Per-plugin, only when the launcher is unavailable:
|
|
252
|
+
|
|
253
|
+
```sh
|
|
254
|
+
claude plugin marketplace update super-ux && claude plugin update super-ux@super-ux
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Do not run a bare `npx skills update <skill>` for a skill you installed as a
|
|
258
|
+
plugin.** Without an explicit `--agent` list the skills CLI detects Claude Code
|
|
259
|
+
and re-creates `~/.claude/skills/<skill>` as a plain copy, which shadows the
|
|
260
|
+
plugin and serves its frozen version forever. Nothing reports this: the plugin
|
|
261
|
+
updates, the copy does not, and the copy is what loads.
|
|
262
|
+
|
|
243
263
|
Cursor rules and the seeded `docs/ux/lint.py` are per-project (Cursor has no
|
|
244
264
|
global rules directory) — refresh each project you use:
|
|
245
265
|
|
package/bin/super-ux.js
CHANGED
|
@@ -20,7 +20,7 @@ const REPO = 'ssheleg/super-ux';
|
|
|
20
20
|
|
|
21
21
|
const MENU_ITEMS = [
|
|
22
22
|
{ key: 'skills', label: 'Skills for any AI agent (Claude Code, Codex, Cursor, 70+ — opens agent picker)' },
|
|
23
|
-
{ key: 'cursor', label: 'Cursor rules
|
|
23
|
+
{ key: 'cursor', label: 'Cursor rules + docs/ux skeleton + docs/brand pack + linters, into a project' },
|
|
24
24
|
{ key: 'claude', label: 'Claude Code plugin (skills + /ux commands, user-global)' },
|
|
25
25
|
];
|
|
26
26
|
|
|
@@ -35,9 +35,12 @@ Usage:
|
|
|
35
35
|
Menu items (select any combination, 'a' = all):
|
|
36
36
|
1. Skills for any AI agent (Claude Code, Codex, Cursor, 70+) — delegates to
|
|
37
37
|
'npx skills add ${REPO}' with its agent/global/project picker.
|
|
38
|
-
2. Cursor rules: cursor/rules/*.mdc -> <project>/.cursor/rules
|
|
39
|
-
docs/ux skeleton
|
|
40
|
-
|
|
38
|
+
2. Cursor rules: cursor/rules/*.mdc -> <project>/.cursor/rules/, plus the
|
|
39
|
+
docs/ux skeleton, the docs/brand pack, and all three linters
|
|
40
|
+
(docs/ux/lint.py, docs/ux/doctor.py, docs/brand/lint.py). Existing
|
|
41
|
+
scenario base and brand pack are NEVER overwritten; existing rule files
|
|
42
|
+
are skipped unless --force. docs/ux/vision.md is not seeded: an empty
|
|
43
|
+
vision reads as a decided one — write it with the vision skill.
|
|
41
44
|
3. Claude Code plugin (skills + /ux commands, user-global) — runs
|
|
42
45
|
'claude plugin marketplace add ${REPO}' + 'claude plugin install' when
|
|
43
46
|
the claude CLI is available, otherwise prints the /plugin commands.`);
|
|
@@ -59,6 +62,7 @@ function installCursor(target, force) {
|
|
|
59
62
|
|
|
60
63
|
let installed = 0;
|
|
61
64
|
let skipped = 0;
|
|
65
|
+
let seeded = 0;
|
|
62
66
|
const rules = fs.readdirSync(rulesSrc).filter((f) => f.endsWith('.mdc')).sort();
|
|
63
67
|
if (rules.length === 0) fail(`no .mdc rules found in ${rulesSrc}`);
|
|
64
68
|
|
|
@@ -84,6 +88,7 @@ function installCursor(target, force) {
|
|
|
84
88
|
} else {
|
|
85
89
|
fs.copyFileSync(path.join(ROOT, 'templates', `${tpl}.md`), dst);
|
|
86
90
|
console.log(`seed: ${dst}`);
|
|
91
|
+
seeded += 1;
|
|
87
92
|
}
|
|
88
93
|
}
|
|
89
94
|
// The brand pack lives beside the UX chain, not inside it: it also governs
|
|
@@ -98,6 +103,7 @@ function installCursor(target, force) {
|
|
|
98
103
|
} else {
|
|
99
104
|
fs.copyFileSync(path.join(ROOT, 'templates', 'brand', `${tpl}.md`), dst);
|
|
100
105
|
console.log(`seed: ${dst}`);
|
|
106
|
+
seeded += 1;
|
|
101
107
|
}
|
|
102
108
|
}
|
|
103
109
|
{
|
|
@@ -105,6 +111,7 @@ function installCursor(target, force) {
|
|
|
105
111
|
if (!fs.existsSync(dst)) {
|
|
106
112
|
fs.copyFileSync(path.join(ROOT, 'templates', 'brand', 'locale.md'), dst);
|
|
107
113
|
console.log(`seed: ${dst}`);
|
|
114
|
+
seeded += 1;
|
|
108
115
|
}
|
|
109
116
|
}
|
|
110
117
|
|
|
@@ -131,7 +138,12 @@ function installCursor(target, force) {
|
|
|
131
138
|
}
|
|
132
139
|
}
|
|
133
140
|
|
|
134
|
-
|
|
141
|
+
// Report what happened, not one third of it: the old line counted rules
|
|
142
|
+
// only, so a run that wrote twenty files announced eight.
|
|
143
|
+
console.log(
|
|
144
|
+
`done: ${installed} rule(s) installed, ${skipped} skipped, ` +
|
|
145
|
+
`${seeded} doc(s) seeded, linters synced`
|
|
146
|
+
);
|
|
135
147
|
}
|
|
136
148
|
|
|
137
149
|
function run(cmd, args) {
|
|
@@ -329,6 +341,10 @@ async function menu() {
|
|
|
329
341
|
if (keys.includes('cursor')) installCursor(cursorDir, false);
|
|
330
342
|
if (keys.includes('claude')) installClaudePlugin();
|
|
331
343
|
if (keys.includes('skills')) installSkillsCli();
|
|
344
|
+
|
|
345
|
+
// Same offer the --cursor flag path makes. Two doors into one install that
|
|
346
|
+
// behave differently is how a feature comes to exist for half its users.
|
|
347
|
+
offerRouters();
|
|
332
348
|
}
|
|
333
349
|
|
|
334
350
|
/**
|
|
@@ -349,8 +365,8 @@ function offerRouters() {
|
|
|
349
365
|
);
|
|
350
366
|
if (r.status !== 0) {
|
|
351
367
|
console.log(
|
|
352
|
-
'\
|
|
353
|
-
'
|
|
368
|
+
'\nTo have these skills apply by default in every project, add the\n' +
|
|
369
|
+
"family's routing block to your agent's global instructions:\n\n" +
|
|
354
370
|
' npx --yes sshlg-skills routers --member super-ux\n'
|
|
355
371
|
);
|
|
356
372
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Write or rewrite any text a user reads (interface strings, errors, empty states, landing and pricing pages, posts, changelogs, store listings, ads, email): apply when asked to write copy, rewrite a headline or CTA, fix microcopy, or when a draft reads machine-made"
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# copywriting — write it in the product's own voice
|
|
7
|
+
|
|
8
|
+
`brand-voice` decides how the product speaks. This is the pass that writes
|
|
9
|
+
in it. The two are not interchangeable: defining a voice mid-sentence to
|
|
10
|
+
finish a button is how a product ends up with six voices.
|
|
11
|
+
|
|
12
|
+
## First action, every time
|
|
13
|
+
|
|
14
|
+
Read the pack: `docs/brand/voice.md`, `terminology.md`, and the
|
|
15
|
+
`channels.md` record for the surface being written. **No pack? Stop and run
|
|
16
|
+
`/brand-init`.** Guessing the voice and being wrong costs more than the
|
|
17
|
+
pause.
|
|
18
|
+
|
|
19
|
+
**Never write to `docs/brand/`.** A term missing from the dictionary, or a
|
|
20
|
+
number with no row in `facts.md`, is **reported** — never invented to finish
|
|
21
|
+
the sentence. Adding it is `brand-voice`'s decision, not this pass's.
|
|
22
|
+
|
|
23
|
+
## Rules that hold in every voice
|
|
24
|
+
|
|
25
|
+
- **Every figure comes from `facts.md`.** No sourced fact, no number. Never
|
|
26
|
+
fabricate a statistic, a quote, or an expert.
|
|
27
|
+
- **One action keeps one name** across button, confirmation, toast, history,
|
|
28
|
+
notification and accessible name. Search `strings.md` before naming one.
|
|
29
|
+
- **No humor, exclamation marks or emoji** on error, destructive confirm,
|
|
30
|
+
billing or paywall surfaces — including in a playful voice.
|
|
31
|
+
- **Errors say what happened and how to recover**, in the user's words, and
|
|
32
|
+
never blame the user.
|
|
33
|
+
- **A register is a delta on the voice axes**, never a crossing of the
|
|
34
|
+
invariants recorded in `voice.md`.
|
|
35
|
+
- **Every new interface string gets a `strings.md` row**: key, `file:line`,
|
|
36
|
+
and the scenario it serves. A string with no scenario is a candidate for
|
|
37
|
+
deletion, not translation.
|
|
38
|
+
- **Claims reach a consequence.** "AI-powered" is a mechanism; what the user
|
|
39
|
+
stops doing is the copy.
|
|
40
|
+
|
|
41
|
+
## The any-other-SaaS test
|
|
42
|
+
|
|
43
|
+
Could this sentence sit unchanged on a competitor's page? Then it says
|
|
44
|
+
nothing. Rewrite it until it could not.
|
|
45
|
+
|
|
46
|
+
Machine-made tells to strip: tricolons that add nothing, "seamless /
|
|
47
|
+
effortless / unlock / elevate / robust", em-dash cadence in every paragraph,
|
|
48
|
+
"it's not just X, it's Y", hedges stacked two deep, and a closing sentence
|
|
49
|
+
that summarizes what was just said.
|
|
50
|
+
|
|
51
|
+
## Localization
|
|
52
|
+
|
|
53
|
+
A locale is not a translation of the English: the length coefficient, the
|
|
54
|
+
address form, and the dead idioms are recorded per locale. A locale that
|
|
55
|
+
lags the primary says so rather than pretending to be current.
|
|
56
|
+
|
|
57
|
+
## Check
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
python3 docs/brand/lint.py
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Exit 0 clean, 1 warnings, 2 errors. Clean means *checkable*, not *good* —
|
|
64
|
+
tone drift, unproven claims and a voice that overshot its declared failure
|
|
65
|
+
mode are judged by `/ux-audit copy`.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Product vision (docs/ux/vision.md): apply when defining what the product is and what it refuses to become, when a proposed feature feels off-strategy, or when a scope argument has no document that settles it"
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# vision — what the product is, and what it refuses to become
|
|
7
|
+
|
|
8
|
+
The layer above the chain. `foundation.md` answers **who** uses the product
|
|
9
|
+
and **why**; `vision.md` answers **what the product is**. Do not confuse it
|
|
10
|
+
with `scenarios.md`, the other layer whose name starts with "what":
|
|
11
|
+
scenarios say what the product **does**, step by step. A feature can satisfy
|
|
12
|
+
every scenario and still violate the anti-vision — that case is the whole
|
|
13
|
+
reason this layer exists.
|
|
14
|
+
|
|
15
|
+
## Before writing a word
|
|
16
|
+
|
|
17
|
+
Read the project, not its README: source entry points, the schema, the
|
|
18
|
+
routing table, the live UI, `package.json`, and `docs/ux/foundation.md` if
|
|
19
|
+
it exists. **A vision written from the README alone describes the README.**
|
|
20
|
+
The gap between what a project claims and what its code does is usually
|
|
21
|
+
where the real vision is hiding, and naming that gap is often the most
|
|
22
|
+
valuable thing this pass does.
|
|
23
|
+
|
|
24
|
+
## The document — nine sections, these headings, in this order
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
## 1. Essence one sentence; survives a rewrite on another stack
|
|
28
|
+
## 2. Core idea an observation about the world, not a problem statement
|
|
29
|
+
## 3. What the system does continuous behaviour, verbs not nouns
|
|
30
|
+
## 4. The user's role what the user becomes: directs, judges, decides
|
|
31
|
+
## 5. Principles "we do X, not Y" — Y is something reasonable people pick
|
|
32
|
+
## 6. Anti-vision what it refuses to become, named product by product
|
|
33
|
+
## 7. Horizon a direction for two to three years, never a roadmap
|
|
34
|
+
## 8. The one sentence if someone remembers exactly one thing
|
|
35
|
+
## 9. The alignment test three to five questions a feature must pass
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`**Status:** draft | approved` and `**Last reviewed:** YYYY-MM-DD` at the
|
|
39
|
+
top. A section with no rejected alternative is a slogan; a vision with no
|
|
40
|
+
anti-vision settles no argument.
|
|
41
|
+
|
|
42
|
+
## The half that is actually the point
|
|
43
|
+
|
|
44
|
+
Writing the document is half the job. The other half is the alignment rule
|
|
45
|
+
in this project's own instruction file (`.cursor/rules/`, `AGENTS.md` or
|
|
46
|
+
`CLAUDE.md` — whichever this project reads), so that later features get
|
|
47
|
+
checked against the vision instead of the vision being written once and
|
|
48
|
+
never read. **A vision nothing reads is a document, not a constraint.**
|
|
49
|
+
|
|
50
|
+
Before planning any new feature, check it against the anti-vision and the
|
|
51
|
+
alignment test. Aligned → proceed, saying in one line which part it serves.
|
|
52
|
+
Misaligned → stop before writing code, name the conflict quoting the layer
|
|
53
|
+
it contradicts, and offer two paths: reshape the feature, or amend the
|
|
54
|
+
vision and say what that costs. Do not pick one silently.
|
|
55
|
+
|
|
56
|
+
**Do NOT trigger for** bug fixes, refactors, dependency work, tests, docs,
|
|
57
|
+
or anything with no user-facing surface. A vision check on a typo fix is how
|
|
58
|
+
a team learns to skip the check that matters.
|
|
59
|
+
|
|
60
|
+
## Check
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
python3 docs/ux/lint.py
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
It reports missing sections, an approved vision with an empty anti-vision,
|
|
67
|
+
and a `vision.md` whose alignment rule was never installed.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "super-ux",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "Scenario-driven UI development for AI agents (Claude Code, Cursor, 70+ agents): a versioned design chain in docs/ux/, a scenario-first hard rule, a deterministic drift linter, and evidence-backed UX audits. This package is the installer CLI.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"super-ux": "bin/super-ux.js"
|
|
@@ -89,7 +89,12 @@ def table_rows(text: str) -> list[list[str]]:
|
|
|
89
89
|
line = line.strip()
|
|
90
90
|
if not line.startswith("|") or not line.endswith("|"):
|
|
91
91
|
continue
|
|
92
|
-
|
|
92
|
+
# A markdown cell escapes a literal pipe as `\|`. Splitting on a raw
|
|
93
|
+
# pipe therefore tore `Select [e.g. 1,3 | all | q]:` into three cells
|
|
94
|
+
# and pointed a registry row at a file called "all". A string registry
|
|
95
|
+
# that cannot hold a string containing a pipe cannot describe a CLI.
|
|
96
|
+
parts = re.split(r"(?<!\\)\|", line.strip("|"))
|
|
97
|
+
cells = [c.strip().replace("\\|", "|") for c in parts]
|
|
93
98
|
if all(re.fullmatch(r":?-{2,}:?", c) for c in cells if c):
|
|
94
99
|
continue
|
|
95
100
|
rows.append(cells)
|
|
@@ -297,14 +302,50 @@ WEAK_LABELS = {
|
|
|
297
302
|
"learn more", "get started", "continue",
|
|
298
303
|
}
|
|
299
304
|
|
|
300
|
-
LITERAL_RE = re.compile(r"""["']([
|
|
305
|
+
LITERAL_RE = re.compile(r"""(["'`])((?:(?!\1)[^\n]){3,200})\1""")
|
|
306
|
+
|
|
307
|
+
# A template literal's interpolations split it into pieces, and the pieces are
|
|
308
|
+
# not strings: `${a} : ${b}` yielded a literal " : ". Running this check over
|
|
309
|
+
# super-ux's own installer produced 598 such fragments and buried the four real
|
|
310
|
+
# findings under them. A check whose output nobody reads is not a check.
|
|
311
|
+
CODE_FRAGMENT_RE = re.compile(
|
|
312
|
+
r"""
|
|
313
|
+
^\s*(?:\|\||&&|\?|:|\}|\{|\)) # opens with an operator or a brace
|
|
314
|
+
| (?:\|\||&&|===|!==|=>)\s*$ # ends with one
|
|
315
|
+
| \$\{ # carries an interpolation opener
|
|
316
|
+
| ^\s*[:?]\s*$ # is only a ternary arm separator
|
|
317
|
+
""",
|
|
318
|
+
re.VERBOSE,
|
|
319
|
+
)
|
|
301
320
|
|
|
302
321
|
|
|
303
322
|
def _looks_like_copy(literal: str) -> bool:
|
|
304
323
|
"""A quoted literal that could plausibly be user-visible text."""
|
|
305
324
|
if not literal or literal[0].islower() and " " not in literal:
|
|
306
325
|
return False
|
|
307
|
-
|
|
326
|
+
if CODE_FRAGMENT_RE.search(literal):
|
|
327
|
+
return False
|
|
328
|
+
# Prose has at least two word characters in a row somewhere, and at least
|
|
329
|
+
# one letter. "} ${selected.has(i) ? " has neither once operators are gone.
|
|
330
|
+
if not re.search(r"[A-Za-z]{2,}", literal):
|
|
331
|
+
return False
|
|
332
|
+
# A lone ALL-CAPS token is an identifier -- ENOENT, README. A lone
|
|
333
|
+
# capitalised one is a button label -- Publish, Archive -- and excluding
|
|
334
|
+
# those would silently switch B022 off for exactly the strings it exists
|
|
335
|
+
# to catch.
|
|
336
|
+
if " " not in literal and literal.isupper():
|
|
337
|
+
return False
|
|
338
|
+
# A lone token opening with `-` or `.` is a flag or a file extension:
|
|
339
|
+
# `--force`, `.mdc`, `.cursor`. Nobody reads them as sentences.
|
|
340
|
+
if " " not in literal and literal[0] in "-.":
|
|
341
|
+
return False
|
|
342
|
+
if literal.strip() in DIRECTIVES:
|
|
343
|
+
return False
|
|
344
|
+
return True
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
# Language directives that are quoted strings and never reach a user.
|
|
348
|
+
DIRECTIVES = {"use strict", "use client", "use server"}
|
|
308
349
|
|
|
309
350
|
|
|
310
351
|
def check_consistency(brand_dir: Path, sources: dict) -> list[Finding]:
|
|
@@ -315,6 +356,7 @@ def check_consistency(brand_dir: Path, sources: dict) -> list[Finding]:
|
|
|
315
356
|
_, _, entities = dictionary(brand_dir)
|
|
316
357
|
entity_words = {name for name, _ in entities}
|
|
317
358
|
|
|
359
|
+
swept: set[str] = set() # one unregistered-literal sweep per file
|
|
318
360
|
by_key: dict[str, set] = {}
|
|
319
361
|
for row in rows:
|
|
320
362
|
by_key.setdefault(row["key"], set()).add(row["text"])
|
|
@@ -340,27 +382,48 @@ def check_consistency(brand_dir: Path, sources: dict) -> list[Finding]:
|
|
|
340
382
|
|
|
341
383
|
body = read(target) or ""
|
|
342
384
|
literals = [
|
|
343
|
-
lit for lit in LITERAL_RE.findall(body) if _looks_like_copy(lit)
|
|
385
|
+
lit for _q, lit in LITERAL_RE.findall(body) if _looks_like_copy(lit)
|
|
344
386
|
]
|
|
345
387
|
if literals:
|
|
346
|
-
if row["text"] not in body:
|
|
388
|
+
if row["text"] not in body and row["text"].strip() not in body:
|
|
347
389
|
findings.append(Finding(
|
|
348
390
|
"B021", SEVERITY_ERROR, location, 0,
|
|
349
391
|
f"`{row['key']}` is \"{row['text']}\" in the registry, "
|
|
350
392
|
f"but that text is not in {file_part}",
|
|
351
393
|
))
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
394
|
+
if file_part not in swept:
|
|
395
|
+
swept.add(file_part)
|
|
396
|
+
known = {r["text"] for r in rows}
|
|
397
|
+
trimmed = {t.strip() for t in known}
|
|
398
|
+
for lit in dict.fromkeys(literals): # one report per literal
|
|
399
|
+
if lit not in known and lit.strip() not in trimmed:
|
|
400
|
+
findings.append(Finding(
|
|
401
|
+
"B022", SEVERITY_WARN, f"{file_part}", 0,
|
|
402
|
+
f"\"{lit}\" is in the code with no registry row -- "
|
|
403
|
+
f"agree it or retire it",
|
|
404
|
+
))
|
|
405
|
+
|
|
406
|
+
proper: set[str] = set()
|
|
407
|
+
for name in entity_words:
|
|
408
|
+
proper.update(name.split())
|
|
361
409
|
for row in rows:
|
|
362
|
-
words
|
|
363
|
-
|
|
410
|
+
# Escape sequences are not words: "\\n--- Skills for ..." begins with a
|
|
411
|
+
# token whose only letter is the n of \\n.
|
|
412
|
+
readable = re.sub(r"\\[nrt]|\\x[0-9a-fA-F]{2}\[[0-9;]*[A-Za-z]", " ",
|
|
413
|
+
row["text"])
|
|
414
|
+
words = readable.split()
|
|
415
|
+
first = next(
|
|
416
|
+
(i for i, w in enumerate(words) if re.search(r"[A-Za-z]", w)), 0
|
|
417
|
+
)
|
|
418
|
+
for index, word in enumerate(words[first + 1:], start=first + 1):
|
|
419
|
+
stripped = word.strip(".,:;!?()[]\"'")
|
|
420
|
+
if stripped in proper or (stripped.isupper() and len(stripped) > 1):
|
|
421
|
+
continue # a declared entity, or an acronym like AI / CLI
|
|
422
|
+
# A capital after a full stop is a sentence, not Title Case. The
|
|
423
|
+
# check read "…development. Select what to install:" as miscased
|
|
424
|
+
# until super-ux ran it over its own menu.
|
|
425
|
+
if words[index - 1].rstrip('"\')').endswith((".", "!", "?", ":")):
|
|
426
|
+
continue
|
|
364
427
|
bare = word.strip(".,:;!?()")
|
|
365
428
|
if not bare or bare in entity_words:
|
|
366
429
|
continue
|
|
@@ -384,7 +447,26 @@ def check_consistency(brand_dir: Path, sources: dict) -> list[Finding]:
|
|
|
384
447
|
|
|
385
448
|
|
|
386
449
|
FRONT_MATTER_RE = re.compile(r"^---\n(.*?)\n---\n", re.S)
|
|
387
|
-
|
|
450
|
+
# A figure is a claim. An identifier, a standard's designation and a year are
|
|
451
|
+
# not, and B030 read all three as unsourced claims until this project ran the
|
|
452
|
+
# linter over its own README: `BP-079`, `NIST SP 800-63B` and `Apple HIG 2025`
|
|
453
|
+
# each produced an error nobody could act on. The check had the right shape
|
|
454
|
+
# and the wrong meaning, and only a pack pointed at real prose could tell.
|
|
455
|
+
NUMBER_RE = re.compile(
|
|
456
|
+
r"""
|
|
457
|
+
\d+\s?% # 40%, 40 %
|
|
458
|
+
| [$€£]\s?\d[\d,.]* # $3.10, €1,200
|
|
459
|
+
| (?<![A-Za-z]-) # not the tail of BP-079, SCN-001, PRN-24
|
|
460
|
+
(?<!\.\.) # not the far end of a range: BP-079..090
|
|
461
|
+
(?<!\d-) # not the tail of 800-63B
|
|
462
|
+
\b\d{3,}\b
|
|
463
|
+
(?![-\d]) # not the head of 800-63B
|
|
464
|
+
""",
|
|
465
|
+
re.VERBOSE,
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
# A bare four-digit year dates a claim; it is not the claim.
|
|
469
|
+
YEAR_RE = re.compile(r"^(?:19|20)\d{2}$")
|
|
388
470
|
SUPERLATIVES = (
|
|
389
471
|
"the best", "best-in-class", "leading", "fastest", "most trusted",
|
|
390
472
|
"#1", "number one", "world-class", "unmatched",
|
|
@@ -473,6 +555,8 @@ def check_facts(brand_dir: Path, sources: dict) -> list[Finding]:
|
|
|
473
555
|
for path, _fields, body in documents(brand_dir, sources, "marketing"):
|
|
474
556
|
for number in NUMBER_RE.findall(body):
|
|
475
557
|
compact = number.replace(" ", "")
|
|
558
|
+
if YEAR_RE.match(compact):
|
|
559
|
+
continue
|
|
476
560
|
if compact not in known.replace(" ", ""):
|
|
477
561
|
findings.append(Finding(
|
|
478
562
|
"B030", SEVERITY_ERROR, path, 0,
|
|
@@ -38,7 +38,7 @@ ADDITIVE = [
|
|
|
38
38
|
("foundation.md", "**Kill criteria:**", "`Kill criteria` on stories — gives `dropped` a definition, 0.28.0"),
|
|
39
39
|
]
|
|
40
40
|
|
|
41
|
-
ARTIFACTS = ["foundation.md", "flows.md", "screens.md", "scenarios.md"]
|
|
41
|
+
ARTIFACTS = ["vision.md", "foundation.md", "flows.md", "screens.md", "scenarios.md"]
|
|
42
42
|
|
|
43
43
|
# Names the contract owns, and the near-misses seen in the wild.
|
|
44
44
|
RENAMES = {
|
|
@@ -47,7 +47,7 @@ def read(path: Path) -> str:
|
|
|
47
47
|
def find_ux_dir(arg: str | None) -> Path | None:
|
|
48
48
|
base = Path(arg) if arg else Path.cwd()
|
|
49
49
|
for cand in (base, base / "docs" / "ux", base.parent if base.name else base):
|
|
50
|
-
if (cand /
|
|
50
|
+
if any((cand / n).exists() for n in ("scenarios.md", "foundation.md", "vision.md")):
|
|
51
51
|
return cand
|
|
52
52
|
return None
|
|
53
53
|
|
|
@@ -102,6 +102,60 @@ def screen_blocks(text: str) -> dict[str, str]:
|
|
|
102
102
|
return out
|
|
103
103
|
|
|
104
104
|
|
|
105
|
+
VISION_SECTIONS = [
|
|
106
|
+
"1. Essence",
|
|
107
|
+
"2. Core idea",
|
|
108
|
+
"3. What the system does",
|
|
109
|
+
"4. The user's role",
|
|
110
|
+
"5. Principles",
|
|
111
|
+
"6. Anti-vision",
|
|
112
|
+
"7. Horizon",
|
|
113
|
+
"8. The one sentence",
|
|
114
|
+
"9. The alignment test",
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
VISION_RULE_HEADING = "## Vision alignment — hard rule (super-ux)"
|
|
118
|
+
INSTRUCTION_FILES = ("CLAUDE.md", "AGENTS.md", "GEMINI.md")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def check_vision(ux: Path, vision: str) -> None:
|
|
122
|
+
"""The vision layer: all nine sections, and the rule that makes it read.
|
|
123
|
+
|
|
124
|
+
A vision with no alignment rule in the project's instruction file is a
|
|
125
|
+
document, not a constraint — and its absence looks exactly like
|
|
126
|
+
compliance, which is why it is checked rather than trusted.
|
|
127
|
+
"""
|
|
128
|
+
if not vision.strip():
|
|
129
|
+
return
|
|
130
|
+
for section in VISION_SECTIONS:
|
|
131
|
+
if not re.search(rf"^##\s+{re.escape(section)}\s*$", vision, re.MULTILINE):
|
|
132
|
+
err(f"vision.md: missing section '## {section}'")
|
|
133
|
+
# Emptiness is a defect only once the document claims to be finished.
|
|
134
|
+
# A freshly seeded template is all headings and no content by design, and
|
|
135
|
+
# a linter that fails on its own seed teaches people to skip the linter.
|
|
136
|
+
approved = bool(re.search(r"\*\*Status:\*\*\s*approved", vision, re.IGNORECASE))
|
|
137
|
+
if approved:
|
|
138
|
+
for section in ("6. Anti-vision", "9. The alignment test"):
|
|
139
|
+
body = re.split(rf"^##\s+{re.escape(section)}\s*$", vision, maxsplit=1,
|
|
140
|
+
flags=re.MULTILINE)
|
|
141
|
+
if len(body) == 2:
|
|
142
|
+
tail = re.split(r"^##\s", body[1], maxsplit=1, flags=re.MULTILINE)[0]
|
|
143
|
+
if not tail.strip():
|
|
144
|
+
err(f"vision.md: approved but '## {section}' is empty — "
|
|
145
|
+
f"the section that settles arguments cannot be blank")
|
|
146
|
+
|
|
147
|
+
root = ux.parent.parent if ux.name == "ux" else ux.parent
|
|
148
|
+
present = [root / n for n in INSTRUCTION_FILES if (root / n).is_file()]
|
|
149
|
+
if not present:
|
|
150
|
+
warn("vision.md exists but the project has no CLAUDE.md / AGENTS.md / "
|
|
151
|
+
"GEMINI.md — the alignment rule has nowhere to live")
|
|
152
|
+
return
|
|
153
|
+
if not any(VISION_RULE_HEADING in read(p) for p in present):
|
|
154
|
+
warn(f"vision.md exists but no '{VISION_RULE_HEADING}' block in "
|
|
155
|
+
f"{', '.join(p.name for p in present)} — nothing ever reads the vision "
|
|
156
|
+
f"(run the `vision` skill's step 4)")
|
|
157
|
+
|
|
158
|
+
|
|
105
159
|
def check_links(ux: Path) -> None:
|
|
106
160
|
link_re = re.compile(r"\[[^\]]*\]\(([^)\s]+)\)")
|
|
107
161
|
for md in sorted(ux.rglob("*.md")):
|
|
@@ -122,6 +176,7 @@ def main() -> int:
|
|
|
122
176
|
print("no UX docs found (docs/ux/scenarios.md). Run /ux to set up.")
|
|
123
177
|
return 2
|
|
124
178
|
|
|
179
|
+
vision = read(ux / "vision.md")
|
|
125
180
|
foundation = read(ux / "foundation.md")
|
|
126
181
|
flows = read(ux / "flows.md")
|
|
127
182
|
screens = read(ux / "screens.md")
|
|
@@ -210,6 +265,7 @@ def main() -> int:
|
|
|
210
265
|
if status == "built" and (not cov or cov.lower().startswith("none")):
|
|
211
266
|
warn(f"screens.md: {sid} is 'built' but has no Coverage")
|
|
212
267
|
|
|
268
|
+
check_vision(ux, vision)
|
|
213
269
|
check_links(ux)
|
|
214
270
|
|
|
215
271
|
# --- Report ---
|
package/templates/README.md
CHANGED
|
@@ -6,32 +6,49 @@ sync with the code on every interface change.
|
|
|
6
6
|
## The pipeline
|
|
7
7
|
|
|
8
8
|
```
|
|
9
|
-
Personas · JTBD · Journeys · Stories → Flows → Screens → Scenarios → Audits → Plans
|
|
10
|
-
|
|
9
|
+
Vision → Personas · JTBD · Journeys · Stories → Flows → Screens → Scenarios → Audits → Plans
|
|
10
|
+
vision.md foundation.md flows.md screens.md scenarios.md audits/ plans/
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
| File | Holds |
|
|
14
14
|
|------|-------|
|
|
15
|
+
| `vision.md` | WHAT IT IS: essence, core idea, principles, the anti-vision, the alignment test. Optional layer; when present, new features are checked against it before anything is designed |
|
|
15
16
|
| `foundation.md` | WHO & WHY: personas, jobs-to-be-done, journeys, user stories, monetization, Figma on/off + file URL |
|
|
16
17
|
| `flows.md` | HOW: user-flow diagrams (screens, branches, error paths), referencing screens by `SCR-ID` |
|
|
17
18
|
| `screens.md` | THE UI MAP: every screen and state with its Figma frame link, wireframe, code coverage, scenarios, resources |
|
|
18
|
-
| `scenarios.md` | WHAT
|
|
19
|
+
| `scenarios.md` | WHAT IT DOES: use-case scenarios — the source of truth for behavior |
|
|
19
20
|
| `audits/` | audit reports (code vs the chain) |
|
|
20
21
|
| `plans/` | UX plans (target interface + what to create/modify/delete) |
|
|
21
22
|
| `wireframes/` | optional low-fi wireframes / storyboards |
|
|
22
23
|
| `lint.py` | the integrity/drift linter |
|
|
24
|
+
| `doctor.py` | the contract doctor — mixed or stale contract versions across the artifacts |
|
|
25
|
+
|
|
26
|
+
**Two files answer a question shaped like "what".** `vision.md` says what
|
|
27
|
+
the product **is**; `scenarios.md` says what it **does**. A feature can
|
|
28
|
+
satisfy every scenario and still violate the anti-vision.
|
|
29
|
+
|
|
30
|
+
## Its sibling: `docs/brand/`
|
|
31
|
+
|
|
32
|
+
`docs/ux/` decides what the product does. **`docs/brand/`** decides how it
|
|
33
|
+
speaks — `voice.md`, `terminology.md`, `facts.md`, `channels.md`,
|
|
34
|
+
`strings.md`, `locales/<code>.md`, and its own `lint.py`. It is a separate
|
|
35
|
+
root because the brand also governs surfaces that are not UX: a store
|
|
36
|
+
listing, an ad, a post. Seed it with `/brand-init`.
|
|
23
37
|
|
|
24
38
|
## Rules
|
|
25
39
|
|
|
26
40
|
1. **Design before you build.** A new feature starts here (job → flow →
|
|
27
|
-
screen → scenario), approved, before any UI code.
|
|
41
|
+
screen → scenario), approved, before any UI code. With `vision.md`
|
|
42
|
+
present, it is checked against the anti-vision first.
|
|
28
43
|
2. **Update in the same change.** Any interface change updates
|
|
29
44
|
`scenarios.md`, affected flows, the affected screens in `screens.md`,
|
|
30
45
|
and (Figma on) the Figma frame plus its link — together, not later.
|
|
46
|
+
Any change to public text updates `docs/brand/` the same way.
|
|
31
47
|
3. **No drift.** Code that diverges from a screen's record, or a stale Figma
|
|
32
48
|
link, is a bug to fix. The same goes for the look: one style pack is
|
|
33
49
|
recorded in `screens.md` → Design system (pick it with the
|
|
34
50
|
**sheleg-design** skill) and every screen obeys it.
|
|
35
|
-
4. **Lint it.** Run `python3 docs/ux/lint.py` after changes and in CI
|
|
51
|
+
4. **Lint it.** Run `python3 docs/ux/lint.py` after changes and in CI —
|
|
52
|
+
plus `python3 docs/brand/lint.py` after any text change.
|
|
36
53
|
|
|
37
54
|
Maintained with the super-ux plugin. In Claude Code, run `/ux`.
|
package/templates/claude-rule.md
CHANGED
|
@@ -26,9 +26,11 @@
|
|
|
26
26
|
- After any UX change and before calling the work done, run the linter
|
|
27
27
|
`python3 docs/ux/lint.py` — it must pass (errors are drift/broken
|
|
28
28
|
structure; wire it into CI/pre-commit).
|
|
29
|
-
- Use `/ux` as the entry point; skills: `
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
- Use `/ux` as the entry point; skills: `vision` (what the product is and
|
|
30
|
+
refuses to become), `ux-foundation`, `ux-flows` (flows + Figma mockups),
|
|
31
|
+
`ux-scenarios` for maintenance, `ux-audit` for evidence-backed
|
|
32
|
+
verification, `brand-voice` and `copywriting` for everything the user
|
|
33
|
+
reads. Full map: the plugin's system-map reference.
|
|
32
34
|
|
|
33
35
|
## Brand voice — hard rule (super-ux)
|
|
34
36
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## Vision alignment — hard rule (super-ux)
|
|
2
|
+
|
|
3
|
+
Before planning any new feature, capability or significant change, check it
|
|
4
|
+
against `docs/ux/vision.md` — specifically the **anti-vision** and the
|
|
5
|
+
**alignment test**.
|
|
6
|
+
|
|
7
|
+
**Aligned** → proceed, and say in one line which part of the vision it serves.
|
|
8
|
+
|
|
9
|
+
**Misaligned** → stop and say so before writing code:
|
|
10
|
+
1. Name the conflict — which layer it contradicts, quoting that layer.
|
|
11
|
+
2. Offer two paths: (a) reshape the feature to fit, with the specific change;
|
|
12
|
+
(b) amend the vision, saying which layer changes and what that costs.
|
|
13
|
+
3. Wait for the decision. Do not pick one silently.
|
|
14
|
+
|
|
15
|
+
**Do NOT trigger for:** bug fixes, refactors, dependency work, tests,
|
|
16
|
+
documentation, or anything with no user-facing surface. A vision check on a
|
|
17
|
+
typo fix is how a team learns to skip the check that matters.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# <Product> — Vision
|
|
2
|
+
|
|
3
|
+
<!-- Managed with super-ux (ux-contract v4). The layer above the chain. -->
|
|
4
|
+
|
|
5
|
+
<!--
|
|
6
|
+
Owned by the `vision` skill (super-ux). Nine sections, these headings, in
|
|
7
|
+
this order — `python3 docs/ux/lint.py` keys off them.
|
|
8
|
+
|
|
9
|
+
This file says what the product IS. `scenarios.md` says what it DOES.
|
|
10
|
+
A feature can satisfy every scenario and still violate the anti-vision;
|
|
11
|
+
that case is the whole reason this layer exists.
|
|
12
|
+
|
|
13
|
+
Writing this file is half the job. The other half is the
|
|
14
|
+
`## Vision alignment — hard rule (super-ux)` block in the project's own
|
|
15
|
+
instruction file — without it nothing ever reads this document.
|
|
16
|
+
-->
|
|
17
|
+
|
|
18
|
+
**Status:** draft
|
|
19
|
+
**Last reviewed:** <YYYY-MM-DD>
|
|
20
|
+
|
|
21
|
+
## 1. Essence
|
|
22
|
+
|
|
23
|
+
<!-- One sentence: "[Product] is [type of system] that changes how [user] [does X]".
|
|
24
|
+
No feature names, no UI, no technology. Test: would it survive a total
|
|
25
|
+
rewrite on a different stack? If not, it names an implementation. -->
|
|
26
|
+
|
|
27
|
+
## 2. Core idea
|
|
28
|
+
|
|
29
|
+
<!-- An observation about the world, not a problem statement:
|
|
30
|
+
[X] is abundant.
|
|
31
|
+
[Y] is scarce.
|
|
32
|
+
-> this product bridges the gap.
|
|
33
|
+
One observation, not a list. -->
|
|
34
|
+
|
|
35
|
+
## 3. What the system does
|
|
36
|
+
|
|
37
|
+
<!-- Continuous behaviour, verbs not nouns: observes ... understands ...
|
|
38
|
+
transforms ... surfaces ... "Has a dashboard" is not behaviour. -->
|
|
39
|
+
|
|
40
|
+
## 4. The user's role
|
|
41
|
+
|
|
42
|
+
<!-- What the user becomes. Not "the user clicks" — the user directs, judges,
|
|
43
|
+
decides. If it reads like operating any other tool, go deeper. -->
|
|
44
|
+
|
|
45
|
+
## 5. Principles
|
|
46
|
+
|
|
47
|
+
<!-- Three to five, each "we do X, not Y", where Y is something a reasonable
|
|
48
|
+
team would genuinely have picked. A principle with no rejected side is a
|
|
49
|
+
slogan. -->
|
|
50
|
+
|
|
51
|
+
## 6. Anti-vision
|
|
52
|
+
|
|
53
|
+
<!-- What this product refuses to become, explicitly. Name the adjacent
|
|
54
|
+
products it could drift into and say no to each. This is the layer teams
|
|
55
|
+
skip and the only one that ever settles an argument. -->
|
|
56
|
+
|
|
57
|
+
## 7. Horizon
|
|
58
|
+
|
|
59
|
+
<!-- Two to three years, as a direction. Not a roadmap — roadmaps belong in
|
|
60
|
+
the tracker and go stale in a quarter. -->
|
|
61
|
+
|
|
62
|
+
## 8. The one sentence
|
|
63
|
+
|
|
64
|
+
<!-- If someone remembers exactly one thing. -->
|
|
65
|
+
|
|
66
|
+
## 9. The alignment test
|
|
67
|
+
|
|
68
|
+
<!-- Three to five questions any proposed feature must pass. These are what
|
|
69
|
+
the hard rule in the instruction file actually runs. A stranger should be
|
|
70
|
+
able to reject a plausible feature with them. -->
|
|
71
|
+
|
|
72
|
+
1. <question>
|
|
73
|
+
2. <question>
|
|
74
|
+
3. <question>
|