sdtk-design-kit 0.1.1 → 0.2.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.
@@ -75,6 +75,197 @@ function includesAny(content, terms) {
75
75
  return terms.some((term) => lower.includes(term.toLowerCase()));
76
76
  }
77
77
 
78
+ function readInputContractState(paths) {
79
+ if (!fs.existsSync(paths.designStartInputStatePath) || !fs.statSync(paths.designStartInputStatePath).isFile()) {
80
+ return null;
81
+ }
82
+ try {
83
+ return JSON.parse(fs.readFileSync(paths.designStartInputStatePath, "utf-8"));
84
+ } catch (_err) {
85
+ return null;
86
+ }
87
+ }
88
+
89
+ function normalizeScreenRole(screen) {
90
+ const token = `${screen.screenId || ""} ${screen.title || ""}`.toLowerCase();
91
+ if (token.includes("home")) return "home";
92
+ if (token.includes("category")) return "category";
93
+ if (token.includes("product-detail") || token.includes("product detail") || token.includes("pdp")) return "product-detail";
94
+ if (token.includes("search")) return "search";
95
+ if (token.includes("cart")) return "cart";
96
+ if (token.includes("checkout")) return "checkout";
97
+ if (token.includes("order-history") || token.includes("order history")) return "order-history";
98
+ if (token.includes("order-detail") || token.includes("order detail")) return "order-detail";
99
+ if (token.includes("account")) return "account-info";
100
+ if (token.includes("configurator") || token.includes("mode-b")) return "mode-b-configurator";
101
+ return screen.screenId || "screen";
102
+ }
103
+
104
+ function requiredRoleMarkers(role) {
105
+ const map = {
106
+ home: ["hero", "featured products", "configurator"],
107
+ category: ["filter sidebar", "product grid"],
108
+ "product-detail": ["spec-table", "add to cart"],
109
+ search: ["search-toolbar", "no-result"],
110
+ cart: ["cart-table", "checkout"],
111
+ checkout: ["checkout-stepper", "summary card"],
112
+ "order-history": ["order history list"],
113
+ "order-detail": ["timeline", "detail summary"],
114
+ "account-info": ["account sidebar", "view or edit form"],
115
+ "mode-b-configurator": ["configurator wizard", "preview panel", "bom table", "recalculate", "add bom to cart"],
116
+ };
117
+ return map[role] || [];
118
+ }
119
+
120
+ function listPrototypeScreenFiles(paths) {
121
+ if (!fs.existsSync(paths.prototypeScreensPath) || !fs.statSync(paths.prototypeScreensPath).isDirectory()) {
122
+ return [];
123
+ }
124
+ return fs
125
+ .readdirSync(paths.prototypeScreensPath)
126
+ .filter((name) => name.toLowerCase().endsWith(".html"))
127
+ .sort()
128
+ .map((name) => path.join(paths.prototypeScreensPath, name));
129
+ }
130
+
131
+ function combinedPrototypeReviewContent({ artifactPath, artifactContent, paths }) {
132
+ const screenFiles = listPrototypeScreenFiles(paths);
133
+ if (screenFiles.length === 0) {
134
+ return artifactContent;
135
+ }
136
+ const parts = [artifactContent];
137
+ if (path.resolve(artifactPath) !== path.resolve(paths.prototypeIndexPath) && fs.existsSync(paths.prototypeIndexPath)) {
138
+ parts.push(fs.readFileSync(paths.prototypeIndexPath, "utf-8"));
139
+ }
140
+ for (const filePath of screenFiles) {
141
+ if (path.resolve(filePath) === path.resolve(artifactPath)) {
142
+ continue;
143
+ }
144
+ parts.push(fs.readFileSync(filePath, "utf-8"));
145
+ }
146
+ return parts.join("\n");
147
+ }
148
+
149
+ function evaluateHighFidelityGate({ artifactContent, statePayload, projectPath, paths }) {
150
+ const html = String(artifactContent || "").toLowerCase();
151
+ const screens = statePayload.screenModel.screens || [];
152
+ const profile = statePayload.profileSelection || "none";
153
+ const hasTokens = fs.existsSync(paths.designTokensPath);
154
+ const hasComponentLibrary = fs.existsSync(paths.componentPatternLibraryPath);
155
+ const hasPre = html.includes("<pre");
156
+ const hasRawJsonRisk = html.includes("json.stringify") || html.includes("raw json") || html.includes("{\"") || html.includes("not_found");
157
+ const hasNav = html.includes('aria-label="screen navigation"') || html.includes("screen navigation");
158
+ const hasStateChips = html.includes('data-chip-type="state"') || html.includes("chip");
159
+ const sectionRows = [];
160
+ let missingCount = 0;
161
+ for (const screen of screens) {
162
+ const id = String(screen.screenId || "");
163
+ const title = String(screen.title || id);
164
+ const role = normalizeScreenRole(screen);
165
+ const idMarker = `id="screen-${id}"`;
166
+ const navMarkers = [`href="#screen-${id}"`, `href="screens/${id}.html"`, `href="${id}.html"`];
167
+ const roleMarkers = requiredRoleMarkers(role);
168
+ const missing = [];
169
+ if (!html.includes(idMarker.toLowerCase())) missing.push("missing screen section id");
170
+ if (!navMarkers.some((marker) => html.includes(marker.toLowerCase()))) missing.push("missing navigation link");
171
+ for (const marker of roleMarkers) {
172
+ if (!html.includes(marker)) missing.push(`missing component marker: ${marker}`);
173
+ }
174
+ if (missing.length > 0) missingCount += 1;
175
+ sectionRows.push({
176
+ title,
177
+ role,
178
+ missing,
179
+ pass: missing.length === 0,
180
+ });
181
+ }
182
+
183
+ let verdict = "PASS_HIGH_FIDELITY_READY";
184
+ const blockers = [];
185
+ if (screens.length === 0) {
186
+ verdict = "FAIL_WITH_PRODUCT_GAPS";
187
+ blockers.push("explicit screen model is empty");
188
+ }
189
+ if (!hasComponentLibrary) {
190
+ verdict = "FAIL_WITH_PRODUCT_GAPS";
191
+ blockers.push("component pattern library missing");
192
+ }
193
+ if (!hasTokens) {
194
+ verdict = "FAIL_WITH_PRODUCT_GAPS";
195
+ blockers.push("design tokens missing");
196
+ }
197
+ if (!hasNav) {
198
+ verdict = "FAIL_WITH_PRODUCT_GAPS";
199
+ blockers.push("inter-screen navigation contract missing");
200
+ }
201
+ if (!hasStateChips) {
202
+ verdict = "FAIL_WITH_PRODUCT_GAPS";
203
+ blockers.push("state chip coverage missing");
204
+ }
205
+ if (missingCount > 0) {
206
+ verdict = "FAIL_WITH_PRODUCT_GAPS";
207
+ blockers.push(`${missingCount} screen(s) missing required component markers`);
208
+ }
209
+ if (hasPre || hasRawJsonRisk) {
210
+ verdict = "FAIL_WITH_PRODUCT_GAPS";
211
+ blockers.push("debug/raw JSON UI risk detected");
212
+ }
213
+ if (profile === "b2b-commerce" && !html.includes("configurator wizard")) {
214
+ verdict = "FAIL_WITH_PRODUCT_GAPS";
215
+ blockers.push("configurator completeness missing");
216
+ }
217
+ if (html.includes("main layout</h3>") && html.includes("primary action</h3>")) {
218
+ verdict = "FAIL_HIGH_FIDELITY_NOT_READY";
219
+ blockers.push("generic scaffold markers detected (Main layout / Primary action)");
220
+ }
221
+ return {
222
+ verdict,
223
+ blockers,
224
+ profile,
225
+ sectionRows,
226
+ hasTokens,
227
+ hasComponentLibrary,
228
+ hasNav,
229
+ hasStateChips,
230
+ hasPre,
231
+ hasRawJsonRisk,
232
+ projectPath,
233
+ };
234
+ }
235
+
236
+ function fidelityReviewContent(result) {
237
+ return [
238
+ "# Design Fidelity Review",
239
+ "",
240
+ `- Verdict: ${result.verdict}`,
241
+ `- Profile: ${result.profile}`,
242
+ `- Component library: ${result.hasComponentLibrary ? "present" : "missing"}`,
243
+ `- Design tokens: ${result.hasTokens ? "present" : "missing"}`,
244
+ `- Navigation contract: ${result.hasNav ? "present" : "missing"}`,
245
+ `- State coverage markers: ${result.hasStateChips ? "present" : "missing"}`,
246
+ `- Raw <pre> debug risk: ${result.hasPre ? "detected" : "not detected"}`,
247
+ `- Raw JSON debug risk: ${result.hasRawJsonRisk ? "detected" : "not detected"}`,
248
+ "",
249
+ "## Screen-By-Screen Coverage",
250
+ "",
251
+ "| Screen | Role | Result | Actionable notes |",
252
+ "|---|---|---|---|",
253
+ ...result.sectionRows.map((row) => `| ${row.title} | ${row.role} | ${row.pass ? "PASS" : "FAIL"} | ${row.pass ? "coverage complete" : row.missing.join("; ")} |`),
254
+ "",
255
+ "## Gate Findings",
256
+ "",
257
+ ...(result.blockers.length > 0 ? result.blockers.map((item) => `- ${item}`) : ["- No blocking fidelity gaps found."]),
258
+ "",
259
+ "## Actions",
260
+ "",
261
+ "- Fill missing component markers per failed screen.",
262
+ "- Ensure design tokens and component pattern library are generated before review.",
263
+ "- Remove raw debug UI surfaces (`<pre>` and JSON dumps) from customer-facing prototype output.",
264
+ "- Re-run `sdtk-design prototype` and `sdtk-design review --artifact docs/design/prototype/index.html`.",
265
+ "",
266
+ ].join("\n");
267
+ }
268
+
78
269
  function visualPolishChecks({ artifactRelativePath, artifactContent, prototypeHtml }) {
79
270
  const lower = String(artifactContent || "").toLowerCase();
80
271
  const hasLanding = lower.includes("section-landing") || lower.includes("landing");
@@ -191,12 +382,47 @@ function runDesignReview({ artifact, projectPath, force = false }) {
191
382
 
192
383
  const artifactContent = fs.readFileSync(artifactPath, "utf-8");
193
384
  const artifactRelativePath = toPosix(path.relative(resolvedProjectPath, artifactPath));
385
+ const statePayload = readInputContractState(paths);
386
+ const shouldRunFidelityGate =
387
+ isPrototypeHtmlArtifact(artifactRelativePath, artifactContent) &&
388
+ statePayload &&
389
+ statePayload.mode === "from-spec" &&
390
+ statePayload.analysisStatus === "INPUT_CONTRACT_READY" &&
391
+ statePayload.screenModel &&
392
+ Array.isArray(statePayload.screenModel.screens) &&
393
+ statePayload.screenModel.screens.length >= 4;
194
394
  fs.mkdirSync(paths.reviewsPath, { recursive: true });
195
395
  fs.writeFileSync(reportPath, reviewContent({ artifactRelativePath, artifactContent }), "utf-8");
196
396
 
397
+ let fidelityReportRelativePath = null;
398
+ let fidelityVerdict = null;
399
+ if (shouldRunFidelityGate) {
400
+ const fidelityArtifactContent = combinedPrototypeReviewContent({
401
+ artifactPath,
402
+ artifactContent,
403
+ paths,
404
+ });
405
+ const fidelityResult = evaluateHighFidelityGate({
406
+ artifactContent: fidelityArtifactContent,
407
+ statePayload,
408
+ projectPath: resolvedProjectPath,
409
+ paths,
410
+ });
411
+ const fidelityName = `DESIGN_FIDELITY_REVIEW_${formatDateYYYYMMDD()}.md`;
412
+ const fidelityPath = path.join(paths.reviewsPath, fidelityName);
413
+ if (fs.existsSync(fidelityPath) && !force) {
414
+ throw new ValidationError(`docs/design/reviews/${fidelityName} already exists. Re-run with --force to replace this managed fidelity review report.`);
415
+ }
416
+ fs.writeFileSync(fidelityPath, fidelityReviewContent(fidelityResult), "utf-8");
417
+ fidelityReportRelativePath = `docs/design/reviews/${fidelityName}`;
418
+ fidelityVerdict = fidelityResult.verdict;
419
+ }
420
+
197
421
  return {
198
422
  projectPath: resolvedProjectPath,
199
423
  relativeReportPath: `docs/design/reviews/${reportName}`,
424
+ fidelityReportRelativePath,
425
+ fidelityVerdict,
200
426
  forced: Boolean(force),
201
427
  };
202
428
  }
@@ -212,6 +438,10 @@ function cmdReview(args) {
212
438
  });
