@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
|
@@ -55,6 +55,7 @@ from smoke_assertions import (
|
|
|
55
55
|
assert_check_examples_json_component_spec,
|
|
56
56
|
assert_check_stdin_json_component_spec,
|
|
57
57
|
assert_command_alias_output,
|
|
58
|
+
assert_design_ai_mcp_protocol_responses,
|
|
58
59
|
assert_examples_human_output,
|
|
59
60
|
assert_examples_json_route_hit,
|
|
60
61
|
assert_force_overwrite_replaced,
|
|
@@ -127,6 +128,7 @@ from smoke_assertions import (
|
|
|
127
128
|
format_cmd,
|
|
128
129
|
help_alias_script,
|
|
129
130
|
help_topic_script,
|
|
131
|
+
mcp_smoke_input,
|
|
130
132
|
parse_help_topics,
|
|
131
133
|
passing_doctor_report_json,
|
|
132
134
|
passing_check_artifact_content,
|
|
@@ -154,6 +156,104 @@ from smoke_assertions import (
|
|
|
154
156
|
ROOT = Path(__file__).resolve().parents[2]
|
|
155
157
|
PACKAGE = json.loads((ROOT / "package.json").read_text(encoding="utf-8"))
|
|
156
158
|
DEFAULT_PACKAGE_SPEC = f"{PACKAGE['name']}@{PACKAGE['version']}"
|
|
159
|
+
EXPECTED_SITE_MCP_PROBE_COUNTS = {"count": 4, "pass": 4, "warn": 0, "fail": 0}
|
|
160
|
+
EXPECTED_SITE_BUNDLE_MCP_PROBES_KEYS = [
|
|
161
|
+
"enabled",
|
|
162
|
+
"mode",
|
|
163
|
+
"externalCalls",
|
|
164
|
+
"status",
|
|
165
|
+
"count",
|
|
166
|
+
"pass",
|
|
167
|
+
"warn",
|
|
168
|
+
"fail",
|
|
169
|
+
"items",
|
|
170
|
+
]
|
|
171
|
+
EXPECTED_SITE_BUNDLE_MCP_PROBE_ITEM_KEYS = [
|
|
172
|
+
"id",
|
|
173
|
+
"key",
|
|
174
|
+
"label",
|
|
175
|
+
"requestedStatus",
|
|
176
|
+
"level",
|
|
177
|
+
"passed",
|
|
178
|
+
"message",
|
|
179
|
+
"evidence",
|
|
180
|
+
"actions",
|
|
181
|
+
]
|
|
182
|
+
EXPECTED_SITE_BUNDLE_MCP_PROBE_IDS = [
|
|
183
|
+
"github-repo-reference",
|
|
184
|
+
"figma-url-reference",
|
|
185
|
+
"browser-smoke-target",
|
|
186
|
+
"deploy-provider-reference",
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def assert_site_mcp_probe_counts(
|
|
191
|
+
actual: object,
|
|
192
|
+
*,
|
|
193
|
+
context: str,
|
|
194
|
+
label: str,
|
|
195
|
+
) -> None:
|
|
196
|
+
if actual != EXPECTED_SITE_MCP_PROBE_COUNTS:
|
|
197
|
+
raise SystemExit(f"{label} after {context} MCP probe counts changed: {actual!r}")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def assert_smoke_json_keys(
|
|
201
|
+
payload: object,
|
|
202
|
+
expected_keys: list[str],
|
|
203
|
+
*,
|
|
204
|
+
label: str,
|
|
205
|
+
context: str,
|
|
206
|
+
command_label: str,
|
|
207
|
+
) -> dict:
|
|
208
|
+
if not isinstance(payload, dict):
|
|
209
|
+
raise SystemExit(f"{command_label} after {context} {label} must be an object")
|
|
210
|
+
keys = list(payload.keys())
|
|
211
|
+
if keys != expected_keys:
|
|
212
|
+
raise SystemExit(f"{command_label} after {context} {label} keys changed: {keys!r}")
|
|
213
|
+
return payload
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def assert_site_bundle_mcp_probes_payload(payload: object, *, context: str) -> None:
|
|
217
|
+
checked = assert_smoke_json_keys(
|
|
218
|
+
payload,
|
|
219
|
+
EXPECTED_SITE_BUNDLE_MCP_PROBES_KEYS,
|
|
220
|
+
label="mcp-probes.json",
|
|
221
|
+
context=context,
|
|
222
|
+
command_label="site bundle",
|
|
223
|
+
)
|
|
224
|
+
if checked.get("enabled") is not True or checked.get("mode") != "read-only-local":
|
|
225
|
+
raise SystemExit(f"site bundle after {context} mcp-probes.json mode changed")
|
|
226
|
+
if checked.get("externalCalls") is not False:
|
|
227
|
+
raise SystemExit(f"site bundle after {context} mcp-probes.json must remain read-only")
|
|
228
|
+
if checked.get("status") != "pass":
|
|
229
|
+
raise SystemExit(f"site bundle after {context} mcp-probes.json status changed")
|
|
230
|
+
assert_site_mcp_probe_counts(
|
|
231
|
+
{key: checked.get(key) for key in ("count", "pass", "warn", "fail")},
|
|
232
|
+
context=context,
|
|
233
|
+
label="site bundle mcp-probes.json",
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
items = checked.get("items")
|
|
237
|
+
if not isinstance(items, list) or len(items) != len(EXPECTED_SITE_BUNDLE_MCP_PROBE_IDS):
|
|
238
|
+
raise SystemExit(f"site bundle after {context} mcp-probes.json item count changed")
|
|
239
|
+
checked_ids = []
|
|
240
|
+
for item in items:
|
|
241
|
+
checked_item = assert_smoke_json_keys(
|
|
242
|
+
item,
|
|
243
|
+
EXPECTED_SITE_BUNDLE_MCP_PROBE_ITEM_KEYS,
|
|
244
|
+
label="mcp-probes.json item",
|
|
245
|
+
context=context,
|
|
246
|
+
command_label="site bundle",
|
|
247
|
+
)
|
|
248
|
+
checked_ids.append(checked_item.get("id"))
|
|
249
|
+
if checked_item.get("level") != "pass" or checked_item.get("passed") is not True:
|
|
250
|
+
raise SystemExit(f"site bundle after {context} mcp-probes.json item should pass: {checked_item.get('id')}")
|
|
251
|
+
if not isinstance(checked_item.get("evidence"), list) or not checked_item.get("evidence"):
|
|
252
|
+
raise SystemExit(f"site bundle after {context} mcp-probes.json item evidence missing")
|
|
253
|
+
if not isinstance(checked_item.get("actions"), list):
|
|
254
|
+
raise SystemExit(f"site bundle after {context} mcp-probes.json item actions must be an array")
|
|
255
|
+
if checked_ids != EXPECTED_SITE_BUNDLE_MCP_PROBE_IDS:
|
|
256
|
+
raise SystemExit(f"site bundle after {context} mcp-probes.json item order changed")
|
|
157
257
|
|
|
158
258
|
|
|
159
259
|
def npm_exec_cmd(package_spec: str, *args: str) -> list[str]:
|
|
@@ -169,6 +269,18 @@ def npm_exec_cmd(package_spec: str, *args: str) -> list[str]:
|
|
|
169
269
|
]
|
|
170
270
|
|
|
171
271
|
|
|
272
|
+
def npm_exec_mcp_cmd(package_spec: str) -> list[str]:
|
|
273
|
+
return [
|
|
274
|
+
"npm",
|
|
275
|
+
"exec",
|
|
276
|
+
"--yes",
|
|
277
|
+
"--package",
|
|
278
|
+
package_spec,
|
|
279
|
+
"--",
|
|
280
|
+
"design-ai-mcp",
|
|
281
|
+
]
|
|
282
|
+
|
|
283
|
+
|
|
172
284
|
def npm_exec_shell_cmd(package_spec: str, script: str) -> list[str]:
|
|
173
285
|
return [
|
|
174
286
|
"npm",
|
|
@@ -2621,6 +2733,45 @@ def assert_version_json_smoke(cmd: list[str], *, env: dict[str, str], cwd: Path
|
|
|
2621
2733
|
assert_version_json(result.stdout, context=context, cmd=cmd)
|
|
2622
2734
|
|
|
2623
2735
|
|
|
2736
|
+
def assert_design_ai_mcp_protocol_smoke(
|
|
2737
|
+
cmd: list[str],
|
|
2738
|
+
*,
|
|
2739
|
+
env: dict[str, str],
|
|
2740
|
+
cwd: Path | None,
|
|
2741
|
+
context: str,
|
|
2742
|
+
) -> None:
|
|
2743
|
+
print(f"$ {format_cmd(cmd)} < MCP smoke", flush=True)
|
|
2744
|
+
result = subprocess.run(
|
|
2745
|
+
cmd,
|
|
2746
|
+
cwd=cwd,
|
|
2747
|
+
env=env,
|
|
2748
|
+
input=mcp_smoke_input(),
|
|
2749
|
+
text=True,
|
|
2750
|
+
capture_output=True,
|
|
2751
|
+
timeout=15,
|
|
2752
|
+
)
|
|
2753
|
+
if result.stdout:
|
|
2754
|
+
print(result.stdout, end="")
|
|
2755
|
+
if result.stderr:
|
|
2756
|
+
print(result.stderr, end="", file=sys.stderr)
|
|
2757
|
+
|
|
2758
|
+
if result.returncode != 0:
|
|
2759
|
+
raise SystemExit(f"command failed with exit code {result.returncode}: {format_cmd(cmd)}")
|
|
2760
|
+
|
|
2761
|
+
output = f"{result.stdout}\n{result.stderr}"
|
|
2762
|
+
assert_no_ansi(output, cmd)
|
|
2763
|
+
|
|
2764
|
+
responses = []
|
|
2765
|
+
for line in result.stdout.splitlines():
|
|
2766
|
+
if not line.strip():
|
|
2767
|
+
continue
|
|
2768
|
+
try:
|
|
2769
|
+
responses.append(json.loads(line))
|
|
2770
|
+
except json.JSONDecodeError as error:
|
|
2771
|
+
raise SystemExit(f"{context}: MCP stdout line is not JSON: {error}") from error
|
|
2772
|
+
assert_design_ai_mcp_protocol_responses(responses, context=context, cmd=cmd)
|
|
2773
|
+
|
|
2774
|
+
|
|
2624
2775
|
def assert_workspace_json_smoke(cmd: list[str], *, env: dict[str, str], cwd: Path | None = None, context: str) -> None:
|
|
2625
2776
|
result = run_plain(cmd, cwd=cwd, env=env)
|
|
2626
2777
|
assert_workspace_json(result.stdout, context=context, cmd=cmd)
|
|
@@ -3031,6 +3182,7 @@ def assert_site_bundle_smoke(
|
|
|
3031
3182
|
"summary.json",
|
|
3032
3183
|
"website-workspace.tasks.json",
|
|
3033
3184
|
"mcp-check.json",
|
|
3185
|
+
"mcp-probes.json",
|
|
3034
3186
|
"mcp-action-plan.md",
|
|
3035
3187
|
"website-handoff.md",
|
|
3036
3188
|
"website-prompts.md",
|
|
@@ -3046,8 +3198,26 @@ def assert_site_bundle_smoke(
|
|
|
3046
3198
|
raise SystemExit(f"site bundle after {context} summary status changed: {summary.get('status')!r}")
|
|
3047
3199
|
if summary.get("taskGeneration", {}).get("totalTasks") != 3:
|
|
3048
3200
|
raise SystemExit(f"site bundle after {context} expected 3 generated/retained tasks")
|
|
3201
|
+
evidence_counts = summary.get("implementationEvidence")
|
|
3202
|
+
if (
|
|
3203
|
+
not isinstance(evidence_counts, dict)
|
|
3204
|
+
or evidence_counts.get("executedWork") != 0
|
|
3205
|
+
or evidence_counts.get("verificationResults") != 0
|
|
3206
|
+
or evidence_counts.get("remainingRisks") != 3
|
|
3207
|
+
or evidence_counts.get("nextActions") != 0
|
|
3208
|
+
):
|
|
3209
|
+
raise SystemExit(f"site bundle after {context} implementation evidence counts changed")
|
|
3049
3210
|
if summary.get("files") != expected_files:
|
|
3050
3211
|
raise SystemExit(f"site bundle after {context} file manifest changed")
|
|
3212
|
+
execution_checklist = summary.get("handoff", {}).get("executionChecklist")
|
|
3213
|
+
if not isinstance(execution_checklist, list) or [item.get("id") for item in execution_checklist] != [
|
|
3214
|
+
"confirm-target-repo",
|
|
3215
|
+
"inspect-architecture",
|
|
3216
|
+
"apply-focused-task",
|
|
3217
|
+
"verify-quality-gates",
|
|
3218
|
+
"record-handoff-evidence",
|
|
3219
|
+
]:
|
|
3220
|
+
raise SystemExit(f"site bundle after {context} handoff execution checklist changed")
|
|
3051
3221
|
checksums = summary.get("checksums", {})
|
|
3052
3222
|
checksum_files = checksums.get("files", {})
|
|
3053
3223
|
if checksums.get("algorithm") != "sha256":
|
|
@@ -3066,9 +3236,14 @@ def assert_site_bundle_smoke(
|
|
|
3066
3236
|
task_ids = [task.get("id") for task in tasks.get("refactorTasks", [])]
|
|
3067
3237
|
if task_ids != ["task-homepage-cta", "task-accessibility", "task-content-quality"]:
|
|
3068
3238
|
raise SystemExit(f"site bundle after {context} task ids changed: {task_ids!r}")
|
|
3239
|
+
evidence = tasks.get("implementationEvidence")
|
|
3240
|
+
if not isinstance(evidence, dict) or len(evidence.get("remainingRisks", [])) != 3:
|
|
3241
|
+
raise SystemExit(f"site bundle after {context} did not preserve implementationEvidence in workspace JSON")
|
|
3069
3242
|
|
|
3070
3243
|
mcp_check = json.loads((out_dir / "mcp-check.json").read_text(encoding="utf-8"))
|
|
3071
3244
|
assert_site_mcp_check_json(json.dumps(mcp_check), context=context, cmd=cmd)
|
|
3245
|
+
mcp_probes = json.loads((out_dir / "mcp-probes.json").read_text(encoding="utf-8"))
|
|
3246
|
+
assert_site_bundle_mcp_probes_payload(mcp_probes, context=context)
|
|
3072
3247
|
assert_site_mcp_plan_markdown((out_dir / "mcp-action-plan.md").read_text(encoding="utf-8"), context=context, cmd=cmd)
|
|
3073
3248
|
implementation_prompt = (out_dir / "codex-implementation.md").read_text(encoding="utf-8")
|
|
3074
3249
|
assert_no_ansi(implementation_prompt, cmd)
|
|
@@ -3083,6 +3258,8 @@ def assert_site_bundle_smoke(
|
|
|
3083
3258
|
readme = (out_dir / "README.md").read_text(encoding="utf-8")
|
|
3084
3259
|
if "Website improvement handoff bundle" not in readme or "does not call external MCPs" not in readme:
|
|
3085
3260
|
raise SystemExit(f"site bundle after {context} README missing bundle boundary guidance")
|
|
3261
|
+
if "Target Repo Execution Checklist" not in readme or "Run target repo quality gates" not in readme:
|
|
3262
|
+
raise SystemExit(f"site bundle after {context} README missing target repo execution checklist")
|
|
3086
3263
|
|
|
3087
3264
|
|
|
3088
3265
|
def assert_site_warning_bundle_smoke(
|
|
@@ -3141,22 +3318,82 @@ def assert_site_bundle_check_json_smoke(
|
|
|
3141
3318
|
env: dict[str, str],
|
|
3142
3319
|
cwd: Path | None = None,
|
|
3143
3320
|
context: str,
|
|
3321
|
+
expected_evidence_counts: dict[str, int] | None = None,
|
|
3144
3322
|
) -> None:
|
|
3323
|
+
if expected_evidence_counts is None:
|
|
3324
|
+
expected_evidence_counts = {
|
|
3325
|
+
"executedWork": 0,
|
|
3326
|
+
"verificationResults": 0,
|
|
3327
|
+
"remainingRisks": 3,
|
|
3328
|
+
"nextActions": 0,
|
|
3329
|
+
}
|
|
3145
3330
|
result = run_plain(cmd, cwd=cwd, env=env)
|
|
3146
3331
|
assert_no_ansi(result.stdout, cmd)
|
|
3147
3332
|
payload = json.loads(result.stdout)
|
|
3148
3333
|
if payload.get("status") != "pass" or payload.get("valid") is not True:
|
|
3149
3334
|
raise SystemExit(f"site bundle check after {context} expected pass/valid output")
|
|
3150
|
-
if payload.get("
|
|
3151
|
-
raise SystemExit(f"site bundle check after {context}
|
|
3152
|
-
|
|
3153
|
-
|
|
3335
|
+
if payload.get("externalCalls") is not False or payload.get("targetRepoMutation") is not False:
|
|
3336
|
+
raise SystemExit(f"site bundle check after {context} boundary flags changed")
|
|
3337
|
+
boundaries = payload.get("boundaries")
|
|
3338
|
+
if (
|
|
3339
|
+
not isinstance(boundaries, list)
|
|
3340
|
+
or "deterministic-local" not in boundaries
|
|
3341
|
+
or "no-external-mcp-calls" not in boundaries
|
|
3342
|
+
or "no-target-repo-mutation" not in boundaries
|
|
3343
|
+
):
|
|
3344
|
+
raise SystemExit(f"site bundle check after {context} boundary list changed: {boundaries!r}")
|
|
3345
|
+
if payload.get("counts", {}).get("presentFiles") != 9:
|
|
3346
|
+
raise SystemExit(f"site bundle check after {context} expected 9 present files")
|
|
3347
|
+
if payload.get("counts", {}).get("verifiedChecksumFiles") != 8:
|
|
3348
|
+
raise SystemExit(f"site bundle check after {context} expected 8 verified checksum files")
|
|
3154
3349
|
if payload.get("counts", {}).get("checksumFailures") != 0:
|
|
3155
3350
|
raise SystemExit(f"site bundle check after {context} expected no checksum failures")
|
|
3351
|
+
if payload.get("counts", {}).get("verifiedGeneratedFiles") != 8:
|
|
3352
|
+
raise SystemExit(f"site bundle check after {context} expected 8 current-contract generated files")
|
|
3353
|
+
if payload.get("counts", {}).get("generatedFailures") != 0:
|
|
3354
|
+
raise SystemExit(f"site bundle check after {context} expected no generated bundle contract failures")
|
|
3355
|
+
generated_contract = payload.get("generatedContract")
|
|
3356
|
+
if not isinstance(generated_contract, dict) or generated_contract.get("available") is not True:
|
|
3357
|
+
raise SystemExit(f"site bundle check after {context} generated contract diagnostics missing")
|
|
3358
|
+
if generated_contract.get("expectedFiles") != 8 or generated_contract.get("verifiedFiles") != 8:
|
|
3359
|
+
raise SystemExit(f"site bundle check after {context} generated contract file counts changed")
|
|
3360
|
+
if generated_contract.get("driftFiles") != []:
|
|
3361
|
+
raise SystemExit(f"site bundle check after {context} expected no generated contract drift files")
|
|
3362
|
+
generated_files = generated_contract.get("files")
|
|
3363
|
+
if not isinstance(generated_files, list) or len(generated_files) != 8:
|
|
3364
|
+
raise SystemExit(f"site bundle check after {context} expected 8 generated contract file diagnostics")
|
|
3365
|
+
for item in generated_files:
|
|
3366
|
+
if item.get("present") is not True or item.get("matches") is not True:
|
|
3367
|
+
raise SystemExit(f"site bundle check after {context} generated contract file did not match: {item!r}")
|
|
3368
|
+
for key in ("expectedDigest", "actualDigest"):
|
|
3369
|
+
digest = item.get(key)
|
|
3370
|
+
if not isinstance(digest, str) or len(digest) != 64 or any(char not in "0123456789abcdef" for char in digest):
|
|
3371
|
+
raise SystemExit(f"site bundle check after {context} generated contract {key} is not a SHA-256 hex digest")
|
|
3372
|
+
repair_guidance = payload.get("repairGuidance")
|
|
3373
|
+
if not isinstance(repair_guidance, dict) or repair_guidance.get("available") is not True:
|
|
3374
|
+
raise SystemExit(f"site bundle check after {context} repair guidance missing")
|
|
3375
|
+
if repair_guidance.get("targetRepoMutation") is not False or repair_guidance.get("externalCalls") is not False:
|
|
3376
|
+
raise SystemExit(f"site bundle check after {context} repair guidance boundary flags changed")
|
|
3377
|
+
repair_command = repair_guidance.get("command")
|
|
3378
|
+
verify_command = repair_guidance.get("verifyCommand")
|
|
3379
|
+
if (
|
|
3380
|
+
not isinstance(repair_command, str)
|
|
3381
|
+
or "website-workspace.tasks.json --bundle --out " not in repair_command
|
|
3382
|
+
or " --force" not in repair_command
|
|
3383
|
+
):
|
|
3384
|
+
raise SystemExit(f"site bundle check after {context} repair command changed: {repair_command!r}")
|
|
3385
|
+
if not isinstance(verify_command, str) or "--bundle-check --strict --json" not in verify_command:
|
|
3386
|
+
raise SystemExit(f"site bundle check after {context} repair verify command changed: {verify_command!r}")
|
|
3156
3387
|
if payload.get("summary", {}).get("totalTasks") != 3:
|
|
3157
3388
|
raise SystemExit(f"site bundle check after {context} expected 3 tasks")
|
|
3158
3389
|
if payload.get("summary", {}).get("siteName") != "Korean SaaS marketing site":
|
|
3159
3390
|
raise SystemExit(f"site bundle check after {context} site name changed")
|
|
3391
|
+
evidence_counts = payload.get("summary", {}).get("implementationEvidence")
|
|
3392
|
+
if not isinstance(evidence_counts, dict):
|
|
3393
|
+
raise SystemExit(f"site bundle check after {context} implementationEvidence counts missing")
|
|
3394
|
+
for key, expected in expected_evidence_counts.items():
|
|
3395
|
+
if evidence_counts.get(key) != expected:
|
|
3396
|
+
raise SystemExit(f"site bundle check after {context} evidence count {key} changed: {evidence_counts.get(key)!r}")
|
|
3160
3397
|
if payload.get("summary", {}).get("checksumAlgorithm") != "sha256":
|
|
3161
3398
|
raise SystemExit(f"site bundle check after {context} checksum algorithm changed")
|
|
3162
3399
|
bundle_digest = payload.get("summary", {}).get("checksumBundleDigest")
|
|
@@ -3164,6 +3401,18 @@ def assert_site_bundle_check_json_smoke(
|
|
|
3164
3401
|
raise SystemExit(f"site bundle check after {context} bundle digest changed")
|
|
3165
3402
|
if payload.get("mcpStatus") != "pass":
|
|
3166
3403
|
raise SystemExit(f"site bundle check after {context} MCP status changed")
|
|
3404
|
+
if payload.get("mcpProbeStatus") != "pass":
|
|
3405
|
+
raise SystemExit(f"site bundle check after {context} MCP probe status changed")
|
|
3406
|
+
assert_site_mcp_probe_counts(
|
|
3407
|
+
payload.get("mcpProbeCounts"),
|
|
3408
|
+
context=context,
|
|
3409
|
+
label="site bundle check",
|
|
3410
|
+
)
|
|
3411
|
+
assert_site_mcp_probe_counts(
|
|
3412
|
+
payload.get("summary", {}).get("mcpProbeCounts"),
|
|
3413
|
+
context=context,
|
|
3414
|
+
label="site bundle check summary",
|
|
3415
|
+
)
|
|
3167
3416
|
issue_ids = [issue.get("id") for issue in payload.get("issues", [])]
|
|
3168
3417
|
if issue_ids != ["bundle-ready"]:
|
|
3169
3418
|
raise SystemExit(f"site bundle check after {context} expected bundle-ready only, got {issue_ids!r}")
|
|
@@ -3175,7 +3424,15 @@ def assert_site_bundle_compare_json_smoke(
|
|
|
3175
3424
|
env: dict[str, str],
|
|
3176
3425
|
cwd: Path | None = None,
|
|
3177
3426
|
context: str,
|
|
3427
|
+
expected_evidence_counts: dict[str, int] | None = None,
|
|
3178
3428
|
) -> None:
|
|
3429
|
+
if expected_evidence_counts is None:
|
|
3430
|
+
expected_evidence_counts = {
|
|
3431
|
+
"executedWork": 0,
|
|
3432
|
+
"verificationResults": 0,
|
|
3433
|
+
"remainingRisks": 3,
|
|
3434
|
+
"nextActions": 0,
|
|
3435
|
+
}
|
|
3179
3436
|
result = run_plain(cmd, cwd=cwd, env=env)
|
|
3180
3437
|
assert_no_ansi(result.stdout, cmd)
|
|
3181
3438
|
payload = json.loads(result.stdout)
|
|
@@ -3191,6 +3448,21 @@ def assert_site_bundle_compare_json_smoke(
|
|
|
3191
3448
|
raise SystemExit(f"site bundle compare after {context} {side} bundle digest changed")
|
|
3192
3449
|
if payload.get(side, {}).get("siteName") != "Korean SaaS marketing site":
|
|
3193
3450
|
raise SystemExit(f"site bundle compare after {context} {side} site name changed")
|
|
3451
|
+
assert_site_mcp_probe_counts(
|
|
3452
|
+
payload.get(side, {}).get("mcpProbeCounts"),
|
|
3453
|
+
context=context,
|
|
3454
|
+
label=f"site bundle compare {side}",
|
|
3455
|
+
)
|
|
3456
|
+
if payload.get(side, {}).get("verifiedGeneratedFiles") != 8 or payload.get(side, {}).get("generatedFailures") != 0:
|
|
3457
|
+
raise SystemExit(f"site bundle compare after {context} {side} generated bundle contract verification changed")
|
|
3458
|
+
if payload.get(side, {}).get("generatedDriftFiles") != []:
|
|
3459
|
+
raise SystemExit(f"site bundle compare after {context} {side} generated bundle contract drift changed")
|
|
3460
|
+
evidence_counts = payload.get(side, {}).get("implementationEvidence")
|
|
3461
|
+
if not isinstance(evidence_counts, dict):
|
|
3462
|
+
raise SystemExit(f"site bundle compare after {context} {side} implementationEvidence counts missing")
|
|
3463
|
+
for key, expected in expected_evidence_counts.items():
|
|
3464
|
+
if evidence_counts.get(key) != expected:
|
|
3465
|
+
raise SystemExit(f"site bundle compare after {context} {side} evidence count {key} changed: {evidence_counts.get(key)!r}")
|
|
3194
3466
|
issue_ids = [issue.get("id") for issue in payload.get("issues", [])]
|
|
3195
3467
|
if issue_ids != ["bundle-compare-identical"]:
|
|
3196
3468
|
raise SystemExit(f"site bundle compare after {context} expected bundle-compare-identical only, got {issue_ids!r}")
|
|
@@ -3202,17 +3474,72 @@ def assert_site_bundle_handoff_json_smoke(
|
|
|
3202
3474
|
env: dict[str, str],
|
|
3203
3475
|
cwd: Path | None = None,
|
|
3204
3476
|
context: str,
|
|
3477
|
+
expected_evidence_counts: dict[str, int] | None = None,
|
|
3205
3478
|
) -> None:
|
|
3479
|
+
if expected_evidence_counts is None:
|
|
3480
|
+
expected_evidence_counts = {
|
|
3481
|
+
"executedWork": 0,
|
|
3482
|
+
"verificationResults": 0,
|
|
3483
|
+
"remainingRisks": 3,
|
|
3484
|
+
"nextActions": 0,
|
|
3485
|
+
}
|
|
3206
3486
|
result = run_plain(cmd, cwd=cwd, env=env)
|
|
3207
3487
|
assert_no_ansi(result.stdout, cmd)
|
|
3208
3488
|
payload = json.loads(result.stdout)
|
|
3209
3489
|
if payload.get("status") != "pass" or payload.get("valid") is not True:
|
|
3210
3490
|
raise SystemExit(f"site bundle handoff after {context} expected pass/valid output")
|
|
3491
|
+
expected_boundaries = [
|
|
3492
|
+
"deterministic-local",
|
|
3493
|
+
"no-external-mcp-calls",
|
|
3494
|
+
"no-target-repo-mutation",
|
|
3495
|
+
"no-lighthouse-axe-visual-diff",
|
|
3496
|
+
"target-repo-work-after-handoff",
|
|
3497
|
+
]
|
|
3498
|
+
if payload.get("boundaries") != expected_boundaries:
|
|
3499
|
+
raise SystemExit(f"site bundle handoff after {context} boundary list changed: {payload.get('boundaries')!r}")
|
|
3500
|
+
if payload.get("externalCalls") is not False or payload.get("targetRepoMutation") is not False:
|
|
3501
|
+
raise SystemExit(f"site bundle handoff after {context} boundary flags changed")
|
|
3211
3502
|
bundle = payload.get("bundle", {})
|
|
3503
|
+
source_bundle = payload.get("sourceBundle")
|
|
3504
|
+
if not isinstance(source_bundle, dict):
|
|
3505
|
+
raise SystemExit(f"site bundle handoff after {context} source bundle provenance missing")
|
|
3506
|
+
if bundle.get("sourceBundle") != source_bundle:
|
|
3507
|
+
raise SystemExit(f"site bundle handoff after {context} source bundle provenance is not mirrored under bundle")
|
|
3508
|
+
if (
|
|
3509
|
+
source_bundle.get("status") != "pass"
|
|
3510
|
+
or source_bundle.get("valid") is not True
|
|
3511
|
+
or source_bundle.get("sourceWorkspace") != "stdin"
|
|
3512
|
+
or source_bundle.get("siteName") != "Korean SaaS marketing site"
|
|
3513
|
+
):
|
|
3514
|
+
raise SystemExit(f"site bundle handoff after {context} source bundle provenance summary changed: {source_bundle!r}")
|
|
3515
|
+
if (
|
|
3516
|
+
source_bundle.get("verifiedGeneratedFiles") != 8
|
|
3517
|
+
or source_bundle.get("expectedGeneratedFiles") != 8
|
|
3518
|
+
or source_bundle.get("verifiedChecksumFiles") != 8
|
|
3519
|
+
or source_bundle.get("expectedChecksumFiles") != 8
|
|
3520
|
+
):
|
|
3521
|
+
raise SystemExit(f"site bundle handoff after {context} source bundle provenance verification counts changed: {source_bundle!r}")
|
|
3212
3522
|
if bundle.get("siteName") != "Korean SaaS marketing site":
|
|
3213
3523
|
raise SystemExit(f"site bundle handoff after {context} site name changed")
|
|
3214
|
-
if bundle.get("
|
|
3524
|
+
if bundle.get("boundaries") != expected_boundaries:
|
|
3525
|
+
raise SystemExit(f"site bundle handoff after {context} bundle boundary list changed: {bundle.get('boundaries')!r}")
|
|
3526
|
+
if bundle.get("externalCalls") is not False or bundle.get("targetRepoMutation") is not False:
|
|
3527
|
+
raise SystemExit(f"site bundle handoff after {context} bundle boundary flags changed")
|
|
3528
|
+
if bundle.get("verifiedChecksumFiles") != 8 or bundle.get("checksumFailures") != 0:
|
|
3215
3529
|
raise SystemExit(f"site bundle handoff after {context} checksum verification changed")
|
|
3530
|
+
if bundle.get("verifiedGeneratedFiles") != 8 or bundle.get("generatedFailures") != 0:
|
|
3531
|
+
raise SystemExit(f"site bundle handoff after {context} generated bundle contract verification changed")
|
|
3532
|
+
assert_site_mcp_probe_counts(
|
|
3533
|
+
bundle.get("mcpProbeCounts"),
|
|
3534
|
+
context=context,
|
|
3535
|
+
label="site bundle handoff",
|
|
3536
|
+
)
|
|
3537
|
+
evidence_counts = bundle.get("implementationEvidence")
|
|
3538
|
+
if not isinstance(evidence_counts, dict):
|
|
3539
|
+
raise SystemExit(f"site bundle handoff after {context} implementationEvidence counts missing")
|
|
3540
|
+
for key, expected in expected_evidence_counts.items():
|
|
3541
|
+
if evidence_counts.get(key) != expected:
|
|
3542
|
+
raise SystemExit(f"site bundle handoff after {context} evidence count {key} changed: {evidence_counts.get(key)!r}")
|
|
3216
3543
|
digest = bundle.get("checksumBundleDigest")
|
|
3217
3544
|
if not isinstance(digest, str) or len(digest) != 64:
|
|
3218
3545
|
raise SystemExit(f"site bundle handoff after {context} bundle digest changed")
|
|
@@ -3221,9 +3548,9 @@ def assert_site_bundle_handoff_json_smoke(
|
|
|
3221
3548
|
raise SystemExit(f"site bundle handoff after {context} prompt missing")
|
|
3222
3549
|
for fragment in (
|
|
3223
3550
|
"Website improvement target-repo handoff prompt",
|
|
3224
|
-
"
|
|
3225
|
-
"
|
|
3226
|
-
"
|
|
3551
|
+
"SHA-256 bundle digest:",
|
|
3552
|
+
"Source bundle strict check command: `design-ai site",
|
|
3553
|
+
"You are Codex working in the target website repository, not in the design-ai repository.",
|
|
3227
3554
|
"# Codex implementation prompt",
|
|
3228
3555
|
):
|
|
3229
3556
|
if fragment not in prompt:
|
|
@@ -4502,7 +4829,7 @@ def assert_learning_signals_report_smoke(
|
|
|
4502
4829
|
cwd: Path | None = None,
|
|
4503
4830
|
context: str,
|
|
4504
4831
|
) -> None:
|
|
4505
|
-
seed_force_overwrite_target(output_path, context=context, cmd=["design-ai", "learn", "--signals", "--report"])
|
|
4832
|
+
seed_force_overwrite_target(output_path, context=context, cmd=["design-ai", "learn", "--signals", "--report", "--force"])
|
|
4506
4833
|
cmd = command_factory(
|
|
4507
4834
|
"learn",
|
|
4508
4835
|
"--signals",
|
|
@@ -4536,7 +4863,7 @@ def assert_learning_agent_backlog_report_smoke(
|
|
|
4536
4863
|
cwd: Path | None = None,
|
|
4537
4864
|
context: str,
|
|
4538
4865
|
) -> None:
|
|
4539
|
-
seed_force_overwrite_target(output_path, context=context, cmd=["design-ai", "learn", "--agent-backlog", "--report"])
|
|
4866
|
+
seed_force_overwrite_target(output_path, context=context, cmd=["design-ai", "learn", "--agent-backlog", "--report", "--force"])
|
|
4540
4867
|
cmd = command_factory(
|
|
4541
4868
|
"learn",
|
|
4542
4869
|
"--agent-backlog",
|
|
@@ -4800,6 +5127,12 @@ def smoke_registry_package(package_spec: str, *, retries: int, delay: float) ->
|
|
|
4800
5127
|
env=env,
|
|
4801
5128
|
context="registry smoke npm exec version JSON",
|
|
4802
5129
|
)
|
|
5130
|
+
assert_design_ai_mcp_protocol_smoke(
|
|
5131
|
+
npm_exec_mcp_cmd(package_spec),
|
|
5132
|
+
cwd=npx_root,
|
|
5133
|
+
env=env,
|
|
5134
|
+
context="registry smoke npm exec design-ai-mcp protocol",
|
|
5135
|
+
)
|
|
4803
5136
|
assert_workspace_json_smoke(
|
|
4804
5137
|
npm_exec_cmd(package_spec, "workspace", "--json"),
|
|
4805
5138
|
cwd=npx_root,
|
|
@@ -6192,6 +6525,41 @@ def run_self_test() -> None:
|
|
|
6192
6525
|
expected="without external calls",
|
|
6193
6526
|
scope="registry smoke",
|
|
6194
6527
|
)
|
|
6528
|
+
mcp_protocol_cmd = ["design-ai-mcp"]
|
|
6529
|
+
assert_design_ai_mcp_protocol_responses(
|
|
6530
|
+
[
|
|
6531
|
+
{"jsonrpc": "2.0", "id": 1, "result": {"serverInfo": {"name": "design-ai"}}},
|
|
6532
|
+
{"jsonrpc": "2.0", "id": 2, "result": {"tools": [{"name": "design_ai_route"}, {"name": "design_ai_search"}]}},
|
|
6533
|
+
{
|
|
6534
|
+
"jsonrpc": "2.0",
|
|
6535
|
+
"id": 3,
|
|
6536
|
+
"error": {"code": -32602, "message": "design_ai_search.limit must be an integer"},
|
|
6537
|
+
},
|
|
6538
|
+
],
|
|
6539
|
+
context="registry smoke self-test design-ai MCP protocol",
|
|
6540
|
+
cmd=mcp_protocol_cmd,
|
|
6541
|
+
)
|
|
6542
|
+
expect_self_test_failure(
|
|
6543
|
+
lambda: assert_design_ai_mcp_protocol_responses(
|
|
6544
|
+
[
|
|
6545
|
+
{"jsonrpc": "2.0", "id": 1, "result": {"serverInfo": {"name": "design-ai"}}},
|
|
6546
|
+
{
|
|
6547
|
+
"jsonrpc": "2.0",
|
|
6548
|
+
"id": 2,
|
|
6549
|
+
"result": {"tools": [{"name": "design_ai_route"}, {"name": "design_ai_search"}]},
|
|
6550
|
+
},
|
|
6551
|
+
{
|
|
6552
|
+
"jsonrpc": "2.0",
|
|
6553
|
+
"id": 3,
|
|
6554
|
+
"error": {"code": -32602, "message": "wrong validation message"},
|
|
6555
|
+
},
|
|
6556
|
+
],
|
|
6557
|
+
context="registry smoke self-test design-ai MCP protocol",
|
|
6558
|
+
cmd=mcp_protocol_cmd,
|
|
6559
|
+
),
|
|
6560
|
+
expected="MCP invalid argument response did not preserve invalid params validation",
|
|
6561
|
+
scope="registry smoke",
|
|
6562
|
+
)
|
|
6195
6563
|
assert_site_mcp_plan_markdown(
|
|
6196
6564
|
passing_site_mcp_plan_markdown(),
|
|
6197
6565
|
context="registry smoke self-test site mcp-plan",
|