super-ux 0.49.0 → 0.50.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 +66 -0
- package/README.md +1 -1
- package/bin/super-ux.js +126 -5
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,71 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.50.0 — the templates ship where the texts say they are
|
|
4
|
+
|
|
5
|
+
- **`templates/` now travels with everything that names it** (SUX-01, family audit
|
|
6
|
+
2026-08-29). Six shipped texts pointed at "the plugin's `templates/`" while the
|
|
7
|
+
marketplace ships `./plugins/super-ux` and the directory lived at the repo root only —
|
|
8
|
+
verified absent from all 13 cached installed versions, so `/brand-init`, `/brand`,
|
|
9
|
+
`/ux-rule` step 2 and the three seeding skills dead-ended at a path that resolves in
|
|
10
|
+
the one place users never run from: this repository's own checkout. The repo root stays
|
|
11
|
+
the single source (the hard rules and the installer CLI read it);
|
|
12
|
+
`test/sync_references.py` now mirrors the full tree into `plugins/super-ux/templates/`
|
|
13
|
+
and the named seeds into each seeding skill's own directory — a skill installed by the
|
|
14
|
+
skills CLI has no plugin root to reach up to — and the three skill texts say "this
|
|
15
|
+
skill's own `templates/…`" while the three command texts keep "the plugin's
|
|
16
|
+
`templates/…`", which is now true.
|
|
17
|
+
- **The class is gated, not just the instance.** `validate_shipped_templates` refuses a
|
|
18
|
+
copy that drifts from its source and a copy with no source, in both homes;
|
|
19
|
+
`validate_shipped_paths` requires every backticked `templates/…` or `scripts/…` token
|
|
20
|
+
in a shipped text to resolve inside what actually ships — the plugin root for commands,
|
|
21
|
+
the skill's own directory for skill files. Six defects planted, each caught by its own
|
|
22
|
+
branch with its own message, each reverted; the sync verified idempotent across three
|
|
23
|
+
runs. The validator grows 4111 → 4174 checks and `test/floors.json` ratchets with it.
|
|
24
|
+
- **`/ux-audit` admits its whole scope surface** (SUX-06). `copy` and
|
|
25
|
+
`benchmark:<competitor>` join the `argument-hint` and the step-1 enumeration — the body
|
|
26
|
+
has treated both as legal scopes since they shipped, while the two places an agent
|
|
27
|
+
reads first omitted them.
|
|
28
|
+
- **Trigger hygiene in three descriptions** (SUX-07, SUX-08, SUX-11). `ux-scenarios`
|
|
29
|
+
defers the empty-project start up the chain — vision and ux-foundation own it — instead
|
|
30
|
+
of claiming "ANY new feature or project" unqualified; `copywriting` narrows "build a
|
|
31
|
+
landing page / сделай лендинг" to the copy for it, naming sheleg-design as the visual
|
|
32
|
+
half; `ux-flows` drops the bare "figma"/"фигма" claim and delegates the visual system
|
|
33
|
+
and Figma variables to sheleg-design, mirroring its own body. Every phrase the family
|
|
34
|
+
umbrella routes on remains a literal substring of its description — verified with the
|
|
35
|
+
umbrella's own `advertised_check.js` (43/43, and watched failing against a dropped
|
|
36
|
+
«мокап»).
|
|
37
|
+
|
|
38
|
+
## 0.49.1 — the skills handoff refuses the shadow it used to create
|
|
39
|
+
|
|
40
|
+
- **The skills-menu item now consults the target home before delegating.** `npx skills add`
|
|
41
|
+
auto-detects Claude Code and writes a plain `~/.claude/skills/super-ux` copy even when
|
|
42
|
+
claude-code is never picked, and on a machine where super-ux is installed as a Claude
|
|
43
|
+
Code plugin that copy shadows the plugin and serves the version it was copied from
|
|
44
|
+
forever. The handoff now reads `~/.claude/plugins/installed_plugins.json` — the record of
|
|
45
|
+
what is actually installed, under any marketplace name — and refuses with **exit 3**, the
|
|
46
|
+
remedy in the refusal (`claude plugin marketplace update super-ux`, `claude plugin update
|
|
47
|
+
<spec from the JSON>`, and the family launcher), and `npx super-ux --force` as the named
|
|
48
|
+
override. The `plugins/marketplaces/super-ux` directory is kept only as the fallback
|
|
49
|
+
signal: it under-reports, because a directory-sourced marketplace has no dir there and
|
|
50
|
+
plugin names differ from marketplace names. A missing or corrupt JSON reads as "no
|
|
51
|
+
plugin" — fail open, never crash. Only the Claude Code channel is gated; `--cursor`
|
|
52
|
+
installs into a project beside the plugin exactly as before. Canon:
|
|
53
|
+
make-skill v0.25.0, `references/distribution.md` §3; reproduced live 2026-08-29 when a
|
|
54
|
+
bare `npx @ssheleg/telegram-dev` shipped three shadows past this exact class of hole.
|
|
55
|
+
- **A successful run now ends by saying how the next version arrives** — `npx
|
|
56
|
+
super-ux@latest` for this member, the family launcher for every channel at once. An
|
|
57
|
+
installer that never mentions updates has still chosen an update model: never.
|
|
58
|
+
- **`test/installer_test.js`** runs both installers against throwaway HOMEs — the
|
|
59
|
+
plugin-present refusal (exit 3, remedy, nothing delegated, nothing written), the
|
|
60
|
+
differently-named marketplace spec in the remedy, `--force`, corrupt-JSON fail-open, a
|
|
61
|
+
prefix collider (`super-ux-extra@x`), the marketplaces-dir fallback, the fresh HOME, and
|
|
62
|
+
that `install.sh` never touches `~/.claude` at all. Wired into `npm test` and CI; watched
|
|
63
|
+
failing against the pre-fix installer (7 of 10 cases red, the plugin-present case
|
|
64
|
+
delegating with exit 0) before the fix was un-stashed.
|
|
65
|
+
- SCN-016 and SCN-017 record the refusal and the update line in `docs/ux/scenarios.md`;
|
|
66
|
+
SCR-05 gains the `refused` state; the string registry carries the new copy and the
|
|
67
|
+
`refused:` prefix joins the state vocabulary.
|
|
68
|
+
|
|
3
69
|
## 0.49.0 — the humanization pass names what it cannot prove
|
|
4
70
|
|
|
5
71
|
- **`ai-tells.md` now says these markers are not a verdict, and who they misjudge.** Every
|
package/README.md
CHANGED
|
@@ -248,7 +248,7 @@ to is a skill nobody runs.
|
|
|
248
248
|
| `/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 |
|
|
249
249
|
| `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, so wire it into CI and drift can't merge |
|
|
250
250
|
| `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) |
|
|
251
|
-
| `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 |
|
|
251
|
+
| `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. The tree is mirrored into `plugins/super-ux/templates/` (and the seeds each skill names into that skill's own directory) by `test/sync_references.py`, so installed channels carry the seeds their texts point at; the validator refuses drift between the copies |
|
|
252
252
|
|
|
253
253
|
The contracts every skill reads:
|
|
254
254
|
|
package/bin/super-ux.js
CHANGED
|
@@ -7,16 +7,60 @@
|
|
|
7
7
|
* rules into a project, Claude Code plugin user-globally. Non-TTY stdin gets
|
|
8
8
|
* a text fallback ("1,3" / "all"). Flags keep the non-interactive paths:
|
|
9
9
|
* --cursor [dir] [--force].
|
|
10
|
+
*
|
|
11
|
+
* The skills handoff is gated: while super-ux is installed as a Claude Code
|
|
12
|
+
* plugin, delegating to the skills CLI would recreate the plain
|
|
13
|
+
* ~/.claude/skills/super-ux copy that shadows the plugin, so the handoff is
|
|
14
|
+
* refused (exit 3) unless --force records the two-channel choice.
|
|
10
15
|
*/
|
|
11
16
|
'use strict';
|
|
12
17
|
|
|
13
18
|
const fs = require('fs');
|
|
19
|
+
const os = require('os');
|
|
14
20
|
const path = require('path');
|
|
15
21
|
const readline = require('readline');
|
|
16
22
|
const { spawnSync } = require('child_process');
|
|
17
23
|
|
|
18
24
|
const ROOT = path.resolve(__dirname, '..');
|
|
19
25
|
const REPO = 'ssheleg/super-ux';
|
|
26
|
+
const NAME = 'super-ux';
|
|
27
|
+
|
|
28
|
+
// Exit codes are the contract: 0 installed or nothing selected, 1 error,
|
|
29
|
+
// 3 refused — the plugin channel owns this agent (--force overrides).
|
|
30
|
+
const EXIT_PLUGIN_PRESENT = 3;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The plugin spec (`<name>@<marketplace>`) installed for `name` in this home,
|
|
34
|
+
* or null.
|
|
35
|
+
*
|
|
36
|
+
* `installed_plugins.json` is the record of what is actually installed. The
|
|
37
|
+
* `plugins/marketplaces/<name>` directory under-reports: a marketplace added
|
|
38
|
+
* from a local `directory` source has no dir there at all, and plugin names
|
|
39
|
+
* differ from marketplace names, so a check keyed on it stays green while the
|
|
40
|
+
* shadow lands. Absence and corruption both read as "no plugin": the fresh
|
|
41
|
+
* HOME is the common case, and an installer that crashes on a parse error
|
|
42
|
+
* refuses the machines that need it most.
|
|
43
|
+
*/
|
|
44
|
+
function installedPluginSpec(home, name) {
|
|
45
|
+
try {
|
|
46
|
+
const raw = fs.readFileSync(
|
|
47
|
+
path.join(home, '.claude', 'plugins', 'installed_plugins.json'), 'utf8');
|
|
48
|
+
const parsed = JSON.parse(raw);
|
|
49
|
+
const plugins =
|
|
50
|
+
parsed && typeof parsed === 'object' &&
|
|
51
|
+
parsed.plugins && typeof parsed.plugins === 'object'
|
|
52
|
+
? parsed.plugins
|
|
53
|
+
: parsed;
|
|
54
|
+
if (!plugins || typeof plugins !== 'object') return null;
|
|
55
|
+
for (const spec of Object.keys(plugins)) {
|
|
56
|
+
if (spec === name) return `${name}@${name}`;
|
|
57
|
+
if (spec.startsWith(name + '@')) return spec;
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
// missing or corrupt = no plugin — fail open on absence, never crash
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
20
64
|
|
|
21
65
|
const MENU_ITEMS = [
|
|
22
66
|
{ key: 'skills', label: 'Skills for any AI agent (Claude Code, Codex, Cursor, 70+; opens agent picker)' },
|
|
@@ -28,13 +72,20 @@ function usage() {
|
|
|
28
72
|
console.log(`super-ux installer
|
|
29
73
|
|
|
30
74
|
Usage:
|
|
31
|
-
npx super-ux
|
|
75
|
+
npx super-ux [--force] interactive menu (multi-select)
|
|
32
76
|
npx super-ux --cursor [project-dir] [--force] Cursor rules, non-interactive
|
|
33
77
|
npx super-ux --help
|
|
34
78
|
|
|
79
|
+
Exit codes:
|
|
80
|
+
0 installed or nothing selected 1 error
|
|
81
|
+
3 refused: the super-ux PLUGIN is installed in this home, and the skills
|
|
82
|
+
CLI would write the plain ~/.claude/skills copy that shadows it
|
|
83
|
+
(pass --force to run the picker anyway)
|
|
84
|
+
|
|
35
85
|
Menu items (select any combination, 'a' = all):
|
|
36
86
|
1. Skills for any AI agent (Claude Code, Codex, Cursor, 70+) — delegates to
|
|
37
87
|
'npx skills add ${REPO}' with its agent/global/project picker.
|
|
88
|
+
Refused while the super-ux plugin is installed (see exit code 3).
|
|
38
89
|
2. Cursor rules: cursor/rules/*.mdc -> <project>/.cursor/rules/, plus the
|
|
39
90
|
docs/ux skeleton, the docs/brand pack, and all three linters
|
|
40
91
|
(docs/ux/lint.py, docs/ux/doctor.py, docs/brand/lint.py). Existing
|
|
@@ -152,10 +203,62 @@ function run(cmd, args) {
|
|
|
152
203
|
return result.status === 0 ? 'ok' : 'failed';
|
|
153
204
|
}
|
|
154
205
|
|
|
155
|
-
|
|
206
|
+
/**
|
|
207
|
+
* One channel per agent. The skills CLI auto-detects Claude Code and writes
|
|
208
|
+
* ~/.claude/skills/super-ux even when claude-code is never picked, and on a
|
|
209
|
+
* machine where super-ux is installed as a Claude Code plugin that plain copy
|
|
210
|
+
* shadows the plugin and serves the version it was copied from forever. So
|
|
211
|
+
* the handoff consults the TARGET home's installed_plugins.json first and
|
|
212
|
+
* refuses LOUDLY: a refusal that exits 0 reads as success to every script
|
|
213
|
+
* above it. The marketplaces/<name> dir is only the fallback signal — a
|
|
214
|
+
* directory-sourced marketplace has no dir there, and plugin names differ
|
|
215
|
+
* from marketplace names. Reproduced live 2026-08-29: a bare
|
|
216
|
+
* `npx @ssheleg/telegram-dev` shipped three shadows past exactly this class
|
|
217
|
+
* of hole while the plugin was enabled.
|
|
218
|
+
*/
|
|
219
|
+
function installSkillsCli(force) {
|
|
220
|
+
const home = os.homedir();
|
|
221
|
+
const spec = installedPluginSpec(home, NAME);
|
|
222
|
+
const marketplace = path.join(home, '.claude', 'plugins', 'marketplaces', NAME);
|
|
223
|
+
const viaMarketplaceDir = !spec && fs.existsSync(marketplace);
|
|
224
|
+
if ((spec || viaMarketplaceDir) && !force) {
|
|
225
|
+
const found = spec
|
|
226
|
+
? `installed as the Claude Code plugin ${spec}\n` +
|
|
227
|
+
' (declared in ~/.claude/plugins/installed_plugins.json)'
|
|
228
|
+
: `registered as a Claude Code marketplace\n (${marketplace})`;
|
|
229
|
+
console.error(
|
|
230
|
+
`refused: super-ux is already ${found}.\n` +
|
|
231
|
+
' The skills CLI auto-detects Claude Code and would write a plain copy\n' +
|
|
232
|
+
' to ~/.claude/skills/super-ux, which shadows the plugin and serves the\n' +
|
|
233
|
+
' version it was copied from forever. Update the plugin channel instead:\n' +
|
|
234
|
+
' claude plugin marketplace update super-ux\n' +
|
|
235
|
+
` claude plugin update ${spec || 'super-ux@super-ux'}\n` +
|
|
236
|
+
' Family launcher (updates every member, prunes shadow copies):\n' +
|
|
237
|
+
' npx --yes sshlg-skills@latest update\n' +
|
|
238
|
+
' Pass --force (npx super-ux --force) to run the picker anyway: a\n' +
|
|
239
|
+
' deliberate choice to run two channels, where the stale one wins.'
|
|
240
|
+
);
|
|
241
|
+
return 'refused';
|
|
242
|
+
}
|
|
156
243
|
console.log(`\n--- Skills for any agent: delegating to the skills CLI picker ---`);
|
|
157
244
|
const status = run('npx', ['--yes', 'skills', 'add', REPO]);
|
|
158
245
|
if (status !== 'ok') console.error(`warning: 'npx skills add ${REPO}' ${status}`);
|
|
246
|
+
return status;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* The last line of a successful run says how the next version arrives —
|
|
251
|
+
* "Installed" is not a complete sentence. Auto-update is off on purpose:
|
|
252
|
+
* this member composes with its family, and per-marketplace autoUpdate moves
|
|
253
|
+
* each member on its own clock, into combinations nobody tested together.
|
|
254
|
+
*/
|
|
255
|
+
function printUpdateLine() {
|
|
256
|
+
console.log(
|
|
257
|
+
'\nUpdates: rerun npx super-ux@latest (--cursor <dir> --force refreshes a\n' +
|
|
258
|
+
"project's rules and linters), or refresh the whole family with\n" +
|
|
259
|
+
'npx --yes sshlg-skills@latest update (every channel, and it prunes plain\n' +
|
|
260
|
+
'copies that would shadow a plugin).'
|
|
261
|
+
);
|
|
159
262
|
}
|
|
160
263
|
|
|
161
264
|
function installClaudePlugin() {
|
|
@@ -305,7 +408,7 @@ async function selectFallback(items, prompter) {
|
|
|
305
408
|
return picked;
|
|
306
409
|
}
|
|
307
410
|
|
|
308
|
-
async function menu() {
|
|
411
|
+
async function menu(force) {
|
|
309
412
|
console.log('super-ux: scenario-driven UI development. Select what to install:\n');
|
|
310
413
|
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
311
414
|
|
|
@@ -340,11 +443,21 @@ async function menu() {
|
|
|
340
443
|
|
|
341
444
|
if (keys.includes('cursor')) installCursor(cursorDir, false);
|
|
342
445
|
if (keys.includes('claude')) installClaudePlugin();
|
|
343
|
-
|
|
446
|
+
let refused = false;
|
|
447
|
+
if (keys.includes('skills')) refused = installSkillsCli(force) === 'refused';
|
|
344
448
|
|
|
345
449
|
// Same offer the --cursor flag path makes. Two doors into one install that
|
|
346
450
|
// behave differently is how a feature comes to exist for half its users.
|
|
451
|
+
// Offered on the refused path too: the skill IS present on this machine —
|
|
452
|
+
// as the plugin — so the routing block is exactly as wanted.
|
|
347
453
|
offerRouters();
|
|
454
|
+
if (refused) {
|
|
455
|
+
// The refusal already carries the update commands; repeating the update
|
|
456
|
+
// line under it would bury the remedy. Exit 3 so scripts read the refusal.
|
|
457
|
+
process.exitCode = EXIT_PLUGIN_PRESENT;
|
|
458
|
+
} else {
|
|
459
|
+
printUpdateLine();
|
|
460
|
+
}
|
|
348
461
|
}
|
|
349
462
|
|
|
350
463
|
/**
|
|
@@ -379,7 +492,14 @@ function main() {
|
|
|
379
492
|
return;
|
|
380
493
|
}
|
|
381
494
|
if (args.length === 0) {
|
|
382
|
-
menu();
|
|
495
|
+
menu(false);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
// `--force` alone still opens the menu: it is the named override for the
|
|
499
|
+
// skills-handoff refusal, so it must be reachable from the same door the
|
|
500
|
+
// refusal names.
|
|
501
|
+
if (args.length === 1 && args[0] === '--force') {
|
|
502
|
+
menu(true);
|
|
383
503
|
return;
|
|
384
504
|
}
|
|
385
505
|
if (args[0] !== '--cursor') {
|
|
@@ -391,6 +511,7 @@ function main() {
|
|
|
391
511
|
const dirArg = args[1] && args[1] !== '--force' ? args[1] : '.';
|
|
392
512
|
installCursor(path.resolve(dirArg), force);
|
|
393
513
|
offerRouters();
|
|
514
|
+
printUpdateLine();
|
|
394
515
|
}
|
|
395
516
|
|
|
396
517
|
main();
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "super-ux",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.0",
|
|
4
4
|
"scripts": {
|
|
5
|
-
"test": "python3 test/validate.py && python3 test/brand_lint_test.py && python3 test/ux_lint_test.py && python3 docs/ux/lint.py && python3 docs/brand/lint.py"
|
|
5
|
+
"test": "python3 test/validate.py && python3 test/brand_lint_test.py && python3 test/ux_lint_test.py && python3 docs/ux/lint.py && python3 docs/brand/lint.py && node test/installer_test.js"
|
|
6
6
|
},
|
|
7
7
|
"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.",
|
|
8
8
|
"bin": {
|