213
439
 
214
440
  console.log(`[design] Wrote ${result.relativeReportPath}: ${result.projectPath}`);
441
+ if (result.fidelityReportRelativePath) {
442
+ console.log(`[design] Wrote ${result.fidelityReportRelativePath}: ${result.projectPath}`);
443
+ console.log(`[design] Fidelity verdict: ${result.fidelityVerdict}`);
444
+ }
215
445
  console.log(`[design] Review mode: local artifact`);
216
446
  console.log(`[design] Overwrite: ${result.forced ? "enabled by --force" : "not needed"}`);
217
447
  console.log("[design] No URL, browser, screenshot, vision, network, .sdtk/atlas, or SDTK-WIKI output was used.");
@@ -8,6 +8,10 @@ const { runDesignSystem } = require("./system");
8
8
  const { runDesignWireframe } = require("./wireframe");
9
9
  const { parseFlags } = require("../lib/args");
10
10
  const { describeDesignPaths, resolveProjectPath } = require("../lib/design-paths");
11
+ const { availableProfileNames } = require("../lib/design-profiles");
12
+ const { buildInputContractState, writeInputContractState } = require("../lib/design-input-contract");
13
+ const { writeScreenBriefArtifacts } = require("../lib/screen-briefs");
14
+ const { writeComponentContractArtifacts } = require("../lib/component-contract");
11
15
  const { ValidationError } = require("../lib/errors");
