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.
- package/package.json +1 -1
- package/src/commands/handoff.js +357 -203
- package/src/commands/help.js +3 -0
- package/src/commands/prototype.js +436 -26
- package/src/commands/review.js +230 -0
- package/src/commands/start.js +105 -8
- package/src/commands/status.js +37 -1
- package/src/lib/component-contract.js +142 -0
- package/src/lib/design-input-contract.js +683 -0
- package/src/lib/design-paths.js +27 -0
- package/src/lib/design-profiles.js +58 -0
- package/src/lib/prototype-density.js +147 -0
- package/src/lib/prototype-renderer.js +325 -0
- package/src/lib/screen-briefs.js +340 -0
package/src/commands/review.js
CHANGED
|
@@ -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.");
|
package/src/commands/start.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
};
|
package/src/commands/status.js
CHANGED
|
@@ -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 (
|
|
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
|
+
|