@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,297 @@
|
|
|
1
|
+
// Website Improvement workspace validation and summary helpers.
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
import { normalizeStringArray } from "./site-strings.mjs";
|
|
7
|
+
import {
|
|
8
|
+
AUDIT_CATEGORIES,
|
|
9
|
+
CHECKLIST_STATUS_OPTIONS,
|
|
10
|
+
CMS_OPTIONS,
|
|
11
|
+
DATABASE_OPTIONS,
|
|
12
|
+
DEPLOY_OPTIONS,
|
|
13
|
+
EFFORT_OPTIONS,
|
|
14
|
+
IMPACT_OPTIONS,
|
|
15
|
+
MCP_ITEMS,
|
|
16
|
+
MCP_STATUS_OPTIONS,
|
|
17
|
+
PRIORITY_OPTIONS,
|
|
18
|
+
VIEWPORT_OPTIONS,
|
|
19
|
+
} from "./site-options.mjs";
|
|
20
|
+
import {
|
|
21
|
+
IMPLEMENTATION_EVIDENCE_KEYS,
|
|
22
|
+
normalizeImplementationEvidence,
|
|
23
|
+
} from "./site-evidence.mjs";
|
|
24
|
+
import {
|
|
25
|
+
normalizeObject,
|
|
26
|
+
normalizeSiteWorkspace,
|
|
27
|
+
} from "./site-workspace.mjs";
|
|
28
|
+
|
|
29
|
+
export function addIssue(issues, level, id, message) {
|
|
30
|
+
issues.push({ level, id, message });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function assertEnumIssue(issues, value, allowed, id, label) {
|
|
34
|
+
if (!allowed.includes(value)) {
|
|
35
|
+
addIssue(issues, "fail", id, `${label} must be one of: ${allowed.join(", ")}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function validateRawWorkspace(raw) {
|
|
40
|
+
const issues = [];
|
|
41
|
+
const root = normalizeObject(raw);
|
|
42
|
+
const profile = normalizeObject(root.siteProfile);
|
|
43
|
+
const checklist = normalizeObject(root.auditChecklist);
|
|
44
|
+
const mcpReadiness = normalizeObject(root.mcpReadiness);
|
|
45
|
+
|
|
46
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
47
|
+
addIssue(issues, "fail", "workspace-object", "Workspace JSON must be an object");
|
|
48
|
+
return issues;
|
|
49
|
+
}
|
|
50
|
+
if (root.version !== 1) {
|
|
51
|
+
addIssue(issues, "fail", "workspace-version", "Workspace version must be 1");
|
|
52
|
+
}
|
|
53
|
+
if (!root.siteProfile || typeof root.siteProfile !== "object" || Array.isArray(root.siteProfile)) {
|
|
54
|
+
addIssue(issues, "fail", "site-profile", "siteProfile object is required");
|
|
55
|
+
}
|
|
56
|
+
if (!root.auditChecklist || typeof root.auditChecklist !== "object" || Array.isArray(root.auditChecklist)) {
|
|
57
|
+
addIssue(issues, "fail", "audit-checklist", "auditChecklist object is required");
|
|
58
|
+
}
|
|
59
|
+
if (!root.mcpReadiness || typeof root.mcpReadiness !== "object" || Array.isArray(root.mcpReadiness)) {
|
|
60
|
+
addIssue(issues, "fail", "mcp-readiness", "mcpReadiness object is required");
|
|
61
|
+
}
|
|
62
|
+
if (!Array.isArray(root.refactorTasks)) {
|
|
63
|
+
addIssue(issues, "fail", "refactor-tasks", "refactorTasks array is required");
|
|
64
|
+
}
|
|
65
|
+
if (root.implementationEvidence !== undefined) {
|
|
66
|
+
const evidence = normalizeObject(root.implementationEvidence);
|
|
67
|
+
if (root.implementationEvidence === null || typeof root.implementationEvidence !== "object" || Array.isArray(root.implementationEvidence)) {
|
|
68
|
+
addIssue(issues, "fail", "implementation-evidence", "implementationEvidence must be an object when provided");
|
|
69
|
+
} else {
|
|
70
|
+
for (const key of IMPLEMENTATION_EVIDENCE_KEYS) {
|
|
71
|
+
if (evidence[key] !== undefined && !Array.isArray(evidence[key])) {
|
|
72
|
+
addIssue(issues, "fail", `implementation-evidence-${key}`, `implementationEvidence.${key} must be an array`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!String(profile.name || "").trim()) {
|
|
79
|
+
addIssue(issues, "fail", "site-name", "siteProfile.name is required");
|
|
80
|
+
}
|
|
81
|
+
if (!String(profile.liveUrl || "").trim()) {
|
|
82
|
+
addIssue(issues, "fail", "site-live-url", "siteProfile.liveUrl is required");
|
|
83
|
+
}
|
|
84
|
+
if (!Array.isArray(profile.pages) || normalizeStringArray(profile.pages).length === 0) {
|
|
85
|
+
addIssue(issues, "warn", "site-pages", "siteProfile.pages should include at least one priority page");
|
|
86
|
+
}
|
|
87
|
+
if (!Array.isArray(profile.userFlows) || normalizeStringArray(profile.userFlows).length === 0) {
|
|
88
|
+
addIssue(issues, "warn", "site-user-flows", "siteProfile.userFlows should include at least one primary user flow");
|
|
89
|
+
}
|
|
90
|
+
if (!Array.isArray(profile.viewports) || normalizeStringArray(profile.viewports).length === 0) {
|
|
91
|
+
addIssue(issues, "warn", "site-viewports", "siteProfile.viewports should include desktop, tablet, or mobile");
|
|
92
|
+
} else {
|
|
93
|
+
for (const viewport of normalizeStringArray(profile.viewports)) {
|
|
94
|
+
assertEnumIssue(issues, viewport, VIEWPORT_OPTIONS, "site-viewport-value", `Viewport '${viewport}'`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (!String(profile.repoUrl || "").trim() && !String(profile.localPath || "").trim()) {
|
|
98
|
+
addIssue(issues, "warn", "site-repo-location", "Provide siteProfile.repoUrl or siteProfile.localPath before Codex implementation handoff");
|
|
99
|
+
}
|
|
100
|
+
if (profile.deployProvider !== undefined) {
|
|
101
|
+
assertEnumIssue(issues, profile.deployProvider, DEPLOY_OPTIONS, "deploy-provider", "siteProfile.deployProvider");
|
|
102
|
+
}
|
|
103
|
+
if (profile.cms !== undefined) {
|
|
104
|
+
assertEnumIssue(issues, profile.cms, CMS_OPTIONS, "cms", "siteProfile.cms");
|
|
105
|
+
}
|
|
106
|
+
if (profile.database !== undefined) {
|
|
107
|
+
assertEnumIssue(issues, profile.database, DATABASE_OPTIONS, "database", "siteProfile.database");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const category of AUDIT_CATEGORIES) {
|
|
111
|
+
const row = normalizeObject(checklist[category.id]);
|
|
112
|
+
if (!checklist[category.id]) {
|
|
113
|
+
addIssue(issues, "warn", `audit-${category.id}`, `${category.label} audit row is missing`);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
assertEnumIssue(issues, row.status, CHECKLIST_STATUS_OPTIONS, `audit-${category.id}-status`, `${category.label} status`);
|
|
117
|
+
if (row.findings !== undefined && !Array.isArray(row.findings)) {
|
|
118
|
+
addIssue(issues, "fail", `audit-${category.id}-findings`, `${category.label} findings must be an array`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
for (const [key, label] of MCP_ITEMS) {
|
|
123
|
+
if (mcpReadiness[key] === undefined) {
|
|
124
|
+
addIssue(issues, "warn", `mcp-${key}`, `${label} MCP readiness status is missing`);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
assertEnumIssue(issues, mcpReadiness[key], MCP_STATUS_OPTIONS, `mcp-${key}-status`, `${label} MCP status`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (Array.isArray(root.refactorTasks)) {
|
|
131
|
+
for (const [index, task] of root.refactorTasks.entries()) {
|
|
132
|
+
const item = normalizeObject(task);
|
|
133
|
+
const label = item.title ? `Task '${item.title}'` : `Task ${index + 1}`;
|
|
134
|
+
if (!String(item.title || "").trim()) {
|
|
135
|
+
addIssue(issues, "warn", `task-${index + 1}-title`, `${label} should include a title`);
|
|
136
|
+
}
|
|
137
|
+
if (!String(item.problem || "").trim()) {
|
|
138
|
+
addIssue(issues, "warn", `task-${index + 1}-problem`, `${label} should describe the problem`);
|
|
139
|
+
}
|
|
140
|
+
assertEnumIssue(
|
|
141
|
+
issues,
|
|
142
|
+
item.category,
|
|
143
|
+
AUDIT_CATEGORIES.map((category) => category.id),
|
|
144
|
+
`task-${index + 1}-category`,
|
|
145
|
+
`${label} category`,
|
|
146
|
+
);
|
|
147
|
+
assertEnumIssue(issues, item.impact, IMPACT_OPTIONS, `task-${index + 1}-impact`, `${label} impact`);
|
|
148
|
+
assertEnumIssue(issues, item.effort, EFFORT_OPTIONS, `task-${index + 1}-effort`, `${label} effort`);
|
|
149
|
+
assertEnumIssue(issues, item.priority, PRIORITY_OPTIONS, `task-${index + 1}-priority`, `${label} priority`);
|
|
150
|
+
if (!String(item.codexPrompt || "").trim()) {
|
|
151
|
+
addIssue(issues, "warn", `task-${index + 1}-codex-prompt`, `${label} should include a Codex implementation prompt`);
|
|
152
|
+
}
|
|
153
|
+
if (!Array.isArray(item.verification) || normalizeStringArray(item.verification).length === 0) {
|
|
154
|
+
addIssue(issues, "warn", `task-${index + 1}-verification`, `${label} should include verification steps`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return issues;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function countBy(items, keyFn, allowed = []) {
|
|
163
|
+
const counts = Object.fromEntries(allowed.map((item) => [item, 0]));
|
|
164
|
+
for (const item of items) {
|
|
165
|
+
const key = keyFn(item);
|
|
166
|
+
counts[key] = (counts[key] || 0) + 1;
|
|
167
|
+
}
|
|
168
|
+
return counts;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function statusFromIssues(issues) {
|
|
172
|
+
if (issues.some((issue) => issue.level === "fail")) return "fail";
|
|
173
|
+
if (issues.some((issue) => issue.level === "warn")) return "warn";
|
|
174
|
+
return "pass";
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function summarizeWorkspace(workspace, issues, filePath) {
|
|
178
|
+
const auditRows = AUDIT_CATEGORIES.map((category) => ({
|
|
179
|
+
category,
|
|
180
|
+
row: workspace.auditChecklist[category.id],
|
|
181
|
+
}));
|
|
182
|
+
const mcpRows = MCP_ITEMS.map(([key, label]) => ({
|
|
183
|
+
key,
|
|
184
|
+
label,
|
|
185
|
+
status: workspace.mcpReadiness[key],
|
|
186
|
+
}));
|
|
187
|
+
const totalFindings = auditRows.reduce((sum, item) => sum + item.row.findings.length, 0);
|
|
188
|
+
const requiredMcp = mcpRows.filter((item) => item.status === "required").map((item) => item.key);
|
|
189
|
+
const evidence = normalizeImplementationEvidence(workspace.implementationEvidence);
|
|
190
|
+
const topTasks = workspace.refactorTasks
|
|
191
|
+
.slice()
|
|
192
|
+
.sort((a, b) => PRIORITY_OPTIONS.indexOf(a.priority) - PRIORITY_OPTIONS.indexOf(b.priority))
|
|
193
|
+
.slice(0, 5)
|
|
194
|
+
.map((task) => ({
|
|
195
|
+
id: task.id,
|
|
196
|
+
title: task.title,
|
|
197
|
+
priority: task.priority,
|
|
198
|
+
category: task.category,
|
|
199
|
+
impact: task.impact,
|
|
200
|
+
effort: task.effort,
|
|
201
|
+
pages: task.pages,
|
|
202
|
+
}));
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
filePath,
|
|
206
|
+
valid: statusFromIssues(issues) !== "fail",
|
|
207
|
+
status: statusFromIssues(issues),
|
|
208
|
+
site: {
|
|
209
|
+
id: workspace.siteProfile.id,
|
|
210
|
+
name: workspace.siteProfile.name,
|
|
211
|
+
liveUrl: workspace.siteProfile.liveUrl,
|
|
212
|
+
repoUrl: workspace.siteProfile.repoUrl,
|
|
213
|
+
localPath: workspace.siteProfile.localPath,
|
|
214
|
+
deployProvider: workspace.siteProfile.deployProvider,
|
|
215
|
+
cms: workspace.siteProfile.cms,
|
|
216
|
+
database: workspace.siteProfile.database,
|
|
217
|
+
pages: workspace.siteProfile.pages,
|
|
218
|
+
userFlows: workspace.siteProfile.userFlows,
|
|
219
|
+
viewports: workspace.siteProfile.viewports,
|
|
220
|
+
},
|
|
221
|
+
counts: {
|
|
222
|
+
pages: workspace.siteProfile.pages.length,
|
|
223
|
+
userFlows: workspace.siteProfile.userFlows.length,
|
|
224
|
+
viewports: workspace.siteProfile.viewports.length,
|
|
225
|
+
auditCategories: AUDIT_CATEGORIES.length,
|
|
226
|
+
auditFindings: totalFindings,
|
|
227
|
+
refactorTasks: workspace.refactorTasks.length,
|
|
228
|
+
executedWork: evidence.executedWork.length,
|
|
229
|
+
verificationResults: evidence.verificationResults.length,
|
|
230
|
+
remainingRisks: evidence.remainingRisks.length,
|
|
231
|
+
nextActions: evidence.nextActions.length,
|
|
232
|
+
requiredMcp: requiredMcp.length,
|
|
233
|
+
optionalMcp: mcpRows.filter((item) => item.status === "optional").length,
|
|
234
|
+
unavailableMcp: mcpRows.filter((item) => item.status === "unavailable").length,
|
|
235
|
+
},
|
|
236
|
+
auditStatusCounts: countBy(auditRows, (item) => item.row.status, CHECKLIST_STATUS_OPTIONS),
|
|
237
|
+
mcpStatusCounts: countBy(mcpRows, (item) => item.status, MCP_STATUS_OPTIONS),
|
|
238
|
+
taskPriorityCounts: countBy(workspace.refactorTasks, (task) => task.priority, PRIORITY_OPTIONS),
|
|
239
|
+
requiredMcp,
|
|
240
|
+
topTasks,
|
|
241
|
+
issues,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function analyzeSiteWorkspace(raw, { filePath = "workspace.json" } = {}) {
|
|
246
|
+
const issues = validateRawWorkspace(raw);
|
|
247
|
+
const workspace = normalizeSiteWorkspace(raw);
|
|
248
|
+
const summary = summarizeWorkspace(workspace, issues, filePath);
|
|
249
|
+
|
|
250
|
+
if (summary.status === "pass") {
|
|
251
|
+
addIssue(summary.issues, "pass", "workspace-ready", "Workspace is ready for report and prompt generation");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
workspace,
|
|
256
|
+
summary: {
|
|
257
|
+
...summary,
|
|
258
|
+
status: statusFromIssues(summary.issues),
|
|
259
|
+
valid: statusFromIssues(summary.issues) !== "fail",
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function loadSiteWorkspaceInput({
|
|
265
|
+
target = "",
|
|
266
|
+
stdin = false,
|
|
267
|
+
cwd = process.cwd(),
|
|
268
|
+
readStdin = () => readFileSync(0, "utf8"),
|
|
269
|
+
} = {}) {
|
|
270
|
+
const filePath = stdin ? "stdin" : path.resolve(cwd, target);
|
|
271
|
+
const rawText = stdin ? String(readStdin()) : readFileSync(filePath, "utf8");
|
|
272
|
+
let parsed;
|
|
273
|
+
try {
|
|
274
|
+
parsed = JSON.parse(rawText);
|
|
275
|
+
} catch (error) {
|
|
276
|
+
throw new Error(`Invalid Website Improvement workspace JSON in ${filePath}: ${error.message}`);
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
filePath,
|
|
280
|
+
rawText,
|
|
281
|
+
raw: parsed,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function buildSiteReport({
|
|
286
|
+
target = "",
|
|
287
|
+
stdin = false,
|
|
288
|
+
cwd = process.cwd(),
|
|
289
|
+
readStdin,
|
|
290
|
+
} = {}) {
|
|
291
|
+
const input = loadSiteWorkspaceInput({ target, stdin, cwd, readStdin });
|
|
292
|
+
return analyzeSiteWorkspace(input.raw, { filePath: input.filePath });
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function formatSiteJson(report) {
|
|
296
|
+
return JSON.stringify(report, null, 2);
|
|
297
|
+
}
|