@design-ai/cli 4.55.0 → 4.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +39 -0
- package/README.ko.md +7 -7
- package/README.md +9 -9
- package/cli/lib/mcp-server.mjs +208 -10
- package/cli/lib/site-analysis.mjs +297 -0
- package/cli/lib/site-args.mjs +433 -0
- package/cli/lib/site-bundle-build.mjs +127 -0
- package/cli/lib/site-bundle-check.mjs +454 -0
- package/cli/lib/site-bundle-commands.mjs +95 -0
- package/cli/lib/site-bundle-compare.mjs +157 -0
- package/cli/lib/site-bundle-contract.mjs +79 -0
- package/cli/lib/site-bundle-files.mjs +87 -0
- package/cli/lib/site-bundle-handoff-runbook-actions.mjs +113 -0
- package/cli/lib/site-bundle-handoff-runbook-evidence-fields.mjs +164 -0
- package/cli/lib/site-bundle-handoff-runbook-evidence.mjs +334 -0
- package/cli/lib/site-bundle-handoff-runbook-format.mjs +31 -0
- package/cli/lib/site-bundle-handoff-runbook-stage-metadata.mjs +84 -0
- package/cli/lib/site-bundle-handoff-runbook.mjs +1331 -0
- package/cli/lib/site-bundle-handoff-summary.mjs +183 -0
- package/cli/lib/site-bundle-handoff.mjs +271 -0
- package/cli/lib/site-bundle-readme.mjs +98 -0
- package/cli/lib/site-bundle-repair-report.mjs +143 -0
- package/cli/lib/site-bundle-repair.mjs +68 -0
- package/cli/lib/site-content.mjs +399 -0
- package/cli/lib/site-evidence.mjs +35 -0
- package/cli/lib/site-mcp-commands.mjs +28 -0
- package/cli/lib/site-mcp-probes.mjs +159 -0
- package/cli/lib/site-mcp-readiness.mjs +157 -0
- package/cli/lib/site-mcp-report.mjs +324 -0
- package/cli/lib/site-next-actions.mjs +333 -0
- package/cli/lib/site-options.mjs +104 -0
- package/cli/lib/site-prompts.mjs +332 -0
- package/cli/lib/site-starter.mjs +153 -0
- package/cli/lib/site-strings.mjs +23 -0
- package/cli/lib/site-tasks.mjs +93 -0
- package/cli/lib/site-workflow-graph.mjs +309 -0
- package/cli/lib/site-workspace.mjs +492 -0
- package/cli/lib/site.mjs +108 -6617
- package/docs/DISTRIBUTION.ko.md +35 -6
- package/docs/DISTRIBUTION.md +35 -8
- package/docs/RELEASE-CHECKLIST.md +20 -3
- package/docs/ROADMAP.md +2179 -0
- package/docs/external-status.md +22 -7
- package/docs/integrations/design-ai-mcp-server.md +32 -0
- package/docs/integrations/vscode-walkthrough.ko.md +3 -3
- package/docs/integrations/vscode-walkthrough.md +3 -3
- package/docs/site-overrides/main.html +1 -1
- package/package.json +1 -1
- package/tools/audit/package-smoke.py +106 -0
- package/tools/audit/registry-smoke.py +378 -10
- package/tools/audit/smoke_assertions.py +83 -22
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
// Website Improvement workspace creation and normalization helpers.
|
|
2
|
+
|
|
3
|
+
import { normalizeStringArray } from "./site-strings.mjs";
|
|
4
|
+
import {
|
|
5
|
+
AUDIT_CATEGORIES,
|
|
6
|
+
CHECKLIST_STATUS_OPTIONS,
|
|
7
|
+
CMS_OPTIONS,
|
|
8
|
+
DATABASE_OPTIONS,
|
|
9
|
+
DEPLOY_OPTIONS,
|
|
10
|
+
EFFORT_OPTIONS,
|
|
11
|
+
IMPACT_OPTIONS,
|
|
12
|
+
MCP_ITEMS,
|
|
13
|
+
MCP_STATUS_OPTIONS,
|
|
14
|
+
PRIORITY_OPTIONS,
|
|
15
|
+
VIEWPORT_OPTIONS,
|
|
16
|
+
} from "./site-options.mjs";
|
|
17
|
+
import {
|
|
18
|
+
DEFAULT_IMPLEMENTATION_RISKS,
|
|
19
|
+
normalizeImplementationEvidence,
|
|
20
|
+
} from "./site-evidence.mjs";
|
|
21
|
+
import { createSampleSiteWorkspace } from "./site-starter.mjs";
|
|
22
|
+
|
|
23
|
+
function uniqueNormalizedStrings(items, fallback = []) {
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
const result = [];
|
|
26
|
+
const normalized = normalizeStringArray(items);
|
|
27
|
+
const source = normalized.length > 0 ? normalized : normalizeStringArray(fallback);
|
|
28
|
+
for (const item of source) {
|
|
29
|
+
const key = item.toLowerCase();
|
|
30
|
+
if (seen.has(key)) continue;
|
|
31
|
+
seen.add(key);
|
|
32
|
+
result.push(item);
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function slugifySiteId(value) {
|
|
38
|
+
const slug = String(value || "")
|
|
39
|
+
.trim()
|
|
40
|
+
.toLowerCase()
|
|
41
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
42
|
+
.replace(/^-+|-+$/g, "");
|
|
43
|
+
return slug || "website-project";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizeEnum(value, allowed, fallback) {
|
|
47
|
+
return allowed.includes(value) ? value : fallback;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function normalizeObject(value) {
|
|
51
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function defaultChecklist() {
|
|
55
|
+
return Object.fromEntries(
|
|
56
|
+
AUDIT_CATEGORIES.map((category) => [
|
|
57
|
+
category.id,
|
|
58
|
+
{
|
|
59
|
+
status: "todo",
|
|
60
|
+
notes: "",
|
|
61
|
+
findings: [],
|
|
62
|
+
},
|
|
63
|
+
]),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizeChecklist(value) {
|
|
68
|
+
const source = normalizeObject(value);
|
|
69
|
+
const fallback = defaultChecklist();
|
|
70
|
+
return Object.fromEntries(
|
|
71
|
+
AUDIT_CATEGORIES.map((category) => {
|
|
72
|
+
const row = normalizeObject(source[category.id]);
|
|
73
|
+
return [
|
|
74
|
+
category.id,
|
|
75
|
+
{
|
|
76
|
+
status: normalizeEnum(row.status, CHECKLIST_STATUS_OPTIONS, fallback[category.id].status),
|
|
77
|
+
notes: String(row.notes || ""),
|
|
78
|
+
findings: normalizeStringArray(row.findings),
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalizeMcpReadiness(value) {
|
|
86
|
+
const source = normalizeObject(value);
|
|
87
|
+
return Object.fromEntries(
|
|
88
|
+
MCP_ITEMS.map(([key]) => [
|
|
89
|
+
key,
|
|
90
|
+
normalizeEnum(source[key], MCP_STATUS_OPTIONS, "unused"),
|
|
91
|
+
]),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function normalizeTasks(value) {
|
|
96
|
+
if (!Array.isArray(value)) return [];
|
|
97
|
+
const categoryIds = AUDIT_CATEGORIES.map((category) => category.id);
|
|
98
|
+
return value.map((task, index) => {
|
|
99
|
+
const item = normalizeObject(task);
|
|
100
|
+
return {
|
|
101
|
+
id: String(item.id || `task-${index + 1}`),
|
|
102
|
+
title: String(item.title || "Untitled website improvement task"),
|
|
103
|
+
category: normalizeEnum(item.category, categoryIds, "ux-flow"),
|
|
104
|
+
problem: String(item.problem || ""),
|
|
105
|
+
evidence: String(item.evidence || ""),
|
|
106
|
+
impact: normalizeEnum(item.impact, IMPACT_OPTIONS, "medium"),
|
|
107
|
+
effort: normalizeEnum(item.effort, EFFORT_OPTIONS, "medium"),
|
|
108
|
+
priority: normalizeEnum(item.priority, PRIORITY_OPTIONS, "p2"),
|
|
109
|
+
pages: normalizeStringArray(item.pages),
|
|
110
|
+
recommendedMcp: normalizeStringArray(item.recommendedMcp),
|
|
111
|
+
codexPrompt: String(item.codexPrompt || ""),
|
|
112
|
+
verification: normalizeStringArray(item.verification),
|
|
113
|
+
risks: normalizeStringArray(item.risks),
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function normalizeSiteWorkspace(raw) {
|
|
119
|
+
const fallback = createSampleSiteWorkspace();
|
|
120
|
+
const source = normalizeObject(raw);
|
|
121
|
+
const profile = normalizeObject(source.siteProfile);
|
|
122
|
+
const viewports = normalizeStringArray(profile.viewports, fallback.siteProfile.viewports)
|
|
123
|
+
.filter((viewport) => VIEWPORT_OPTIONS.includes(viewport));
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
version: 1,
|
|
127
|
+
updatedAt: String(source.updatedAt || fallback.updatedAt),
|
|
128
|
+
siteProfile: {
|
|
129
|
+
id: String(profile.id || fallback.siteProfile.id),
|
|
130
|
+
name: String(profile.name || fallback.siteProfile.name),
|
|
131
|
+
liveUrl: String(profile.liveUrl || ""),
|
|
132
|
+
repoUrl: String(profile.repoUrl || ""),
|
|
133
|
+
localPath: String(profile.localPath || ""),
|
|
134
|
+
figmaUrl: String(profile.figmaUrl || ""),
|
|
135
|
+
brandNotes: String(profile.brandNotes || ""),
|
|
136
|
+
deployProvider: normalizeEnum(profile.deployProvider, DEPLOY_OPTIONS, "none"),
|
|
137
|
+
sentryProject: String(profile.sentryProject || ""),
|
|
138
|
+
cms: normalizeEnum(profile.cms, CMS_OPTIONS, "none"),
|
|
139
|
+
database: normalizeEnum(profile.database, DATABASE_OPTIONS, "none"),
|
|
140
|
+
pages: normalizeStringArray(profile.pages, fallback.siteProfile.pages),
|
|
141
|
+
userFlows: normalizeStringArray(profile.userFlows, fallback.siteProfile.userFlows),
|
|
142
|
+
viewports: viewports.length ? viewports : ["desktop"],
|
|
143
|
+
},
|
|
144
|
+
auditChecklist: normalizeChecklist(source.auditChecklist || fallback.auditChecklist),
|
|
145
|
+
mcpReadiness: normalizeMcpReadiness(source.mcpReadiness || fallback.mcpReadiness),
|
|
146
|
+
refactorTasks: normalizeTasks(source.refactorTasks || fallback.refactorTasks),
|
|
147
|
+
implementationEvidence: normalizeImplementationEvidence(source.implementationEvidence || fallback.implementationEvidence),
|
|
148
|
+
reportNotes: String(source.reportNotes || ""),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function buildInitAuditChecklist(profile) {
|
|
153
|
+
const pageList = profile.pages.slice(0, 4).join(", ");
|
|
154
|
+
const flowList = profile.userFlows.slice(0, 3).join("; ");
|
|
155
|
+
return Object.fromEntries(
|
|
156
|
+
AUDIT_CATEGORIES.map((category) => {
|
|
157
|
+
const notes = {
|
|
158
|
+
"visual-design": `Review layout, typography, color, spacing, hierarchy, and CTA treatment across ${pageList}.`,
|
|
159
|
+
"ux-flow": `Map and test the primary flow(s): ${flowList}.`,
|
|
160
|
+
responsive: `Verify configured viewports: ${profile.viewports.join(", ")}.`,
|
|
161
|
+
accessibility: "Check keyboard navigation, focus indicators, semantic structure, ARIA usage, and contrast before implementation handoff.",
|
|
162
|
+
performance: "Run target-repo or deployment performance checks after the first visual/UX pass.",
|
|
163
|
+
seo: "Inspect title, description, canonical, OG metadata, sitemap exposure, and heading order for priority pages.",
|
|
164
|
+
"technical-quality": "Inspect target repo architecture before editing; preserve existing components, tokens, styling conventions, and verification commands.",
|
|
165
|
+
"runtime-issues": "Use Browser/Chrome DevTools or deployment logs to check console errors, network failures, hydration issues, and broken assets.",
|
|
166
|
+
"content-quality": "Review copy clarity, information architecture, proof points, trust signals, Korean/English tone, and CTA wording.",
|
|
167
|
+
}[category.id];
|
|
168
|
+
return [
|
|
169
|
+
category.id,
|
|
170
|
+
{
|
|
171
|
+
status: "todo",
|
|
172
|
+
notes,
|
|
173
|
+
findings: [],
|
|
174
|
+
},
|
|
175
|
+
];
|
|
176
|
+
}),
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function buildInitMcpReadiness(profile) {
|
|
181
|
+
const hasRepoReference = Boolean(profile.repoUrl || profile.localPath);
|
|
182
|
+
return {
|
|
183
|
+
github: hasRepoReference ? "required" : "optional",
|
|
184
|
+
figma: profile.figmaUrl ? "optional" : "unused",
|
|
185
|
+
browser: "required",
|
|
186
|
+
chromeDevtools: "optional",
|
|
187
|
+
deploy: profile.deployProvider && profile.deployProvider !== "none" ? "required" : "optional",
|
|
188
|
+
sentry: profile.sentryProject ? "optional" : "unused",
|
|
189
|
+
database: profile.database && profile.database !== "none" ? "optional" : "unused",
|
|
190
|
+
cms: profile.cms && profile.cms !== "none" ? "optional" : "unused",
|
|
191
|
+
collaboration: "optional",
|
|
192
|
+
research: "optional",
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function createSiteWorkspaceFromInitOptions(options = {}) {
|
|
197
|
+
const name = String(options.name || "").trim();
|
|
198
|
+
const liveUrl = String(options.liveUrl || "").trim();
|
|
199
|
+
if (!name) {
|
|
200
|
+
throw new Error("--init requires --name");
|
|
201
|
+
}
|
|
202
|
+
if (!liveUrl) {
|
|
203
|
+
throw new Error("--init requires --live-url");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const profile = {
|
|
207
|
+
id: slugifySiteId(name),
|
|
208
|
+
name,
|
|
209
|
+
liveUrl,
|
|
210
|
+
repoUrl: String(options.repoUrl || "").trim(),
|
|
211
|
+
localPath: String(options.localPath || "").trim(),
|
|
212
|
+
figmaUrl: String(options.figmaUrl || "").trim(),
|
|
213
|
+
brandNotes: String(options.brandNotes || "").trim(),
|
|
214
|
+
deployProvider: normalizeEnum(options.deployProvider, DEPLOY_OPTIONS, "none"),
|
|
215
|
+
sentryProject: String(options.sentryProject || "").trim(),
|
|
216
|
+
cms: normalizeEnum(options.cms, CMS_OPTIONS, "none"),
|
|
217
|
+
database: normalizeEnum(options.database, DATABASE_OPTIONS, "none"),
|
|
218
|
+
pages: uniqueNormalizedStrings(options.pages, ["/"]),
|
|
219
|
+
userFlows: uniqueNormalizedStrings(options.userFlows, [
|
|
220
|
+
"Visitor reviews the site and completes the primary conversion flow",
|
|
221
|
+
]),
|
|
222
|
+
viewports: uniqueNormalizedStrings(options.viewports, ["desktop", "tablet", "mobile"])
|
|
223
|
+
.filter((viewport) => VIEWPORT_OPTIONS.includes(viewport)),
|
|
224
|
+
};
|
|
225
|
+
if (profile.viewports.length === 0) {
|
|
226
|
+
profile.viewports = ["desktop", "tablet", "mobile"];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return normalizeSiteWorkspace({
|
|
230
|
+
version: 1,
|
|
231
|
+
updatedAt: new Date().toISOString(),
|
|
232
|
+
siteProfile: profile,
|
|
233
|
+
auditChecklist: buildInitAuditChecklist(profile),
|
|
234
|
+
mcpReadiness: buildInitMcpReadiness(profile),
|
|
235
|
+
refactorTasks: [],
|
|
236
|
+
implementationEvidence: {
|
|
237
|
+
executedWork: [],
|
|
238
|
+
verificationResults: [],
|
|
239
|
+
remainingRisks: [...DEFAULT_IMPLEMENTATION_RISKS],
|
|
240
|
+
nextActions: [
|
|
241
|
+
"Run `design-ai site <workspace.json> --mcp-check --probes --json` before target-repo implementation.",
|
|
242
|
+
"Add audit findings in the Website Console, then run `design-ai site <workspace.json> --tasks --out website-workspace.tasks.json`.",
|
|
243
|
+
],
|
|
244
|
+
},
|
|
245
|
+
reportNotes: "Generated by `design-ai site --init` for real-project Website Improvement intake. Actual target repo code changes happen outside this design-ai repository.",
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function normalizeIntakeLookupKey(value) {
|
|
250
|
+
return String(value || "")
|
|
251
|
+
.replace(/`/g, "")
|
|
252
|
+
.replace(/\[[^\]]+\]\([^)]+\)/g, "")
|
|
253
|
+
.trim()
|
|
254
|
+
.toLowerCase()
|
|
255
|
+
.replace(/[^a-z0-9가-힣]+/g, " ")
|
|
256
|
+
.replace(/\s+/g, " ")
|
|
257
|
+
.trim();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function cleanIntakeCell(value) {
|
|
261
|
+
return String(value || "")
|
|
262
|
+
.replace(/<br\s*\/?>/gi, " ")
|
|
263
|
+
.replace(/\\\|/g, "|")
|
|
264
|
+
.replace(/^`|`$/g, "")
|
|
265
|
+
.trim();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function isBlankIntakeCell(value) {
|
|
269
|
+
const text = cleanIntakeCell(value);
|
|
270
|
+
if (!text) return true;
|
|
271
|
+
if (/^<[^>]+>$/.test(text)) return true;
|
|
272
|
+
if (text.includes(" / ") && (text.match(/`/g) || []).length >= 4) return true;
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function intakeSection(markdown, headingNames) {
|
|
277
|
+
const wanted = new Set(headingNames.map(normalizeIntakeLookupKey));
|
|
278
|
+
const lines = String(markdown || "").split(/\r?\n/);
|
|
279
|
+
const collected = [];
|
|
280
|
+
let active = false;
|
|
281
|
+
|
|
282
|
+
for (const line of lines) {
|
|
283
|
+
const heading = line.match(/^##\s+(.+?)\s*$/);
|
|
284
|
+
if (heading) {
|
|
285
|
+
const key = normalizeIntakeLookupKey(heading[1]);
|
|
286
|
+
active = wanted.has(key);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (active) {
|
|
290
|
+
collected.push(line);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return collected.join("\n");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function intakeTableRows(section) {
|
|
298
|
+
return String(section || "")
|
|
299
|
+
.split(/\r?\n/)
|
|
300
|
+
.map((line) => line.trim())
|
|
301
|
+
.filter((line) => line.startsWith("|") && line.endsWith("|"))
|
|
302
|
+
.filter((line) => !/^\|\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|$/.test(line))
|
|
303
|
+
.map((line) => line.slice(1, -1).split("|").map(cleanIntakeCell));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function firstValidIntakeEnum(value, allowed, fallback = "none") {
|
|
307
|
+
const text = cleanIntakeCell(value).replace(/`/g, "").trim().toLowerCase();
|
|
308
|
+
if (!text || text.includes(" / ")) return fallback;
|
|
309
|
+
return allowed.find((item) => text === item || text.includes(item)) || fallback;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function nonPlaceholderIntakeValue(value) {
|
|
313
|
+
if (isBlankIntakeCell(value)) return "";
|
|
314
|
+
const text = cleanIntakeCell(value);
|
|
315
|
+
if (/^(field|value|항목|값|priority|우선순위|path or url|path 또는 url|flow|status|상태|finding|evidence|page|category)$/i.test(text)) {
|
|
316
|
+
return "";
|
|
317
|
+
}
|
|
318
|
+
return text;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const INTAKE_PROFILE_FIELD_MAP = new Map([
|
|
322
|
+
["site name", "name"],
|
|
323
|
+
["사이트 이름", "name"],
|
|
324
|
+
["live url", "liveUrl"],
|
|
325
|
+
["target repo url", "repoUrl"],
|
|
326
|
+
["대상 repo url", "repoUrl"],
|
|
327
|
+
["target repo local path", "localPath"],
|
|
328
|
+
["대상 repo local path", "localPath"],
|
|
329
|
+
["figma url", "figmaUrl"],
|
|
330
|
+
["deploy provider", "deployProvider"],
|
|
331
|
+
["deploy platform", "deployProvider"],
|
|
332
|
+
["배포 플랫폼", "deployProvider"],
|
|
333
|
+
["sentry project", "sentryProject"],
|
|
334
|
+
["sentry 프로젝트", "sentryProject"],
|
|
335
|
+
["cms", "cms"],
|
|
336
|
+
["database", "database"],
|
|
337
|
+
]);
|
|
338
|
+
|
|
339
|
+
const INTAKE_MCP_FIELD_MAP = new Map([
|
|
340
|
+
["github", "github"],
|
|
341
|
+
["figma", "figma"],
|
|
342
|
+
["browser playwright", "browser"],
|
|
343
|
+
["chrome devtools", "chromeDevtools"],
|
|
344
|
+
["deploy provider", "deploy"],
|
|
345
|
+
["배포 플랫폼", "deploy"],
|
|
346
|
+
["sentry", "sentry"],
|
|
347
|
+
["database", "database"],
|
|
348
|
+
["cms", "cms"],
|
|
349
|
+
["collaboration tool", "collaboration"],
|
|
350
|
+
["협업 도구", "collaboration"],
|
|
351
|
+
["research tool", "research"],
|
|
352
|
+
["리서치 도구", "research"],
|
|
353
|
+
]);
|
|
354
|
+
|
|
355
|
+
const INTAKE_AUDIT_CATEGORY_MAP = new Map([
|
|
356
|
+
["visual design", "visual-design"],
|
|
357
|
+
["ux flow", "ux-flow"],
|
|
358
|
+
["responsive", "responsive"],
|
|
359
|
+
["accessibility", "accessibility"],
|
|
360
|
+
["performance", "performance"],
|
|
361
|
+
["seo", "seo"],
|
|
362
|
+
["technical quality", "technical-quality"],
|
|
363
|
+
["runtime issues", "runtime-issues"],
|
|
364
|
+
["content quality", "content-quality"],
|
|
365
|
+
]);
|
|
366
|
+
|
|
367
|
+
function parseSiteProfileFromIntake(markdown) {
|
|
368
|
+
const profile = {
|
|
369
|
+
name: "",
|
|
370
|
+
liveUrl: "",
|
|
371
|
+
repoUrl: "",
|
|
372
|
+
localPath: "",
|
|
373
|
+
figmaUrl: "",
|
|
374
|
+
brandNotes: "",
|
|
375
|
+
deployProvider: "none",
|
|
376
|
+
sentryProject: "",
|
|
377
|
+
cms: "none",
|
|
378
|
+
database: "none",
|
|
379
|
+
pages: [],
|
|
380
|
+
userFlows: [],
|
|
381
|
+
viewports: ["desktop", "tablet", "mobile"],
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
for (const row of intakeTableRows(intakeSection(markdown, ["Site Profile"]))) {
|
|
385
|
+
const field = INTAKE_PROFILE_FIELD_MAP.get(normalizeIntakeLookupKey(row[0]));
|
|
386
|
+
if (!field) continue;
|
|
387
|
+
if (field === "deployProvider") {
|
|
388
|
+
profile.deployProvider = firstValidIntakeEnum(row[1], DEPLOY_OPTIONS, "none");
|
|
389
|
+
} else if (field === "cms") {
|
|
390
|
+
profile.cms = firstValidIntakeEnum(row[1], CMS_OPTIONS, "none");
|
|
391
|
+
} else if (field === "database") {
|
|
392
|
+
profile.database = firstValidIntakeEnum(row[1], DATABASE_OPTIONS, "none");
|
|
393
|
+
} else {
|
|
394
|
+
profile[field] = nonPlaceholderIntakeValue(row[1]);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const brandNotes = [];
|
|
399
|
+
for (const row of intakeTableRows(intakeSection(markdown, ["Brand And Content Notes"]))) {
|
|
400
|
+
const area = nonPlaceholderIntakeValue(row[0]);
|
|
401
|
+
const note = nonPlaceholderIntakeValue(row[1]);
|
|
402
|
+
if (area && note) {
|
|
403
|
+
brandNotes.push(`${area}: ${note}`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (brandNotes.length > 0) {
|
|
407
|
+
profile.brandNotes = brandNotes.join("\n");
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
for (const row of intakeTableRows(intakeSection(markdown, ["Priority Pages", "우선순위 페이지"]))) {
|
|
411
|
+
const page = nonPlaceholderIntakeValue(row[1]);
|
|
412
|
+
if (page && !/^path\b/i.test(page)) {
|
|
413
|
+
profile.pages.push(page);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
for (const row of intakeTableRows(intakeSection(markdown, ["Primary User Flows", "주요 사용자 흐름"]))) {
|
|
418
|
+
const flow = nonPlaceholderIntakeValue(row[1]);
|
|
419
|
+
if (flow && normalizeIntakeLookupKey(flow) !== "flow") {
|
|
420
|
+
profile.userFlows.push(flow);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return profile;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function applyMcpReadinessFromIntake(workspace, markdown) {
|
|
428
|
+
const mcpReadiness = { ...workspace.mcpReadiness };
|
|
429
|
+
for (const row of intakeTableRows(intakeSection(markdown, ["MCP Readiness Notes"]))) {
|
|
430
|
+
const key = INTAKE_MCP_FIELD_MAP.get(normalizeIntakeLookupKey(row[0]));
|
|
431
|
+
if (!key) continue;
|
|
432
|
+
const status = firstValidIntakeEnum(row[1], MCP_STATUS_OPTIONS, "");
|
|
433
|
+
if (status) {
|
|
434
|
+
mcpReadiness[key] = status;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return mcpReadiness;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function applyAuditFindingsFromIntake(workspace, markdown) {
|
|
441
|
+
const auditChecklist = structuredClone(workspace.auditChecklist);
|
|
442
|
+
const pages = new Set(workspace.siteProfile.pages);
|
|
443
|
+
for (const row of intakeTableRows(intakeSection(markdown, ["Initial Audit Findings", "초기 Audit Findings"]))) {
|
|
444
|
+
const categoryId = INTAKE_AUDIT_CATEGORY_MAP.get(normalizeIntakeLookupKey(row[0]));
|
|
445
|
+
const finding = nonPlaceholderIntakeValue(row[1]);
|
|
446
|
+
if (!categoryId || !finding) continue;
|
|
447
|
+
|
|
448
|
+
const evidence = nonPlaceholderIntakeValue(row[2]);
|
|
449
|
+
const page = nonPlaceholderIntakeValue(row[3]);
|
|
450
|
+
const findingText = [
|
|
451
|
+
finding,
|
|
452
|
+
evidence ? `Evidence: ${evidence}` : "",
|
|
453
|
+
page ? `Page: ${page}` : "",
|
|
454
|
+
].filter(Boolean).join(" | ");
|
|
455
|
+
|
|
456
|
+
const current = auditChecklist[categoryId] || { status: "todo", notes: "", findings: [] };
|
|
457
|
+
auditChecklist[categoryId] = {
|
|
458
|
+
...current,
|
|
459
|
+
status: current.status === "done" || current.status === "blocked" ? current.status : "in-progress",
|
|
460
|
+
findings: uniqueNormalizedStrings([...current.findings, findingText]),
|
|
461
|
+
};
|
|
462
|
+
if (page) {
|
|
463
|
+
pages.add(page);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return { auditChecklist, pages: Array.from(pages) };
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export function createSiteWorkspaceFromIntakeMarkdown(markdown, { filePath = "company-website-intake.md" } = {}) {
|
|
470
|
+
const profile = parseSiteProfileFromIntake(markdown);
|
|
471
|
+
if (!profile.name) {
|
|
472
|
+
throw new Error(`Intake template ${filePath} requires Site name`);
|
|
473
|
+
}
|
|
474
|
+
if (!profile.liveUrl) {
|
|
475
|
+
throw new Error(`Intake template ${filePath} requires Live URL`);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const baseWorkspace = createSiteWorkspaceFromInitOptions(profile);
|
|
479
|
+
const { auditChecklist, pages } = applyAuditFindingsFromIntake(baseWorkspace, markdown);
|
|
480
|
+
const workspace = {
|
|
481
|
+
...baseWorkspace,
|
|
482
|
+
siteProfile: {
|
|
483
|
+
...baseWorkspace.siteProfile,
|
|
484
|
+
pages: uniqueNormalizedStrings(pages, ["/"]),
|
|
485
|
+
},
|
|
486
|
+
auditChecklist,
|
|
487
|
+
mcpReadiness: applyMcpReadinessFromIntake(baseWorkspace, markdown),
|
|
488
|
+
reportNotes: `Generated by \`design-ai site --from-intake ${filePath}\` from a local company website intake Markdown file. Actual target repo code changes happen outside this design-ai repository.`,
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
return normalizeSiteWorkspace(workspace);
|
|
492
|
+
}
|