@motion-proto/live-tokens 0.74.0 → 0.76.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/.claude/skills/live-tokens-check-compliance/SKILL.md +33 -38
- package/.claude/skills/live-tokens-create-component/SKILL copy.md +196 -0
- package/.claude/skills/live-tokens-create-component/SKILL.md +198 -146
- package/.claude/skills/live-tokens-create-component/references/contract-tests.md +95 -52
- package/.claude/skills/live-tokens-create-component/references/intrinsics.md +2 -2
- package/.claude/skills/live-tokens-create-component/references/linked-siblings.md +2 -2
- package/.claude/skills/live-tokens-create-component/references/sketch-mode.md +12 -12
- package/.claude/skills/live-tokens-create-component/references/token-naming.md +2 -1
- package/.claude/skills/live-tokens-create-page/SKILL.md +187 -0
- package/.claude/skills/live-tokens-create-page/references/interaction-sources.md +66 -0
- package/.claude/skills/live-tokens-create-page/references/layout-sources.md +87 -0
- package/.claude/skills/live-tokens-create-theme/SKILL.md +52 -48
- package/.claude/skills/live-tokens-create-theme/references/design-directions.md +1 -1
- package/.claude/skills/live-tokens-fix-findings/SKILL.md +69 -60
- package/.claude/skills/live-tokens-pick-component/SKILL.md +64 -79
- package/.claude/skills/live-tokens-set-colors/SKILL.md +47 -38
- package/.claude/skills/live-tokens-set-geometry/SKILL.md +58 -35
- package/.claude/skills/live-tokens-set-geometry/references/geometry-anchors.md +5 -3
- package/.claude/skills/live-tokens-set-type/SKILL.md +30 -30
- package/CHANGELOG.md +138 -0
- package/README.md +17 -7
- package/bin/check-component.mjs +91 -19
- package/bin/check-page.mjs +102 -21
- package/bin/cli.mjs +87 -113
- package/bin/contractRunner.mjs +945 -0
- package/bin/create.mjs +1 -1
- package/bin/lib/catalogue.mjs +37 -30
- package/bin/lib/findings.mjs +46 -15
- package/bin/lib/report.mjs +3 -3
- package/bin/lib/tokenVocabulary.mjs +4 -4
- package/bin/migrate-routes.mjs +5 -5
- package/bin/migrate.mjs +4 -4
- package/bin/save-theme.mjs +8 -9
- package/bin/set-colors.mjs +9 -11
- package/bin/set-geometry.mjs +7 -7
- package/bin/set-type.mjs +5 -7
- package/bin/setup-claude.mjs +110 -0
- package/dist-plugin/{chunk-W6Y4BWFB.js → chunk-6WGFOJXO.js} +22 -0
- package/dist-plugin/{chunk-7VRTBGJT.js → chunk-PDNL4NC5.js} +9 -2
- package/dist-plugin/{chunk-V3YF6CGT.js → chunk-SWXRVZKT.js} +43 -1
- package/dist-plugin/{dataPaths-BhWzd5cL.d.cts → dataPaths-BpIK_Obx.d.cts} +1 -0
- package/dist-plugin/{dataPaths-BhWzd5cL.d.ts → dataPaths-BpIK_Obx.d.ts} +1 -0
- package/dist-plugin/index.cjs +310 -147
- package/dist-plugin/index.d.cts +1 -1
- package/dist-plugin/index.d.ts +1 -1
- package/dist-plugin/index.js +218 -122
- package/dist-plugin/migrateData/index.cjs +66 -2
- package/dist-plugin/migrateData/index.d.cts +1 -1
- package/dist-plugin/migrateData/index.d.ts +1 -1
- package/dist-plugin/migrateData/index.js +3 -3
- package/dist-plugin/setColors/index.cjs +61 -4
- package/dist-plugin/setColors/index.d.cts +1 -1
- package/dist-plugin/setColors/index.d.ts +1 -1
- package/dist-plugin/setColors/index.js +5 -5
- package/dist-plugin/setGeometry/index.cjs +107 -44
- package/dist-plugin/setGeometry/index.d.cts +5 -5
- package/dist-plugin/setGeometry/index.d.ts +5 -5
- package/dist-plugin/setGeometry/index.js +51 -45
- package/dist-plugin/setType/index.cjs +15 -0
- package/dist-plugin/setType/index.d.cts +1 -1
- package/dist-plugin/setType/index.d.ts +1 -1
- package/dist-plugin/setType/index.js +1 -1
- package/dist-plugin/tokensCssMigrations/index.d.cts +1 -1
- package/dist-plugin/tokensCssMigrations/index.d.ts +1 -1
- package/dist-plugin/tokensCssMigrations/index.js +1 -1
- package/package.json +35 -8
- package/src/app/site.css +19 -9
- package/src/editor/bootstrap.ts +2 -0
- package/src/editor/component-editor/CollapsibleSectionEditor.svelte +4 -4
- package/src/editor/component-editor/DialogEditor.svelte +4 -4
- package/src/editor/component-editor/MenuSelectEditor.svelte +4 -1
- package/src/editor/component-editor/SegmentedControlEditor.svelte +6 -1
- package/src/editor/component-editor/TabBarEditor.svelte +1 -1
- package/src/editor/component-editor/TableEditor.svelte +2 -2
- package/src/editor/component-editor/scaffolding/TokenLayout.svelte +6 -3
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +41 -20
- package/src/editor/core/components/adjustAliases.ts +59 -45
- package/src/editor/core/components/aliasKinds.ts +9 -5
- package/src/editor/core/preview/themePreview.ts +9 -2
- package/src/editor/core/sketch/sketchLayer.ts +22 -0
- package/src/editor/core/store/editorStore.ts +10 -1
- package/src/editor/core/themes/buildColors.ts +3 -3
- package/src/editor/core/themes/liveStateStream.ts +26 -0
- package/src/editor/core/themes/migrations/2026-09-07-stroke-role-renames.ts +52 -0
- package/src/editor/core/themes/migrations/index.ts +2 -0
- package/src/editor/core/themes/themeDocumentSync.ts +22 -11
- package/src/editor/core/themes/themeService.ts +9 -2
- package/src/editor/pages/ComponentEditorPage.svelte +17 -1
- package/src/editor/pages/liveTokensEditorHandle.ts +23 -0
- package/src/editor/skill-atlas/SkillAtlas.svelte +75 -573
- package/src/editor/skill-atlas/TreeCanvas.svelte +263 -0
- package/src/editor/skill-atlas/TreeNodeCard.svelte +136 -53
- package/src/editor/skill-atlas/edges.ts +31 -0
- package/src/editor/skill-atlas/skillSources.generated.ts +20 -19
- package/src/editor/skill-atlas/skillTrees.ts +19 -3840
- package/src/editor/skill-atlas/trees/check-compliance.ts +183 -0
- package/src/editor/skill-atlas/trees/create-component.ts +275 -0
- package/src/editor/skill-atlas/trees/create-page.ts +320 -0
- package/src/editor/skill-atlas/trees/create-theme.ts +255 -0
- package/src/editor/skill-atlas/trees/fix-findings.ts +469 -0
- package/src/editor/skill-atlas/trees/pick-component.ts +300 -0
- package/src/editor/skill-atlas/trees/set-colors.ts +148 -0
- package/src/editor/skill-atlas/trees/set-geometry.ts +136 -0
- package/src/editor/skill-atlas/trees/set-type.ts +142 -0
- package/src/editor/skill-atlas/types.ts +3 -4
- package/src/editor/skill-atlas/wireLayout.ts +287 -0
- package/src/live-tokens/data/themes/autumn.json +15 -15
- package/src/live-tokens/data/themes/halloween.json +15 -15
- package/src/live-tokens/data/themes/midnight-study.json +15 -15
- package/src/live-tokens/data/themes/ocean.json +15 -15
- package/src/live-tokens/data/themes/royal-velvet.json +15 -15
- package/src/live-tokens/data/themes/sketchy.json +15 -15
- package/src/live-tokens/data/themes/spring-meadow.json +15 -15
- package/src/live-tokens/data/themes/sunset.json +15 -15
- package/src/system/components/Badge.svelte +7 -0
- package/src/system/components/Button.svelte +7 -0
- package/src/system/components/Callout.svelte +10 -6
- package/src/system/components/Card.svelte +23 -4
- package/src/system/components/CodeSnippet.svelte +4 -3
- package/src/system/components/CollapsibleSection.svelte +23 -8
- package/src/system/components/CornerBadge.svelte +6 -0
- package/src/system/components/Dialog.svelte +13 -6
- package/src/system/components/IconButton.svelte +9 -0
- package/src/system/components/Image.svelte +8 -0
- package/src/system/components/ImageLightbox.svelte +6 -0
- package/src/system/components/InlineEditActions.svelte +7 -0
- package/src/system/components/Input.svelte +7 -0
- package/src/system/components/MenuSelect.svelte +7 -0
- package/src/system/components/Notification.svelte +7 -0
- package/src/system/components/Panel.svelte +6 -0
- package/src/system/components/ProgressBar.svelte +5 -0
- package/src/system/components/RadioButton.svelte +11 -5
- package/src/system/components/SectionDivider.svelte +8 -0
- package/src/system/components/SegmentedControl.svelte +6 -0
- package/src/system/components/SideNavigation.svelte +6 -0
- package/src/system/components/Slider.svelte +7 -4
- package/src/system/components/TabBar.svelte +15 -9
- package/src/system/components/Table.svelte +8 -3
- package/src/system/components/Toggle.svelte +4 -4
- package/src/system/components/Tooltip.svelte +6 -0
- package/src/testing-js/chunk-AO7EZHYV.js +776 -0
- package/src/testing-js/chunk-AO7EZHYV.js.map +1 -0
- package/src/testing-js/chunk-FAFOAWYL.js +39 -0
- package/src/testing-js/chunk-FAFOAWYL.js.map +1 -0
- package/src/testing-js/chunk-L73N4NSO.js +23 -0
- package/src/testing-js/chunk-L73N4NSO.js.map +1 -0
- package/src/testing-js/chunk-LXR3MN6N.js +3063 -0
- package/src/testing-js/chunk-LXR3MN6N.js.map +1 -0
- package/src/testing-js/chunk-ZMSX6CXR.js +53 -0
- package/src/testing-js/chunk-ZMSX6CXR.js.map +1 -0
- package/src/testing-js/component-alias.contract.js +81 -0
- package/src/testing-js/component-alias.contract.js.map +1 -0
- package/src/testing-js/component-editor.contract.js +62 -0
- package/src/testing-js/component-editor.contract.js.map +1 -0
- package/src/testing-js/component-render.contract.js +568 -0
- package/src/testing-js/component-render.contract.js.map +1 -0
- package/src/testing-js/index.d.ts +293 -0
- package/src/testing-js/index.js +222 -0
- package/src/testing-js/index.js.map +1 -0
- package/src/testing-js/registry.contract.js +39 -0
- package/src/testing-js/registry.contract.js.map +1 -0
- package/src/testing-js/vitest-BE6uGF31.d.ts +73 -0
- package/src/testing-js/vitest.d.ts +3 -0
- package/src/testing-js/vitest.js +13 -0
- package/src/testing-js/vitest.js.map +1 -0
- package/template/README.md +13 -0
- package/template/_gitignore +6 -0
- package/template/package.json +3 -1
- package/template/src/pages/Home.svelte +4 -18
- package/.claude/skills/live-tokens-build-page/SKILL.md +0 -103
- package/.claude/skills/live-tokens-build-page/references/layout-sources.md +0 -48
package/bin/create.mjs
CHANGED
|
@@ -78,7 +78,7 @@ export function runCreate({ targetDir, pkgRoot, force = false }) {
|
|
|
78
78
|
export function formatCreateResult({ appName, targetDir }, targetArg) {
|
|
79
79
|
return [
|
|
80
80
|
``,
|
|
81
|
-
`Scaffolded ${appName}
|
|
81
|
+
`Scaffolded ${appName} in ${targetDir}`,
|
|
82
82
|
``,
|
|
83
83
|
`Next steps:`,
|
|
84
84
|
` cd ${targetArg}`,
|
package/bin/lib/catalogue.mjs
CHANGED
|
@@ -1,28 +1,33 @@
|
|
|
1
1
|
// The registry as a query. Every component a project has, shipped or its own,
|
|
2
2
|
// with the props each takes and the tokens each declares, and every theme token
|
|
3
|
-
// grouped by
|
|
3
|
+
// grouped by scale. Read from files through the same vocabulary the checkers
|
|
4
4
|
// use, so a skill or a script sees exactly what the checkers will hold it to.
|
|
5
5
|
|
|
6
6
|
import { readFileSync } from 'node:fs';
|
|
7
7
|
import { relative } from 'node:path';
|
|
8
|
-
import {
|
|
8
|
+
import { CONTRACT_SCALES } from './tokenVocabulary.mjs';
|
|
9
9
|
|
|
10
|
-
/** The runtime file's leading HTML comment, which is where a component says what
|
|
10
|
+
/** The runtime file's leading HTML comment, which is where a component says what
|
|
11
|
+
it is for. A labelled line (`Use for:`, `Not for:`, `Emphasis:`) opens a line
|
|
12
|
+
of the description and every other line continues the one above it, so the
|
|
13
|
+
comment wraps in the source and still reads as its four lines here. */
|
|
11
14
|
function descriptionOf(source) {
|
|
12
15
|
const m = source.match(/^\s*<!--([\s\S]*?)-->/);
|
|
13
16
|
if (!m) return '';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
.
|
|
19
|
-
.
|
|
20
|
-
|
|
17
|
+
const lines = [];
|
|
18
|
+
for (const raw of m[1].split('\n')) {
|
|
19
|
+
const line = raw.trim().replace(/\s+/g, ' ');
|
|
20
|
+
if (!line) continue;
|
|
21
|
+
if (lines.length && !/^[A-Z][A-Za-z ]{0,20}:/.test(line)) lines[lines.length - 1] += ` ${line}`;
|
|
22
|
+
else lines.push(line);
|
|
23
|
+
}
|
|
24
|
+
if (lines.length) lines[0] = lines[0].replace(/^\S+\.svelte\s*(?:[—–-]+|\.)\s*/, '');
|
|
25
|
+
return lines.join('\n');
|
|
21
26
|
}
|
|
22
27
|
|
|
23
|
-
function
|
|
28
|
+
function scaleOf(name) {
|
|
24
29
|
const stem = name.replace(/^--/, '');
|
|
25
|
-
const hit =
|
|
30
|
+
const hit = CONTRACT_SCALES
|
|
26
31
|
.filter((f) => stem === f || stem.startsWith(`${f}-`))
|
|
27
32
|
.sort((a, b) => b.length - a.length)[0];
|
|
28
33
|
return hit ?? stem.split('-')[0];
|
|
@@ -62,15 +67,15 @@ export function describeTokens(vocab, { root = process.cwd() } = {}) {
|
|
|
62
67
|
if (!values.has(m[1])) values.set(m[1], m[2].trim());
|
|
63
68
|
}
|
|
64
69
|
}
|
|
65
|
-
const
|
|
70
|
+
const byScale = new Map();
|
|
66
71
|
for (const name of vocab.themeTokens) {
|
|
67
|
-
const
|
|
68
|
-
if (!
|
|
69
|
-
|
|
72
|
+
const scale = scaleOf(name);
|
|
73
|
+
if (!byScale.has(scale)) byScale.set(scale, []);
|
|
74
|
+
byScale.get(scale).push({ name, value: values.get(name) ?? '' });
|
|
70
75
|
}
|
|
71
76
|
return {
|
|
72
77
|
tokensCss: vocab.tokensCssPath ? relative(root, vocab.tokensCssPath) : null,
|
|
73
|
-
|
|
78
|
+
scales: [...byScale].map(([scale, tokens]) => ({ scale, tokens })),
|
|
74
79
|
components: [...vocab.components.values()].map((c) => ({
|
|
75
80
|
id: c.id,
|
|
76
81
|
tokens: [...c.tokens].map(([name, value]) => ({ name, default: value })),
|
|
@@ -78,13 +83,15 @@ export function describeTokens(vocab, { root = process.cwd() } = {}) {
|
|
|
78
83
|
};
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
const describeLines = (c) => (c.description ? c.description.split('\n') : []);
|
|
87
|
+
|
|
81
88
|
export function formatComponents(list, { id } = {}) {
|
|
82
89
|
const lines = [];
|
|
83
90
|
if (id) {
|
|
84
91
|
const c = list.find((x) => x.id === id);
|
|
85
92
|
if (!c) return `No component "${id}". Run \`live-tokens components\` for the list.`;
|
|
86
|
-
lines.push(`${c.name} (${c.id}, ${c.origin}${c.registered ? '' : ',
|
|
87
|
-
|
|
93
|
+
lines.push(`${c.name} (${c.id}, ${c.origin}${c.registered ? '' : ', unregistered'}) ${c.file}`);
|
|
94
|
+
for (const line of describeLines(c)) lines.push(` ${line}`);
|
|
88
95
|
if (c.props.length) {
|
|
89
96
|
lines.push(' props:');
|
|
90
97
|
for (const p of c.props) lines.push(` ${p.name}${p.values ? `: ${p.values.join(' | ')}` : p.type ? `: ${p.type}` : ''}`);
|
|
@@ -96,7 +103,7 @@ export function formatComponents(list, { id } = {}) {
|
|
|
96
103
|
for (const c of list) {
|
|
97
104
|
const variants = c.variants.length ? ` variants: ${c.variants.join(', ')}` : '';
|
|
98
105
|
lines.push(`${c.id.padEnd(20)} ${c.origin.padEnd(8)} ${c.name}${c.registered ? '' : ' (NOT registered)'}${variants}`);
|
|
99
|
-
|
|
106
|
+
for (const line of describeLines(c)) lines.push(` ${line}`);
|
|
100
107
|
}
|
|
101
108
|
lines.push('');
|
|
102
109
|
const unregistered = list.filter((c) => !c.registered).length;
|
|
@@ -107,21 +114,21 @@ export function formatComponents(list, { id } = {}) {
|
|
|
107
114
|
return lines.join('\n');
|
|
108
115
|
}
|
|
109
116
|
|
|
110
|
-
export function formatTokens(desc, {
|
|
117
|
+
export function formatTokens(desc, { scale } = {}) {
|
|
111
118
|
const lines = [];
|
|
112
|
-
const
|
|
113
|
-
if (
|
|
114
|
-
return `No
|
|
119
|
+
const scales = scale ? desc.scales.filter((s) => s.scale === scale) : desc.scales;
|
|
120
|
+
if (scale && scales.length === 0) {
|
|
121
|
+
return `No token scale "${scale}". Scales: ${desc.scales.map((s) => s.scale).join(', ')}.`;
|
|
115
122
|
}
|
|
116
|
-
lines.push(`
|
|
117
|
-
for (const
|
|
123
|
+
lines.push(`Design tokens from ${desc.tokensCss ?? '(no tokens.css found)'}`);
|
|
124
|
+
for (const s of scales) {
|
|
118
125
|
lines.push('');
|
|
119
|
-
lines.push(`${
|
|
120
|
-
for (const t of
|
|
126
|
+
lines.push(`${s.scale} (${s.tokens.length})`);
|
|
127
|
+
for (const t of s.tokens) lines.push(` ${t.name}: ${t.value}`);
|
|
121
128
|
}
|
|
122
|
-
if (!
|
|
129
|
+
if (!scale) {
|
|
123
130
|
lines.push('');
|
|
124
|
-
lines.push(`
|
|
131
|
+
lines.push(`Semantic properties: ${desc.components.reduce((n, c) => n + c.tokens.length, 0)} across ${desc.components.length} component(s). \`live-tokens components <id>\` lists one component's.`);
|
|
125
132
|
}
|
|
126
133
|
return lines.join('\n');
|
|
127
134
|
}
|
package/bin/lib/findings.mjs
CHANGED
|
@@ -52,38 +52,65 @@ export function isExcluded(relPath, root) {
|
|
|
52
52
|
* Unrecognised flags are returned in `rest` for the caller to handle.
|
|
53
53
|
*/
|
|
54
54
|
export function parseCheckFlags(argv) {
|
|
55
|
-
const opts = { off: [], warn: [], error: [], strict: false, json: false, rest: [] };
|
|
55
|
+
const opts = { off: [], warn: [], error: [], strict: false, json: false, tests: false, rest: [] };
|
|
56
56
|
for (const arg of argv) {
|
|
57
57
|
const m = arg.match(/^--(off|warn|error)=(.+)$/);
|
|
58
58
|
if (m) {
|
|
59
59
|
opts[m[1]].push(...m[2].split(',').map((s) => s.trim()).filter(Boolean));
|
|
60
60
|
} else if (arg === '--strict') opts.strict = true;
|
|
61
61
|
else if (arg === '--json') opts.json = true;
|
|
62
|
+
else if (arg === '--tests') opts.tests = true;
|
|
62
63
|
else opts.rest.push(arg);
|
|
63
64
|
}
|
|
64
65
|
return opts;
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
/** The same last-wins resolution `applySeverity` applies per finding, exposed
|
|
69
|
+
* standalone so coverage (which has no findings to attach a severity to, but
|
|
70
|
+
* still has to honor `--off`) can ask the same question. */
|
|
71
|
+
export function resolveRuleSeverity(ruleId, rules, opts = {}, config = {}) {
|
|
72
|
+
const configured = config.rules ?? {};
|
|
73
|
+
let severity = rules[ruleId] ?? 'error';
|
|
74
|
+
if (SEVERITIES.includes(configured[ruleId])) severity = configured[ruleId];
|
|
75
|
+
if (opts.off?.includes(ruleId)) severity = 'off';
|
|
76
|
+
if (opts.warn?.includes(ruleId)) severity = 'warn';
|
|
77
|
+
if (opts.error?.includes(ruleId)) severity = 'error';
|
|
78
|
+
if (opts.strict && severity === 'warn') severity = 'error';
|
|
79
|
+
return severity;
|
|
80
|
+
}
|
|
81
|
+
|
|
67
82
|
/**
|
|
68
83
|
* Resolve each finding's severity and drop the ones turned off.
|
|
69
84
|
* `rules` maps rule id to its default severity.
|
|
70
85
|
*/
|
|
71
|
-
export function applySeverity(findings, rules, opts = {}, config = {}) {
|
|
72
|
-
const configured = config.rules ?? {};
|
|
73
|
-
const resolve = (id) => {
|
|
74
|
-
let severity = rules[id] ?? 'error';
|
|
75
|
-
if (SEVERITIES.includes(configured[id])) severity = configured[id];
|
|
76
|
-
if (opts.off?.includes(id)) severity = 'off';
|
|
77
|
-
if (opts.warn?.includes(id)) severity = 'warn';
|
|
78
|
-
if (opts.error?.includes(id)) severity = 'error';
|
|
79
|
-
if (opts.strict && severity === 'warn') severity = 'error';
|
|
80
|
-
return severity;
|
|
81
|
-
};
|
|
86
|
+
export function applySeverity(findings, rules, opts = {}, config = {}, fixes = {}) {
|
|
82
87
|
return findings
|
|
83
|
-
.map((f) => ({
|
|
88
|
+
.map((f) => ({
|
|
89
|
+
...f,
|
|
90
|
+
severity: resolveRuleSeverity(f.rule, rules, opts, config),
|
|
91
|
+
...(fixes[f.rule] ? { fix: fixes[f.rule] } : {}),
|
|
92
|
+
}))
|
|
84
93
|
.filter((f) => f.severity !== 'off');
|
|
85
94
|
}
|
|
86
95
|
|
|
96
|
+
/**
|
|
97
|
+
* A coverage entry for a rule resolved to severity 'off' becomes 'disabled',
|
|
98
|
+
* regardless of what actually happened: the plan requires `--off` to stay
|
|
99
|
+
* visible as disabled coverage rather than silently reading as a pass (an
|
|
100
|
+
* obligation that failed but was silenced is not the same as one that ran
|
|
101
|
+
* clean), and disabled coverage can never establish a complete pass.
|
|
102
|
+
*/
|
|
103
|
+
export function applyCoverageSeverity(coverage, rules, opts = {}, config = {}) {
|
|
104
|
+
const out = {};
|
|
105
|
+
for (const [id, ruleMap] of Object.entries(coverage)) {
|
|
106
|
+
out[id] = {};
|
|
107
|
+
for (const [rule, entry] of Object.entries(ruleMap)) {
|
|
108
|
+
out[id][rule] = resolveRuleSeverity(rule, rules, opts, config) === 'off' ? { status: 'disabled' } : entry;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
|
|
87
114
|
export function countBySeverity(findings) {
|
|
88
115
|
return {
|
|
89
116
|
errors: findings.filter((f) => f.severity === 'error').length,
|
|
@@ -117,7 +144,11 @@ export function formatFindings(findings, { label, checked = 0 } = {}) {
|
|
|
117
144
|
return lines.join('\n');
|
|
118
145
|
}
|
|
119
146
|
|
|
120
|
-
export function toJson(findings, { label, checked = 0 } = {}) {
|
|
147
|
+
export function toJson(findings, { label, checked = 0, coverage } = {}) {
|
|
121
148
|
const { errors, warnings } = countBySeverity(findings);
|
|
122
|
-
return JSON.stringify(
|
|
149
|
+
return JSON.stringify(
|
|
150
|
+
{ check: label, checked, errors, warnings, findings, ...(coverage ? { coverage } : {}) },
|
|
151
|
+
null,
|
|
152
|
+
2,
|
|
153
|
+
);
|
|
123
154
|
}
|
package/bin/lib/report.mjs
CHANGED
|
@@ -120,7 +120,7 @@ const list = (items, max = 20) =>
|
|
|
120
120
|
|
|
121
121
|
export function formatReport(r) {
|
|
122
122
|
const out = [];
|
|
123
|
-
out.push(`Project: ${r.project.pages} page file(s), ${r.project.components} component(s), ${r.project.themeTokens}
|
|
123
|
+
out.push(`Project: ${r.project.pages} page file(s), ${r.project.components} component(s), ${r.project.themeTokens} design tokens from ${r.project.tokensCss ?? '(no tokens.css)'}`);
|
|
124
124
|
if (r.migrations) {
|
|
125
125
|
out.push('');
|
|
126
126
|
out.push(`Migrations: ${r.migrations.status}${r.migrations.pending?.length ? ` (${list(r.migrations.pending)})` : ''}`);
|
|
@@ -131,11 +131,11 @@ export function formatReport(r) {
|
|
|
131
131
|
const unread = r.components.filter((c) => c.unread.length);
|
|
132
132
|
out.push(` tokens declared and read by their own CSS: ${r.components.reduce((n, c) => n + c.tokens - c.unread.length, 0)} of ${r.components.reduce((n, c) => n + c.tokens, 0)}`);
|
|
133
133
|
for (const c of unread) out.push(` ${c.id}: ${c.unread.length} unread (${list(c.unread, 6)})`);
|
|
134
|
+
const undescribed = r.components.filter((c) => !c.described).map((c) => c.id);
|
|
135
|
+
if (undescribed.length) out.push(` no description comment: ${list(undescribed)}`);
|
|
134
136
|
const custom = r.components.filter((c) => c.origin === 'custom');
|
|
135
137
|
out.push(` custom: ${custom.length}${custom.length ? ` (${list(custom.map((c) => c.id))})` : ''}`);
|
|
136
138
|
if (r.usage.customUnregistered.length) out.push(` not registered: ${list(r.usage.customUnregistered)}`);
|
|
137
|
-
const undescribed = custom.filter((c) => !c.described).map((c) => c.id);
|
|
138
|
-
if (undescribed.length) out.push(` no description comment: ${list(undescribed)}`);
|
|
139
139
|
|
|
140
140
|
out.push('');
|
|
141
141
|
out.push('Usage');
|
|
@@ -25,18 +25,18 @@ const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
|
|
|
25
25
|
|
|
26
26
|
const SHIPPED_COMPONENTS_DIR = 'src/system/components';
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
export const
|
|
28
|
+
/** Token scales whose names are governed by the token contract (see TOKENS.md). */
|
|
29
|
+
export const CONTRACT_SCALES = [
|
|
30
30
|
'surface', 'text', 'border', 'color', 'space', 'radius', 'font', 'line-height',
|
|
31
31
|
'letter-spacing', 'shadow', 'blur', 'icon-size', 'scrim', 'tint', 'columns',
|
|
32
32
|
'heading', 'body', 'editorial', 'eyebrow', 'code', 'easing', 'duration', 'zoom',
|
|
33
33
|
'gradient', 'stroke',
|
|
34
34
|
];
|
|
35
35
|
|
|
36
|
-
/** True when `name` belongs to a contract-governed
|
|
36
|
+
/** True when `name` belongs to a contract-governed scale, so a miss is a typo. */
|
|
37
37
|
export function isContractToken(name) {
|
|
38
38
|
const stem = name.replace(/^--/, '');
|
|
39
|
-
return
|
|
39
|
+
return CONTRACT_SCALES.some((f) => stem === f || stem.startsWith(`${f}-`));
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/** Every `--name:` declared anywhere in a stylesheet or style block. */
|
package/bin/migrate-routes.mjs
CHANGED
|
@@ -149,8 +149,8 @@ export function formatRouteResult(result, { check = false } = {}) {
|
|
|
149
149
|
const { rewritten, pendingWrite, advisory } = result;
|
|
150
150
|
if (!rewritten.length && !pendingWrite.length && !advisory.length) return '';
|
|
151
151
|
|
|
152
|
-
const ref = (h) => ` ${h.file}:${h.line} ${h.kind} '${h.old}'
|
|
153
|
-
const lines = ['Route references
|
|
152
|
+
const ref = (h) => ` ${h.file}:${h.line} ${h.kind} '${h.old}' to '${h.next}'`;
|
|
153
|
+
const lines = ['Route references: /editor, /components, /docs moved to /live-tokens/* in 0.35.0:'];
|
|
154
154
|
|
|
155
155
|
if (rewritten.length) {
|
|
156
156
|
lines.push(` ✓ Rewrote ${rewritten.length} reference(s):`);
|
|
@@ -160,7 +160,7 @@ export function formatRouteResult(result, { check = false } = {}) {
|
|
|
160
160
|
lines.push(
|
|
161
161
|
check
|
|
162
162
|
? ` Would rewrite ${pendingWrite.length} reference(s) with --write:`
|
|
163
|
-
: ` ${pendingWrite.length} reference(s) can be rewritten
|
|
163
|
+
: ` ${pendingWrite.length} reference(s) can be rewritten. Re-run with --write to apply:`,
|
|
164
164
|
);
|
|
165
165
|
pendingWrite.forEach((h) => lines.push(ref(h)));
|
|
166
166
|
}
|
|
@@ -169,8 +169,8 @@ export function formatRouteResult(result, { check = false } = {}) {
|
|
|
169
169
|
advisory.forEach((h) => {
|
|
170
170
|
const why =
|
|
171
171
|
h.reason === 'docs-never'
|
|
172
|
-
? `/docs is never auto-rewritten
|
|
173
|
-
: `
|
|
172
|
+
? `/docs is never auto-rewritten. Update to '${h.next}' only when it points at the package guide`
|
|
173
|
+
: `the project declares or relocates '${h.old}'. Leave it when it is the project's own route`;
|
|
174
174
|
lines.push(` ${h.file}:${h.line} ${h.kind} '${h.old}' (${why})`);
|
|
175
175
|
});
|
|
176
176
|
}
|
package/bin/migrate.mjs
CHANGED
|
@@ -127,13 +127,13 @@ export function formatMigrateDataResult(result) {
|
|
|
127
127
|
// `sketch-presets/` → `sketch-styles/`. Told apart by the destination
|
|
128
128
|
// name, since `LegacyRename` carries no reason of its own.
|
|
129
129
|
const reason = basename(to) === 'sketch-styles' ? 'sketchstyle rename' : '0.48 layout';
|
|
130
|
-
lines.push(` ${planned ? 'would move' : 'moved'} ${rel(from)}
|
|
130
|
+
lines.push(` ${planned ? 'would move' : 'moved'} ${rel(from)} to ${rel(to)} (${reason})`);
|
|
131
131
|
}
|
|
132
132
|
for (const p of result.upgradedThemes) {
|
|
133
133
|
lines.push(` ${planned ? 'would carry' : 'carried'} ${rel(p)} by value (was a pre-v3 theme naming files)`);
|
|
134
134
|
}
|
|
135
135
|
if (result.production) {
|
|
136
|
-
lines.push(` production theme
|
|
136
|
+
lines.push(` production theme: "${result.production.slug}" (${HOW_SAID[result.production.how]})`);
|
|
137
137
|
}
|
|
138
138
|
if (result.recoveredThemePath) {
|
|
139
139
|
lines.push(` ${planned ? 'would write' : 'wrote'} ${rel(result.recoveredThemePath)}`);
|
|
@@ -148,13 +148,13 @@ export function formatMigrateDataResult(result) {
|
|
|
148
148
|
lines.push(` ${planned ? 'would delete' : 'deleted'} ${rel(p)}: a copy of a saved theme`);
|
|
149
149
|
}
|
|
150
150
|
for (const p of result.keptUserFiles) {
|
|
151
|
-
lines.push(` kept ${rel(p)}: it matches no theme, so it
|
|
151
|
+
lines.push(` kept ${rel(p)}: it matches no theme, so it stays`);
|
|
152
152
|
}
|
|
153
153
|
for (const note of result.droppedRefs) {
|
|
154
154
|
lines.push(` ${note}`);
|
|
155
155
|
}
|
|
156
156
|
for (const p of result.notThemes) {
|
|
157
|
-
lines.push(` left ${rel(p)} alone: it is a colors-and-type file
|
|
157
|
+
lines.push(` left ${rel(p)} alone: it is a colors-and-type file`);
|
|
158
158
|
}
|
|
159
159
|
for (const p of result.shadowedDefaults) {
|
|
160
160
|
lines.push(` kept ${rel(p)}: it shadows the default the package ships, which has moved on`);
|
package/bin/save-theme.mjs
CHANGED
|
@@ -142,35 +142,34 @@ export function formatSaveThemeResult(result) {
|
|
|
142
142
|
const root = process.cwd();
|
|
143
143
|
const lines = [];
|
|
144
144
|
const wrote = result.dryRun ? 'Would write' : result.existed ? 'Updated' : 'Created';
|
|
145
|
-
lines.push(`${wrote} theme "${result.name}"
|
|
145
|
+
lines.push(`${wrote} theme "${result.name}" at ${relative(root, result.themePath)}`);
|
|
146
146
|
lines.push(`It carries the colors and type and ${result.components} component config(s) by value.`);
|
|
147
147
|
|
|
148
148
|
const edited = [
|
|
149
149
|
...(result.buffered.colorsAndType ? ['colors and type'] : []),
|
|
150
150
|
...result.buffered.components,
|
|
151
151
|
];
|
|
152
|
-
const open = result.openTheme ? `the open theme "${result.openTheme}"` : 'the
|
|
152
|
+
const open = result.openTheme ? `the open theme "${result.openTheme}"` : 'the shipped defaults';
|
|
153
153
|
const kept = result.dryRun ? 'Would save' : 'Saved';
|
|
154
154
|
if (edited.length > 0) {
|
|
155
|
-
lines.push(`${kept}
|
|
155
|
+
lines.push(`${kept} the buffer: ${edited.join(', ')}.`);
|
|
156
156
|
lines.push(`Everything else came from ${open}, or the shipped defaults where it carries no entry.`);
|
|
157
157
|
} else {
|
|
158
|
-
lines.push(`No
|
|
158
|
+
lines.push(`No buffer. ${kept} a copy of ${open}.`);
|
|
159
159
|
}
|
|
160
160
|
if (result.sketchSettings) lines.push(`Sketch settings rode through from the open theme.`);
|
|
161
161
|
|
|
162
162
|
if (result.activated) {
|
|
163
163
|
lines.push(
|
|
164
|
-
`\
|
|
165
|
-
`
|
|
166
|
-
`Adopt it there to publish it to tokens.generated.css.`,
|
|
164
|
+
`\nLoaded "${result.slug}" (previously open: "${result.previousActive}"). ` +
|
|
165
|
+
`Adopt in the editor ships it to tokens.generated.css.`,
|
|
167
166
|
);
|
|
168
167
|
} else if (result.dryRun) {
|
|
169
168
|
lines.push(`\nDry run: nothing written under ${relative(root, dirname(result.themePath))}.`);
|
|
170
169
|
} else {
|
|
171
170
|
lines.push(
|
|
172
|
-
`\nNot
|
|
173
|
-
`save-theme starts from the same
|
|
171
|
+
`\nNot loaded (--no-activate). The buffer is still open, so the next ` +
|
|
172
|
+
`save-theme starts from the same theme.`,
|
|
174
173
|
);
|
|
175
174
|
}
|
|
176
175
|
return lines.join('\n');
|
package/bin/set-colors.mjs
CHANGED
|
@@ -28,9 +28,9 @@ const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
|
28
28
|
const ENGINE = resolve(pkgRoot, 'dist-plugin/setColors/index.js');
|
|
29
29
|
|
|
30
30
|
const SOURCE_LABELS = {
|
|
31
|
-
working: '
|
|
31
|
+
working: 'the buffer',
|
|
32
32
|
theme: 'the open theme',
|
|
33
|
-
default: 'the
|
|
33
|
+
default: 'the shipped default',
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
async function loadEngine() {
|
|
@@ -142,7 +142,7 @@ export function formatSetColorsResult(result) {
|
|
|
142
142
|
if (result.ignoredName !== null) {
|
|
143
143
|
lines.push(
|
|
144
144
|
`Ignored "name": "${result.ignoredName}". The base color file no longer names a theme; ` +
|
|
145
|
-
`name it
|
|
145
|
+
`name it in save-theme.`,
|
|
146
146
|
);
|
|
147
147
|
}
|
|
148
148
|
|
|
@@ -156,13 +156,13 @@ export function formatSetColorsResult(result) {
|
|
|
156
156
|
);
|
|
157
157
|
}
|
|
158
158
|
if (result.report.failures.length > 0) {
|
|
159
|
-
lines.push(`\nUnmet floors
|
|
159
|
+
lines.push(`\nUnmet floors. Adjust the base colors and re-run:`);
|
|
160
160
|
for (const f of result.report.failures) lines.push(` ! ${f}`);
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
lines.push(
|
|
164
164
|
result.report.gradients === 'carried'
|
|
165
|
-
? '\nGradients: kept
|
|
165
|
+
? '\nGradients: kept the tuned swatch gradients.'
|
|
166
166
|
: '\nGradients: swatch tokens rebuilt from the theme families.',
|
|
167
167
|
);
|
|
168
168
|
lines.push(`Shadows: ${result.report.shadows}; carried geometry kept.`);
|
|
@@ -172,17 +172,15 @@ export function formatSetColorsResult(result) {
|
|
|
172
172
|
|
|
173
173
|
if (result.wrote === 'buffer') {
|
|
174
174
|
lines.push(
|
|
175
|
-
`\
|
|
176
|
-
`editor's Theme panel to keep it, or run save-theme to write a new one.`,
|
|
175
|
+
`\nThe buffer holds this edit. Run save-theme to keep it as a theme.`,
|
|
177
176
|
);
|
|
178
177
|
} else if (result.wrote === 'cleared') {
|
|
179
178
|
const held =
|
|
180
|
-
result.savedSource === 'theme' ? `theme "${result.openTheme}"` : 'the
|
|
179
|
+
result.savedSource === 'theme' ? `theme "${result.openTheme}"` : 'the shipped default';
|
|
181
180
|
lines.push(
|
|
182
181
|
result.source === 'working'
|
|
183
|
-
? `\nThat is what ${held} already holds, so the
|
|
184
|
-
|
|
185
|
-
: `\nThat is what ${held} already holds, and there was no unsaved buffer, so nothing was written.`,
|
|
182
|
+
? `\nThat is what ${held} already holds, so the buffer was discarded.`
|
|
183
|
+
: `\nThat is what ${held} already holds, and there was no buffer, so nothing was written.`,
|
|
186
184
|
);
|
|
187
185
|
} else if (result.dryRun) {
|
|
188
186
|
lines.push(`\nDry run: nothing written under ${relative(root, result.colorsAndTypeDir)}.`);
|
package/bin/set-geometry.mjs
CHANGED
|
@@ -17,15 +17,16 @@ const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
|
17
17
|
const ENGINE = resolve(pkgRoot, 'dist-plugin/setGeometry/index.js');
|
|
18
18
|
|
|
19
19
|
const SOURCE_LABELS = {
|
|
20
|
-
working: '
|
|
20
|
+
working: 'the buffer',
|
|
21
21
|
theme: 'the open theme',
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
const SKIP_LABELS = [
|
|
25
|
-
['raw-value', 'raw value
|
|
26
|
-
['off-
|
|
27
|
-
['clamped', '
|
|
25
|
+
['raw-value', 'raw value'],
|
|
26
|
+
['off-scale', 'off the scale'],
|
|
27
|
+
['clamped', 'at the end of the scale'],
|
|
28
28
|
['pill-preserved', 'pill preserved (pass "full": true to move it)'],
|
|
29
|
+
['none-preserved', 'no line drawn, preserved (use "set" to draw one)'],
|
|
29
30
|
];
|
|
30
31
|
|
|
31
32
|
async function loadEngine() {
|
|
@@ -174,7 +175,7 @@ export function formatSetGeometryResult(result) {
|
|
|
174
175
|
|
|
175
176
|
const width = Math.max(0, ...entry.changes.map((c) => c.variable.length));
|
|
176
177
|
for (const c of entry.changes) {
|
|
177
|
-
lines.push(` ${c.variable.padEnd(width)} ${c.from}
|
|
178
|
+
lines.push(` ${c.variable.padEnd(width)} ${c.from} to ${c.to}`);
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
for (const [reason, label] of SKIP_LABELS) {
|
|
@@ -189,8 +190,7 @@ export function formatSetGeometryResult(result) {
|
|
|
189
190
|
);
|
|
190
191
|
if (result.buffered) {
|
|
191
192
|
lines.push(
|
|
192
|
-
`
|
|
193
|
-
`editor's Theme panel to keep it, or load a theme to discard it.`,
|
|
193
|
+
`The buffer holds this edit. Run save-theme to keep it as a theme.`,
|
|
194
194
|
);
|
|
195
195
|
} else if (result.dryRun) {
|
|
196
196
|
lines.push(`Dry run: nothing written under ${relative(root, result.configsDir)}.`);
|
package/bin/set-type.mjs
CHANGED
|
@@ -24,9 +24,9 @@ const BROWSER_UA =
|
|
|
24
24
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36';
|
|
25
25
|
|
|
26
26
|
const SOURCE_LABELS = {
|
|
27
|
-
working: '
|
|
27
|
+
working: 'the buffer',
|
|
28
28
|
theme: 'the open theme',
|
|
29
|
-
default: 'the
|
|
29
|
+
default: 'the shipped default',
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
async function loadEngine() {
|
|
@@ -196,7 +196,7 @@ export function formatSetTypeResult(result) {
|
|
|
196
196
|
|
|
197
197
|
const width = Math.max(0, ...result.changes.map((c) => c.variable.length));
|
|
198
198
|
for (const change of result.changes) {
|
|
199
|
-
lines.push(` ${change.variable.padEnd(width)} ${change.from ?? '(none)'}
|
|
199
|
+
lines.push(` ${change.variable.padEnd(width)} ${change.from ?? '(none)'} to ${change.to}`);
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
for (const face of result.faces) {
|
|
@@ -231,13 +231,11 @@ export function formatSetTypeResult(result) {
|
|
|
231
231
|
|
|
232
232
|
if (result.wrote === 'buffer') {
|
|
233
233
|
lines.push(
|
|
234
|
-
`\
|
|
235
|
-
`editor's Theme panel to keep it, or load a theme to discard it.`,
|
|
234
|
+
`\nThe buffer holds this edit. Run save-theme to keep it as a theme.`,
|
|
236
235
|
);
|
|
237
236
|
} else if (result.wrote === 'cleared') {
|
|
238
237
|
lines.push(
|
|
239
|
-
`\nThat is what the open theme already holds, so the
|
|
240
|
-
`Reload the app to see it.`,
|
|
238
|
+
`\nThat is what the open theme already holds, so the buffer was discarded.`,
|
|
241
239
|
);
|
|
242
240
|
} else if (result.dryRun) {
|
|
243
241
|
lines.push(`\nDry run: nothing written under ${relative(root, result.colorsAndTypeDir)}.`);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Installs the bundled Claude Code skills into a project's ./.claude/skills/.
|
|
2
|
+
//
|
|
3
|
+
// Claude Code discovers skills only under .claude/skills/, never inside
|
|
4
|
+
// node_modules, so the package's own copy has to be duplicated into each
|
|
5
|
+
// project that wants them. That makes this command the only thing keeping the
|
|
6
|
+
// two trees in step, and `--force` has to mean "make the destination match this
|
|
7
|
+
// release" rather than "write over the files I happen to ship today":
|
|
8
|
+
//
|
|
9
|
+
// - a skill this release renamed leaves its old directory behind, where it
|
|
10
|
+
// shadows the new one with stale instructions;
|
|
11
|
+
// - a reference file this release dropped survives inside a skill that is
|
|
12
|
+
// otherwise current, because cpSync merges into an existing directory.
|
|
13
|
+
//
|
|
14
|
+
// Both are invisible until a model reads the stale text, so --force replaces
|
|
15
|
+
// each directory outright and prunes the ones this release no longer ships.
|
|
16
|
+
|
|
17
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
|
|
20
|
+
// The package owns this prefix in a project's skills directory. A skill without
|
|
21
|
+
// it is the project's own and is never touched, whatever the flags say.
|
|
22
|
+
const SKILL_PREFIX = 'live-tokens-';
|
|
23
|
+
|
|
24
|
+
const SAMPLE_PROMPTS = {
|
|
25
|
+
'live-tokens-create-page': 'build a pricing page using live-tokens components',
|
|
26
|
+
'live-tokens-pick-component': "what's the difference between TabBar and SegmentedControl?",
|
|
27
|
+
'live-tokens-create-component': 'author a new Toggle component for my live-tokens project',
|
|
28
|
+
'live-tokens-create-theme': 'make me a bright and cheerful theme',
|
|
29
|
+
'live-tokens-set-colors': 'give me a cooler palette, same fonts',
|
|
30
|
+
'live-tokens-set-type': 'pair some fonts for this theme',
|
|
31
|
+
'live-tokens-set-geometry': 'make the buttons pill shaped',
|
|
32
|
+
'live-tokens-fix-findings': 'make check:design pass',
|
|
33
|
+
'live-tokens-check-compliance': 'check this project against the design system',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function directoriesIn(dir) {
|
|
37
|
+
if (!existsSync(dir)) return [];
|
|
38
|
+
return readdirSync(dir).filter((name) => statSync(join(dir, name)).isDirectory());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function runSetupClaude({ pkgRoot, cwd, force = false }) {
|
|
42
|
+
const srcSkills = join(pkgRoot, '.claude', 'skills');
|
|
43
|
+
|
|
44
|
+
if (!existsSync(srcSkills)) {
|
|
45
|
+
throw new Error(`No bundled skills found at ${srcSkills}. Is the package installed correctly?`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const shipped = directoriesIn(srcSkills);
|
|
49
|
+
if (shipped.length === 0) {
|
|
50
|
+
throw new Error('No bundled skills to install.');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const destSkills = join(cwd, '.claude', 'skills');
|
|
54
|
+
mkdirSync(destSkills, { recursive: true });
|
|
55
|
+
|
|
56
|
+
const installed = [];
|
|
57
|
+
const skipped = [];
|
|
58
|
+
const removed = [];
|
|
59
|
+
|
|
60
|
+
for (const skill of shipped) {
|
|
61
|
+
const dest = join(destSkills, skill);
|
|
62
|
+
if (existsSync(dest) && !force) {
|
|
63
|
+
skipped.push(skill);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// Replace rather than merge: a file this release dropped must not survive
|
|
67
|
+
// inside a directory that otherwise looks current.
|
|
68
|
+
rmSync(dest, { recursive: true, force: true });
|
|
69
|
+
cpSync(join(srcSkills, skill), dest, { recursive: true });
|
|
70
|
+
installed.push(skill);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (force) {
|
|
74
|
+
for (const name of directoriesIn(destSkills)) {
|
|
75
|
+
if (!name.startsWith(SKILL_PREFIX) || shipped.includes(name)) continue;
|
|
76
|
+
rmSync(join(destSkills, name), { recursive: true, force: true });
|
|
77
|
+
removed.push(name);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { installed, skipped, removed, destSkills, shipped };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function formatSetupResult({ installed, skipped, removed, destSkills, shipped }) {
|
|
85
|
+
const lines = [];
|
|
86
|
+
|
|
87
|
+
for (const skill of shipped) {
|
|
88
|
+
if (installed.includes(skill)) lines.push(` ok ${skill}`);
|
|
89
|
+
else lines.push(` skip ${skill} (already exists; pass --force to overwrite)`);
|
|
90
|
+
}
|
|
91
|
+
for (const skill of removed) {
|
|
92
|
+
lines.push(` gone ${skill} (this release no longer ships it; removed)`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const counts = [`${installed.length} installed`, `${skipped.length} skipped`];
|
|
96
|
+
if (removed.length > 0) counts.push(`${removed.length} removed`);
|
|
97
|
+
lines.push(`\n${counts.join(', ')}, in ${destSkills}`);
|
|
98
|
+
|
|
99
|
+
if (skipped.length > 0 && removed.length === 0) {
|
|
100
|
+
lines.push(`\nRe-run with --force to bring every skill up to this release.`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const samples = shipped.map((s) => SAMPLE_PROMPTS[s] && [s, SAMPLE_PROMPTS[s]]).filter(Boolean);
|
|
104
|
+
if (samples.length > 0) {
|
|
105
|
+
lines.push(`\nIn Claude Code, prompts like these auto-trigger the matching skill:`);
|
|
106
|
+
for (const [skill, prompt] of samples) lines.push(` "${prompt}"\n ${skill}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return lines.join('\n');
|
|
110
|
+
}
|