sdtk-design-kit 0.2.1 → 0.3.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.
@@ -18,17 +18,18 @@ function cmdSystemHelp() {
18
18
  console.log(`SDTK-DESIGN System
19
19
 
20
20
  Usage:
21
- sdtk-design system [--style <preset>] [--project-path <path>] [--force]
21
+ sdtk-design system [--project-path <path>] [--force]
22
22
 
23
23
  Examples:
24
24
  sdtk-design system
25
- sdtk-design system --style premium-dashboard
25
+ sdtk-design system --force
26
26
 
27
- Style presets:
28
- ${availableStyleNames().join(", ")}
29
- Default: ${DEFAULT_STYLE}
27
+ Reads (token-driven path, from-spec flow):
28
+ docs/design/DESIGN_TOKENS.json (schema sdtk.design.tokens.v2 from start --from-spec)
29
+ docs/design/DESIGN_BRIEF.md
30
+ docs/design/SCREEN_MAP.md
30
31
 
31
- Reads:
32
+ Reads (legacy path, start --idea flow):
32
33
  docs/design/DESIGN_BRIEF.md
33
34
  docs/design/SCREEN_MAP.md
34
35
 
@@ -44,6 +45,7 @@ Safety:
44
45
  return 0;
45
46
  }
46
47
 
48
+ // Legacy preset-driven path (used when DESIGN_TOKENS.json is absent — start --idea flow).
47
49
  function includesAny(text, terms) {
48
50
  const value = text.toLowerCase();
49
51
  return terms.some((term) => value.includes(term));
@@ -53,8 +55,9 @@ function linesForBullets(items) {
53
55
  return items.map((item) => `- ${item}`);
54
56
  }
55
57
 
56
- function systemContent(briefContent, screenMapContent, style = DEFAULT_STYLE) {
57
- const preset = getStylePreset(style);
58
+ function systemContent(briefContent, screenMapContent, style) {
59
+ const resolvedStyle = style || DEFAULT_STYLE;
60
+ const preset = getStylePreset(resolvedStyle);
58
61
  const source = `${briefContent}\n${screenMapContent}`;
59
62
  const isCrm = includesAny(source, ["crm", "lead", "follow-up", "pipeline"]);
60
63
  const primaryAction = isCrm ? "Add first lead" : "Start first workflow";
@@ -71,7 +74,7 @@ function systemContent(briefContent, screenMapContent, style = DEFAULT_STYLE) {
71
74
  "",
72
75
  "## Visual Preset",
73
76
  "",
74
- `- Preset: ${resolveStyleName(style)} (${preset.label}).`,
77
+ `- Preset: ${resolveStyleName(resolvedStyle)} (${preset.label}).`,
75
78
  `- Direction: ${preset.summary}`,
76
79
  "- Presets are compact SDTK-DESIGN guidance adapted from reference patterns, not imported runtime code.",
77
80
  "",
@@ -162,6 +165,159 @@ function systemContent(briefContent, screenMapContent, style = DEFAULT_STYLE) {
162
165
  ].join("\n");
163
166
  }
164
167
 
168
+ // Token-driven path (BK-224): 9-section content from DESIGN_TOKENS.json v2.
169
+ function designSystemContentFromTokens({ tokens }) {
170
+ const { brand, palette, typography, section, component, profile } = tokens;
171
+ const state = (palette && palette.state) || {};
172
+ const ramp = (typography && typography.ramp) || {};
173
+ const fontFamily = (typography && typography.fontFamily) || {};
174
+ const radius = (component && component.radius) || {};
175
+ const shadow = (component && component.shadow) || {};
176
+ const borderWidth = (component && component.borderWidth) || {};
177
+
178
+ const stateRow = (stateKey) => {
179
+ const s = state[stateKey];
180
+ return s ? `${s.base} / soft: ${s.soft}` : "N/A";
181
+ };
182
+
183
+ const rampRows = Object.entries(ramp).map(
184
+ ([level, spec]) => `- ${level}: ${spec.fontSize}px / weight ${spec.fontWeight} / line-height ${spec.lineHeight}`
185
+ );
186
+
187
+ return [
188
+ "# Design System",
189
+ "",
190
+ "## Visual Theme & Atmosphere",
191
+ "",
192
+ `- Profile: ${profile}`,
193
+ `- Brand mood: ${brand.mood}`,
194
+ `- Density: ${brand.density}`,
195
+ `- Surface: ${palette.surface} (primary background)`,
196
+ `- Surface alt: ${palette.surfaceAlt} (content areas, alternating rows)`,
197
+ `- Primary color: ${palette.primary}`,
198
+ `- Accent color: ${palette.accent}`,
199
+ "- Favor clarity, scannability, and obvious next actions over decorative marketing composition.",
200
+ "- No decorative background gradients, glow layers, or unsourced pattern fills.",
201
+ "",
202
+ "## Color Palette & Roles",
203
+ "",
204
+ `- Primary: ${palette.primary} / hover: ${palette.primaryHover} / soft: ${palette.primarySoft}`,
205
+ `- Accent: ${palette.accent} / hover: ${palette.accentHover} / soft: ${palette.accentSoft}`,
206
+ `- Surface: ${palette.surface}`,
207
+ `- Surface alt: ${palette.surfaceAlt}`,
208
+ `- Surface inverse: ${palette.surfaceInverse}`,
209
+ `- Text primary: ${palette.textPrimary}`,
210
+ `- Text secondary: ${palette.textSecondary}`,
211
+ `- Text muted: ${palette.textMuted}`,
212
+ `- Border: ${palette.border}`,
213
+ `- Border strong: ${palette.borderStrong}`,
214
+ `- State success: ${stateRow("success")}`,
215
+ `- State warning: ${stateRow("warning")}`,
216
+ `- State error: ${stateRow("error")}`,
217
+ `- State info: ${stateRow("info")}`,
218
+ "",
219
+ "## Typography Rules",
220
+ "",
221
+ `- Font family display: ${fontFamily.display || "N/A"}`,
222
+ `- Font family body: ${fontFamily.body || "N/A"}`,
223
+ `- Font family mono: ${fontFamily.mono || "N/A"}`,
224
+ ...rampRows,
225
+ "- Do not scale font size with viewport width.",
226
+ "- Keep letter spacing at 0 unless explicitly overridden by brand-tokens.",
227
+ "- One screen-level h1 before any section heading.",
228
+ "",
229
+ "## Component Stylings",
230
+ "",
231
+ `- Card radius: ${radius.card}px`,
232
+ `- Control radius: ${radius.control}px`,
233
+ `- Pill radius: ${radius.pill}px`,
234
+ `- Border width default: ${borderWidth.default}px solid ${palette.border}`,
235
+ `- Border width strong: ${borderWidth.strong}px solid ${palette.borderStrong}`,
236
+ "- Primary button: filled palette.primary, white label, 40px minimum height.",
237
+ "- Secondary button: palette.surface background, palette.border outline, palette.textPrimary label.",
238
+ "- Disabled: 45% opacity, no hover elevation.",
239
+ "- Cards: use only for repeated records or framed tool surfaces; no nested cards.",
240
+ "",
241
+ "## Layout Principles",
242
+ "",
243
+ `- Container max width: ${section.containerMaxWidth}px`,
244
+ `- Content max width: ${section.contentMaxWidth}px`,
245
+ `- Section gap: ${section.sectionGap}px`,
246
+ `- Hero min height: ${section.heroMinHeight}px`,
247
+ `- Grid columns: ${section.gridColumns}`,
248
+ `- Density: ${brand.density}`,
249
+ "- Navigation: deterministic route and section order from explicit screen model.",
250
+ "- Keep fixed-format controls stable with explicit min-height and predictable grid tracks.",
251
+ "",
252
+ "## Depth & Elevation",
253
+ "",
254
+ `- Shadow none: ${shadow.none}`,
255
+ `- Shadow sm: ${shadow.sm}`,
256
+ `- Shadow md: ${shadow.md}`,
257
+ `- Shadow lg: ${shadow.lg}`,
258
+ "- Use shadow.sm for cards; shadow.md for modals and dialogs.",
259
+ "- Do not use heavy drop shadows for decorative layering.",
260
+ "",
261
+ "## Do's & Don'ts",
262
+ "",
263
+ "- Do: use palette token paths (e.g. palette.primary, palette.state.success.base) in component references.",
264
+ "- Do: use typography.ramp token paths for heading hierarchy.",
265
+ "- Do: use section token paths for layout rhythm and container widths.",
266
+ "- Don't: embed raw hex values outside of generated CSS variables.",
267
+ "- Don't: extract brand colors or copy from requirement text.",
268
+ "- Don't: add unsupported decorative backgrounds, blurs, or gradient panels.",
269
+ "- Don't: invent copy, headings, or imagery from requirement text.",
270
+ "- Don't: use placeholder or TBD content in renderer output.",
271
+ "",
272
+ "## Responsive Behavior",
273
+ "",
274
+ `- Max layout width: ${section.containerMaxWidth}px; below this, flow to 100%.`,
275
+ `- Grid: ${section.gridColumns} columns desktop; collapse to 1 column mobile.`,
276
+ `- Section gap: ${section.sectionGap}px desktop; reduce proportionally at tablet/mobile.`,
277
+ "- Typography ramp: use ramp values as-is; do not scale with viewport.",
278
+ "- Navigation: collapse to hamburger or bottom nav at mobile breakpoint.",
279
+ "- Touch targets: minimum 44px by 44px for interactive controls.",
280
+ "",
281
+ "## Renderer Prompt Guide",
282
+ "",
283
+ "- Use token key paths in all component references: palette.primary, typography.ramp.h1, section.heroMinHeight, component.radius.card.",
284
+ "- Enforce one screen-level h1 before any section heading.",
285
+ "- Use semantic HTML landmarks: header, main, nav, section, footer.",
286
+ "- Preserve data/state/component markers from the screen brief; do not invent new ones.",
287
+ "- Avoid placeholder panels; emit NEEDS_* markers for unsourced content.",
288
+ `- Token reference: palette.primary=${palette.primary}, typography.ramp.h1=${ramp.h1 ? ramp.h1.fontSize + "px/" + ramp.h1.fontWeight : "N/A"}, section.heroMinHeight=${section.heroMinHeight}px, component.radius.card=${radius.card}px.`,
289
+ "- State tokens: palette.state.success.base for success states, palette.state.error.base for errors.",
290
+ `- Layout: respect section.containerMaxWidth (${section.containerMaxWidth}px) and section.gridColumns (${section.gridColumns}) from this contract.`,
291
+ "",
292
+ ].join("\n");
293
+ }
294
+
295
+ function loadDesignTokensV2ForSystem(projectPath) {
296
+ const paths = describeDesignPaths(projectPath);
297
+ if (!fs.existsSync(paths.designTokensPath)) {
298
+ return null;
299
+ }
300
+ let raw;
301
+ try {
302
+ raw = JSON.parse(fs.readFileSync(paths.designTokensPath, "utf-8"));
303
+ } catch (_err) {
304
+ throw new ValidationError(
305
+ "docs/design/DESIGN_TOKENS.json is not valid JSON. Re-run sdtk-design start --from-spec to regenerate. No project files were changed."
306
+ );
307
+ }
308
+ if (raw.schema === "sdtk.design.tokens.v1") {
309
+ throw new ValidationError(
310
+ "docs/design/DESIGN_TOKENS.json is schema v1. Re-run sdtk-design start --from-spec to upgrade to v2 tokens. No project files were changed."
311
+ );
312
+ }
313
+ if (raw.schema !== "sdtk.design.tokens.v2") {
314
+ throw new ValidationError(
315
+ "docs/design/DESIGN_TOKENS.json has an unrecognized schema. Re-run sdtk-design start --from-spec to regenerate. No project files were changed."
316
+ );
317
+ }
318
+ return raw;
319
+ }
320
+
165
321
  function runDesignSystem({ projectPath, force = false, style = DEFAULT_STYLE }) {
166
322
  const styleName = resolveStyleName(style);
167
323
  const resolvedProjectPath = resolveProjectPath(projectPath || process.cwd());
@@ -180,16 +336,29 @@ function runDesignSystem({ projectPath, force = false, style = DEFAULT_STYLE })
180
336
  throw new ValidationError("docs/design/DESIGN_SYSTEM.md already exists. Re-run with --force to replace this managed design system.");
181
337
  }
182
338
 
183
- const briefContent = fs.readFileSync(paths.designBriefPath, "utf-8");
184
- const screenMapContent = fs.readFileSync(paths.screenMapPath, "utf-8");
339
+ // Try v2 token-driven path; fall back to legacy preset path when tokens are absent.
340
+ const tokens = loadDesignTokensV2ForSystem(resolvedProjectPath);
341
+
342
+ let content;
343
+ let tokenDriven = false;
344
+ if (tokens) {
345
+ content = designSystemContentFromTokens({ tokens });
346
+ tokenDriven = true;
347
+ } else {
348
+ const briefContent = fs.readFileSync(paths.designBriefPath, "utf-8");
349
+ const screenMapContent = fs.readFileSync(paths.screenMapPath, "utf-8");
350
+ content = systemContent(briefContent, screenMapContent, styleName);
351
+ }
352
+
185
353
  fs.mkdirSync(path.dirname(paths.designSystemPath), { recursive: true });
186
- fs.writeFileSync(paths.designSystemPath, systemContent(briefContent, screenMapContent, styleName), "utf-8");
354
+ fs.writeFileSync(paths.designSystemPath, content, "utf-8");
187
355
 
188
356
  return {
189
357
  projectPath: resolvedProjectPath,
190
358
  relativeDesignSystemPath: "docs/design/DESIGN_SYSTEM.md",
191
359
  forced: Boolean(force),
192
- style: styleName,
360
+ style: tokenDriven ? null : styleName,
361
+ tokenDriven,
193
362
  };
194
363
  }
195
364
 
@@ -204,7 +373,9 @@ function cmdSystem(args) {
204
373
  });
205
374
 
206
375
  console.log(`[design] Wrote ${result.relativeDesignSystemPath}: ${result.projectPath}`);
207
- console.log(`[design] Style: ${result.style}`);
376
+ if (!result.tokenDriven && result.style) {
377
+ console.log(`[design] Style: ${result.style}`);
378
+ }
208
379
  console.log(`[design] Overwrite: ${result.forced ? "enabled by --force" : "not needed"}`);
209
380
  console.log("[design] No .sdtk/atlas, SDTK-WIKI output, source files, network, or app code was modified.");
210
381
  console.log("[design] Next: sdtk-design handoff");
@@ -216,4 +387,5 @@ module.exports = {
216
387
  cmdSystemHelp,
217
388
  runDesignSystem,
218
389
  systemContent,
390
+ designSystemContentFromTokens,
219
391
  };
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+
3
+ const { executeUpdate } = require("../lib/update");
4
+
5
+ async function cmdUpdate(args) {
6
+ return executeUpdate(args);
7
+ }
8
+
9
+ module.exports = {
10
+ cmdUpdate,
11
+ };
package/src/index.js CHANGED
@@ -10,6 +10,7 @@ const { cmdScreens } = require("./commands/screens");
10
10
  const { cmdStart } = require("./commands/start");
11
11
  const { cmdStatus } = require("./commands/status");
12
12
  const { cmdSystem } = require("./commands/system");
13
+ const { cmdUpdate } = require("./commands/update");
13
14
  const { cmdWireframe } = require("./commands/wireframe");
14
15
  const { ValidationError } = require("./lib/errors");
15
16
 
@@ -76,6 +77,9 @@ async function run(argv) {
76
77
  if (command === "status") {
77
78
  return cmdStatus(args);
78
79
  }
80
+ if (command === "update") {
81
+ return cmdUpdate(args);
82
+ }
79
83
  if (DEFERRED_COMMANDS.has(command)) {
80
84
  throw new ValidationError(
81
85
  `Command "${command}" is planned for ${DEFERRED_COMMANDS.get(command)} and is not implemented yet. Run "sdtk-design --help" for the current Foundation Beta surface.`
@@ -109,17 +109,6 @@ function lintArtifactHtml(html, options = {}) {
109
109
  "Pull the metric from a real source or remove the claim.",
110
110
  screenId
111
111
  );
112
- addRegexFindings(
113
- findings,
114
- clean,
115
- /class="[^"]*\bdensity-evidence\b[^"]*"/i,
116
- "P0",
117
- "density-evidence-residue",
118
- "Legacy density-evidence filler residue detected.",
119
- "Remove filler section emitters from the renderer.",
120
- screenId
121
- );
122
-
123
112
  const counts = new Map();
124
113
  for (const text of articleTexts(clean)) counts.set(text, (counts.get(text) || 0) + 1);
125
114
  const fullDuplicates = new Set();