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.
- package/README.md +67 -125
- package/package.json +2 -1
- package/skills/design-prototype/SKILL.md +276 -0
- package/skills/design-prototype/references/craft.md +75 -0
- package/skills/design-prototype/references/designer-charter.md +56 -0
- package/src/commands/handoff.js +220 -220
- package/src/commands/help.js +7 -3
- package/src/commands/init.js +53 -0
- package/src/commands/prototype.js +140 -653
- package/src/commands/review.js +129 -139
- package/src/commands/start.js +22 -5
- package/src/commands/status.js +10 -2
- package/src/commands/system.js +186 -14
- package/src/commands/update.js +11 -0
- package/src/index.js +4 -0
- package/src/lib/anti-slop-lint.js +0 -11
- package/src/lib/component-contract.js +300 -34
- package/src/lib/design-input-contract.js +3 -2
- package/src/lib/design-paths.js +3 -0
- package/src/lib/design-profiles.js +31 -0
- package/src/lib/screen-briefs.js +235 -24
- package/src/lib/update.js +219 -0
- package/src/lib/prototype-briefs.js +0 -125
- package/src/lib/prototype-component-map.js +0 -219
- package/src/lib/prototype-density.js +0 -377
- package/src/lib/prototype-renderer.js +0 -382
package/src/commands/review.js
CHANGED
|
@@ -87,37 +87,6 @@ function readInputContractState(paths) {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
function normalizeScreenRole(screen) {
|
|
91
|
-
const token = `${screen.screenId || ""} ${screen.title || ""}`.toLowerCase();
|
|
92
|
-
if (token.includes("home")) return "home";
|
|
93
|
-
if (token.includes("category")) return "category";
|
|
94
|
-
if (token.includes("product-detail") || token.includes("product detail") || token.includes("pdp")) return "product-detail";
|
|
95
|
-
if (token.includes("search")) return "search";
|
|
96
|
-
if (token.includes("cart")) return "cart";
|
|
97
|
-
if (token.includes("checkout")) return "checkout";
|
|
98
|
-
if (token.includes("order-history") || token.includes("order history")) return "order-history";
|
|
99
|
-
if (token.includes("order-detail") || token.includes("order detail")) return "order-detail";
|
|
100
|
-
if (token.includes("account")) return "account-info";
|
|
101
|
-
if (token.includes("configurator") || token.includes("mode-b")) return "mode-b-configurator";
|
|
102
|
-
return screen.screenId || "screen";
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function requiredRoleMarkers(role) {
|
|
106
|
-
const map = {
|
|
107
|
-
home: ["hero", "featured products", "configurator"],
|
|
108
|
-
category: ["filter sidebar", "product grid"],
|
|
109
|
-
"product-detail": ["spec-table", "add to cart"],
|
|
110
|
-
search: ["search-toolbar", "no-result"],
|
|
111
|
-
cart: ["cart-table", "checkout"],
|
|
112
|
-
checkout: ["checkout-stepper", "summary card"],
|
|
113
|
-
"order-history": ["order history list"],
|
|
114
|
-
"order-detail": ["timeline", "detail summary"],
|
|
115
|
-
"account-info": ["account sidebar", "view or edit form"],
|
|
116
|
-
"mode-b-configurator": ["configurator wizard", "preview panel", "bom table", "recalculate", "add bom to cart"],
|
|
117
|
-
};
|
|
118
|
-
return map[role] || [];
|
|
119
|
-
}
|
|
120
|
-
|
|
121
90
|
function listPrototypeScreenFiles(paths) {
|
|
122
91
|
if (!fs.existsSync(paths.prototypeScreensPath) || !fs.statSync(paths.prototypeScreensPath).isDirectory()) {
|
|
123
92
|
return [];
|
|
@@ -129,27 +98,9 @@ function listPrototypeScreenFiles(paths) {
|
|
|
129
98
|
.map((name) => path.join(paths.prototypeScreensPath, name));
|
|
130
99
|
}
|
|
131
100
|
|
|
132
|
-
function combinedPrototypeReviewContent({ artifactPath, artifactContent, paths }) {
|
|
133
|
-
const screenFiles = listPrototypeScreenFiles(paths);
|
|
134
|
-
if (screenFiles.length === 0) {
|
|
135
|
-
return artifactContent;
|
|
136
|
-
}
|
|
137
|
-
const parts = [artifactContent];
|
|
138
|
-
if (path.resolve(artifactPath) !== path.resolve(paths.prototypeIndexPath) && fs.existsSync(paths.prototypeIndexPath)) {
|
|
139
|
-
parts.push(fs.readFileSync(paths.prototypeIndexPath, "utf-8"));
|
|
140
|
-
}
|
|
141
|
-
for (const filePath of screenFiles) {
|
|
142
|
-
if (path.resolve(filePath) === path.resolve(artifactPath)) {
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
parts.push(fs.readFileSync(filePath, "utf-8"));
|
|
146
|
-
}
|
|
147
|
-
return parts.join("\n");
|
|
148
|
-
}
|
|
149
|
-
|
|
150
101
|
function resolveAntiSlopLintMode(env = process.env) {
|
|
151
|
-
const value = String(env.SDTK_DESIGN_ANTI_SLOP_LINT || "
|
|
152
|
-
return value === "
|
|
102
|
+
const value = String(env.SDTK_DESIGN_ANTI_SLOP_LINT || "on").toLowerCase();
|
|
103
|
+
return value === "off" ? "off" : "on";
|
|
153
104
|
}
|
|
154
105
|
|
|
155
106
|
function collectPrototypeHtmlMap({ artifactPath, artifactContent, paths }) {
|
|
@@ -157,19 +108,18 @@ function collectPrototypeHtmlMap({ artifactPath, artifactContent, paths }) {
|
|
|
157
108
|
const add = (screenId, html) => {
|
|
158
109
|
if (!map.has(screenId)) map.set(screenId, html);
|
|
159
110
|
};
|
|
160
|
-
add(path.basename(artifactPath, ".html") || "artifact", artifactContent);
|
|
161
|
-
if (fs.existsSync(paths.prototypeIndexPath)) {
|
|
162
|
-
add("index", fs.readFileSync(paths.prototypeIndexPath, "utf-8"));
|
|
163
|
-
}
|
|
164
111
|
for (const filePath of listPrototypeScreenFiles(paths)) {
|
|
165
112
|
add(path.basename(filePath, ".html"), fs.readFileSync(filePath, "utf-8"));
|
|
166
113
|
}
|
|
114
|
+
if (map.size === 0 && path.resolve(path.dirname(artifactPath)) === path.resolve(paths.prototypeScreensPath)) {
|
|
115
|
+
add(path.basename(artifactPath, ".html") || "artifact", artifactContent);
|
|
116
|
+
}
|
|
167
117
|
return map;
|
|
168
118
|
}
|
|
169
119
|
|
|
170
120
|
function applyAntiSlopVerdict({ structuralVerdict, lintFindings, lintMode }) {
|
|
171
121
|
const lintSummary = summarizeLintFindings(lintFindings);
|
|
172
|
-
const lintBlocking = lintMode
|
|
122
|
+
const lintBlocking = lintMode !== "off" && lintSummary.p0 > 0;
|
|
173
123
|
return {
|
|
174
124
|
verdict: lintBlocking ? "FAIL_WITH_RENDERER_SLOP" : structuralVerdict,
|
|
175
125
|
lintSummary,
|
|
@@ -177,78 +127,112 @@ function applyAntiSlopVerdict({ structuralVerdict, lintFindings, lintMode }) {
|
|
|
177
127
|
};
|
|
178
128
|
}
|
|
179
129
|
|
|
180
|
-
function
|
|
181
|
-
const
|
|
130
|
+
function extractTagContent(html, tagName) {
|
|
131
|
+
const match = String(html || "").match(new RegExp(`<${tagName}\\b[^>]*>([\\s\\S]*?)<\\/${tagName}>`, "i"));
|
|
132
|
+
return match ? match[1] : "";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function stripTags(value) {
|
|
136
|
+
return String(value || "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function countTag(html, tagName) {
|
|
140
|
+
return (String(html || "").match(new RegExp(`<${tagName}\\b`, "gi")) || []).length;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function collectDesignTokenReferences(paths) {
|
|
144
|
+
if (!fs.existsSync(paths.designTokensPath)) return [];
|
|
145
|
+
try {
|
|
146
|
+
const payload = JSON.parse(fs.readFileSync(paths.designTokensPath, "utf-8"));
|
|
147
|
+
const values = [];
|
|
148
|
+
const visit = (value) => {
|
|
149
|
+
if (value == null) return;
|
|
150
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
151
|
+
const token = String(value).trim();
|
|
152
|
+
if (token) values.push(token.toLowerCase());
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (Array.isArray(value)) {
|
|
156
|
+
value.forEach(visit);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (typeof value === "object") Object.values(value).forEach(visit);
|
|
160
|
+
};
|
|
161
|
+
visit(payload);
|
|
162
|
+
return values.filter((value) => value.length >= 3);
|
|
163
|
+
} catch (_err) {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function hasDesignTokenReference(html, tokenReferences) {
|
|
169
|
+
const lower = String(html || "").toLowerCase();
|
|
170
|
+
if (/var\(--[a-z0-9-]+\)/i.test(lower)) return true;
|
|
171
|
+
return tokenReferences.some((token) => lower.includes(token));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function screenStructuralFindings({ screenId, html, tokenReferences }) {
|
|
175
|
+
const findings = [];
|
|
176
|
+
const lower = String(html || "").toLowerCase();
|
|
177
|
+
const title = stripTags(extractTagContent(html, "title"));
|
|
178
|
+
const body = extractTagContent(html, "body");
|
|
179
|
+
if (!lower.trimStart().startsWith("<!doctype html>")) findings.push("missing doctype");
|
|
180
|
+
if (!/<html\b/i.test(html)) findings.push("missing html element");
|
|
181
|
+
if (!/<head\b/i.test(html)) findings.push("missing head element");
|
|
182
|
+
if (!/<body\b/i.test(html)) findings.push("missing body element");
|
|
183
|
+
if (!title) findings.push("missing or empty title");
|
|
184
|
+
const h1Count = countTag(html, "h1");
|
|
185
|
+
if (h1Count !== 1) findings.push(`expected exactly one h1, found ${h1Count}`);
|
|
186
|
+
if (!stripTags(body)) findings.push("empty body");
|
|
187
|
+
if (!hasDesignTokenReference(html, tokenReferences)) findings.push("missing design token reference");
|
|
188
|
+
return {
|
|
189
|
+
screenId,
|
|
190
|
+
pass: findings.length === 0,
|
|
191
|
+
findings,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function evaluateHighFidelityGate({ statePayload, projectPath, paths }) {
|
|
182
196
|
const screens = statePayload.screenModel.screens || [];
|
|
183
197
|
const profile = statePayload.profileSelection || "none";
|
|
184
198
|
const hasTokens = fs.existsSync(paths.designTokensPath);
|
|
185
199
|
const hasComponentLibrary = fs.existsSync(paths.componentPatternLibraryPath);
|
|
186
|
-
const
|
|
187
|
-
const hasRawJsonRisk = html.includes("json.stringify") || html.includes("raw json") || html.includes("{\"") || html.includes("not_found");
|
|
188
|
-
const hasNav = html.includes('aria-label="screen navigation"') || html.includes("screen navigation");
|
|
189
|
-
const hasStateChips = html.includes('data-chip-type="state"') || html.includes("chip");
|
|
200
|
+
const tokenReferences = collectDesignTokenReferences(paths);
|
|
190
201
|
const sectionRows = [];
|
|
191
|
-
|
|
202
|
+
const blockers = [];
|
|
192
203
|
for (const screen of screens) {
|
|
193
204
|
const id = String(screen.screenId || "");
|
|
194
205
|
const title = String(screen.title || id);
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (!navMarkers.some((marker) => html.includes(marker.toLowerCase()))) missing.push("missing navigation link");
|
|
202
|
-
for (const marker of roleMarkers) {
|
|
203
|
-
if (!html.includes(marker)) missing.push(`missing component marker: ${marker}`);
|
|
206
|
+
const filePath = path.join(paths.prototypeScreensPath, `${id}.html`);
|
|
207
|
+
if (!fs.existsSync(filePath)) {
|
|
208
|
+
const missing = [`missing screen file: docs/design/prototype/screens/${id}.html`];
|
|
209
|
+
blockers.push(`${title}: ${missing.join("; ")}`);
|
|
210
|
+
sectionRows.push({ title, role: id, missing, pass: false });
|
|
211
|
+
continue;
|
|
204
212
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
missing,
|
|
210
|
-
pass: missing.length === 0,
|
|
213
|
+
const structural = screenStructuralFindings({
|
|
214
|
+
screenId: id,
|
|
215
|
+
html: fs.readFileSync(filePath, "utf-8"),
|
|
216
|
+
tokenReferences,
|
|
211
217
|
});
|
|
218
|
+
if (!structural.pass) blockers.push(`${title}: ${structural.findings.join("; ")}`);
|
|
219
|
+
sectionRows.push({ title, role: id, missing: structural.findings, pass: structural.pass });
|
|
212
220
|
}
|
|
213
221
|
|
|
214
222
|
let verdict = "PASS_HIGH_FIDELITY_READY";
|
|
215
|
-
const blockers = [];
|
|
216
223
|
if (screens.length === 0) {
|
|
217
|
-
verdict = "
|
|
224
|
+
verdict = "FAIL_STRUCTURE";
|
|
218
225
|
blockers.push("explicit screen model is empty");
|
|
219
226
|
}
|
|
220
227
|
if (!hasComponentLibrary) {
|
|
221
|
-
verdict = "
|
|
228
|
+
verdict = "FAIL_STRUCTURE";
|
|
222
229
|
blockers.push("component pattern library missing");
|
|
223
230
|
}
|
|
224
231
|
if (!hasTokens) {
|
|
225
|
-
verdict = "
|
|
232
|
+
verdict = "FAIL_STRUCTURE";
|
|
226
233
|
blockers.push("design tokens missing");
|
|
227
234
|
}
|
|
228
|
-
if (!
|
|
229
|
-
verdict = "FAIL_WITH_PRODUCT_GAPS";
|
|
230
|
-
blockers.push("inter-screen navigation contract missing");
|
|
231
|
-
}
|
|
232
|
-
if (!hasStateChips) {
|
|
233
|
-
verdict = "FAIL_WITH_PRODUCT_GAPS";
|
|
234
|
-
blockers.push("state chip coverage missing");
|
|
235
|
-
}
|
|
236
|
-
if (missingCount > 0) {
|
|
237
|
-
verdict = "FAIL_WITH_PRODUCT_GAPS";
|
|
238
|
-
blockers.push(`${missingCount} screen(s) missing required component markers`);
|
|
239
|
-
}
|
|
240
|
-
if (hasPre || hasRawJsonRisk) {
|
|
241
|
-
verdict = "FAIL_WITH_PRODUCT_GAPS";
|
|
242
|
-
blockers.push("debug/raw JSON UI risk detected");
|
|
243
|
-
}
|
|
244
|
-
if (profile === "b2b-commerce" && !html.includes("configurator wizard")) {
|
|
245
|
-
verdict = "FAIL_WITH_PRODUCT_GAPS";
|
|
246
|
-
blockers.push("configurator completeness missing");
|
|
247
|
-
}
|
|
248
|
-
if (html.includes("main layout</h3>") && html.includes("primary action</h3>")) {
|
|
249
|
-
verdict = "FAIL_HIGH_FIDELITY_NOT_READY";
|
|
250
|
-
blockers.push("generic scaffold markers detected (Main layout / Primary action)");
|
|
251
|
-
}
|
|
235
|
+
if (sectionRows.some((row) => !row.pass)) verdict = "FAIL_STRUCTURE";
|
|
252
236
|
return {
|
|
253
237
|
verdict,
|
|
254
238
|
blockers,
|
|
@@ -256,10 +240,6 @@ function evaluateHighFidelityGate({ artifactContent, statePayload, projectPath,
|
|
|
256
240
|
sectionRows,
|
|
257
241
|
hasTokens,
|
|
258
242
|
hasComponentLibrary,
|
|
259
|
-
hasNav,
|
|
260
|
-
hasStateChips,
|
|
261
|
-
hasPre,
|
|
262
|
-
hasRawJsonRisk,
|
|
263
243
|
projectPath,
|
|
264
244
|
};
|
|
265
245
|
}
|
|
@@ -272,12 +252,8 @@ function fidelityReviewContent(result) {
|
|
|
272
252
|
`- Profile: ${result.profile}`,
|
|
273
253
|
`- Component library: ${result.hasComponentLibrary ? "present" : "missing"}`,
|
|
274
254
|
`- Design tokens: ${result.hasTokens ? "present" : "missing"}`,
|
|
275
|
-
`- Navigation contract: ${result.hasNav ? "present" : "missing"}`,
|
|
276
|
-
`- State coverage markers: ${result.hasStateChips ? "present" : "missing"}`,
|
|
277
|
-
`- Raw <pre> debug risk: ${result.hasPre ? "detected" : "not detected"}`,
|
|
278
|
-
`- Raw JSON debug risk: ${result.hasRawJsonRisk ? "detected" : "not detected"}`,
|
|
279
255
|
"",
|
|
280
|
-
"## Screen-By-Screen
|
|
256
|
+
"## Screen-By-Screen Structural Sanity",
|
|
281
257
|
"",
|
|
282
258
|
"| Screen | Role | Result | Actionable notes |",
|
|
283
259
|
"|---|---|---|---|",
|
|
@@ -300,41 +276,61 @@ function fidelityReviewContent(result) {
|
|
|
300
276
|
"",
|
|
301
277
|
"## Actions",
|
|
302
278
|
"",
|
|
303
|
-
"-
|
|
279
|
+
"- Fix failed screen HTML documents so each has doctype, html/head/body, title, one h1, non-empty body, and token references.",
|
|
304
280
|
"- Ensure design tokens and component pattern library are generated before review.",
|
|
305
|
-
"- Remove
|
|
306
|
-
"- Re-run
|
|
281
|
+
"- Remove anti-slop P0 findings from customer-facing prototype output.",
|
|
282
|
+
"- Re-run the design-prototype skill and `sdtk-design review --artifact docs/design/prototype/index.html`.",
|
|
307
283
|
"",
|
|
308
284
|
].join("\n");
|
|
309
285
|
}
|
|
310
286
|
|
|
311
287
|
function visualPolishChecks({ artifactRelativePath, artifactContent, prototypeHtml }) {
|
|
312
288
|
const lower = String(artifactContent || "").toLowerCase();
|
|
313
|
-
const hasLanding = lower.includes("section-landing") || lower.includes("landing");
|
|
314
|
-
const hasOnboarding = lower.includes("section-onboarding") || lower.includes("onboarding");
|
|
315
|
-
const hasDashboard = lower.includes("section-dashboard") || lower.includes("dashboard");
|
|
316
|
-
const hasPreset = lower.includes("data-style-preset") || lower.includes("style-premium-dashboard") || lower.includes("style-minimal-saas") || lower.includes("style-bold-founder") || lower.includes("style-warm-editorial");
|
|
317
289
|
const hasResponsive = lower.includes("@media") || lower.includes("viewport") || lower.includes("mobile");
|
|
318
|
-
const hasCta = includesAny(artifactContent, ["btn-primary", "Primary CTA", "Create workspace", "Add lead", "Continue", "Start"]);
|
|
319
|
-
const hasDashboardDensity = hasDashboard && includesAny(artifactContent, ["metric", "kpi", "pipeline", "status", "lead", "table", "card"]);
|
|
290
|
+
const hasCta = includesAny(artifactContent, ["btn-primary", "Primary CTA", "Create workspace", "Add lead", "Continue", "Start", "button", "href="]);
|
|
320
291
|
const hasAccessibility = includesAny(artifactContent, ["accessibility", "aria-", "button", "label", "44px", "focus"]);
|
|
321
292
|
const hasSpacing = includesAny(artifactContent, ["gap:", "padding:", "spacing", "density", "grid", "margin"]);
|
|
322
293
|
const modeEvidence = prototypeHtml
|
|
323
|
-
? "Prototype HTML
|
|
294
|
+
? "Prototype HTML visual polish uses standalone HTML signals; structural readiness is checked separately by the fidelity gate."
|
|
324
295
|
: "Markdown artifact is checked for implementation-ready visual direction.";
|
|
325
296
|
|
|
297
|
+
const rows = [];
|
|
298
|
+
if (prototypeHtml) {
|
|
299
|
+
const hasSemanticLandmarks = includesAny(artifactContent, ["<main", "<header", "<nav", "<section", "role=", "aria-label"]);
|
|
300
|
+
const h1Count = (String(artifactContent || "").match(/<h1\b/gi) || []).length;
|
|
301
|
+
const hasSupportingHeading = /<h[2-3]\b/i.test(String(artifactContent || ""));
|
|
302
|
+
const hasHeadingOutline = h1Count === 1 && hasSupportingHeading;
|
|
303
|
+
const hasTokenUsage = /var\(--[a-z0-9-]+\)/i.test(String(artifactContent || ""));
|
|
304
|
+
const hasStubRisk = includesAny(artifactContent, ["placeholder text", "sample content", "lorem ipsum", "wireframe"]);
|
|
305
|
+
rows.push(
|
|
306
|
+
`| HTML Semantics | ${hasSemanticLandmarks ? "Pass" : "Needs attention"} | ${hasSemanticLandmarks ? "Prototype includes semantic landmarks or labelled regions." : "Add semantic landmarks such as main, nav, header, section, or labelled regions."} |`,
|
|
307
|
+
`| Heading Outline | ${hasHeadingOutline ? "Pass" : "Needs attention"} | ${hasHeadingOutline ? "Prototype has one h1 and supporting h2/h3 structure." : "Use one h1 with supporting section headings."} |`,
|
|
308
|
+
`| Responsive / Mobile Risk | ${hasResponsive ? "Pass" : "Needs attention"} | ${hasResponsive ? "Responsive viewport, media, or mobile behavior is represented." : "Add viewport or mobile breakpoint behavior before implementation."} |`,
|
|
309
|
+
`| Token Usage | ${hasTokenUsage ? "Pass" : "Needs attention"} | ${hasTokenUsage ? "Prototype references CSS custom properties for visual tokens." : "Reference design tokens via CSS custom properties in generated HTML."} |`,
|
|
310
|
+
`| CTA Clarity | ${hasCta ? "Pass" : "Needs attention"} | ${hasCta ? "A concrete action link or button is visible in the artifact." : "Add one dominant action link or button with concrete copy."} |`,
|
|
311
|
+
`| Stub / Wireframe Risk | ${!hasStubRisk ? "Pass" : "Needs attention"} | ${!hasStubRisk ? "No obvious placeholder or wireframe copy is present in the reviewed HTML." : "Replace placeholder or wireframe copy with generated product content."} |`
|
|
312
|
+
);
|
|
313
|
+
} else {
|
|
314
|
+
const hasLanding = lower.includes("landing");
|
|
315
|
+
const hasOnboarding = lower.includes("onboarding");
|
|
316
|
+
const hasDashboard = lower.includes("dashboard");
|
|
317
|
+
const hasDashboardDensity = hasDashboard && includesAny(artifactContent, ["metric", "kpi", "pipeline", "status", "lead", "table", "card"]);
|
|
318
|
+
rows.push(
|
|
319
|
+
`| Hierarchy | ${hasLanding && hasOnboarding && hasDashboard ? "Pass" : "Needs attention"} | ${hasLanding && hasOnboarding && hasDashboard ? "Landing, onboarding, and dashboard sections create a clear read order." : "Ensure the artifact clearly separates landing, onboarding, and dashboard intent."} |`,
|
|
320
|
+
`| Spacing / Density | ${hasSpacing ? "Pass" : "Needs attention"} | ${hasSpacing ? "Artifact includes grid, gap, padding, margin, or spacing guidance." : "Add spacing and density rules so the implementation does not regress into a flat wireframe."} |`,
|
|
321
|
+
`| CTA Clarity | ${hasCta ? "Pass" : "Needs attention"} | ${hasCta ? "A concrete primary action is visible in the artifact." : "Add one dominant CTA with concrete action copy."} |`,
|
|
322
|
+
`| Dashboard Information Density | ${hasDashboardDensity ? "Pass" : "Needs attention"} | ${hasDashboardDensity ? "Dashboard direction includes metrics, pipeline/status, cards, lists, or table surfaces." : "Dashboard needs enough KPI, status, list, or table detail to guide a real MVP screen."} |`,
|
|
323
|
+
`| Responsive / Mobile Risk | ${hasResponsive ? "Pass" : "Needs attention"} | ${hasResponsive ? "Responsive or mobile behavior is represented." : "Add mobile breakpoint or narrow-screen behavior before implementation."} |`,
|
|
324
|
+
`| Accessibility Baseline | ${hasAccessibility ? "Pass" : "Needs attention"} | ${hasAccessibility ? "Artifact includes accessible-control or baseline accessibility signals." : "Add labels, focus, touch-target, and color-not-alone guidance."} |`
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
326
328
|
return [
|
|
327
329
|
"## Visual Polish Checks",
|
|
328
330
|
"",
|
|
329
331
|
"| Check | Result | Evidence |",
|
|
330
332
|
"|---|---|---|",
|
|
331
|
-
|
|
332
|
-
`| Spacing / Density | ${hasSpacing ? "Pass" : "Needs attention"} | ${hasSpacing ? "Artifact includes grid, gap, padding, margin, or spacing guidance." : "Add spacing and density rules so the implementation does not regress into a flat wireframe."} |`,
|
|
333
|
-
`| CTA Clarity | ${hasCta ? "Pass" : "Needs attention"} | ${hasCta ? "A concrete primary action is visible in the artifact." : "Add one dominant CTA with concrete action copy."} |`,
|
|
334
|
-
`| Dashboard Information Density | ${hasDashboardDensity ? "Pass" : "Needs attention"} | ${hasDashboardDensity ? "Dashboard direction includes metrics, pipeline/status, cards, lists, or table surfaces." : "Dashboard needs enough KPI, status, list, or table detail to guide a real MVP screen."} |`,
|
|
335
|
-
`| Responsive / Mobile Risk | ${hasResponsive ? "Pass" : "Needs attention"} | ${hasResponsive ? "Responsive or mobile behavior is represented." : "Add mobile breakpoint or narrow-screen behavior before implementation."} |`,
|
|
336
|
-
`| Accessibility Baseline | ${hasAccessibility ? "Pass" : "Needs attention"} | ${hasAccessibility ? "Artifact includes accessible-control or baseline accessibility signals." : "Add labels, focus, touch-target, and color-not-alone guidance."} |`,
|
|
337
|
-
`| Anti-Wireframe / Mockup Warning | ${prototypeHtml && hasPreset ? "Pass" : "Needs attention"} | ${prototypeHtml && hasPreset ? "Prototype includes style preset tokens/classes and should guide visual polish beyond basic wireframes." : "Do not treat the artifact as complete UI; add concrete style preset tokens/classes or visual contract details."} |`,
|
|
333
|
+
...rows,
|
|
338
334
|
"",
|
|
339
335
|
`- Reviewed visual artifact: \`${artifactRelativePath}\`.`,
|
|
340
336
|
`- ${modeEvidence}`,
|
|
@@ -440,13 +436,7 @@ function runDesignReview({ artifact, projectPath, force = false }) {
|
|
|
440
436
|
let fidelityVerdict = null;
|
|
441
437
|
let antiSlopLint = null;
|
|
442
438
|
if (shouldRunFidelityGate) {
|
|
443
|
-
const fidelityArtifactContent = combinedPrototypeReviewContent({
|
|
444
|
-
artifactPath,
|
|
445
|
-
artifactContent,
|
|
446
|
-
paths,
|
|
447
|
-
});
|
|
448
439
|
const fidelityResult = evaluateHighFidelityGate({
|
|
449
|
-
artifactContent: fidelityArtifactContent,
|
|
450
440
|
statePayload,
|
|
451
441
|
projectPath: resolvedProjectPath,
|
|
452
442
|
paths,
|
|
@@ -500,7 +490,7 @@ function cmdReview(args) {
|
|
|
500
490
|
console.log(`[design] Fidelity verdict: ${result.fidelityVerdict}`);
|
|
501
491
|
if (result.antiSlopLint) {
|
|
502
492
|
if (result.antiSlopLint.mode === "off") {
|
|
503
|
-
console.log("[design] Anti-slop lint: OFF (
|
|
493
|
+
console.log("[design] Anti-slop lint: OFF (emergency rollback)");
|
|
504
494
|
} else if (result.antiSlopLint.summary.p0 > 0) {
|
|
505
495
|
console.log(`[design] Anti-slop lint: ${result.antiSlopLint.summary.p0} P0 findings; see review report`);
|
|
506
496
|
} else {
|
package/src/commands/start.js
CHANGED
|
@@ -11,7 +11,7 @@ const { describeDesignPaths, resolveProjectPath } = require("../lib/design-paths
|
|
|
11
11
|
const { availableProfileNames } = require("../lib/design-profiles");
|
|
12
12
|
const { buildInputContractState, writeInputContractState } = require("../lib/design-input-contract");
|
|
13
13
|
const { writeScreenBriefArtifacts } = require("../lib/screen-briefs");
|
|
14
|
-
const { writeComponentContractArtifacts } = require("../lib/component-contract");
|
|
14
|
+
const { writeComponentContractArtifacts, loadBrandTokensV2, loadProjectFactsV1 } = require("../lib/component-contract");
|
|
15
15
|
const { ValidationError } = require("../lib/errors");
|
|
16
16
|
const { DEFAULT_STYLE, availableStyleNames, resolveStyleName } = require("../lib/style-presets");
|
|
17
17
|
|
|
@@ -25,6 +25,8 @@ const START_FLAG_DEFS = {
|
|
|
25
25
|
"design-brief": { type: "string" },
|
|
26
26
|
"reference-dir": { type: "string" },
|
|
27
27
|
profile: { type: "string" },
|
|
28
|
+
"brand-tokens": { type: "string" },
|
|
29
|
+
"project-facts": { type: "string" },
|
|
28
30
|
};
|
|
29
31
|
|
|
30
32
|
const REQUIRED_WIREFRAME_FILES = ["LANDING.md", "ONBOARDING.md", "DASHBOARD.md"];
|
|
@@ -34,12 +36,14 @@ function cmdStartHelp() {
|
|
|
34
36
|
|
|
35
37
|
Usage:
|
|
36
38
|
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>]
|
|
39
|
+
sdtk-design start --from-spec <projectPath> [--design-brief <file>] [--reference-dir <dir>] [--profile <name>] [--brand-tokens <file>] [--project-facts <file>] [--project-path <path>]
|
|
38
40
|
|
|
39
41
|
Example:
|
|
40
42
|
sdtk-design start --idea "I want to build a lightweight CRM for solo consultants to track leads."
|
|
41
43
|
sdtk-design start --idea "ClientPulse for consultants" --style premium-dashboard
|
|
42
44
|
sdtk-design start --from-spec . --reference-dir ./docs/design/reference-export --profile b2b-commerce
|
|
45
|
+
sdtk-design start --from-spec . --profile b2b-commerce --brand-tokens ./brand-tokens.json
|
|
46
|
+
sdtk-design start --from-spec . --profile b2b-industrial-commerce --project-facts ./facts.json
|
|
43
47
|
|
|
44
48
|
Style presets:
|
|
45
49
|
${availableStyleNames().join(", ")}
|
|
@@ -134,7 +138,12 @@ function runDesignStartFromSpec({
|
|
|
134
138
|
designBrief,
|
|
135
139
|
referenceDir,
|
|
136
140
|
profile,
|
|
141
|
+
brandTokensPath,
|
|
142
|
+
projectFactsPath,
|
|
137
143
|
}) {
|
|
144
|
+
// BK-223 + BK-229: validate explicit inputs BEFORE any artifact write (fail-closed).
|
|
145
|
+
if (brandTokensPath) loadBrandTokensV2(brandTokensPath);
|
|
146
|
+
if (projectFactsPath) loadProjectFactsV1(projectFactsPath);
|
|
138
147
|
const contractState = buildInputContractState({
|
|
139
148
|
fromSpecPath,
|
|
140
149
|
projectPath,
|
|
@@ -147,7 +156,10 @@ function runDesignStartFromSpec({
|
|
|
147
156
|
let componentContract = null;
|
|
148
157
|
if (contractState.analysisStatus === "INPUT_CONTRACT_READY") {
|
|
149
158
|
screenBriefs = writeScreenBriefArtifacts(contractState.projectPath, contractState);
|
|
150
|
-
componentContract = writeComponentContractArtifacts(contractState.projectPath, contractState
|
|
159
|
+
componentContract = writeComponentContractArtifacts(contractState.projectPath, contractState, {
|
|
160
|
+
brandTokensPath: brandTokensPath || null,
|
|
161
|
+
projectFactsPath: projectFactsPath || null,
|
|
162
|
+
});
|
|
151
163
|
}
|
|
152
164
|
return {
|
|
153
165
|
...contractState,
|
|
@@ -162,7 +174,10 @@ function cmdStart(args) {
|
|
|
162
174
|
const { flags } = parseFlags(args || [], START_FLAG_DEFS);
|
|
163
175
|
if (flags.help) return cmdStartHelp();
|
|
164
176
|
|
|
165
|
-
const contractMode = Boolean(
|
|
177
|
+
const contractMode = Boolean(
|
|
178
|
+
flags["from-spec"] || flags["design-brief"] || flags["reference-dir"] ||
|
|
179
|
+
flags.profile || flags["brand-tokens"] || flags["project-facts"]
|
|
180
|
+
);
|
|
166
181
|
if (contractMode) {
|
|
167
182
|
const contractResult = runDesignStartFromSpec({
|
|
168
183
|
fromSpecPath: flags["from-spec"],
|
|
@@ -170,6 +185,8 @@ function cmdStart(args) {
|
|
|
170
185
|
designBrief: flags["design-brief"],
|
|
171
186
|
referenceDir: flags["reference-dir"],
|
|
172
187
|
profile: flags.profile,
|
|
188
|
+
brandTokensPath: flags["brand-tokens"] || null,
|
|
189
|
+
projectFactsPath: flags["project-facts"] || null,
|
|
173
190
|
});
|
|
174
191
|
if (contractResult.blockers.length > 0) {
|
|
175
192
|
throw new ValidationError(
|
|
@@ -216,7 +233,7 @@ function cmdStart(args) {
|
|
|
216
233
|
console.log(`[design] Style: ${result.style}`);
|
|
217
234
|
console.log(`[design] Overwrite: ${result.forced ? "enabled by --force" : "not needed"}`);
|
|
218
235
|
console.log("[design] No review, handoff, URL, browser, screenshot, vision, network, .sdtk/atlas, or SDTK-WIKI output was used.");
|
|
219
|
-
console.log("[design] Next: sdtk-design
|
|
236
|
+
console.log("[design] Next: sdtk-design handoff");
|
|
220
237
|
return 0;
|
|
221
238
|
}
|
|
222
239
|
|
package/src/commands/status.js
CHANGED
|
@@ -85,13 +85,21 @@ function inspectDesignStatus(projectPath) {
|
|
|
85
85
|
if (inputContractState && inputContractState.mode === "from-spec") {
|
|
86
86
|
if (Array.isArray(inputContractState.blockers) && inputContractState.blockers.length > 0) {
|
|
87
87
|
nextCommand = "Provide missing explicit SPEC/design artifacts and re-run sdtk-design start --from-spec.";
|
|
88
|
-
} else {
|
|
88
|
+
} else if (prototypeMissing) {
|
|
89
89
|
nextCommand = "sdtk-design prototype";
|
|
90
|
+
} else if (!reviewExists) {
|
|
91
|
+
nextCommand = "sdtk-design review --artifact docs/design/prototype/index.html";
|
|
92
|
+
} else if (handoffMissing) {
|
|
93
|
+
nextCommand = "sdtk-design handoff";
|
|
94
|
+
} else {
|
|
95
|
+
nextCommand = "SDTK-CODE can consume docs/design/DESIGN_HANDOFF.md";
|
|
90
96
|
}
|
|
91
97
|
} else if (coreMissing.length > 0) {
|
|
92
98
|
nextCommand = 'sdtk-design start --idea "<idea>"';
|
|
99
|
+
} else if (prototypeMissing && handoffMissing) {
|
|
100
|
+
nextCommand = "sdtk-design handoff";
|
|
93
101
|
} else if (prototypeMissing) {
|
|
94
|
-
nextCommand = "sdtk-design prototype";
|
|
102
|
+
nextCommand = "Run sdtk-design start --from-spec with explicit design inputs before sdtk-design prototype.";
|
|
95
103
|
} else if (!reviewExists) {
|
|
96
104
|
nextCommand = "sdtk-design review --artifact docs/design/prototype/index.html";
|
|
97
105
|
} else if (handoffMissing) {
|