12
16
  const { DEFAULT_STYLE, availableStyleNames, resolveStyleName } = require("../lib/style-presets");
13
17
 
@@ -17,6 +21,10 @@ const START_FLAG_DEFS = {
17
21
  "project-path": { type: "string" },
18
22
  force: { type: "boolean" },
19
23
  style: { type: "string" },
24
+ "from-spec": { type: "string" },
25
+ "design-brief": { type: "string" },
26
+ "reference-dir": { type: "string" },
27
+ profile: { type: "string" },
20
28
  };
21
29
 
22
30
  const REQUIRED_WIREFRAME_FILES = ["LANDING.md", "ONBOARDING.md", "DASHBOARD.md"];
@@ -26,29 +34,45 @@ function cmdStartHelp() {
26
34
 
27
35
  Usage:
28
36
  sdtk-design start --idea "<rough MVP idea>" [--style <preset>] [--project-path <path>] [--force]
37
+ sdtk-design start --from-spec <projectPath> [--design-brief <file>] [--reference-dir <dir>] [--profile <name>] [--project-path <path>]
29
38
 
30
39
  Example:
31
40
  sdtk-design start --idea "I want to build a lightweight CRM for solo consultants to track leads."
32
41
  sdtk-design start --idea "ClientPulse for consultants" --style premium-dashboard
42
+ sdtk-design start --from-spec . --reference-dir ./docs/design/reference-export --profile b2b-commerce
33
43
 
34
- Style presets:
44
+ Style presets:
35
45
  ${availableStyleNames().join(", ")}
36
46
  Default: ${DEFAULT_STYLE}
37
47
 
48
+ Design profiles (optional in from-spec mode):
49
+ ${availableProfileNames().join(", ")}
50
+
38
51
  Runs:
39
- brief -> screens -> wireframe --screen all -> system
52
+ idea mode:
53
+ brief -> screens -> wireframe --screen all -> system
54
+ from-spec mode:
55
+ explicit SPEC/design artifact intake -> contract state write
40
56
 
41
57
  Creates:
42
- docs/design/DESIGN_BRIEF.md
43
- docs/design/SCREEN_MAP.md
44
- docs/design/wireframes/LANDING.md
45
- docs/design/wireframes/ONBOARDING.md
46
- docs/design/wireframes/DASHBOARD.md
47
- docs/design/DESIGN_SYSTEM.md
58
+ idea mode:
59
+ docs/design/DESIGN_BRIEF.md
60
+ docs/design/SCREEN_MAP.md
61
+ docs/design/wireframes/LANDING.md
62
+ docs/design/wireframes/ONBOARDING.md
63
+ docs/design/wireframes/DASHBOARD.md
64
+ docs/design/DESIGN_SYSTEM.md
65
+ from-spec mode:
66
+ .sdtk/design/START_INPUT_STATE.json
67
+ docs/design/screens/*_DESIGN_BRIEF.md (when INPUT_CONTRACT_READY)
68
+ .sdtk/design/screen-briefs/*.json (managed sidecars for deterministic renderer intake)
69
+ docs/design/COMPONENT_PATTERN_LIBRARY.md (when INPUT_CONTRACT_READY)
70
+ docs/design/DESIGN_TOKENS.json (when INPUT_CONTRACT_READY)
48
71
 
49
72
  Safety:
50
73
  Local files only.
51
74
  Existing managed core design outputs are not overwritten unless --force is explicit.
75
+ --from-spec consumes explicit artifacts; no raw requirement semantic parsing.
52
76
  No review, handoff, URL, browser, screenshot, vision, or DOM work.
53
77
  No .sdtk/atlas creation or mutation.
54
78
  No SDTK-WIKI output mutation.
@@ -104,10 +128,82 @@ function runDesignStart({ idea, projectPath, force = false, style = DEFAULT_STYL
104
128
  };
105
129
  }
106
130
 
131
+ function runDesignStartFromSpec({
132
+ fromSpecPath,
133
+ projectPath,
134
+ designBrief,
135
+ referenceDir,
136
+ profile,
137
+ }) {
138
+ const contractState = buildInputContractState({
139
+ fromSpecPath,
140
+ projectPath,
141
+ designBriefPath: designBrief,
142
+ referenceDir,
143
+ profile,
144
+ });
145
+ const statePath = writeInputContractState(contractState.projectPath, contractState);
146
+ let screenBriefs = null;
147
+ let componentContract = null;
148
+ if (contractState.analysisStatus === "INPUT_CONTRACT_READY") {
149
+ screenBriefs = writeScreenBriefArtifacts(contractState.projectPath, contractState);
150
+ componentContract = writeComponentContractArtifacts(contractState.projectPath, contractState);
151
+ }
152
+ return {
153
+ ...contractState,
154
+ statePath,
155
+ stateRelativePath: ".sdtk/design/START_INPUT_STATE.json",
156
+ screenBriefs,
157
+ componentContract,
158
+ };
159
+ }
160
+
107
161
  function cmdStart(args) {
108
162
  const { flags } = parseFlags(args || [], START_FLAG_DEFS);
109
163
  if (flags.help) return cmdStartHelp();
110
164
 
165
+ const contractMode = Boolean(flags["from-spec"] || flags["design-brief"] || flags["reference-dir"] || flags.profile);
166
+ if (contractMode) {
167
+ const contractResult = runDesignStartFromSpec({
168
+ fromSpecPath: flags["from-spec"],
169
+ projectPath: flags["project-path"],
170
+ designBrief: flags["design-brief"],
171
+ referenceDir: flags["reference-dir"],
172
+ profile: flags.profile,
173
+ });
174
+ if (contractResult.blockers.length > 0) {
175
+ throw new ValidationError(
176
+ `SDTK-DESIGN input contract blocked: ${contractResult.blockers.join(", ")}. Review ${contractResult.stateRelativePath}.`
177
+ );
178
+ }
179
+ console.log(`[design] Started SDTK-DESIGN package: ${contractResult.projectPath}`);
180
+ console.log(`[design] Mode: from-spec`);
181
+ console.log(`[design] Wrote ${contractResult.stateRelativePath}`);
182
+ if (contractResult.screenBriefs) {
183
+ console.log(`[design] Wrote per-screen briefs: ${contractResult.screenBriefs.generatedCount} artifact(s)`);
184
+ }
185
+ if (contractResult.componentContract) {
186
+ console.log(
187
+ `[design] Wrote component/token contract: ${contractResult.componentContract.componentLibraryRelativePath}, ${contractResult.componentContract.designTokensRelativePath}`
188
+ );
189
+ }
190
+ console.log(`[design] Screen model: ${contractResult.screenModel.totalScreens} explicit screen(s), readiness=${contractResult.screenModel.readiness}`);
191
+ if (contractResult.referenceDirectory && contractResult.referenceDirectory.provided) {
192
+ console.log(
193
+ `[design] Reference map: ${contractResult.referenceMap.mappedCount}/${contractResult.referenceMap.totalFiles} mapped (${contractResult.referenceMap.unmappedCount} unmapped)`
194
+ );
195
+ }
196
+ console.log(
197
+ `[design] Inputs: ${Object.entries(contractResult.artifacts)
198
+ .filter(([, artifact]) => artifact.found)
199
+ .map(([name, artifact]) => `${name}=${artifact.relativeToSpecRoot}`)
200
+ .join(", ")}`
201
+ );
202
+ console.log("[design] No raw requirement semantic parsing, .sdtk/atlas, SDTK-WIKI output, or network activity was used.");
203
+ console.log("[design] Next: sdtk-design prototype");
204
+ return 0;
205
+ }
206
+
111
207
  const result = runDesignStart({
112
208
  idea: flags.idea,
113
209
  projectPath: flags["project-path"],
@@ -128,5 +224,6 @@ module.exports = {
128
224
  cmdStart,
129
225
  cmdStartHelp,
130
226
  coreOutputTargets,
227
+ runDesignStartFromSpec,
131
228
  runDesignStart,
132
229
  };
@@ -53,6 +53,17 @@ function hasReview(paths) {
53
53
  return fs.readdirSync(paths.reviewsPath).some((name) => /^DESIGN_REVIEW_\d{8}\.md$/.test(name));
54
54
  }
55
55
 
56
+ function readInputContractState(paths) {
57
+ if (!fs.existsSync(paths.designStartInputStatePath) || !fs.statSync(paths.designStartInputStatePath).isFile()) {
58
+ return null;
59
+ }
60
+ try {
61
+ return JSON.parse(fs.readFileSync(paths.designStartInputStatePath, "utf-8"));
62
+ } catch (_err) {
63
+ return null;
64
+ }
65
+ }
66
+
56
67
  function inspectDesignStatus(projectPath) {
57
68
  const resolvedProjectPath = resolveProjectPath(projectPath || process.cwd());
58
69
  if (!fs.existsSync(resolvedProjectPath) || !fs.statSync(resolvedProjectPath).isDirectory()) {
@@ -65,12 +76,19 @@ function inspectDesignStatus(projectPath) {
65
76
  exists: fs.existsSync(artifact.filePath),
66
77
  }));
67
78
  const reviewExists = hasReview(paths);
79
+ const inputContractState = readInputContractState(paths);
68
80
  const coreMissing = artifacts.filter((artifact) => artifact.phase === "core" && !artifact.exists);
69
81
  const prototypeMissing = artifacts.find((artifact) => artifact.phase === "prototype" && !artifact.exists);
70
82
  const handoffMissing = artifacts.find((artifact) => artifact.phase === "handoff" && !artifact.exists);
71
83
 
72
84
  let nextCommand = "SDTK-CODE can consume docs/design/DESIGN_HANDOFF.md";
73
- if (coreMissing.length > 0) {
85
+ if (inputContractState && inputContractState.mode === "from-spec") {
86
+ if (Array.isArray(inputContractState.blockers) && inputContractState.blockers.length > 0) {
87
+ nextCommand = "Provide missing explicit SPEC/design artifacts and re-run sdtk-design start --from-spec.";
88
+ } else {
89
+ nextCommand = "sdtk-design prototype";
90
+ }
91
+ } else if (coreMissing.length > 0) {
74
92
  nextCommand = 'sdtk-design start --idea "<idea>"';
75
93
  } else if (prototypeMissing) {
76
94
  nextCommand = "sdtk-design prototype";
@@ -83,6 +101,7 @@ function inspectDesignStatus(projectPath) {
83
101
  return {
84
102
  projectPath: resolvedProjectPath,
85
103
  artifacts,
104
+ inputContractState,
86
105
  reviewExists,
87
106
  nextCommand,
88
107
  };
@@ -122,6 +141,23 @@ function cmdStatus(args) {
122
141
  }
123
142
  }
124
143
  console.log("");
144
+ console.log("Input contract:");
145
+ if (!status.inputContractState) {
146
+ console.log(" - not found");
147
+ } else {
148
+ console.log(` - mode: ${status.inputContractState.mode}`);
149
+ console.log(` - status: ${status.inputContractState.analysisStatus}`);
150
+ if (status.inputContractState.screenModel && typeof status.inputContractState.screenModel.totalScreens === "number") {
151
+ console.log(` - explicit screens: ${status.inputContractState.screenModel.totalScreens}`);
152
+ console.log(` - readiness: ${status.inputContractState.screenModel.readiness}`);
153
+ }
154
+ if (status.inputContractState.profileSelection) {
155
+ console.log(` - profile: ${status.inputContractState.profileSelection}`);
156
+ }
157
+ const blockers = Array.isArray(status.inputContractState.blockers) ? status.inputContractState.blockers : [];
158
+ console.log(` - blockers: ${blockers.length > 0 ? blockers.join(", ") : "none"}`);
159
+ }
160
+ console.log("");
125
161
  console.log(`Next recommended command: ${status.nextCommand}`);
126
162
  console.log("No .sdtk/atlas, SDTK-WIKI output, network, or app code was modified.");
127
163
  return 0;
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const { describeDesignPaths } = require("./design-paths");
5
+ const { ValidationError } = require("./errors");
6
+
7
+ const ECOMMERCE_PATTERNS = [
8
+ "Header and navigation shell",
9
+ "Category filter sidebar",
10
+ "Product card grid",
11
+ "Product detail spec table and gallery area",
12
+ "Search result list and no-result state",
13
+ "Cart table and quantity controls",
14
+ "Checkout stepper and summary card",
15
+ "Order history list or table",
16
+ "Order status timeline and detail summary",
17
+ "Account sidebar and view or edit form",
18
+ "Configurator wizard shell",
19
+ "Preview panel",
20
+ "BOM and material table",
21
+ "Exclude and recalculate action",
22
+ ];
23
+
24
+ const BASELINE_PATTERNS = [
25
+ "Header and navigation shell",
26
+ "Primary content section",
27
+ "Secondary support panel",
28
+ "Summary card",
29
+ "State badge and status chip",
30
+ "Primary and secondary action row",
31
+ ];
32
+
33
+ function contractTokens(profile, statePayload) {
34
+ const mappedRefs =
35
+ statePayload &&
36
+ statePayload.referenceMap &&
37
+ typeof statePayload.referenceMap.mappedCount === "number"
38
+ ? statePayload.referenceMap.mappedCount
39
+ : 0;
40
+ return {
41
+ schema: "sdtk.design.tokens.v1",
42
+ profile: profile || "generic",
43
+ color: {
44
+ surface: "#FFFFFF",
45
+ surfaceAlt: "#F7F8FA",
46
+ textPrimary: "#111827",
47
+ textMuted: "#64748B",
48
+ border: "#D8E0EA",
49
+ accentPrimary: profile === "b2b-commerce" ? "#0F766E" : "#2563EB",
50
+ accentSecondary: profile === "b2b-commerce" ? "#2563EB" : "#0F8A5F",
51
+ },
52
+ typography: {
53
+ fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif",
54
+ headingWeight: 800,
55
+ bodyWeight: 400,
56
+ uiWeight: 700,
57
+ letterSpacing: 0,
58
+ },
59
+ spacing: {
60
+ xs: 4,
61
+ sm: 8,
62
+ md: 12,
63
+ lg: 16,
64
+ xl: 24,
65
+ sectionGap: 32,
66
+ },
67
+ radius: {
68
+ card: 8,
69
+ control: 8,
70
+ chip: 999,
71
+ },
72
+ metadata: {
73
+ generatedFrom: "START_INPUT_STATE",
74
+ explicitScreenCount: statePayload.screenModel.totalScreens,
75
+ mappedReferenceCount: mappedRefs,
76
+ },
77
+ };
78
+ }
79
+
80
+ function markdownLibrary(profile, statePayload) {
81
+ const mappedRefs =
82
+ statePayload &&
83
+ statePayload.referenceMap &&
84
+ typeof statePayload.referenceMap.mappedCount === "number"
85
+ ? statePayload.referenceMap.mappedCount
86
+ : 0;
87
+ const patterns = profile === "b2b-commerce" ? ECOMMERCE_PATTERNS : BASELINE_PATTERNS;
88
+ return `# Component Pattern Library
89
+
90
+ ## Contract Meta
91
+ - Profile: ${profile || "generic"}
92
+ - Source: .sdtk/design/START_INPUT_STATE.json
93
+ - Explicit screens: ${statePayload.screenModel.totalScreens}
94
+ - Mapped references: ${mappedRefs}
95
+
96
+ ## Core Pattern Set
97
+ ${patterns.map((item) => `- ${item}`).join("\n")}
98
+
99
+ ## Usage Rules
100
+ - Keep component usage screen-role driven from explicit screen model.
101
+ - Reuse token contract from docs/design/DESIGN_TOKENS.json.
102
+ - Keep customer specifics project-local; runtime library stays profile-generic.
103
+
104
+ ## Accessibility Baseline
105
+ - Interactive controls should preserve clear focus styling.
106
+ - Keep readable contrast and deterministic heading hierarchy.
107
+ - Keep touch targets at least 44px where practical in prototype guidance.
108
+ `;
109
+ }
110
+
111
+ function writeComponentContractArtifacts(projectPath, inputContractState) {
112
+ const paths = describeDesignPaths(projectPath);
113
+ if (
114
+ !inputContractState ||
115
+ inputContractState.analysisStatus !== "INPUT_CONTRACT_READY" ||
116
+ !inputContractState.screenModel ||
117
+ !Array.isArray(inputContractState.screenModel.screens) ||
118
+ inputContractState.screenModel.screens.length === 0
119
+ ) {
120
+ throw new ValidationError(
121
+ "Component and token contract generation requires INPUT_CONTRACT_READY with explicit screens."
122
+ );
123
+ }
124
+ const profile = inputContractState.profileSelection || null;
125
+ const tokensPayload = contractTokens(profile, inputContractState);
126
+ const libraryMarkdown = markdownLibrary(profile, inputContractState);
127
+
128
+ fs.writeFileSync(paths.componentPatternLibraryPath, `${libraryMarkdown}\n`, "utf-8");
129
+ fs.writeFileSync(paths.designTokensPath, `${JSON.stringify(tokensPayload, null, 2)}\n`, "utf-8");
130
+
131
+ return {
132
+ componentLibraryRelativePath: "docs/design/COMPONENT_PATTERN_LIBRARY.md",
133
+ designTokensRelativePath: "docs/design/DESIGN_TOKENS.json",
134
+ profile: profile || "generic",
135
+ patternCount: profile === "b2b-commerce" ? ECOMMERCE_PATTERNS.length : BASELINE_PATTERNS.length,
136
+ };
137
+ }
138
+
139
+ module.exports = {
140
+ writeComponentContractArtifacts,
141
+ };
142
+