@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,157 @@
|
|
|
1
|
+
import { SITE_BUNDLE_CHECKSUM_FILES } from "./site-content.mjs";
|
|
2
|
+
import { IMPLEMENTATION_EVIDENCE_KEYS } from "./site-evidence.mjs";
|
|
3
|
+
import {
|
|
4
|
+
addIssue,
|
|
5
|
+
statusFromIssues,
|
|
6
|
+
} from "./site-analysis.mjs";
|
|
7
|
+
import { buildSiteBundleCheckReport } from "./site-bundle-check.mjs";
|
|
8
|
+
|
|
9
|
+
export function summarizeBundleForCompare(report) {
|
|
10
|
+
return {
|
|
11
|
+
directory: report.directory,
|
|
12
|
+
status: report.status,
|
|
13
|
+
valid: report.valid,
|
|
14
|
+
siteName: report.summary.siteName || "",
|
|
15
|
+
source: report.summary.source || "",
|
|
16
|
+
workspaceStatus: report.workspaceStatus || "unknown",
|
|
17
|
+
mcpStatus: report.mcpStatus || "unknown",
|
|
18
|
+
mcpProbeStatus: report.mcpProbeStatus || "unknown",
|
|
19
|
+
mcpProbeCounts: { ...report.mcpProbeCounts },
|
|
20
|
+
totalTasks: report.summary.totalTasks || 0,
|
|
21
|
+
implementationEvidence: { ...report.summary.implementationEvidence },
|
|
22
|
+
checksumAlgorithm: report.summary.checksumAlgorithm || "",
|
|
23
|
+
checksumBundleDigest: report.summary.checksumBundleDigest || "",
|
|
24
|
+
checksumFailures: report.counts.checksumFailures,
|
|
25
|
+
generatedFailures: report.counts.generatedFailures,
|
|
26
|
+
verifiedGeneratedFiles: report.counts.verifiedGeneratedFiles,
|
|
27
|
+
generatedDriftFiles: [...report.generatedContract.driftFiles],
|
|
28
|
+
issueCount: report.issues.length,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function buildBundleMetadataChanges(left, right) {
|
|
33
|
+
const pairs = [
|
|
34
|
+
["siteName", "Site name", left.summary.siteName || "", right.summary.siteName || ""],
|
|
35
|
+
["source", "Source", left.summary.source || "", right.summary.source || ""],
|
|
36
|
+
["workspaceStatus", "Workspace status", left.workspaceStatus || "unknown", right.workspaceStatus || "unknown"],
|
|
37
|
+
["mcpStatus", "MCP status", left.mcpStatus || "unknown", right.mcpStatus || "unknown"],
|
|
38
|
+
["mcpProbeStatus", "MCP probe status", left.mcpProbeStatus || "unknown", right.mcpProbeStatus || "unknown"],
|
|
39
|
+
...["count", "pass", "warn", "fail"].map((key) => [
|
|
40
|
+
`mcpProbeCounts.${key}`,
|
|
41
|
+
`MCP probe ${key}`,
|
|
42
|
+
String(left.summary.mcpProbeCounts[key] || 0),
|
|
43
|
+
String(right.summary.mcpProbeCounts[key] || 0),
|
|
44
|
+
]),
|
|
45
|
+
["totalTasks", "Task count", String(left.summary.totalTasks || 0), String(right.summary.totalTasks || 0)],
|
|
46
|
+
...IMPLEMENTATION_EVIDENCE_KEYS.map((key) => [
|
|
47
|
+
`implementationEvidence.${key}`,
|
|
48
|
+
`Evidence ${key}`,
|
|
49
|
+
String(left.summary.implementationEvidence[key] || 0),
|
|
50
|
+
String(right.summary.implementationEvidence[key] || 0),
|
|
51
|
+
]),
|
|
52
|
+
];
|
|
53
|
+
return pairs
|
|
54
|
+
.filter(([, , leftValue, rightValue]) => leftValue !== rightValue)
|
|
55
|
+
.map(([key, label, leftValue, rightValue]) => ({ key, label, leftValue, rightValue }));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function buildBundleFileChanges(left, right) {
|
|
59
|
+
return SITE_BUNDLE_CHECKSUM_FILES
|
|
60
|
+
.map((filePath) => ({
|
|
61
|
+
path: filePath,
|
|
62
|
+
leftChecksum: String(left.summary.checksumFiles[filePath] || ""),
|
|
63
|
+
rightChecksum: String(right.summary.checksumFiles[filePath] || ""),
|
|
64
|
+
}))
|
|
65
|
+
.filter((file) => file.leftChecksum !== file.rightChecksum);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function buildSiteBundleCompareReport({ target, compareTarget }) {
|
|
69
|
+
const left = buildSiteBundleCheckReport({ target });
|
|
70
|
+
const right = buildSiteBundleCheckReport({ target: compareTarget });
|
|
71
|
+
const issues = [];
|
|
72
|
+
|
|
73
|
+
if (left.status === "fail") {
|
|
74
|
+
addIssue(issues, "fail", "bundle-compare-left-invalid", "Primary bundle must pass bundle-check before comparison can be trusted");
|
|
75
|
+
} else if (left.status === "warn") {
|
|
76
|
+
addIssue(issues, "warn", "bundle-compare-left-warn", "Primary bundle has bundle-check warnings; review them before target-repo handoff");
|
|
77
|
+
}
|
|
78
|
+
if (right.status === "fail") {
|
|
79
|
+
addIssue(issues, "fail", "bundle-compare-right-invalid", "Comparison bundle must pass bundle-check before comparison can be trusted");
|
|
80
|
+
} else if (right.status === "warn") {
|
|
81
|
+
addIssue(issues, "warn", "bundle-compare-right-warn", "Comparison bundle has bundle-check warnings; review them before target-repo handoff");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const leftDigest = left.summary.checksumBundleDigest || "";
|
|
85
|
+
const rightDigest = right.summary.checksumBundleDigest || "";
|
|
86
|
+
const digestMatch = Boolean(leftDigest && rightDigest && leftDigest === rightDigest);
|
|
87
|
+
const changedFiles = buildBundleFileChanges(left, right);
|
|
88
|
+
const metadataChanges = buildBundleMetadataChanges(left, right);
|
|
89
|
+
const hasDifferences = !digestMatch || changedFiles.length > 0 || metadataChanges.length > 0;
|
|
90
|
+
const hasFailures = issues.some((issue) => issue.level === "fail");
|
|
91
|
+
|
|
92
|
+
if (issues.length === 0 && !hasDifferences) {
|
|
93
|
+
addIssue(issues, "pass", "bundle-compare-identical", "Handoff bundles have the same bundle digest and checksum manifest");
|
|
94
|
+
} else if (!hasFailures && hasDifferences) {
|
|
95
|
+
addIssue(issues, "warn", "bundle-compare-different", "Handoff bundles differ; review changed files before target-repo handoff");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const status = statusFromIssues(issues);
|
|
99
|
+
return {
|
|
100
|
+
status,
|
|
101
|
+
valid: left.valid && right.valid,
|
|
102
|
+
sameBundle: !hasDifferences,
|
|
103
|
+
digestMatch,
|
|
104
|
+
left: summarizeBundleForCompare(left),
|
|
105
|
+
right: summarizeBundleForCompare(right),
|
|
106
|
+
counts: {
|
|
107
|
+
changedFiles: changedFiles.length,
|
|
108
|
+
metadataChanges: metadataChanges.length,
|
|
109
|
+
leftIssues: left.issues.length,
|
|
110
|
+
rightIssues: right.issues.length,
|
|
111
|
+
issues: issues.length,
|
|
112
|
+
warnings: issues.filter((issue) => issue.level === "warn").length,
|
|
113
|
+
failures: issues.filter((issue) => issue.level === "fail").length,
|
|
114
|
+
},
|
|
115
|
+
changedFiles,
|
|
116
|
+
metadataChanges,
|
|
117
|
+
issues,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function formatSiteBundleCompareJson(report) {
|
|
122
|
+
return JSON.stringify(report, null, 2);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function formatMcpProbeCounts(counts = {}) {
|
|
126
|
+
return `${counts.pass || 0}/${counts.count || 0} passing, ${counts.warn || 0} warning, ${counts.fail || 0} failing`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function formatSiteBundleCompareHuman(report) {
|
|
130
|
+
return [
|
|
131
|
+
`Website Improvement handoff bundle compare: ${report.left.directory} -> ${report.right.directory}`,
|
|
132
|
+
"",
|
|
133
|
+
`Status: ${report.status}`,
|
|
134
|
+
`Same bundle: ${report.sameBundle ? "yes" : "no"}`,
|
|
135
|
+
`Digest match: ${report.digestMatch ? "yes" : "no"}`,
|
|
136
|
+
`Left digest: ${report.left.checksumBundleDigest || "not recorded"}`,
|
|
137
|
+
`Right digest: ${report.right.checksumBundleDigest || "not recorded"}`,
|
|
138
|
+
`Changed files: ${report.counts.changedFiles}`,
|
|
139
|
+
`Metadata changes: ${report.counts.metadataChanges}`,
|
|
140
|
+
`Generated contract: left ${report.left.verifiedGeneratedFiles}/${SITE_BUNDLE_CHECKSUM_FILES.length}, right ${report.right.verifiedGeneratedFiles}/${SITE_BUNDLE_CHECKSUM_FILES.length}`,
|
|
141
|
+
`Generated drift files: left ${report.left.generatedDriftFiles.length ? report.left.generatedDriftFiles.join(", ") : "none"}, right ${report.right.generatedDriftFiles.length ? report.right.generatedDriftFiles.join(", ") : "none"}`,
|
|
142
|
+
`MCP probes: left ${formatMcpProbeCounts(report.left.mcpProbeCounts)}, right ${formatMcpProbeCounts(report.right.mcpProbeCounts)}`,
|
|
143
|
+
"",
|
|
144
|
+
"Changed files:",
|
|
145
|
+
...(report.changedFiles.length
|
|
146
|
+
? report.changedFiles.map((file) => `- ${file.path}`)
|
|
147
|
+
: ["- none"]),
|
|
148
|
+
"",
|
|
149
|
+
"Metadata changes:",
|
|
150
|
+
...(report.metadataChanges.length
|
|
151
|
+
? report.metadataChanges.map((item) => `- ${item.label}: ${item.leftValue || "not recorded"} -> ${item.rightValue || "not recorded"}`)
|
|
152
|
+
: ["- none"]),
|
|
153
|
+
"",
|
|
154
|
+
"Issues:",
|
|
155
|
+
...report.issues.map((issue) => `- [${issue.level}] ${issue.id}: ${issue.message}`),
|
|
156
|
+
].join("\n");
|
|
157
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Generated bundle contract helpers for Website Improvement handoff bundles.
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
existsSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
statSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
|
|
10
|
+
import { addIssue } from "./site-analysis.mjs";
|
|
11
|
+
import { buildSiteHandoffBundle } from "./site-bundle-build.mjs";
|
|
12
|
+
import { SITE_BUNDLE_CHECKSUM_FILES } from "./site-content.mjs";
|
|
13
|
+
import {
|
|
14
|
+
sha256Hex,
|
|
15
|
+
shortDigest,
|
|
16
|
+
} from "./site-bundle-files.mjs";
|
|
17
|
+
|
|
18
|
+
export function emptyBundleGeneratedContract(source = "") {
|
|
19
|
+
return {
|
|
20
|
+
available: false,
|
|
21
|
+
source: source || "",
|
|
22
|
+
expectedFiles: SITE_BUNDLE_CHECKSUM_FILES.length,
|
|
23
|
+
verifiedFiles: 0,
|
|
24
|
+
driftFiles: [],
|
|
25
|
+
files: [],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function buildBundleGeneratedContract(directory, workspace, source) {
|
|
30
|
+
const contractSource = source || "website-workspace.tasks.json";
|
|
31
|
+
const expectedBundle = buildSiteHandoffBundle(workspace, { filePath: contractSource });
|
|
32
|
+
const expectedFiles = new Map(expectedBundle.files.map((file) => [file.path, file.content]));
|
|
33
|
+
const files = SITE_BUNDLE_CHECKSUM_FILES.map((filePath) => {
|
|
34
|
+
const expectedContent = expectedFiles.get(filePath);
|
|
35
|
+
const expectedDigest = typeof expectedContent === "string" ? sha256Hex(expectedContent) : "";
|
|
36
|
+
const targetPath = path.join(directory, filePath);
|
|
37
|
+
const present = existsSync(targetPath) && statSync(targetPath).isFile();
|
|
38
|
+
const actualDigest = present ? sha256Hex(readFileSync(targetPath, "utf8")) : "";
|
|
39
|
+
return {
|
|
40
|
+
path: filePath,
|
|
41
|
+
present,
|
|
42
|
+
matches: Boolean(present && expectedDigest && actualDigest === expectedDigest),
|
|
43
|
+
expectedDigest,
|
|
44
|
+
actualDigest,
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
available: true,
|
|
49
|
+
source: contractSource,
|
|
50
|
+
expectedFiles: SITE_BUNDLE_CHECKSUM_FILES.length,
|
|
51
|
+
verifiedFiles: files.filter((file) => file.matches).length,
|
|
52
|
+
driftFiles: files.filter((file) => file.present && !file.matches).map((file) => file.path),
|
|
53
|
+
files,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function addBundleGeneratedContractIssues(generatedContract, issues) {
|
|
58
|
+
if (!generatedContract.available) return;
|
|
59
|
+
for (const file of generatedContract.files) {
|
|
60
|
+
if (!file.present || file.matches) continue;
|
|
61
|
+
addIssue(
|
|
62
|
+
issues,
|
|
63
|
+
"fail",
|
|
64
|
+
`bundle-generated-${file.path}`,
|
|
65
|
+
`${file.path} does not match the current CLI-generated bundle contract (expected ${shortDigest(file.expectedDigest)}, actual ${shortDigest(file.actualDigest)})`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function formatGeneratedContractDriftLines(generatedContract) {
|
|
71
|
+
const driftFiles = generatedContract.files.filter((file) => file.present && !file.matches);
|
|
72
|
+
if (driftFiles.length === 0) return ["- none"];
|
|
73
|
+
return driftFiles.map((file) => `- ${file.path}: expected ${shortDigest(file.expectedDigest)}, actual ${shortDigest(file.actualDigest)}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function formatGeneratedContractDriftSummary(generatedContract) {
|
|
77
|
+
if (!generatedContract.driftFiles.length) return "none";
|
|
78
|
+
return generatedContract.driftFiles.join(", ");
|
|
79
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// File, digest, and validation helpers for Website Improvement handoff bundles.
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
existsSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
statSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
|
|
11
|
+
import { SITE_BUNDLE_CHECKSUM_FILES } from "./site-content.mjs";
|
|
12
|
+
|
|
13
|
+
function addIssue(issues, level, id, message) {
|
|
14
|
+
issues.push({ level, id, message });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function sha256Hex(content) {
|
|
18
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function shortDigest(digest) {
|
|
22
|
+
return digest ? String(digest).slice(0, 12) : "missing";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function buildBundleDigest(checksumFiles) {
|
|
26
|
+
const manifest = SITE_BUNDLE_CHECKSUM_FILES.map((filePath) => `${filePath}\t${checksumFiles[filePath] || ""}`).join("\n");
|
|
27
|
+
return sha256Hex(`${manifest}\n`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function buildBundleChecksums(files) {
|
|
31
|
+
const checksumFiles = Object.fromEntries(
|
|
32
|
+
files
|
|
33
|
+
.filter((file) => file.path !== "summary.json")
|
|
34
|
+
.map((file) => [file.path, sha256Hex(file.content)]),
|
|
35
|
+
);
|
|
36
|
+
return {
|
|
37
|
+
algorithm: "sha256",
|
|
38
|
+
bundleDigest: buildBundleDigest(checksumFiles),
|
|
39
|
+
files: checksumFiles,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function readBundleFile(directory, relativePath, issues) {
|
|
44
|
+
const target = path.join(directory, relativePath);
|
|
45
|
+
if (!existsSync(target)) {
|
|
46
|
+
addIssue(issues, "fail", `bundle-missing-${relativePath}`, `Bundle file is missing: ${relativePath}`);
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (!statSync(target).isFile()) {
|
|
50
|
+
addIssue(issues, "fail", `bundle-file-${relativePath}`, `Bundle path must be a file: ${relativePath}`);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return readFileSync(target, "utf8");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function parseBundleJson(directory, relativePath, issues) {
|
|
57
|
+
const text = readBundleFile(directory, relativePath, issues);
|
|
58
|
+
if (text === null) return null;
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(text);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
addIssue(issues, "fail", `bundle-json-${relativePath}`, `Bundle JSON is invalid in ${relativePath}: ${error.message}`);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function arraysEqual(left, right) {
|
|
68
|
+
if (!Array.isArray(left) || !Array.isArray(right)) return false;
|
|
69
|
+
if (left.length !== right.length) return false;
|
|
70
|
+
return left.every((item, index) => item === right[index]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function addBundleMarkdownIssue(directory, relativePath, fragments, issues) {
|
|
74
|
+
const text = readBundleFile(directory, relativePath, issues);
|
|
75
|
+
if (text === null) return;
|
|
76
|
+
for (const fragment of fragments) {
|
|
77
|
+
if (!text.includes(fragment)) {
|
|
78
|
+
addIssue(issues, "fail", `bundle-markdown-${relativePath}`, `${relativePath} is missing required text: ${fragment}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function readBundleTextIfPresent(directory, relativePath) {
|
|
84
|
+
const targetPath = path.join(directory, relativePath);
|
|
85
|
+
if (!existsSync(targetPath) || !statSync(targetPath).isFile()) return "";
|
|
86
|
+
return readFileSync(targetPath, "utf8").trim();
|
|
87
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Action metadata helpers for Website Improvement bundle handoff runbooks.
|
|
2
|
+
|
|
3
|
+
export function getStageActionType(stage) {
|
|
4
|
+
if (stage.commandCount > 0 && stage.writesLocalFile) return "write-local-output";
|
|
5
|
+
if (stage.commandCount > 0 && stage.required) return "run-local-gate";
|
|
6
|
+
if (stage.commandCount > 0) return "refresh-local-preview";
|
|
7
|
+
if (stage.kind === "manual-target-repo") return "manual-target-repo";
|
|
8
|
+
if (stage.kind === "manual-reporting") return "manual-evidence";
|
|
9
|
+
return "review-stage";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getStageActionLabel(stage) {
|
|
13
|
+
return {
|
|
14
|
+
verifySourceBundle: "Run strict bundle check",
|
|
15
|
+
refreshHandoffSnapshot: "Refresh strict handoff JSON",
|
|
16
|
+
writeEffectiveTaskPrompt: "Write selected task prompt",
|
|
17
|
+
executeInTargetRepo: "Implement in target repo",
|
|
18
|
+
recordEvidence: "Record verification evidence",
|
|
19
|
+
}[stage.key] || stage.label;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getStageActionInstruction(stage) {
|
|
23
|
+
return {
|
|
24
|
+
verifySourceBundle: "Run the strict local bundle check and resolve any checksum or generated-file drift before handoff.",
|
|
25
|
+
refreshHandoffSnapshot: "Optional: regenerate the strict handoff JSON snapshot when a wrapper or GUI needs the latest contract.",
|
|
26
|
+
writeEffectiveTaskPrompt: "Write the selected task prompt to a local Markdown file before switching into the target website repo.",
|
|
27
|
+
executeInTargetRepo: "Manual: open the generated prompt in the target website repo, inspect architecture, implement the scoped task, and run target-repo verification.",
|
|
28
|
+
recordEvidence: "Manual: record changed files, verification commands, viewport checks, accessibility checks, remaining risks, and the bundle digest.",
|
|
29
|
+
}[stage.key] || stage.reason;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getStageActionButtonLabel(stage) {
|
|
33
|
+
return {
|
|
34
|
+
verifySourceBundle: "Run Check",
|
|
35
|
+
refreshHandoffSnapshot: "Refresh JSON",
|
|
36
|
+
writeEffectiveTaskPrompt: "Write Prompt",
|
|
37
|
+
executeInTargetRepo: "Open Target Repo",
|
|
38
|
+
recordEvidence: "Record Evidence",
|
|
39
|
+
}[stage.key] || getStageActionLabel(stage);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getStageActionAffordance(stage) {
|
|
43
|
+
if (stage.commandCount > 0 && stage.writesLocalFile) return "local-output-button";
|
|
44
|
+
if (stage.commandCount > 0 && stage.required) return "primary-command-button";
|
|
45
|
+
if (stage.commandCount > 0) return "secondary-command-button";
|
|
46
|
+
if (stage.kind === "manual-target-repo") return "manual-target-repo-step";
|
|
47
|
+
if (stage.kind === "manual-reporting") return "manual-evidence-step";
|
|
48
|
+
return "review-step";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getStageActionEnabled(stage) {
|
|
52
|
+
return stage.commandCount > 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getStageActionStatus(stage) {
|
|
56
|
+
if (stage.commandCount > 0 && stage.required) return "ready";
|
|
57
|
+
if (stage.commandCount > 0) return "optional";
|
|
58
|
+
if (stage.kind === "manual-target-repo" || stage.kind === "manual-reporting") return "manual";
|
|
59
|
+
return "blocked";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getStageActionStatusLabel(stage) {
|
|
63
|
+
return {
|
|
64
|
+
ready: "Ready",
|
|
65
|
+
optional: "Optional",
|
|
66
|
+
manual: "Manual",
|
|
67
|
+
blocked: "Blocked",
|
|
68
|
+
}[getStageActionStatus(stage)];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function getStageActionStatusTone(stage) {
|
|
72
|
+
return {
|
|
73
|
+
ready: "success",
|
|
74
|
+
optional: "neutral",
|
|
75
|
+
manual: "info",
|
|
76
|
+
blocked: "danger",
|
|
77
|
+
}[getStageActionStatus(stage)];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function getStageActionDisabledReasonCode(stage) {
|
|
81
|
+
if (getStageActionEnabled(stage)) return "";
|
|
82
|
+
if (stage.kind === "manual-target-repo") return "manual-target-repo-step";
|
|
83
|
+
if (stage.kind === "manual-reporting") return "manual-evidence-step";
|
|
84
|
+
return "missing-local-command";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function getStageActionDisabledReason(stage) {
|
|
88
|
+
return {
|
|
89
|
+
"manual-target-repo-step": "No local design-ai command is available for this stage; execute the generated prompt inside the target website repo.",
|
|
90
|
+
"manual-evidence-step": "No local design-ai command is available for this stage; record evidence after target-repo implementation and verification.",
|
|
91
|
+
"missing-local-command": "No local command is available for this stage.",
|
|
92
|
+
}[getStageActionDisabledReasonCode(stage)] || "";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function getStageActionEvidenceTarget(stage) {
|
|
96
|
+
return {
|
|
97
|
+
verifySourceBundle: "local-command-output",
|
|
98
|
+
refreshHandoffSnapshot: "local-command-output",
|
|
99
|
+
writeEffectiveTaskPrompt: "local-output-file",
|
|
100
|
+
executeInTargetRepo: "target-repo-working-tree",
|
|
101
|
+
recordEvidence: "handoff-evidence-record",
|
|
102
|
+
}[stage.key] || "not-applicable";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function getStageActionEvidenceTargetLabel(stage) {
|
|
106
|
+
return {
|
|
107
|
+
"local-command-output": "Local command output",
|
|
108
|
+
"local-output-file": "Local output file",
|
|
109
|
+
"target-repo-working-tree": "Target repo working tree",
|
|
110
|
+
"handoff-evidence-record": "Handoff evidence record",
|
|
111
|
+
"not-applicable": "Not applicable",
|
|
112
|
+
}[getStageActionEvidenceTarget(stage)];
|
|
113
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// Stage-specific evidence capture fields for Website Improvement bundle handoff runbooks.
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getStageActionEvidenceTarget,
|
|
5
|
+
} from "./site-bundle-handoff-runbook-actions.mjs";
|
|
6
|
+
import {
|
|
7
|
+
getEvidenceCaptureFieldAriaLabel,
|
|
8
|
+
getEvidenceCaptureFieldEmptyValue,
|
|
9
|
+
getEvidenceCaptureFieldHelpText,
|
|
10
|
+
getEvidenceCaptureFieldPayloadNamespace,
|
|
11
|
+
getEvidenceCaptureFieldPayloadPath,
|
|
12
|
+
getEvidenceCaptureFieldRequirementLabel,
|
|
13
|
+
getEvidenceCaptureFieldSectionKey,
|
|
14
|
+
getEvidenceCaptureFieldSectionLabel,
|
|
15
|
+
getEvidenceCaptureFieldValueShape,
|
|
16
|
+
} from "./site-bundle-handoff-runbook-evidence.mjs";
|
|
17
|
+
|
|
18
|
+
export function getStageActionEvidenceCaptureFields(stage) {
|
|
19
|
+
return ({
|
|
20
|
+
verifySourceBundle: [
|
|
21
|
+
{
|
|
22
|
+
key: "strictBundleCheckOutput",
|
|
23
|
+
label: "Strict bundle-check output",
|
|
24
|
+
inputType: "textarea",
|
|
25
|
+
required: true,
|
|
26
|
+
evidenceTarget: getStageActionEvidenceTarget(stage),
|
|
27
|
+
placeholder: "Paste the strict bundle-check pass output or JSON status.",
|
|
28
|
+
validationRule: "non-empty-text",
|
|
29
|
+
minLength: 20,
|
|
30
|
+
example: "Status: pass; checksumFailures: 0; generatedFailures: 0",
|
|
31
|
+
validationHint: "Required: paste a passing strict bundle-check result.",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
key: "bundleDigest",
|
|
35
|
+
label: "Bundle digest",
|
|
36
|
+
inputType: "text",
|
|
37
|
+
required: true,
|
|
38
|
+
evidenceTarget: getStageActionEvidenceTarget(stage),
|
|
39
|
+
placeholder: "Record the bundle digest or checksum summary.",
|
|
40
|
+
validationRule: "checksum-or-digest-text",
|
|
41
|
+
minLength: 8,
|
|
42
|
+
example: "7685113af4744990fadf301b220b4739066e5f6ec2c40857825211e1167241aa",
|
|
43
|
+
validationHint: "Required: record a digest, checksum, or equivalent bundle integrity summary.",
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
refreshHandoffSnapshot: [
|
|
47
|
+
{
|
|
48
|
+
key: "handoffJsonSnapshot",
|
|
49
|
+
label: "Strict handoff JSON snapshot",
|
|
50
|
+
inputType: "textarea",
|
|
51
|
+
required: false,
|
|
52
|
+
evidenceTarget: getStageActionEvidenceTarget(stage),
|
|
53
|
+
placeholder: "Paste or link the refreshed strict handoff JSON snapshot when used.",
|
|
54
|
+
validationRule: "optional-json-snapshot",
|
|
55
|
+
minLength: 0,
|
|
56
|
+
example: "{ \"status\": \"pass\", \"operatorRunbook\": { ... } }",
|
|
57
|
+
validationHint: "Optional: paste the refreshed strict handoff JSON snapshot when available.",
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
writeEffectiveTaskPrompt: [
|
|
61
|
+
{
|
|
62
|
+
key: "promptOutputFile",
|
|
63
|
+
label: "Prompt output file",
|
|
64
|
+
inputType: "file-path",
|
|
65
|
+
required: true,
|
|
66
|
+
evidenceTarget: getStageActionEvidenceTarget(stage),
|
|
67
|
+
placeholder: "target-repo-task-...-handoff.md",
|
|
68
|
+
validationRule: "local-markdown-file-path",
|
|
69
|
+
minLength: 12,
|
|
70
|
+
example: "target-repo-task-accessibility-handoff.md",
|
|
71
|
+
validationHint: "Required: record the local Markdown prompt file path generated for the selected task.",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
key: "selectedTaskId",
|
|
75
|
+
label: "Selected task id",
|
|
76
|
+
inputType: "text",
|
|
77
|
+
required: true,
|
|
78
|
+
evidenceTarget: getStageActionEvidenceTarget(stage),
|
|
79
|
+
placeholder: "task-...",
|
|
80
|
+
validationRule: "task-id",
|
|
81
|
+
minLength: 5,
|
|
82
|
+
example: "task-accessibility",
|
|
83
|
+
validationHint: "Required: record the bundle task id used for the target-repo handoff prompt.",
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
executeInTargetRepo: [
|
|
87
|
+
{
|
|
88
|
+
key: "targetRepoChangedFiles",
|
|
89
|
+
label: "Target repo changed files",
|
|
90
|
+
inputType: "list",
|
|
91
|
+
required: true,
|
|
92
|
+
evidenceTarget: getStageActionEvidenceTarget(stage),
|
|
93
|
+
placeholder: "List changed files from the target website repo.",
|
|
94
|
+
validationRule: "non-empty-file-list",
|
|
95
|
+
minLength: 1,
|
|
96
|
+
example: "src/components/Header.tsx",
|
|
97
|
+
validationHint: "Required: list at least one changed target-repo file or a no-change justification.",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
key: "targetRepoVerificationResults",
|
|
101
|
+
label: "Target repo verification results",
|
|
102
|
+
inputType: "textarea",
|
|
103
|
+
required: true,
|
|
104
|
+
evidenceTarget: getStageActionEvidenceTarget(stage),
|
|
105
|
+
placeholder: "Record lint, typecheck, build, test, or equivalent command results.",
|
|
106
|
+
validationRule: "verification-results",
|
|
107
|
+
minLength: 20,
|
|
108
|
+
example: "npm test: pass; npm run build: pass",
|
|
109
|
+
validationHint: "Required: record target-repo verification commands and results.",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
key: "viewportAccessibilityNotes",
|
|
113
|
+
label: "Viewport and accessibility notes",
|
|
114
|
+
inputType: "textarea",
|
|
115
|
+
required: true,
|
|
116
|
+
evidenceTarget: getStageActionEvidenceTarget(stage),
|
|
117
|
+
placeholder: "Record desktop/tablet/mobile checks, keyboard focus, contrast, and screen-reader notes.",
|
|
118
|
+
validationRule: "viewport-accessibility-notes",
|
|
119
|
+
minLength: 20,
|
|
120
|
+
example: "desktop/tablet/mobile checked; focus visible; contrast AA",
|
|
121
|
+
validationHint: "Required: document viewport coverage plus keyboard, contrast, and screen-reader notes.",
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
recordEvidence: [
|
|
125
|
+
{
|
|
126
|
+
key: "finalEvidenceRecord",
|
|
127
|
+
label: "Final evidence record",
|
|
128
|
+
inputType: "textarea",
|
|
129
|
+
required: true,
|
|
130
|
+
evidenceTarget: getStageActionEvidenceTarget(stage),
|
|
131
|
+
placeholder: "Summarize changed files, verification, viewport/accessibility checks, risks, and digest.",
|
|
132
|
+
validationRule: "final-evidence-record",
|
|
133
|
+
minLength: 30,
|
|
134
|
+
example: "Changed files recorded; verification passed; digest captured",
|
|
135
|
+
validationHint: "Required: summarize changes, verification, viewport/accessibility checks, risks, and digest.",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
key: "remainingRisks",
|
|
139
|
+
label: "Remaining risks",
|
|
140
|
+
inputType: "textarea",
|
|
141
|
+
required: true,
|
|
142
|
+
evidenceTarget: getStageActionEvidenceTarget(stage),
|
|
143
|
+
placeholder: "List unresolved risks, skipped checks, or follow-up tasks.",
|
|
144
|
+
validationRule: "risk-notes",
|
|
145
|
+
minLength: 10,
|
|
146
|
+
example: "Stakeholder copy review still pending",
|
|
147
|
+
validationHint: "Required: record unresolved risks, skipped checks, or confirm none remain.",
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
}[stage.key] || []).map((field) => ({
|
|
151
|
+
...field,
|
|
152
|
+
valueShape: getEvidenceCaptureFieldValueShape(field),
|
|
153
|
+
acceptsMultiple: field.inputType === "list",
|
|
154
|
+
defaultValue: getEvidenceCaptureFieldEmptyValue(field),
|
|
155
|
+
emptyValue: getEvidenceCaptureFieldEmptyValue(field),
|
|
156
|
+
requirementLabel: getEvidenceCaptureFieldRequirementLabel(field),
|
|
157
|
+
ariaLabel: getEvidenceCaptureFieldAriaLabel(field),
|
|
158
|
+
helpText: getEvidenceCaptureFieldHelpText(field),
|
|
159
|
+
sectionKey: getEvidenceCaptureFieldSectionKey(field),
|
|
160
|
+
sectionLabel: getEvidenceCaptureFieldSectionLabel(field),
|
|
161
|
+
payloadNamespace: getEvidenceCaptureFieldPayloadNamespace(field),
|
|
162
|
+
payloadPath: getEvidenceCaptureFieldPayloadPath(field),
|
|
163
|
+
}));
|
|
164
|
+
}
|