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 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', 'ux', dst);
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/ux/${dst} was not installed.\n` +
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.29.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"