super-ux 0.29.0 → 0.30.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 +72 -0
- package/README.md +35 -0
- package/bin/super-ux.js +28 -5
- package/cursor/rules/brand-voice.mdc +37 -0
- package/package.json +2 -1
- package/plugins/super-ux/scripts/brand_lint.py +957 -0
- package/plugins/super-ux/scripts/ux_doctor.py +46 -0
- package/templates/brand/README.md +53 -0
- package/templates/brand/channels.md +129 -0
- package/templates/brand/facts.md +36 -0
- package/templates/brand/locale.md +68 -0
- package/templates/brand/strings.md +41 -0
- package/templates/brand/terminology.md +45 -0
- package/templates/brand/voice.md +58 -0
- package/templates/claude-rule.md +20 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,77 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.30.0 — 2026-08-05
|
|
4
|
+
|
|
5
|
+
The verbal identity layer. `docs/ux/` decides what the product does; the new
|
|
6
|
+
`docs/brand/` decides how it speaks — one voice, many registers, and a linter
|
|
7
|
+
that makes copy drift as findable as chain drift.
|
|
8
|
+
|
|
9
|
+
### The contract — `brand-contract v1`
|
|
10
|
+
|
|
11
|
+
`docs/brand/` is a second artifact root beside `docs/ux/`, seeded by the
|
|
12
|
+
installer: `voice.md` (a voice pack, five fixed axes as IS / IS NOT, the
|
|
13
|
+
narrative, the invariants that survive translation), `terminology.md` (our
|
|
14
|
+
words, banned words, exact entity and tier spellings), `facts.md` (the only
|
|
15
|
+
source of any figure in public copy), `channels.md` (one record per surface),
|
|
16
|
+
`strings.md` (the interface string registry, key → `file:line` → scenario),
|
|
17
|
+
and `locales/<code>.md`.
|
|
18
|
+
|
|
19
|
+
Separate root on purpose: the brand also governs surfaces that are not UX at
|
|
20
|
+
all — a store listing, an ad, a post. The pack derives from `foundation.md`
|
|
21
|
+
and never the reverse.
|
|
22
|
+
|
|
23
|
+
### Two skills
|
|
24
|
+
|
|
25
|
+
- **`brand-voice`** — defines and holds the identity. Six shipped voice packs
|
|
26
|
+
(`operator-brief`, `calm-expert`, `peer-builder`, `editorial-premium`,
|
|
27
|
+
`plain-service`, `playful-consumer`), each declaring the degeneration it
|
|
28
|
+
collapses into when overdone, so an overshoot is a finding rather than a
|
|
29
|
+
matter of taste. Invoked with no task it reports state and proposes exactly
|
|
30
|
+
one next action, and never invents a missing fact to close a gap.
|
|
31
|
+
- **`copywriting`** — writes in the voice and never writes *to* it. A missing
|
|
32
|
+
term or an unsourced number is reported, because adding it is the other
|
|
33
|
+
skill's decision.
|
|
34
|
+
|
|
35
|
+
Commands: `/brand`, `/brand-init`, `/brand-update`, `/brand-lint`, `/copy`.
|
|
36
|
+
|
|
37
|
+
### `brand_lint.py` — 31 deterministic checks
|
|
38
|
+
|
|
39
|
+
Seeded as `docs/brand/lint.py`. Contract and sources (B001–B006), terminology
|
|
40
|
+
(B010–B012), string consistency (B020–B025), facts (B030–B032), channel
|
|
41
|
+
physics (B040–B043), bot safety (B050–B054), machine-drafting markers
|
|
42
|
+
(B060–B061), locales (B070–B073). Exit 0 clean, 1 warnings, 2 errors;
|
|
43
|
+
`--fix` touches only casing, the iOS keyword field, and re-pointing a registry
|
|
44
|
+
row whose string is unchanged. 32 fixtures, one per code, each watched failing
|
|
45
|
+
against a planted defect before it was trusted — and they run in CI.
|
|
46
|
+
|
|
47
|
+
Three rules it exists to enforce: one action keeps one name everywhere; a
|
|
48
|
+
number in public copy has a sourced row or is not written; no humor on error,
|
|
49
|
+
destructive-confirm, billing or paywall surfaces, in any voice.
|
|
50
|
+
|
|
51
|
+
### Catalog
|
|
52
|
+
|
|
53
|
+
`BP-182..205` in six clusters of four — voice and consistency, product
|
|
54
|
+
microcopy, conversion copy, bot safety, channel physics, localization — and
|
|
55
|
+
`PRN-22..24`. New entries carry a sixth field, `Checked:`, dating the last
|
|
56
|
+
verification against the source. It starts at BP-182 deliberately:
|
|
57
|
+
backfilling it onto BP-001..181 would record a verification nobody performed.
|
|
58
|
+
New tags `brand-voice` `copy` `narrative` `terminology` `channel-physics`
|
|
59
|
+
`seo` `aeo` `aso` — `voice` was already taken, and it means a voice interface.
|
|
60
|
+
|
|
61
|
+
### Elsewhere
|
|
62
|
+
|
|
63
|
+
`ux-audit` gains scope `copy`, the judgement twin of the linter. `ux_doctor`
|
|
64
|
+
reads the brand marker, because a pack on an old contract is internally
|
|
65
|
+
consistent and the linter stays quiet about it. Scenarios gain an optional
|
|
66
|
+
`Strings:` field. The brand hard rule ships in the Claude rule template and a
|
|
67
|
+
new Cursor rule.
|
|
68
|
+
|
|
69
|
+
### Fixed
|
|
70
|
+
|
|
71
|
+
- `CONTRIBUTING.md` told maintainers to run `npm publish --access public`
|
|
72
|
+
by hand in `sshlg-skills` after a release. Every repo in the family has
|
|
73
|
+
published from CI on a `v*` tag since that text was written.
|
|
74
|
+
|
|
3
75
|
## 0.29.0 — 2026-08-05
|
|
4
76
|
|
|
5
77
|
A pass aimed at the installed base rather than the catalog. Run across the
|
package/README.md
CHANGED
|
@@ -100,6 +100,41 @@ plugin user-globally — any combination in one run. Also works straight from
|
|
|
100
100
|
GitHub: `npx github:ssheleg/super-ux --cursor <dir>`, or clone and run
|
|
101
101
|
`./install.sh --cursor <dir>`.
|
|
102
102
|
|
|
103
|
+
## The brand layer — how the product speaks
|
|
104
|
+
|
|
105
|
+
`docs/ux/` decides what the product does. **`docs/brand/`** decides how it
|
|
106
|
+
speaks, under `brand-contract v1`: one voice, many registers, and a linter
|
|
107
|
+
that makes copy drift as findable as chain drift.
|
|
108
|
+
|
|
109
|
+
| File | Holds |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `voice.md` | the pack, five fixed axes, narrative, invariants, locales |
|
|
112
|
+
| `terminology.md` | our words, banned words, entity and tier names |
|
|
113
|
+
| `facts.md` | canonical figures — the only source of a number in public copy |
|
|
114
|
+
| `channels.md` | one record per surface: register deltas, limits, bans |
|
|
115
|
+
| `strings.md` | the interface string registry → `file:line` → scenario |
|
|
116
|
+
| `locales/<code>.md` | address form, length coefficient, dead idioms, keywords |
|
|
117
|
+
|
|
118
|
+
Two skills: **`brand-voice`** defines and holds the identity (six shipped
|
|
119
|
+
voice packs, each declaring the degeneration it collapses into when overdone);
|
|
120
|
+
**`copywriting`** writes in it and never writes *to* it — a missing term or an
|
|
121
|
+
unsourced number is reported, never invented.
|
|
122
|
+
|
|
123
|
+
Commands: `/brand` (status → one recommended action), `/brand-init`,
|
|
124
|
+
`/brand-update`, `/brand-lint`, `/copy`.
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
python3 docs/brand/lint.py
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
31 deterministic checks — banned words, one action under two names, a figure
|
|
131
|
+
with no sourced fact, a field over its limit with the locale coefficient
|
|
132
|
+
applied, blocked AI crawlers, keyword stuffing, humor on a billing screen,
|
|
133
|
+
a locale that lags without saying so. Exit 0 clean, 1 warnings, 2 errors.
|
|
134
|
+
|
|
135
|
+
Clean means *checkable*, not *good*: tone drift, unproven claims and a voice
|
|
136
|
+
that has overshot its own failure mode are judged by `/ux-audit copy`.
|
|
137
|
+
|
|
103
138
|
## The hard rule
|
|
104
139
|
|
|
105
140
|
Installed into your project's `CLAUDE.md` (and as the always-on Cursor rule):
|
package/bin/super-ux.js
CHANGED
|
@@ -86,23 +86,46 @@ function installCursor(target, force) {
|
|
|
86
86
|
console.log(`seed: ${dst}`);
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
+
// The brand pack lives beside the UX chain, not inside it: it also governs
|
|
90
|
+
// surfaces that are not UX at all — a store listing, an ad, a post.
|
|
91
|
+
fs.mkdirSync(path.join(target, 'docs', 'brand', 'locales'), { recursive: true });
|
|
92
|
+
for (const tpl of [
|
|
93
|
+
'README', 'voice', 'terminology', 'facts', 'channels', 'strings',
|
|
94
|
+
]) {
|
|
95
|
+
const dst = path.join(target, 'docs', 'brand', `${tpl}.md`);
|
|
96
|
+
if (fs.existsSync(dst)) {
|
|
97
|
+
console.log(`keep: ${dst} exists (never overwritten)`);
|
|
98
|
+
} else {
|
|
99
|
+
fs.copyFileSync(path.join(ROOT, 'templates', 'brand', `${tpl}.md`), dst);
|
|
100
|
+
console.log(`seed: ${dst}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
{
|
|
104
|
+
const dst = path.join(target, 'docs', 'brand', 'locales', 'en.md');
|
|
105
|
+
if (!fs.existsSync(dst)) {
|
|
106
|
+
fs.copyFileSync(path.join(ROOT, 'templates', 'brand', 'locale.md'), dst);
|
|
107
|
+
console.log(`seed: ${dst}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
89
111
|
// The linter is code, not a template — refresh it to the shipped version.
|
|
90
112
|
// Shipped via package.json files[]; if that ever regresses, warn instead of
|
|
91
113
|
// dying on an ENOENT stack trace after the rules are already installed.
|
|
92
114
|
// Paths stay literal so test/validate.py can read them out of this source and
|
|
93
115
|
// check them against package.json files[] — a variable segment here silently
|
|
94
116
|
// turns that check into a directory prefix nobody ships.
|
|
95
|
-
for (const [src, from, dst] of [
|
|
96
|
-
['ux_lint.py', path.join(ROOT, 'plugins', 'super-ux', 'scripts', 'ux_lint.py'), 'lint.py'],
|
|
97
|
-
['ux_doctor.py', path.join(ROOT, 'plugins', 'super-ux', 'scripts', 'ux_doctor.py'), 'doctor.py'],
|
|
117
|
+
for (const [src, from, area, dst] of [
|
|
118
|
+
['ux_lint.py', path.join(ROOT, 'plugins', 'super-ux', 'scripts', 'ux_lint.py'), 'ux', 'lint.py'],
|
|
119
|
+
['ux_doctor.py', path.join(ROOT, 'plugins', 'super-ux', 'scripts', 'ux_doctor.py'), 'ux', 'doctor.py'],
|
|
120
|
+
['brand_lint.py', path.join(ROOT, 'plugins', 'super-ux', 'scripts', 'brand_lint.py'), 'brand', 'lint.py'],
|
|
98
121
|
]) {
|
|
99
|
-
const to = path.join(target, 'docs',
|
|
122
|
+
const to = path.join(target, 'docs', area, dst);
|
|
100
123
|
if (fs.existsSync(from)) {
|
|
101
124
|
fs.copyFileSync(from, to);
|
|
102
125
|
console.log(`sync: ${to}`);
|
|
103
126
|
} else {
|
|
104
127
|
console.error(
|
|
105
|
-
`warning: ${src} not found in this package (${from}); docs
|
|
128
|
+
`warning: ${src} not found in this package (${from}); docs/${area}/${dst} was not installed.\n` +
|
|
106
129
|
` Get it from https://github.com/${REPO}/blob/main/plugins/super-ux/scripts/${src}`
|
|
107
130
|
);
|
|
108
131
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Brand voice and copy — docs/brand/ is the source of truth for how the product speaks; any public-facing text change updates it in the same change and passes docs/brand/lint.py
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Brand voice (super-ux)
|
|
7
|
+
|
|
8
|
+
`docs/ux/` decides what the product does. `docs/brand/` decides how it
|
|
9
|
+
speaks, under `brand-contract v1`.
|
|
10
|
+
|
|
11
|
+
## Before writing any user-visible text
|
|
12
|
+
|
|
13
|
+
Read `docs/brand/voice.md`, `terminology.md`, and the `channels.md` record
|
|
14
|
+
for the surface. No pack yet → run `/brand-init` rather than improvising a
|
|
15
|
+
voice; a product whose copy was invented surface by surface is the drift
|
|
16
|
+
this layer removes.
|
|
17
|
+
|
|
18
|
+
## Rules
|
|
19
|
+
|
|
20
|
+
- Any change to public text updates `docs/brand/` in the same change.
|
|
21
|
+
- Never quote a figure with no row in `facts.md`. Never invent a fact,
|
|
22
|
+
statistic, quote or expert — report the gap.
|
|
23
|
+
- One action, one name, everywhere it appears. Check `strings.md` first.
|
|
24
|
+
- A register moves the voice axes; it never crosses the invariants listed in
|
|
25
|
+
`voice.md`.
|
|
26
|
+
- No humor, exclamation marks or emoji on error, destructive confirm,
|
|
27
|
+
billing or paywall surfaces, in any voice.
|
|
28
|
+
- New interface strings get a `strings.md` row: key, `file:line`, scenario.
|
|
29
|
+
|
|
30
|
+
## Check
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
python3 docs/brand/lint.py
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Exit 0 clean, 1 warnings, 2 errors. Clean means checkable, not good — the
|
|
37
|
+
judgement half is `/ux-audit copy`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "super-ux",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.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"
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"templates",
|
|
12
12
|
"plugins/super-ux/scripts/ux_lint.py",
|
|
13
13
|
"plugins/super-ux/scripts/ux_doctor.py",
|
|
14
|
+
"plugins/super-ux/scripts/brand_lint.py",
|
|
14
15
|
"README.md",
|
|
15
16
|
"LICENSE",
|
|
16
17
|
"CHANGELOG.md"
|