@design-ai/cli 4.55.0 → 4.57.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 +109 -25
- package/CHANGELOG.md +75 -0
- package/README.ko.md +15 -32
- package/README.md +18 -31
- package/cli/bin/design-ai.mjs +1 -0
- package/cli/commands/help.mjs +7 -4
- package/cli/commands/index.mjs +292 -0
- package/cli/commands/learn-help.mjs +149 -0
- package/cli/commands/learn-print-profile.mjs +373 -0
- package/cli/commands/learn-print-restore.mjs +349 -0
- package/cli/commands/learn-print-signals.mjs +453 -0
- package/cli/commands/learn.mjs +60 -1281
- package/cli/commands/pack.mjs +7 -3
- package/cli/commands/prompt.mjs +7 -3
- package/cli/commands/search.mjs +94 -1
- package/cli/lib/dispatch.mjs +3 -0
- package/cli/lib/embedding-index.mjs +199 -0
- package/cli/lib/embedding-provider.mjs +121 -0
- package/cli/lib/embedding-rerank.mjs +52 -0
- package/cli/lib/learn-args.mjs +490 -0
- package/cli/lib/learn-backup.mjs +748 -0
- package/cli/lib/learn-curation.mjs +612 -0
- package/cli/lib/learn-eval.mjs +459 -0
- package/cli/lib/learn-profile.mjs +763 -0
- package/cli/lib/learn-select.mjs +223 -0
- package/cli/lib/learn-shared.mjs +158 -0
- package/cli/lib/learn-test-support.mjs +218 -0
- package/cli/lib/learn-usage.mjs +360 -0
- package/cli/lib/learn.mjs +83 -3694
- package/cli/lib/lexical.mjs +137 -0
- package/cli/lib/local-config.mjs +110 -0
- package/cli/lib/mcp-server.mjs +221 -13
- package/cli/lib/pack.mjs +31 -4
- package/cli/lib/prompt.mjs +42 -4
- package/cli/lib/recall.mjs +193 -0
- package/cli/lib/retrieval-index.mjs +0 -0
- package/cli/lib/search-ranked.mjs +170 -0
- package/cli/lib/search.mjs +11 -1
- package/cli/lib/signals-backlog-commands.mjs +673 -0
- package/cli/lib/signals-backlog.mjs +361 -0
- package/cli/lib/signals-eval.mjs +176 -0
- package/cli/lib/signals-readiness.mjs +247 -0
- package/cli/lib/signals-registry.mjs +377 -0
- package/cli/lib/signals-render.mjs +478 -0
- package/cli/lib/signals-shared.mjs +75 -0
- package/cli/lib/signals.mjs +16 -2337
- 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-expected.mjs +173 -0
- package/cli/lib/site-bundle-handoff-runbook-action-summary.mjs +332 -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-human-lines.mjs +167 -0
- package/cli/lib/site-bundle-handoff-runbook-maps.mjs +238 -0
- package/cli/lib/site-bundle-handoff-runbook-next-step.mjs +278 -0
- package/cli/lib/site-bundle-handoff-runbook-stage-metadata.mjs +84 -0
- package/cli/lib/site-bundle-handoff-runbook.mjs +444 -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-test-support.mjs +68 -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/cli/lib/skill-proposals-apply-commands.mjs +135 -0
- package/cli/lib/skill-proposals-apply-contract.mjs +750 -0
- package/cli/lib/skill-proposals-apply-plan.mjs +118 -0
- package/cli/lib/skill-proposals-generate.mjs +298 -0
- package/cli/lib/skill-proposals-render.mjs +532 -0
- package/cli/lib/skill-proposals-review.mjs +262 -0
- package/cli/lib/skill-proposals.mjs +15 -2046
- package/cli/lib/workspace-args.mjs +83 -0
- package/cli/lib/workspace-git.mjs +169 -0
- package/cli/lib/workspace-learning.mjs +483 -0
- package/cli/lib/workspace-repo.mjs +139 -0
- package/cli/lib/workspace-report.mjs +283 -0
- package/cli/lib/workspace-test-support.mjs +99 -0
- package/cli/lib/workspace.mjs +30 -1056
- package/docs/AI-LEARNING-PHASE2.md +215 -0
- package/docs/DISTRIBUTION.ko.md +35 -6
- package/docs/DISTRIBUTION.md +35 -8
- package/docs/NEXT-SURFACE-DECISION.md +125 -0
- package/docs/PRODUCT-READINESS.md +3 -3
- package/docs/RELEASE-CHECKLIST.md +20 -3
- package/docs/RELEASE-GATES.ko.md +35 -0
- package/docs/RELEASE-GATES.md +234 -0
- package/docs/ROADMAP.md +2232 -8
- package/docs/external-status.md +20 -7
- package/docs/inspection-20260630.md +169 -0
- package/docs/integrations/design-ai-mcp-server.md +35 -3
- package/docs/integrations/vscode-walkthrough.ko.md +3 -3
- package/docs/integrations/vscode-walkthrough.md +3 -3
- package/docs/reference/ant-design.md +413 -0
- package/docs/reference/awesome-design-md.md +439 -0
- package/docs/reference/mui.md +783 -0
- package/docs/reference/shadcn-ui.md +298 -0
- package/docs/site-overrides/main.html +1 -1
- package/examples/component-accordion-actions.md +5 -5
- package/examples/component-accordion-details.md +5 -5
- package/examples/component-accordion-summary.md +5 -5
- package/examples/component-accordion.md +3 -3
- package/examples/component-affix.md +1 -1
- package/examples/component-alert-dialog.md +1 -1
- package/examples/component-alert-title.md +1 -1
- package/examples/component-alert.md +3 -3
- package/examples/component-anchor.md +1 -1
- package/examples/component-app-bar.md +1 -1
- package/examples/component-aspect-ratio.md +1 -1
- package/examples/component-auto-complete.md +2 -2
- package/examples/component-avatar-group.md +5 -5
- package/examples/component-avatar.md +3 -3
- package/examples/component-back-top.md +1 -1
- package/examples/component-backdrop.md +1 -1
- package/examples/component-badge.md +3 -3
- package/examples/component-border-beam.md +3 -3
- package/examples/component-bottom-navigation.md +1 -1
- package/examples/component-box.md +1 -1
- package/examples/component-breadcrumb.md +3 -3
- package/examples/component-button-base.md +3 -3
- package/examples/component-button-group.md +2 -2
- package/examples/component-button.md +3 -3
- package/examples/component-calendar.md +2 -2
- package/examples/component-card-actions.md +1 -1
- package/examples/component-card-content.md +1 -1
- package/examples/component-card-header.md +1 -1
- package/examples/component-card-media.md +1 -1
- package/examples/component-card.md +3 -3
- package/examples/component-carousel.md +2 -2
- package/examples/component-cascader.md +1 -1
- package/examples/component-chart.md +1 -1
- package/examples/component-checkbox.md +3 -3
- package/examples/component-click-away-listener.md +1 -1
- package/examples/component-code.md +1 -1
- package/examples/component-collapsible.md +1 -1
- package/examples/component-color-picker.md +1 -1
- package/examples/component-combobox.md +1 -1
- package/examples/component-command.md +1 -1
- package/examples/component-config-provider.md +3 -3
- package/examples/component-context-menu.md +1 -1
- package/examples/component-css-baseline.md +3 -3
- package/examples/component-date-picker.md +2 -2
- package/examples/component-descriptions.md +1 -1
- package/examples/component-dialog-actions.md +1 -1
- package/examples/component-dialog-content-text.md +1 -1
- package/examples/component-dialog-content.md +1 -1
- package/examples/component-dialog-title.md +1 -1
- package/examples/component-dialog.md +2 -2
- package/examples/component-divider.md +3 -3
- package/examples/component-drawer.md +3 -3
- package/examples/component-dropdown.md +3 -3
- package/examples/component-empty.md +2 -2
- package/examples/component-fade.md +1 -1
- package/examples/component-field.md +1 -1
- package/examples/component-filled-input.md +1 -1
- package/examples/component-flex.md +1 -1
- package/examples/component-float-button.md +2 -2
- package/examples/component-form-control-label.md +1 -1
- package/examples/component-form-control.md +1 -1
- package/examples/component-form-controls.md +1 -1
- package/examples/component-form-group.md +1 -1
- package/examples/component-form-helper-text.md +1 -1
- package/examples/component-form-label.md +1 -1
- package/examples/component-form.md +2 -2
- package/examples/component-grid.md +2 -2
- package/examples/component-grow.md +1 -1
- package/examples/component-hover-card.md +1 -1
- package/examples/component-icon-button.md +1 -1
- package/examples/component-icon.md +1 -1
- package/examples/component-image-list.md +1 -1
- package/examples/component-image.md +1 -1
- package/examples/component-input-adornment.md +1 -1
- package/examples/component-input-base.md +1 -1
- package/examples/component-input-number.md +1 -1
- package/examples/component-input-otp.md +2 -2
- package/examples/component-input.md +3 -3
- package/examples/component-item.md +1 -1
- package/examples/component-label.md +1 -1
- package/examples/component-layout.md +1 -1
- package/examples/component-link.md +1 -1
- package/examples/component-list-item-avatar.md +1 -1
- package/examples/component-list-item-button.md +1 -1
- package/examples/component-list-item-icon.md +1 -1
- package/examples/component-list-item-text.md +1 -1
- package/examples/component-list-item.md +1 -1
- package/examples/component-list-subheader.md +1 -1
- package/examples/component-list.md +2 -2
- package/examples/component-masonry.md +1 -1
- package/examples/component-mentions.md +1 -1
- package/examples/component-menu-item.md +1 -1
- package/examples/component-menu-list.md +1 -1
- package/examples/component-menu.md +1 -1
- package/examples/component-menubar.md +1 -1
- package/examples/component-message.md +1 -1
- package/examples/component-mobile-stepper.md +1 -1
- package/examples/component-modal.md +5 -5
- package/examples/component-navigation-menu.md +1 -1
- package/examples/component-notification.md +1 -1
- package/examples/component-outlined-input.md +1 -1
- package/examples/component-pagination.md +3 -3
- package/examples/component-paper.md +1 -1
- package/examples/component-popconfirm.md +1 -1
- package/examples/component-popover.md +3 -3
- package/examples/component-popper.md +1 -1
- package/examples/component-progress.md +2 -2
- package/examples/component-qr-code.md +1 -1
- package/examples/component-radio.md +3 -3
- package/examples/component-rate.md +2 -2
- package/examples/component-resizable.md +1 -1
- package/examples/component-result.md +1 -1
- package/examples/component-scroll-area.md +1 -1
- package/examples/component-segmented.md +3 -3
- package/examples/component-select.md +3 -3
- package/examples/component-separator.md +1 -1
- package/examples/component-sheet.md +1 -1
- package/examples/component-sidebar.md +1 -1
- package/examples/component-skeleton.md +3 -3
- package/examples/component-slide.md +1 -1
- package/examples/component-slider.md +3 -3
- package/examples/component-snackbar-content.md +1 -1
- package/examples/component-snackbar.md +1 -1
- package/examples/component-sonner.md +1 -1
- package/examples/component-space.md +1 -1
- package/examples/component-speed-dial-action.md +1 -1
- package/examples/component-speed-dial.md +1 -1
- package/examples/component-spin.md +2 -2
- package/examples/component-spinner.md +1 -1
- package/examples/component-splitter.md +2 -2
- package/examples/component-stack.md +1 -1
- package/examples/component-statistic.md +1 -1
- package/examples/component-step-button.md +4 -4
- package/examples/component-step-connector.md +4 -4
- package/examples/component-step-content.md +1 -1
- package/examples/component-step-icon.md +1 -1
- package/examples/component-step-label.md +1 -1
- package/examples/component-step.md +2 -2
- package/examples/component-steps.md +2 -2
- package/examples/component-swipeable-drawer.md +1 -1
- package/examples/component-switch.md +3 -3
- package/examples/component-tab-scroll-button.md +5 -5
- package/examples/component-tab.md +1 -1
- package/examples/component-table-body.md +1 -1
- package/examples/component-table-cell.md +1 -1
- package/examples/component-table-container.md +1 -1
- package/examples/component-table-footer.md +1 -1
- package/examples/component-table-head.md +1 -1
- package/examples/component-table-pagination.md +1 -1
- package/examples/component-table-row.md +1 -1
- package/examples/component-table-sort-label.md +1 -1
- package/examples/component-table.md +2 -2
- package/examples/component-tabs.md +3 -3
- package/examples/component-tag-badge.md +2 -2
- package/examples/component-tag.md +1 -1
- package/examples/component-textarea-autosize.md +1 -1
- package/examples/component-textarea.md +1 -1
- package/examples/component-time-picker.md +1 -1
- package/examples/component-timeline.md +1 -1
- package/examples/component-toast.md +4 -4
- package/examples/component-toggle-button.md +1 -1
- package/examples/component-toggle.md +1 -1
- package/examples/component-toolbar.md +1 -1
- package/examples/component-tooltip.md +3 -3
- package/examples/component-tour.md +1 -1
- package/examples/component-transfer.md +1 -1
- package/examples/component-tree-select.md +1 -1
- package/examples/component-tree.md +1 -1
- package/examples/component-typography.md +2 -2
- package/examples/component-upload.md +1 -1
- package/examples/component-watermark.md +1 -1
- package/examples/component-zoom.md +1 -1
- package/knowledge/COVERAGE.md +4 -3
- package/knowledge/components/INDEX.md +202 -202
- package/knowledge/patterns/brand-references.md +72 -72
- package/package.json +1 -1
- package/tools/audit/local-ci.py +16 -1
- package/tools/audit/package-smoke.py +717 -9
- package/tools/audit/registry-smoke.py +912 -10
- package/tools/audit/release-metadata.py +47 -1
- package/tools/audit/smoke_assertions.py +998 -35
- package/tools/migrations/refs-links-to-reference-pages.py +179 -0
|
@@ -34,6 +34,8 @@ from smoke_assertions import (
|
|
|
34
34
|
EXPECTED_HELP_ALIASES,
|
|
35
35
|
EXPECTED_NUMERIC_VALUE_SMOKES,
|
|
36
36
|
EXPECTED_PACK_MAX_BYTES,
|
|
37
|
+
EXPECTED_RANKED_SEARCH_LIMIT,
|
|
38
|
+
EXPECTED_RANKED_SEARCH_NOT_BUILT_NOTICE,
|
|
37
39
|
EXPECTED_REPOSITORY_URL,
|
|
38
40
|
EXPECTED_ROUTE_BRIEF,
|
|
39
41
|
EXPECTED_ROUTE_ID,
|
|
@@ -50,6 +52,7 @@ from smoke_assertions import (
|
|
|
50
52
|
assert_check_examples_json_component_spec,
|
|
51
53
|
assert_check_stdin_json_component_spec,
|
|
52
54
|
assert_command_alias_output,
|
|
55
|
+
assert_design_ai_mcp_protocol_responses,
|
|
53
56
|
assert_doctor_json_clean,
|
|
54
57
|
assert_doctor_strict_output,
|
|
55
58
|
assert_examples_human_output,
|
|
@@ -57,6 +60,9 @@ from smoke_assertions import (
|
|
|
57
60
|
assert_force_overwrite_replaced,
|
|
58
61
|
assert_functional_alias_smokes,
|
|
59
62
|
assert_help_topic_output,
|
|
63
|
+
assert_index_build_json,
|
|
64
|
+
assert_index_status_json,
|
|
65
|
+
assert_index_verify_json,
|
|
60
66
|
assert_install_doctor_lifecycle_output,
|
|
61
67
|
assert_install_output,
|
|
62
68
|
assert_list_catalog_output,
|
|
@@ -75,6 +81,8 @@ from smoke_assertions import (
|
|
|
75
81
|
assert_prompt_json_component_spec,
|
|
76
82
|
assert_prompt_markdown_body_component_spec,
|
|
77
83
|
assert_prompt_markdown_component_spec,
|
|
84
|
+
assert_ranked_search_determinism,
|
|
85
|
+
assert_ranked_search_json,
|
|
78
86
|
assert_route_catalog_json,
|
|
79
87
|
assert_route_explain_human_output,
|
|
80
88
|
assert_route_json_component_spec,
|
|
@@ -135,6 +143,7 @@ from smoke_assertions import (
|
|
|
135
143
|
format_cmd,
|
|
136
144
|
help_alias_script,
|
|
137
145
|
help_topic_script,
|
|
146
|
+
mcp_smoke_input,
|
|
138
147
|
passing_doctor_report_json,
|
|
139
148
|
passing_check_artifact_content,
|
|
140
149
|
passing_site_mcp_check_probes_human,
|
|
@@ -379,6 +388,18 @@ def npm_exec_cmd(tarball: Path, *args: str) -> list[str]:
|
|
|
379
388
|
]
|
|
380
389
|
|
|
381
390
|
|
|
391
|
+
def npm_exec_mcp_cmd(tarball: Path) -> list[str]:
|
|
392
|
+
return [
|
|
393
|
+
"npm",
|
|
394
|
+
"exec",
|
|
395
|
+
"--yes",
|
|
396
|
+
"--package",
|
|
397
|
+
str(tarball),
|
|
398
|
+
"--",
|
|
399
|
+
"design-ai-mcp",
|
|
400
|
+
]
|
|
401
|
+
|
|
402
|
+
|
|
382
403
|
def npm_exec_shell_cmd(tarball: Path, script: str) -> list[str]:
|
|
383
404
|
return [
|
|
384
405
|
"npm",
|
|
@@ -533,6 +554,52 @@ def require_package_smoke(condition: bool, *, context: str, cmd: list[str], mess
|
|
|
533
554
|
fail_package_smoke(context, cmd, message)
|
|
534
555
|
|
|
535
556
|
|
|
557
|
+
def assert_design_ai_mcp_protocol_smoke(
|
|
558
|
+
cmd: list[str],
|
|
559
|
+
*,
|
|
560
|
+
env: dict[str, str],
|
|
561
|
+
cwd: Path | None,
|
|
562
|
+
context: str,
|
|
563
|
+
) -> None:
|
|
564
|
+
print(f"$ {format_cmd(cmd)} < MCP smoke", flush=True)
|
|
565
|
+
result = subprocess.run(
|
|
566
|
+
cmd,
|
|
567
|
+
cwd=cwd,
|
|
568
|
+
env=env,
|
|
569
|
+
input=mcp_smoke_input(),
|
|
570
|
+
text=True,
|
|
571
|
+
capture_output=True,
|
|
572
|
+
timeout=15,
|
|
573
|
+
)
|
|
574
|
+
if result.stdout:
|
|
575
|
+
print(result.stdout, end="")
|
|
576
|
+
if result.stderr:
|
|
577
|
+
print(result.stderr, end="", file=sys.stderr)
|
|
578
|
+
|
|
579
|
+
if is_npm_exec_cache_enoent(cmd, result):
|
|
580
|
+
result = retry_npm_exec_once(cmd, cwd=cwd, env=env, input_text=mcp_smoke_input())
|
|
581
|
+
if result.stdout:
|
|
582
|
+
print(result.stdout, end="")
|
|
583
|
+
if result.stderr:
|
|
584
|
+
print(result.stderr, end="", file=sys.stderr)
|
|
585
|
+
|
|
586
|
+
if result.returncode != 0:
|
|
587
|
+
raise SystemExit(f"command failed with exit code {result.returncode}: {format_cmd(cmd)}")
|
|
588
|
+
|
|
589
|
+
output = f"{result.stdout}\n{result.stderr}"
|
|
590
|
+
assert_no_ansi(output, cmd)
|
|
591
|
+
|
|
592
|
+
responses = []
|
|
593
|
+
for line in result.stdout.splitlines():
|
|
594
|
+
if not line.strip():
|
|
595
|
+
continue
|
|
596
|
+
try:
|
|
597
|
+
responses.append(json.loads(line))
|
|
598
|
+
except json.JSONDecodeError as error:
|
|
599
|
+
fail_package_smoke(context, cmd, f"MCP stdout line is not JSON: {error}")
|
|
600
|
+
assert_design_ai_mcp_protocol_responses(responses, context=context, cmd=cmd)
|
|
601
|
+
|
|
602
|
+
|
|
536
603
|
def run_expected_failure(
|
|
537
604
|
cmd: list[str],
|
|
538
605
|
*,
|
|
@@ -5095,6 +5162,97 @@ def assert_learning_stats_human(raw: str, *, context: str, cmd: list[str]) -> No
|
|
|
5095
5162
|
)
|
|
5096
5163
|
|
|
5097
5164
|
|
|
5165
|
+
def assert_learning_recall_json(
|
|
5166
|
+
raw: str,
|
|
5167
|
+
*,
|
|
5168
|
+
context: str,
|
|
5169
|
+
cmd: list[str],
|
|
5170
|
+
) -> None:
|
|
5171
|
+
assert_no_ansi(raw, cmd)
|
|
5172
|
+
try:
|
|
5173
|
+
payload = json.loads(raw)
|
|
5174
|
+
except json.JSONDecodeError as error:
|
|
5175
|
+
raise SystemExit(f"{context}: failed to parse learn recall JSON") from error
|
|
5176
|
+
|
|
5177
|
+
require_package_smoke(
|
|
5178
|
+
isinstance(payload, dict) and payload.get("query") == "korean mobile",
|
|
5179
|
+
context=context,
|
|
5180
|
+
cmd=cmd,
|
|
5181
|
+
message="learn recall JSON must echo the query",
|
|
5182
|
+
)
|
|
5183
|
+
corpus = payload.get("corpus")
|
|
5184
|
+
require_package_smoke(
|
|
5185
|
+
isinstance(corpus, dict)
|
|
5186
|
+
and isinstance(corpus.get("candidateCount"), int)
|
|
5187
|
+
and corpus.get("candidateCount") > 0
|
|
5188
|
+
and isinstance(corpus.get("selectedCount"), int)
|
|
5189
|
+
and isinstance(corpus.get("selected"), list),
|
|
5190
|
+
context=context,
|
|
5191
|
+
cmd=cmd,
|
|
5192
|
+
message="learn recall corpus block shape changed",
|
|
5193
|
+
)
|
|
5194
|
+
learning = payload.get("learning")
|
|
5195
|
+
require_package_smoke(
|
|
5196
|
+
isinstance(learning, dict)
|
|
5197
|
+
and learning.get("mode") == "brief-relevance"
|
|
5198
|
+
and learning.get("candidateCount") == 3
|
|
5199
|
+
and isinstance(learning.get("selected"), list),
|
|
5200
|
+
context=context,
|
|
5201
|
+
cmd=cmd,
|
|
5202
|
+
message="learn recall learning block shape changed",
|
|
5203
|
+
)
|
|
5204
|
+
selected = learning.get("selected")
|
|
5205
|
+
require_package_smoke(
|
|
5206
|
+
len(selected) >= 1
|
|
5207
|
+
and isinstance(selected[0], dict)
|
|
5208
|
+
and selected[0].get("id") == "learn-korean"
|
|
5209
|
+
and selected[0].get("category") == "korean"
|
|
5210
|
+
and isinstance(selected[0].get("matchedTokens"), list),
|
|
5211
|
+
context=context,
|
|
5212
|
+
cmd=cmd,
|
|
5213
|
+
message="learn recall should rank the Korean learning entry for a Korean query",
|
|
5214
|
+
)
|
|
5215
|
+
|
|
5216
|
+
|
|
5217
|
+
def assert_learning_recall_smoke(
|
|
5218
|
+
command_factory,
|
|
5219
|
+
profile_path: Path,
|
|
5220
|
+
*,
|
|
5221
|
+
env: dict[str, str],
|
|
5222
|
+
cwd: Path | None = None,
|
|
5223
|
+
context: str,
|
|
5224
|
+
) -> None:
|
|
5225
|
+
write_learning_stats_fixture(profile_path)
|
|
5226
|
+
|
|
5227
|
+
json_cmd = command_factory(
|
|
5228
|
+
"learn",
|
|
5229
|
+
"--recall",
|
|
5230
|
+
"korean mobile",
|
|
5231
|
+
"--file",
|
|
5232
|
+
str(profile_path),
|
|
5233
|
+
"--json",
|
|
5234
|
+
)
|
|
5235
|
+
json_result = run_plain(json_cmd, cwd=cwd, env=env)
|
|
5236
|
+
assert_learning_recall_json(json_result.stdout, context=f"{context} JSON", cmd=json_cmd)
|
|
5237
|
+
|
|
5238
|
+
before = profile_path.read_text(encoding="utf-8")
|
|
5239
|
+
human_cmd = command_factory("learn", "--recall", "korean mobile", "--file", str(profile_path))
|
|
5240
|
+
human_result = run_plain(human_cmd, cwd=cwd, env=env)
|
|
5241
|
+
require_package_smoke(
|
|
5242
|
+
"Recall (read-only)" in human_result.stdout
|
|
5243
|
+
and "Privacy: recall is read-only" in human_result.stdout,
|
|
5244
|
+
context=f"{context} human",
|
|
5245
|
+
cmd=human_cmd,
|
|
5246
|
+
message="learn recall human output missing read-only markers",
|
|
5247
|
+
)
|
|
5248
|
+
require_package_smoke(
|
|
5249
|
+
profile_path.read_text(encoding="utf-8") == before,
|
|
5250
|
+
context=f"{context} read-only",
|
|
5251
|
+
cmd=human_cmd,
|
|
5252
|
+
message="learn recall must not mutate the learning profile",
|
|
5253
|
+
)
|
|
5254
|
+
|
|
5255
|
+
|
|
5098
5256
|
def assert_learning_stats_smoke(
|
|
5099
5257
|
command_factory,
|
|
5100
5258
|
profile_path: Path,
|
|
@@ -8155,7 +8313,7 @@ def assert_learning_relevance_context(payload: dict[str, object], *, context: st
|
|
|
8155
8313
|
message="learning selection explanation should mark the relevant entry as a brief match",
|
|
8156
8314
|
)
|
|
8157
8315
|
require_package_smoke(
|
|
8158
|
-
|
|
8316
|
+
type(selected_entry.get("score")) in (int, float) and selected_entry.get("score") > 0,
|
|
8159
8317
|
context=context,
|
|
8160
8318
|
cmd=cmd,
|
|
8161
8319
|
message="learning selection explanation should include a positive relevance score",
|
|
@@ -8208,6 +8366,85 @@ def assert_learning_relevance_context(payload: dict[str, object], *, context: st
|
|
|
8208
8366
|
)
|
|
8209
8367
|
|
|
8210
8368
|
|
|
8369
|
+
def assert_recall_context(payload: dict[str, object], *, context: str, cmd: list[str]) -> None:
|
|
8370
|
+
recall = payload.get("recall")
|
|
8371
|
+
require_package_smoke(
|
|
8372
|
+
isinstance(recall, dict),
|
|
8373
|
+
context=context,
|
|
8374
|
+
cmd=cmd,
|
|
8375
|
+
message="recall should be present when --with-recall is used",
|
|
8376
|
+
)
|
|
8377
|
+
|
|
8378
|
+
require_package_smoke(
|
|
8379
|
+
recall.get("mode") == "lexical",
|
|
8380
|
+
context=context,
|
|
8381
|
+
cmd=cmd,
|
|
8382
|
+
message="recall should use the deterministic lexical scorer",
|
|
8383
|
+
)
|
|
8384
|
+
require_package_smoke(
|
|
8385
|
+
isinstance(recall.get("candidateCount"), int) and recall.get("candidateCount") > 0,
|
|
8386
|
+
context=context,
|
|
8387
|
+
cmd=cmd,
|
|
8388
|
+
message="recall should report the corpus candidate count it scanned",
|
|
8389
|
+
)
|
|
8390
|
+
selected_count = recall.get("selectedCount")
|
|
8391
|
+
require_package_smoke(
|
|
8392
|
+
isinstance(selected_count, int) and selected_count >= 1,
|
|
8393
|
+
context=context,
|
|
8394
|
+
cmd=cmd,
|
|
8395
|
+
message="recall should select at least one corpus file for the Button brief",
|
|
8396
|
+
)
|
|
8397
|
+
|
|
8398
|
+
selected = recall.get("selected")
|
|
8399
|
+
require_package_smoke(
|
|
8400
|
+
isinstance(selected, list) and len(selected) == selected_count and len(selected) >= 1,
|
|
8401
|
+
context=context,
|
|
8402
|
+
cmd=cmd,
|
|
8403
|
+
message="recall selected list should match the reported selected count",
|
|
8404
|
+
)
|
|
8405
|
+
top = selected[0]
|
|
8406
|
+
require_package_smoke(
|
|
8407
|
+
isinstance(top, dict) and isinstance(top.get("id"), str) and top.get("id"),
|
|
8408
|
+
context=context,
|
|
8409
|
+
cmd=cmd,
|
|
8410
|
+
message="recall selection should cite the corpus relPath as its id",
|
|
8411
|
+
)
|
|
8412
|
+
require_package_smoke(
|
|
8413
|
+
type(top.get("score")) in (int, float) and top.get("score") > 0,
|
|
8414
|
+
context=context,
|
|
8415
|
+
cmd=cmd,
|
|
8416
|
+
message="recall selection should include a positive relevance score",
|
|
8417
|
+
)
|
|
8418
|
+
require_package_smoke(
|
|
8419
|
+
isinstance(top.get("matchedTokens"), list) and len(top.get("matchedTokens")) >= 1,
|
|
8420
|
+
context=context,
|
|
8421
|
+
cmd=cmd,
|
|
8422
|
+
message="recall selection should list the matched brief tokens",
|
|
8423
|
+
)
|
|
8424
|
+
|
|
8425
|
+
markdown = recall.get("markdown")
|
|
8426
|
+
require_package_smoke(
|
|
8427
|
+
isinstance(markdown, str) and "## Recalled design knowledge" in markdown,
|
|
8428
|
+
context=context,
|
|
8429
|
+
cmd=cmd,
|
|
8430
|
+
message="recall markdown should carry the Recalled design knowledge section",
|
|
8431
|
+
)
|
|
8432
|
+
require_package_smoke(
|
|
8433
|
+
top.get("id") in markdown,
|
|
8434
|
+
context=context,
|
|
8435
|
+
cmd=cmd,
|
|
8436
|
+
message="recall markdown should cite the top recalled corpus file",
|
|
8437
|
+
)
|
|
8438
|
+
|
|
8439
|
+
prompt = payload.get("prompt")
|
|
8440
|
+
require_package_smoke(
|
|
8441
|
+
isinstance(prompt, str) and "## Recalled design knowledge" in prompt,
|
|
8442
|
+
context=context,
|
|
8443
|
+
cmd=cmd,
|
|
8444
|
+
message="prompt markdown should render the recalled knowledge section",
|
|
8445
|
+
)
|
|
8446
|
+
|
|
8447
|
+
|
|
8211
8448
|
def assert_learning_usage_payload(
|
|
8212
8449
|
payload: dict[str, object],
|
|
8213
8450
|
*,
|
|
@@ -11007,7 +11244,7 @@ def assert_learning_query_json(
|
|
|
11007
11244
|
require_package_smoke(
|
|
11008
11245
|
selected[0].get("id") == "learn-relevant"
|
|
11009
11246
|
and selected[0].get("reason") == "brief-match"
|
|
11010
|
-
and
|
|
11247
|
+
and type(selected[0].get("score")) in (int, float)
|
|
11011
11248
|
and selected[0].get("score") > 0,
|
|
11012
11249
|
context=context,
|
|
11013
11250
|
cmd=cmd,
|
|
@@ -11033,7 +11270,7 @@ def assert_learning_query_human(raw: str, *, context: str, cmd: list[str]) -> No
|
|
|
11033
11270
|
"Limit: 2",
|
|
11034
11271
|
"Explain: selection score, matched tokens, and reason",
|
|
11035
11272
|
"[accessibility] Prioritize keyboard accessibility details for Button component API specs",
|
|
11036
|
-
"matched
|
|
11273
|
+
"matched accessibility, keyboard",
|
|
11037
11274
|
"reason brief-match",
|
|
11038
11275
|
):
|
|
11039
11276
|
require_package_smoke(
|
|
@@ -11243,6 +11480,49 @@ def assert_learning_relevance_smoke(
|
|
|
11243
11480
|
cmd=pack_cmd,
|
|
11244
11481
|
)
|
|
11245
11482
|
|
|
11483
|
+
recall_prompt_cmd = command_factory(
|
|
11484
|
+
"prompt",
|
|
11485
|
+
EXPECTED_ROUTE_BRIEF,
|
|
11486
|
+
"--route",
|
|
11487
|
+
EXPECTED_ROUTE_ID,
|
|
11488
|
+
"--with-recall",
|
|
11489
|
+
"--recall-limit",
|
|
11490
|
+
"3",
|
|
11491
|
+
"--json",
|
|
11492
|
+
)
|
|
11493
|
+
recall_prompt_result = run_plain(recall_prompt_cmd, cwd=cwd, env=relevance_env)
|
|
11494
|
+
try:
|
|
11495
|
+
recall_prompt_payload = json.loads(recall_prompt_result.stdout)
|
|
11496
|
+
except json.JSONDecodeError as error:
|
|
11497
|
+
raise SystemExit(f"{context}: failed to parse prompt recall JSON") from error
|
|
11498
|
+
assert_recall_context(recall_prompt_payload, context=f"{context} prompt recall", cmd=recall_prompt_cmd)
|
|
11499
|
+
|
|
11500
|
+
recall_pack_cmd = command_factory(
|
|
11501
|
+
"pack",
|
|
11502
|
+
EXPECTED_ROUTE_BRIEF,
|
|
11503
|
+
"--route",
|
|
11504
|
+
EXPECTED_ROUTE_ID,
|
|
11505
|
+
"--with-recall",
|
|
11506
|
+
"--recall-limit",
|
|
11507
|
+
"3",
|
|
11508
|
+
"--max-bytes",
|
|
11509
|
+
str(EXPECTED_PACK_MAX_BYTES),
|
|
11510
|
+
"--json",
|
|
11511
|
+
)
|
|
11512
|
+
recall_pack_result = run_plain(recall_pack_cmd, cwd=cwd, env=relevance_env)
|
|
11513
|
+
try:
|
|
11514
|
+
recall_pack_payload = json.loads(recall_pack_result.stdout)
|
|
11515
|
+
except json.JSONDecodeError as error:
|
|
11516
|
+
raise SystemExit(f"{context}: failed to parse pack recall JSON") from error
|
|
11517
|
+
recall_plan = recall_pack_payload.get("plan")
|
|
11518
|
+
require_package_smoke(
|
|
11519
|
+
isinstance(recall_plan, dict),
|
|
11520
|
+
context=f"{context} pack recall",
|
|
11521
|
+
cmd=recall_pack_cmd,
|
|
11522
|
+
message="pack recall plan missing",
|
|
11523
|
+
)
|
|
11524
|
+
assert_recall_context(recall_plan, context=f"{context} pack recall plan", cmd=recall_pack_cmd)
|
|
11525
|
+
|
|
11246
11526
|
usage_human_cmd = command_factory(
|
|
11247
11527
|
"learn",
|
|
11248
11528
|
"--usage",
|
|
@@ -12394,6 +12674,258 @@ def assert_learning_relevance_smoke(
|
|
|
12394
12674
|
)
|
|
12395
12675
|
|
|
12396
12676
|
|
|
12677
|
+
def assert_index_roundtrip_smoke(
|
|
12678
|
+
command_factory,
|
|
12679
|
+
index_dir: Path,
|
|
12680
|
+
profile_path: Path,
|
|
12681
|
+
*,
|
|
12682
|
+
env: dict[str, str],
|
|
12683
|
+
cwd: Path | None = None,
|
|
12684
|
+
context: str,
|
|
12685
|
+
) -> None:
|
|
12686
|
+
index_dir.mkdir(parents=True, exist_ok=True)
|
|
12687
|
+
write_learning_relevance_fixture(profile_path)
|
|
12688
|
+
index_env = env.copy()
|
|
12689
|
+
index_env["DESIGN_AI_INDEX_DIR"] = str(index_dir)
|
|
12690
|
+
index_env["DESIGN_AI_LEARNING_FILE"] = str(profile_path)
|
|
12691
|
+
|
|
12692
|
+
build_cmd = command_factory("index", "--build", "--json")
|
|
12693
|
+
build_result = run_plain(build_cmd, cwd=cwd, env=index_env)
|
|
12694
|
+
assert_index_build_json(
|
|
12695
|
+
build_result.stdout,
|
|
12696
|
+
index_dir=str(index_dir),
|
|
12697
|
+
context=f"{context} build",
|
|
12698
|
+
cmd=build_cmd,
|
|
12699
|
+
)
|
|
12700
|
+
|
|
12701
|
+
status_cmd = command_factory("index", "--status", "--json")
|
|
12702
|
+
status_result = run_plain(status_cmd, cwd=cwd, env=index_env)
|
|
12703
|
+
assert_index_status_json(
|
|
12704
|
+
status_result.stdout,
|
|
12705
|
+
index_dir=str(index_dir),
|
|
12706
|
+
context=f"{context} status",
|
|
12707
|
+
cmd=status_cmd,
|
|
12708
|
+
)
|
|
12709
|
+
|
|
12710
|
+
verify_cmd = command_factory("index", "--verify", "--json")
|
|
12711
|
+
verify_result = run_plain(verify_cmd, cwd=cwd, env=index_env)
|
|
12712
|
+
assert_index_verify_json(
|
|
12713
|
+
verify_result.stdout,
|
|
12714
|
+
index_dir=str(index_dir),
|
|
12715
|
+
context=f"{context} verify",
|
|
12716
|
+
cmd=verify_cmd,
|
|
12717
|
+
)
|
|
12718
|
+
|
|
12719
|
+
|
|
12720
|
+
def assert_ranked_search_determinism_smoke(
|
|
12721
|
+
command_factory,
|
|
12722
|
+
index_dir: Path,
|
|
12723
|
+
*,
|
|
12724
|
+
env: dict[str, str],
|
|
12725
|
+
cwd: Path | None = None,
|
|
12726
|
+
context: str,
|
|
12727
|
+
) -> None:
|
|
12728
|
+
index_dir.mkdir(parents=True, exist_ok=True)
|
|
12729
|
+
ranked_env = env.copy()
|
|
12730
|
+
ranked_env["DESIGN_AI_INDEX_DIR"] = str(index_dir)
|
|
12731
|
+
|
|
12732
|
+
ranked_cmd = command_factory(
|
|
12733
|
+
"search",
|
|
12734
|
+
EXPECTED_CORPUS_SEARCH_QUERY,
|
|
12735
|
+
"--dir",
|
|
12736
|
+
"knowledge",
|
|
12737
|
+
"--limit",
|
|
12738
|
+
str(EXPECTED_RANKED_SEARCH_LIMIT),
|
|
12739
|
+
"--ranked",
|
|
12740
|
+
"--json",
|
|
12741
|
+
)
|
|
12742
|
+
first_result = run_plain(ranked_cmd, cwd=cwd, env=ranked_env)
|
|
12743
|
+
second_result = run_plain(ranked_cmd, cwd=cwd, env=ranked_env)
|
|
12744
|
+
assert_ranked_search_json(
|
|
12745
|
+
first_result.stdout,
|
|
12746
|
+
context=f"{context} payload",
|
|
12747
|
+
cmd=ranked_cmd,
|
|
12748
|
+
expected_notice=EXPECTED_RANKED_SEARCH_NOT_BUILT_NOTICE,
|
|
12749
|
+
)
|
|
12750
|
+
assert_ranked_search_determinism(
|
|
12751
|
+
first_result.stdout,
|
|
12752
|
+
second_result.stdout,
|
|
12753
|
+
context=context,
|
|
12754
|
+
cmd=ranked_cmd,
|
|
12755
|
+
)
|
|
12756
|
+
|
|
12757
|
+
|
|
12758
|
+
def assert_embeddings_off_by_default_smoke(
|
|
12759
|
+
command_factory,
|
|
12760
|
+
index_dir: Path,
|
|
12761
|
+
profile_path: Path,
|
|
12762
|
+
*,
|
|
12763
|
+
env: dict[str, str],
|
|
12764
|
+
cwd: Path | None = None,
|
|
12765
|
+
context: str,
|
|
12766
|
+
) -> None:
|
|
12767
|
+
"""Phase B is opt-in: a plain `index --build` never touches embeddings, and
|
|
12768
|
+
`index --status` reports `embeddings: null` (the feature is simply unused)."""
|
|
12769
|
+
index_dir.mkdir(parents=True, exist_ok=True)
|
|
12770
|
+
write_learning_relevance_fixture(profile_path)
|
|
12771
|
+
index_env = env.copy()
|
|
12772
|
+
index_env["DESIGN_AI_INDEX_DIR"] = str(index_dir)
|
|
12773
|
+
index_env["DESIGN_AI_LEARNING_FILE"] = str(profile_path)
|
|
12774
|
+
# Point at a config file that is guaranteed not to exist, rather than relying on
|
|
12775
|
+
# the ambient environment lacking DESIGN_AI_CONFIG_FILE — a real ~/.design-ai/
|
|
12776
|
+
# config.json on the machine running this smoke must not leak into the test.
|
|
12777
|
+
index_env["DESIGN_AI_CONFIG_FILE"] = str(index_dir.parent / "no-config-here" / "config.json")
|
|
12778
|
+
|
|
12779
|
+
build_cmd = command_factory("index", "--build", "--json")
|
|
12780
|
+
build_result = run_plain(build_cmd, cwd=cwd, env=index_env)
|
|
12781
|
+
build_payload = json.loads(build_result.stdout)
|
|
12782
|
+
if "embeddings" in build_payload:
|
|
12783
|
+
raise SystemExit(f"index build JSON after {context} unexpectedly ran provider execution with plain --build")
|
|
12784
|
+
|
|
12785
|
+
status_cmd = command_factory("index", "--status", "--json")
|
|
12786
|
+
status_result = run_plain(status_cmd, cwd=cwd, env=index_env)
|
|
12787
|
+
assert_index_status_json(
|
|
12788
|
+
status_result.stdout,
|
|
12789
|
+
index_dir=str(index_dir),
|
|
12790
|
+
context=f"{context} status",
|
|
12791
|
+
cmd=status_cmd,
|
|
12792
|
+
expect_embeddings=False,
|
|
12793
|
+
)
|
|
12794
|
+
|
|
12795
|
+
|
|
12796
|
+
def assert_search_embeddings_no_provider_fallback_smoke(
|
|
12797
|
+
command_factory,
|
|
12798
|
+
*,
|
|
12799
|
+
env: dict[str, str],
|
|
12800
|
+
cwd: Path | None = None,
|
|
12801
|
+
context: str,
|
|
12802
|
+
config_file: Path,
|
|
12803
|
+
) -> None:
|
|
12804
|
+
"""`search --ranked --embeddings` with no provider configured (flag or config)
|
|
12805
|
+
degrades to the lexical backend with a notice and exit code 0."""
|
|
12806
|
+
fallback_env = env.copy()
|
|
12807
|
+
# Same isolation rationale as assert_embeddings_off_by_default_smoke: point at a
|
|
12808
|
+
# config path guaranteed not to exist rather than trusting the ambient environment.
|
|
12809
|
+
fallback_env["DESIGN_AI_CONFIG_FILE"] = str(config_file)
|
|
12810
|
+
|
|
12811
|
+
search_cmd = command_factory(
|
|
12812
|
+
"search",
|
|
12813
|
+
EXPECTED_CORPUS_SEARCH_QUERY,
|
|
12814
|
+
"--dir",
|
|
12815
|
+
"knowledge",
|
|
12816
|
+
"--limit",
|
|
12817
|
+
str(EXPECTED_RANKED_SEARCH_LIMIT),
|
|
12818
|
+
"--ranked",
|
|
12819
|
+
"--embeddings",
|
|
12820
|
+
"--json",
|
|
12821
|
+
)
|
|
12822
|
+
result = run_plain(search_cmd, cwd=cwd, env=fallback_env)
|
|
12823
|
+
assert_ranked_search_json(
|
|
12824
|
+
result.stdout,
|
|
12825
|
+
context=f"{context} payload",
|
|
12826
|
+
cmd=search_cmd,
|
|
12827
|
+
expected_backend="lexical",
|
|
12828
|
+
)
|
|
12829
|
+
payload = json.loads(result.stdout)
|
|
12830
|
+
if "embedding provider" not in payload.get("notice", "") and "no embedding provider configured" not in payload.get("notice", ""):
|
|
12831
|
+
raise SystemExit(f"search ranked JSON after {context} fallback notice does not mention the missing provider")
|
|
12832
|
+
|
|
12833
|
+
|
|
12834
|
+
STUB_EMBEDDING_PROVIDER_SCRIPT = """
|
|
12835
|
+
const chunks = [];
|
|
12836
|
+
process.stdin.on("data", (c) => chunks.push(c));
|
|
12837
|
+
process.stdin.on("end", () => {
|
|
12838
|
+
const lines = Buffer.concat(chunks).toString("utf8").trim().split("\\n").filter(Boolean);
|
|
12839
|
+
for (const line of lines) {
|
|
12840
|
+
const { id } = JSON.parse(line);
|
|
12841
|
+
let seed = 0;
|
|
12842
|
+
for (const ch of id) seed = (seed * 31 + ch.charCodeAt(0)) % 997;
|
|
12843
|
+
const vector = [seed / 997, (seed + 1) / 997, (seed + 2) / 997];
|
|
12844
|
+
process.stdout.write(JSON.stringify({ id, vector }) + "\\n");
|
|
12845
|
+
}
|
|
12846
|
+
});
|
|
12847
|
+
"""
|
|
12848
|
+
|
|
12849
|
+
|
|
12850
|
+
def write_stub_embedding_provider(root: Path) -> Path:
|
|
12851
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
12852
|
+
script_path = root / "stub-embedding-provider.mjs"
|
|
12853
|
+
script_path.write_text(STUB_EMBEDDING_PROVIDER_SCRIPT, encoding="utf-8")
|
|
12854
|
+
return script_path
|
|
12855
|
+
|
|
12856
|
+
|
|
12857
|
+
def assert_embedding_stub_provider_roundtrip_smoke(
|
|
12858
|
+
command_factory,
|
|
12859
|
+
index_dir: Path,
|
|
12860
|
+
profile_path: Path,
|
|
12861
|
+
script_dir: Path,
|
|
12862
|
+
*,
|
|
12863
|
+
env: dict[str, str],
|
|
12864
|
+
cwd: Path | None = None,
|
|
12865
|
+
context: str,
|
|
12866
|
+
) -> None:
|
|
12867
|
+
"""A tiny deterministic node stub provider round-trips through `index --build
|
|
12868
|
+
--embeddings --provider`, `index --status`, `index --verify`, and
|
|
12869
|
+
`search --ranked --embeddings` (installed-bin path; documents the --provider
|
|
12870
|
+
"command args" convention of splitting a single string on spaces)."""
|
|
12871
|
+
index_dir.mkdir(parents=True, exist_ok=True)
|
|
12872
|
+
write_learning_relevance_fixture(profile_path)
|
|
12873
|
+
script_path = write_stub_embedding_provider(script_dir)
|
|
12874
|
+
provider_flag = f"node {script_path}"
|
|
12875
|
+
|
|
12876
|
+
index_env = env.copy()
|
|
12877
|
+
index_env["DESIGN_AI_INDEX_DIR"] = str(index_dir)
|
|
12878
|
+
index_env["DESIGN_AI_LEARNING_FILE"] = str(profile_path)
|
|
12879
|
+
index_env.pop("DESIGN_AI_CONFIG_FILE", None)
|
|
12880
|
+
|
|
12881
|
+
build_cmd = command_factory("index", "--build", "--embeddings", "--provider", provider_flag, "--json")
|
|
12882
|
+
build_result = run_plain(build_cmd, cwd=cwd, env=index_env)
|
|
12883
|
+
build_payload = json.loads(build_result.stdout)
|
|
12884
|
+
if build_payload.get("embeddings", {}).get("ok") is not True:
|
|
12885
|
+
raise SystemExit(f"index build JSON after {context} embeddings did not build successfully")
|
|
12886
|
+
|
|
12887
|
+
status_cmd = command_factory("index", "--status", "--json")
|
|
12888
|
+
status_result = run_plain(status_cmd, cwd=cwd, env=index_env)
|
|
12889
|
+
assert_index_status_json(
|
|
12890
|
+
status_result.stdout,
|
|
12891
|
+
index_dir=str(index_dir),
|
|
12892
|
+
context=f"{context} status",
|
|
12893
|
+
cmd=status_cmd,
|
|
12894
|
+
expect_embeddings=True,
|
|
12895
|
+
)
|
|
12896
|
+
|
|
12897
|
+
verify_cmd = command_factory("index", "--verify", "--provider", provider_flag, "--json")
|
|
12898
|
+
verify_result = run_plain(verify_cmd, cwd=cwd, env=index_env)
|
|
12899
|
+
assert_index_verify_json(
|
|
12900
|
+
verify_result.stdout,
|
|
12901
|
+
index_dir=str(index_dir),
|
|
12902
|
+
context=f"{context} verify",
|
|
12903
|
+
cmd=verify_cmd,
|
|
12904
|
+
expect_embeddings_check=True,
|
|
12905
|
+
)
|
|
12906
|
+
|
|
12907
|
+
search_cmd = command_factory(
|
|
12908
|
+
"search",
|
|
12909
|
+
EXPECTED_CORPUS_SEARCH_QUERY,
|
|
12910
|
+
"--dir",
|
|
12911
|
+
"knowledge",
|
|
12912
|
+
"--limit",
|
|
12913
|
+
str(EXPECTED_RANKED_SEARCH_LIMIT),
|
|
12914
|
+
"--ranked",
|
|
12915
|
+
"--embeddings",
|
|
12916
|
+
"--provider",
|
|
12917
|
+
provider_flag,
|
|
12918
|
+
"--json",
|
|
12919
|
+
)
|
|
12920
|
+
search_result = run_plain(search_cmd, cwd=cwd, env=index_env)
|
|
12921
|
+
assert_ranked_search_json(
|
|
12922
|
+
search_result.stdout,
|
|
12923
|
+
context=f"{context} search payload",
|
|
12924
|
+
cmd=search_cmd,
|
|
12925
|
+
expected_backend="embeddings",
|
|
12926
|
+
)
|
|
12927
|
+
|
|
12928
|
+
|
|
12397
12929
|
def run_self_test() -> None:
|
|
12398
12930
|
context = "package smoke self-test"
|
|
12399
12931
|
cmd = ["design-ai", "doctor", "--json"]
|
|
@@ -12482,6 +13014,37 @@ def run_self_test() -> None:
|
|
|
12482
13014
|
site_bundle_mcp_probes_payload,
|
|
12483
13015
|
context=f"{context} site bundle mcp-probes.json",
|
|
12484
13016
|
)
|
|
13017
|
+
mcp_protocol_cmd = ["design-ai-mcp"]
|
|
13018
|
+
assert_design_ai_mcp_protocol_responses(
|
|
13019
|
+
[
|
|
13020
|
+
{"jsonrpc": "2.0", "id": 1, "result": {"serverInfo": {"name": "design-ai"}}},
|
|
13021
|
+
{"jsonrpc": "2.0", "id": 2, "result": {"tools": [{"name": "design_ai_route"}, {"name": "design_ai_search"}]}},
|
|
13022
|
+
{
|
|
13023
|
+
"jsonrpc": "2.0",
|
|
13024
|
+
"id": 3,
|
|
13025
|
+
"error": {"code": -32602, "message": "design_ai_search.limit must be an integer"},
|
|
13026
|
+
},
|
|
13027
|
+
],
|
|
13028
|
+
context=f"{context} design-ai MCP protocol",
|
|
13029
|
+
cmd=mcp_protocol_cmd,
|
|
13030
|
+
)
|
|
13031
|
+
expect_self_test_failure(
|
|
13032
|
+
lambda: assert_design_ai_mcp_protocol_responses(
|
|
13033
|
+
[
|
|
13034
|
+
{"jsonrpc": "2.0", "id": 1, "result": {"serverInfo": {"name": "design-ai"}}},
|
|
13035
|
+
{"jsonrpc": "2.0", "id": 2, "result": {"tools": [{"name": "design_ai_route"}, {"name": "design_ai_search"}]}},
|
|
13036
|
+
{
|
|
13037
|
+
"jsonrpc": "2.0",
|
|
13038
|
+
"id": 3,
|
|
13039
|
+
"error": {"code": -32602, "message": "wrong validation message"},
|
|
13040
|
+
},
|
|
13041
|
+
],
|
|
13042
|
+
context=f"{context} design-ai MCP protocol",
|
|
13043
|
+
cmd=mcp_protocol_cmd,
|
|
13044
|
+
),
|
|
13045
|
+
expected="MCP invalid argument response did not preserve invalid params validation",
|
|
13046
|
+
scope="package smoke",
|
|
13047
|
+
)
|
|
12485
13048
|
for label in (
|
|
12486
13049
|
"site bundle check",
|
|
12487
13050
|
"site bundle check summary",
|
|
@@ -13968,8 +14531,8 @@ def run_self_test() -> None:
|
|
|
13968
14531
|
{
|
|
13969
14532
|
"id": "learn-relevant",
|
|
13970
14533
|
"category": "accessibility",
|
|
13971
|
-
"score":
|
|
13972
|
-
"matchedTokens": ["
|
|
14534
|
+
"score": 2.114533,
|
|
14535
|
+
"matchedTokens": ["accessibility", "keyboard"],
|
|
13973
14536
|
"reason": "brief-match",
|
|
13974
14537
|
},
|
|
13975
14538
|
],
|
|
@@ -14035,7 +14598,7 @@ def run_self_test() -> None:
|
|
|
14035
14598
|
"",
|
|
14036
14599
|
"1. [accessibility] Prioritize keyboard accessibility details for Button component API specs",
|
|
14037
14600
|
" learn-relevant · 2026-05-22T00:00:01.000Z",
|
|
14038
|
-
" score
|
|
14601
|
+
" score 2.114533 · matched accessibility, keyboard · reason brief-match",
|
|
14039
14602
|
]),
|
|
14040
14603
|
context=context,
|
|
14041
14604
|
cmd=learn_query_human_cmd,
|
|
@@ -14049,7 +14612,7 @@ def run_self_test() -> None:
|
|
|
14049
14612
|
"Limit: 2",
|
|
14050
14613
|
"Explain: selection score, matched tokens, and reason",
|
|
14051
14614
|
"[accessibility] Prioritize keyboard accessibility details for Button component API specs",
|
|
14052
|
-
"matched
|
|
14615
|
+
"matched accessibility, keyboard",
|
|
14053
14616
|
]),
|
|
14054
14617
|
context=context,
|
|
14055
14618
|
cmd=learn_query_human_cmd,
|
|
@@ -14076,8 +14639,8 @@ def run_self_test() -> None:
|
|
|
14076
14639
|
{
|
|
14077
14640
|
"id": "learn-relevant",
|
|
14078
14641
|
"category": "accessibility",
|
|
14079
|
-
"score":
|
|
14080
|
-
"matchedTokens": ["
|
|
14642
|
+
"score": 2.114533,
|
|
14643
|
+
"matchedTokens": ["accessibility", "keyboard"],
|
|
14081
14644
|
"reason": "brief-match",
|
|
14082
14645
|
},
|
|
14083
14646
|
],
|
|
@@ -14228,6 +14791,59 @@ def run_self_test() -> None:
|
|
|
14228
14791
|
scope="package smoke",
|
|
14229
14792
|
)
|
|
14230
14793
|
|
|
14794
|
+
recall_payload = {
|
|
14795
|
+
"recall": {
|
|
14796
|
+
"query": EXPECTED_ROUTE_BRIEF,
|
|
14797
|
+
"mode": "lexical",
|
|
14798
|
+
"candidateCount": 42,
|
|
14799
|
+
"selectedCount": 2,
|
|
14800
|
+
"selected": [
|
|
14801
|
+
{
|
|
14802
|
+
"id": "knowledge/components/INDEX.md",
|
|
14803
|
+
"score": 9.5,
|
|
14804
|
+
"matchedTokens": ["button", "component"],
|
|
14805
|
+
},
|
|
14806
|
+
{
|
|
14807
|
+
"id": "knowledge/a11y/keyboard-and-focus.md",
|
|
14808
|
+
"score": 4.2,
|
|
14809
|
+
"matchedTokens": ["accessibility"],
|
|
14810
|
+
},
|
|
14811
|
+
],
|
|
14812
|
+
"markdown": (
|
|
14813
|
+
"## Recalled design knowledge\n\n"
|
|
14814
|
+
"- knowledge/components/INDEX.md\n"
|
|
14815
|
+
" - Component index\n"
|
|
14816
|
+
"- knowledge/a11y/keyboard-and-focus.md\n"
|
|
14817
|
+
" - Keyboard and focus"
|
|
14818
|
+
),
|
|
14819
|
+
},
|
|
14820
|
+
"prompt": (
|
|
14821
|
+
"Recalled corpus knowledge:\n\n"
|
|
14822
|
+
"## Recalled design knowledge\n\n"
|
|
14823
|
+
"- knowledge/components/INDEX.md"
|
|
14824
|
+
),
|
|
14825
|
+
}
|
|
14826
|
+
recall_cmd = ["design-ai", "prompt", EXPECTED_ROUTE_BRIEF, "--with-recall", "--json"]
|
|
14827
|
+
assert_recall_context(recall_payload, context=context, cmd=recall_cmd)
|
|
14828
|
+
expect_self_test_failure(
|
|
14829
|
+
lambda: assert_recall_context(
|
|
14830
|
+
{**recall_payload, "recall": {**recall_payload["recall"], "mode": "embeddings"}},
|
|
14831
|
+
context=context,
|
|
14832
|
+
cmd=recall_cmd,
|
|
14833
|
+
),
|
|
14834
|
+
expected="recall should use the deterministic lexical scorer",
|
|
14835
|
+
scope="package smoke",
|
|
14836
|
+
)
|
|
14837
|
+
expect_self_test_failure(
|
|
14838
|
+
lambda: assert_recall_context(
|
|
14839
|
+
{**recall_payload, "recall": {**recall_payload["recall"], "selectedCount": 0, "selected": []}},
|
|
14840
|
+
context=context,
|
|
14841
|
+
cmd=recall_cmd,
|
|
14842
|
+
),
|
|
14843
|
+
expected="recall should select at least one corpus file",
|
|
14844
|
+
scope="package smoke",
|
|
14845
|
+
)
|
|
14846
|
+
|
|
14231
14847
|
learning_usage_path = Path(tmp) / "learning.usage.json"
|
|
14232
14848
|
learning_usage_path.write_text(
|
|
14233
14849
|
json.dumps({
|
|
@@ -19602,6 +20218,9 @@ def smoke_tarball(tarball: Path) -> None:
|
|
|
19602
20218
|
bin_path = install_root / "node_modules" / ".bin" / "design-ai"
|
|
19603
20219
|
if not bin_path.exists():
|
|
19604
20220
|
raise SystemExit(f"design-ai bin shim not found: {bin_path}")
|
|
20221
|
+
mcp_bin_path = install_root / "node_modules" / ".bin" / "design-ai-mcp"
|
|
20222
|
+
if not mcp_bin_path.exists():
|
|
20223
|
+
raise SystemExit(f"design-ai-mcp bin shim not found: {mcp_bin_path}")
|
|
19605
20224
|
|
|
19606
20225
|
smoke_env = base_env.copy()
|
|
19607
20226
|
smoke_env.update({
|
|
@@ -19620,6 +20239,12 @@ def smoke_tarball(tarball: Path) -> None:
|
|
|
19620
20239
|
env=smoke_env,
|
|
19621
20240
|
context="package smoke installed bin version JSON",
|
|
19622
20241
|
)
|
|
20242
|
+
assert_design_ai_mcp_protocol_smoke(
|
|
20243
|
+
[str(mcp_bin_path)],
|
|
20244
|
+
cwd=install_root,
|
|
20245
|
+
env=smoke_env,
|
|
20246
|
+
context="package smoke installed bin design-ai-mcp protocol",
|
|
20247
|
+
)
|
|
19623
20248
|
assert_workspace_json_smoke(
|
|
19624
20249
|
[str(bin_path), "workspace", "--json"],
|
|
19625
20250
|
cwd=install_root,
|
|
@@ -20465,6 +21090,40 @@ def smoke_tarball(tarball: Path) -> None:
|
|
|
20465
21090
|
env=smoke_env,
|
|
20466
21091
|
context="package smoke installed bin search human corpus",
|
|
20467
21092
|
)
|
|
21093
|
+
assert_index_roundtrip_smoke(
|
|
21094
|
+
lambda *args: [str(bin_path), *args],
|
|
21095
|
+
tmp_root / "installed-retrieval-index",
|
|
21096
|
+
tmp_root / "installed-index-learning.json",
|
|
21097
|
+
env=smoke_env,
|
|
21098
|
+
context="package smoke installed bin index round-trip",
|
|
21099
|
+
)
|
|
21100
|
+
assert_ranked_search_determinism_smoke(
|
|
21101
|
+
lambda *args: [str(bin_path), *args],
|
|
21102
|
+
tmp_root / "installed-ranked-index",
|
|
21103
|
+
env=smoke_env,
|
|
21104
|
+
context="package smoke installed bin ranked search determinism",
|
|
21105
|
+
)
|
|
21106
|
+
assert_embeddings_off_by_default_smoke(
|
|
21107
|
+
lambda *args: [str(bin_path), *args],
|
|
21108
|
+
tmp_root / "installed-embeddings-off-index",
|
|
21109
|
+
tmp_root / "installed-embeddings-off-learning.json",
|
|
21110
|
+
env=smoke_env,
|
|
21111
|
+
context="package smoke installed bin embeddings off by default",
|
|
21112
|
+
)
|
|
21113
|
+
assert_search_embeddings_no_provider_fallback_smoke(
|
|
21114
|
+
lambda *args: [str(bin_path), *args],
|
|
21115
|
+
env=smoke_env,
|
|
21116
|
+
context="package smoke installed bin search embeddings no-provider fallback",
|
|
21117
|
+
config_file=tmp_root / "installed-no-config-here" / "config.json",
|
|
21118
|
+
)
|
|
21119
|
+
assert_embedding_stub_provider_roundtrip_smoke(
|
|
21120
|
+
lambda *args: [str(bin_path), *args],
|
|
21121
|
+
tmp_root / "installed-embedding-index",
|
|
21122
|
+
tmp_root / "installed-embedding-learning.json",
|
|
21123
|
+
tmp_root / "installed-embedding-provider",
|
|
21124
|
+
env=smoke_env,
|
|
21125
|
+
context="package smoke installed bin embedding stub provider round-trip",
|
|
21126
|
+
)
|
|
20468
21127
|
assert_show_smoke(
|
|
20469
21128
|
[str(bin_path), "show", EXPECTED_CORPUS_SHOW_TARGET, "--context", "0", "--json"],
|
|
20470
21129
|
env=smoke_env,
|
|
@@ -20912,6 +21571,12 @@ def smoke_tarball(tarball: Path) -> None:
|
|
|
20912
21571
|
env=smoke_env,
|
|
20913
21572
|
context="package smoke installed bin learn stats",
|
|
20914
21573
|
)
|
|
21574
|
+
assert_learning_recall_smoke(
|
|
21575
|
+
lambda *args: [str(bin_path), *args],
|
|
21576
|
+
tmp_root / "installed-recall-learning.json",
|
|
21577
|
+
env=smoke_env,
|
|
21578
|
+
context="package smoke installed bin learn recall",
|
|
21579
|
+
)
|
|
20915
21580
|
assert_learning_audit_cleanup_smoke(
|
|
20916
21581
|
lambda *args: [str(bin_path), *args],
|
|
20917
21582
|
tmp_root / "installed-learning.json",
|
|
@@ -20999,6 +21664,12 @@ def smoke_tarball(tarball: Path) -> None:
|
|
|
20999
21664
|
env=npx_env,
|
|
21000
21665
|
context="package smoke npm exec version JSON",
|
|
21001
21666
|
)
|
|
21667
|
+
assert_design_ai_mcp_protocol_smoke(
|
|
21668
|
+
npm_exec_mcp_cmd(tarball),
|
|
21669
|
+
cwd=npx_root,
|
|
21670
|
+
env=npx_env,
|
|
21671
|
+
context="package smoke npm exec design-ai-mcp protocol",
|
|
21672
|
+
)
|
|
21002
21673
|
assert_workspace_json_smoke(
|
|
21003
21674
|
npm_exec_cmd(tarball, "workspace", "--json"),
|
|
21004
21675
|
cwd=npx_root,
|
|
@@ -21837,6 +22508,36 @@ def smoke_tarball(tarball: Path) -> None:
|
|
|
21837
22508
|
env=npx_env,
|
|
21838
22509
|
context="package smoke npm exec search human corpus",
|
|
21839
22510
|
)
|
|
22511
|
+
assert_index_roundtrip_smoke(
|
|
22512
|
+
lambda *args: npm_exec_cmd(tarball, *args),
|
|
22513
|
+
npx_root / "npx-retrieval-index",
|
|
22514
|
+
npx_root / "npx-index-learning.json",
|
|
22515
|
+
cwd=npx_root,
|
|
22516
|
+
env=npx_env,
|
|
22517
|
+
context="package smoke npm exec index round-trip",
|
|
22518
|
+
)
|
|
22519
|
+
assert_ranked_search_determinism_smoke(
|
|
22520
|
+
lambda *args: npm_exec_cmd(tarball, *args),
|
|
22521
|
+
npx_root / "npx-ranked-index",
|
|
22522
|
+
cwd=npx_root,
|
|
22523
|
+
env=npx_env,
|
|
22524
|
+
context="package smoke npm exec ranked search determinism",
|
|
22525
|
+
)
|
|
22526
|
+
assert_embeddings_off_by_default_smoke(
|
|
22527
|
+
lambda *args: npm_exec_cmd(tarball, *args),
|
|
22528
|
+
npx_root / "npx-embeddings-off-index",
|
|
22529
|
+
npx_root / "npx-embeddings-off-learning.json",
|
|
22530
|
+
cwd=npx_root,
|
|
22531
|
+
env=npx_env,
|
|
22532
|
+
context="package smoke npm exec embeddings off by default",
|
|
22533
|
+
)
|
|
22534
|
+
assert_search_embeddings_no_provider_fallback_smoke(
|
|
22535
|
+
lambda *args: npm_exec_cmd(tarball, *args),
|
|
22536
|
+
cwd=npx_root,
|
|
22537
|
+
env=npx_env,
|
|
22538
|
+
context="package smoke npm exec search embeddings no-provider fallback",
|
|
22539
|
+
config_file=npx_root / "npx-no-config-here" / "config.json",
|
|
22540
|
+
)
|
|
21840
22541
|
assert_show_smoke(
|
|
21841
22542
|
npm_exec_cmd(tarball, "show", EXPECTED_CORPUS_SHOW_TARGET, "--context", "0", "--json"),
|
|
21842
22543
|
cwd=npx_root,
|
|
@@ -22329,6 +23030,13 @@ def smoke_tarball(tarball: Path) -> None:
|
|
|
22329
23030
|
env=npx_env,
|
|
22330
23031
|
context="package smoke npm exec learn stats",
|
|
22331
23032
|
)
|
|
23033
|
+
assert_learning_recall_smoke(
|
|
23034
|
+
lambda *args: npm_exec_cmd(tarball, *args),
|
|
23035
|
+
npx_root / "npx-recall-learning.json",
|
|
23036
|
+
cwd=npx_root,
|
|
23037
|
+
env=npx_env,
|
|
23038
|
+
context="package smoke npm exec learn recall",
|
|
23039
|
+
)
|
|
22332
23040
|
assert_learning_audit_cleanup_smoke(
|
|
22333
23041
|
lambda *args: npm_exec_cmd(tarball, *args),
|
|
22334
23042
|
npx_root / "npx-learning.json",
|