super-ux 0.48.4 → 0.49.1
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 +55 -0
- package/bin/super-ux.js +126 -5
- package/package.json +2 -2
- package/templates/brand/voice.md +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.49.1 — the skills handoff refuses the shadow it used to create
|
|
4
|
+
|
|
5
|
+
- **The skills-menu item now consults the target home before delegating.** `npx skills add`
|
|
6
|
+
auto-detects Claude Code and writes a plain `~/.claude/skills/super-ux` copy even when
|
|
7
|
+
claude-code is never picked, and on a machine where super-ux is installed as a Claude
|
|
8
|
+
Code plugin that copy shadows the plugin and serves the version it was copied from
|
|
9
|
+
forever. The handoff now reads `~/.claude/plugins/installed_plugins.json` — the record of
|
|
10
|
+
what is actually installed, under any marketplace name — and refuses with **exit 3**, the
|
|
11
|
+
remedy in the refusal (`claude plugin marketplace update super-ux`, `claude plugin update
|
|
12
|
+
<spec from the JSON>`, and the family launcher), and `npx super-ux --force` as the named
|
|
13
|
+
override. The `plugins/marketplaces/super-ux` directory is kept only as the fallback
|
|
14
|
+
signal: it under-reports, because a directory-sourced marketplace has no dir there and
|
|
15
|
+
plugin names differ from marketplace names. A missing or corrupt JSON reads as "no
|
|
16
|
+
plugin" — fail open, never crash. Only the Claude Code channel is gated; `--cursor`
|
|
17
|
+
installs into a project beside the plugin exactly as before. Canon:
|
|
18
|
+
make-skill v0.25.0, `references/distribution.md` §3; reproduced live 2026-08-29 when a
|
|
19
|
+
bare `npx @ssheleg/telegram-dev` shipped three shadows past this exact class of hole.
|
|
20
|
+
- **A successful run now ends by saying how the next version arrives** — `npx
|
|
21
|
+
super-ux@latest` for this member, the family launcher for every channel at once. An
|
|
22
|
+
installer that never mentions updates has still chosen an update model: never.
|
|
23
|
+
- **`test/installer_test.js`** runs both installers against throwaway HOMEs — the
|
|
24
|
+
plugin-present refusal (exit 3, remedy, nothing delegated, nothing written), the
|
|
25
|
+
differently-named marketplace spec in the remedy, `--force`, corrupt-JSON fail-open, a
|
|
26
|
+
prefix collider (`super-ux-extra@x`), the marketplaces-dir fallback, the fresh HOME, and
|
|
27
|
+
that `install.sh` never touches `~/.claude` at all. Wired into `npm test` and CI; watched
|
|
28
|
+
failing against the pre-fix installer (7 of 10 cases red, the plugin-present case
|
|
29
|
+
delegating with exit 0) before the fix was un-stashed.
|
|
30
|
+
- SCN-016 and SCN-017 record the refusal and the update line in `docs/ux/scenarios.md`;
|
|
31
|
+
SCR-05 gains the `refused` state; the string registry carries the new copy and the
|
|
32
|
+
`refused:` prefix joins the state vocabulary.
|
|
33
|
+
|
|
34
|
+
## 0.49.0 — the humanization pass names what it cannot prove
|
|
35
|
+
|
|
36
|
+
- **`ai-tells.md` now says these markers are not a verdict, and who they misjudge.** Every
|
|
37
|
+
marker is more common in model output; none proves a machine wrote anything, and the
|
|
38
|
+
failure is not symmetric. Independent audits found false-positive rates **above 60% on
|
|
39
|
+
non-native English writers** (Liang et al., Stanford, *Patterns*, 2023). Three rules bind
|
|
40
|
+
the rest of the file: never say a text was AI-written, only which markers appear at what
|
|
41
|
+
density; never gate on a marker count; and a second-language writer is not a defect to be
|
|
42
|
+
edited into fluency they did not ask for.
|
|
43
|
+
- **Other implementations are named, compared and pointed at** — `blader/humanizer` and
|
|
44
|
+
`conorbronsdon/avoid-ai-writing`, with a table of what each does that this pack does not
|
|
45
|
+
and the reverse. Reach outward for an audit that changes nothing (only one has a
|
|
46
|
+
detect-only mode), for long-form prose, or when the writer has a sample of their own
|
|
47
|
+
writing to match. Stay here for product copy: the brand pack's registers, terminology and
|
|
48
|
+
canonical facts are the constraint, and a general-purpose humanizer does not read them.
|
|
49
|
+
Two guards remain this pack's own — the 50% change-rate refusal and the mandatory
|
|
50
|
+
semantic-preservation check — and neither external implementation carries an equivalent.
|
|
51
|
+
- **`voice.md` gains an optional `Humanization pass:` field**, and its absence is
|
|
52
|
+
meaningful: it means nobody has been asked. `brand-voice` Init asks once and records the
|
|
53
|
+
answer; `copywriting` reads it rather than asking again. A value naming a tool that is not
|
|
54
|
+
installed falls back to `own` and says so — a missing optional tool must not stop copy
|
|
55
|
+
being written.
|
|
56
|
+
- `npx sshlg-skills humanizers` lists what is installed on the machine.
|
|
57
|
+
|
|
3
58
|
## 0.48.4 — the channel that sends the installs, on npm too
|
|
4
59
|
|
|
5
60
|
- The `skills.sh` badge and the canonical `homepage` reached GitHub in the previous cycle and stopped
|
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.49.1",
|
|
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": {
|
package/templates/brand/voice.md
CHANGED