@qijenchen/design-system 0.1.0-beta.10 → 0.1.0-beta.13
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.md +201 -0
- package/README.md +7 -15
- package/cli-init.mjs +90 -0
- package/ds-canonical/commands/README.md +26 -0
- package/ds-canonical/commands/gov-status.md +79 -0
- package/ds-canonical/hooks/README.md +145 -0
- package/ds-canonical/hooks/_log-fire.sh +44 -0
- package/ds-canonical/hooks/block_prototype_imports.py +111 -0
- package/ds-canonical/hooks/check_app_shell_primary_header_consistency.sh +68 -0
- package/ds-canonical/hooks/check_audit_post_report_validator.sh +88 -0
- package/ds-canonical/hooks/check_audit_sample_escape.sh +73 -0
- package/ds-canonical/hooks/check_benchmark_citation.sh +106 -0
- package/ds-canonical/hooks/check_canonical_propagation.sh +189 -0
- package/ds-canonical/hooks/check_chrome_header_handcraft.sh +70 -0
- package/ds-canonical/hooks/check_codex_brief_invariants.sh +83 -0
- package/ds-canonical/hooks/check_codex_collab_5step.sh +108 -0
- package/ds-canonical/hooks/check_datatable_invariants.sh +117 -0
- package/ds-canonical/hooks/check_dim_count_drift.sh +72 -0
- package/ds-canonical/hooks/check_field_controls_contracts.sh +110 -0
- package/ds-canonical/hooks/check_field_family_invariants.sh +205 -0
- package/ds-canonical/hooks/check_file_size_budget.sh +60 -0
- package/ds-canonical/hooks/check_header_with_tabs_border.sh +87 -0
- package/ds-canonical/hooks/check_main_branch_workbench.sh +93 -0
- package/ds-canonical/hooks/check_naming_and_abstraction.sh +165 -0
- package/ds-canonical/hooks/check_opacity_token_usage.sh +149 -0
- package/ds-canonical/hooks/check_pattern_invariants.sh +194 -0
- package/ds-canonical/hooks/check_peoplepicker_ssot_drift.sh +56 -0
- package/ds-canonical/hooks/check_pixel_quantified_audit.sh +53 -0
- package/ds-canonical/hooks/check_propose_plain_chinese.sh +74 -0
- package/ds-canonical/hooks/check_propose_pre_grep_verify.sh +70 -0
- package/ds-canonical/hooks/check_select_all_canonical.sh +58 -0
- package/ds-canonical/hooks/check_solo_workflow.sh +258 -0
- package/ds-canonical/hooks/check_spec_class_drift.sh +88 -0
- package/ds-canonical/hooks/check_story_invariants.sh +612 -0
- package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +105 -0
- package/ds-canonical/hooks/check_tab_lg_chrome_header_equal.sh +66 -0
- package/ds-canonical/hooks/check_wrapper_primitive_schema_drift.sh +104 -0
- package/ds-canonical/hooks/enforce_home_charter.sh +44 -0
- package/ds-canonical/hooks/inject_pending_self_audit.sh +204 -0
- package/ds-canonical/hooks/lib/_approval_re.sh +33 -0
- package/ds-canonical/hooks/lib/_code_quality.sh +73 -0
- package/ds-canonical/hooks/lib/_cva_default_sync.sh +69 -0
- package/ds-canonical/hooks/lib/_governance_coverage_check.sh +49 -0
- package/ds-canonical/hooks/lib/_hardcoded_strings.sh +163 -0
- package/ds-canonical/hooks/lib/_layout_space_canonical.sh +56 -0
- package/ds-canonical/hooks/lib/_overlay_handcraft.sh +141 -0
- package/ds-canonical/hooks/lib/_person_data_richness.sh +42 -0
- package/ds-canonical/hooks/lib/_story_compile_drift.sh +48 -0
- package/ds-canonical/hooks/lib/_token_hygiene.sh +95 -0
- package/ds-canonical/hooks/log_governance_fires.sh +50 -0
- package/ds-canonical/hooks/log_skill_invokes.sh +41 -0
- package/ds-canonical/hooks/post_edit_dispatcher.sh +62 -0
- package/ds-canonical/hooks/retired/check_anatomy_section_numbering.sh +106 -0
- package/ds-canonical/hooks/retired/check_avatar_hovercard.sh +90 -0
- package/ds-canonical/hooks/retired/check_button_icon_literal.sh.retired-2026-04-28 +38 -0
- package/ds-canonical/hooks/retired/check_container_breathing.sh +142 -0
- package/ds-canonical/hooks/retired/check_governance_compliance.sh +61 -0
- package/ds-canonical/hooks/retired/check_icon_only_padding_formula.sh +104 -0
- package/ds-canonical/hooks/retired/check_item_content_primitive.sh +150 -0
- package/ds-canonical/hooks/retired/check_item_list_gap.sh +153 -0
- package/ds-canonical/hooks/retired/check_sideoffset_canonical.sh +65 -0
- package/ds-canonical/hooks/retired/check_spec_iteration_tag.sh +87 -0
- package/ds-canonical/hooks/retired/check_ssot_consultation.sh +88 -0
- package/ds-canonical/hooks/retired/check_sync_update.sh +20 -0
- package/ds-canonical/hooks/retired/check_third_party_dom_verified.sh +95 -0
- package/ds-canonical/hooks/retired/enforce_home_charter.sh +125 -0
- package/ds-canonical/hooks/retired/post_edit_canonical_interrogate.sh +109 -0
- package/ds-canonical/hooks/retired/pre_edit_spec_check.sh +68 -0
- package/ds-canonical/hooks/retired/pre_new_component_spec.sh +39 -0
- package/ds-canonical/hooks/retired/pre_write_subsumption_check.sh +112 -0
- package/ds-canonical/hooks/retired/stop_meta_self_audit.sh.retired-2026-05-13 +76 -0
- package/ds-canonical/hooks/retired/tests/test_check_anatomy_section_numbering.sh +14 -0
- package/ds-canonical/hooks/retired/tests/test_check_avatar_hovercard.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_check_container_breathing.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_check_governance_compliance.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_check_icon_only_padding_formula.sh +79 -0
- package/ds-canonical/hooks/retired/tests/test_check_item_content_primitive.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_check_item_list_gap.sh +163 -0
- package/ds-canonical/hooks/retired/tests/test_check_sideoffset_canonical.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_check_spec_iteration_tag.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_check_ssot_consultation.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_check_sync_update.sh +14 -0
- package/ds-canonical/hooks/retired/tests/test_check_third_party_dom_verified.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_enforce_home_charter.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_pre_edit_spec_check.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_pre_new_component_spec.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_pre_write_subsumption_check.sh +63 -0
- package/ds-canonical/hooks/session_start_governance_check.sh +263 -0
- package/ds-canonical/hooks/stop_passive_logging.sh +322 -0
- package/ds-canonical/hooks/stop_self_audit.sh +450 -0
- package/ds-canonical/hooks/tests/KNOWN-BROKEN.md +15 -0
- package/ds-canonical/hooks/tests/run-all.sh +76 -0
- package/ds-canonical/hooks/tests/test_block_prototype_imports.sh +143 -0
- package/ds-canonical/hooks/tests/test_check_app_shell_primary_header_consistency.sh +140 -0
- package/ds-canonical/hooks/tests/test_check_audit_post_report_validator.sh +115 -0
- package/ds-canonical/hooks/tests/test_check_audit_sample_escape.sh +93 -0
- package/ds-canonical/hooks/tests/test_check_benchmark_citation.sh +115 -0
- package/ds-canonical/hooks/tests/test_check_canonical_propagation.sh +133 -0
- package/ds-canonical/hooks/tests/test_check_chrome_header_handcraft.sh +123 -0
- package/ds-canonical/hooks/tests/test_check_code_quality.sh +15 -0
- package/ds-canonical/hooks/tests/test_check_codex_collab_5step.sh +96 -0
- package/ds-canonical/hooks/tests/test_check_cva_default_sync.sh +15 -0
- package/ds-canonical/hooks/tests/test_check_datatable_invariants.sh +122 -0
- package/ds-canonical/hooks/tests/test_check_dim_count_drift.sh +98 -0
- package/ds-canonical/hooks/tests/test_check_field_controls_contracts.sh +126 -0
- package/ds-canonical/hooks/tests/test_check_field_family_invariants.sh +194 -0
- package/ds-canonical/hooks/tests/test_check_file_size_budget.sh +32 -0
- package/ds-canonical/hooks/tests/test_check_hardcoded_strings.sh +14 -0
- package/ds-canonical/hooks/tests/test_check_header_with_tabs_border.sh +110 -0
- package/ds-canonical/hooks/tests/test_check_layout_space_canonical.sh +73 -0
- package/ds-canonical/hooks/tests/test_check_main_branch_workbench.sh +147 -0
- package/ds-canonical/hooks/tests/test_check_naming_and_abstraction.sh +136 -0
- package/ds-canonical/hooks/tests/test_check_opacity_token_usage.sh +110 -0
- package/ds-canonical/hooks/tests/test_check_overlay_handcraft.sh +126 -0
- package/ds-canonical/hooks/tests/test_check_pattern_invariants.sh +148 -0
- package/ds-canonical/hooks/tests/test_check_peoplepicker_ssot_drift.sh +108 -0
- package/ds-canonical/hooks/tests/test_check_person_data_richness.sh +58 -0
- package/ds-canonical/hooks/tests/test_check_pixel_quantified_audit.sh +142 -0
- package/ds-canonical/hooks/tests/test_check_propose_plain_chinese.sh +126 -0
- package/ds-canonical/hooks/tests/test_check_propose_pre_grep_verify.sh +117 -0
- package/ds-canonical/hooks/tests/test_check_select_all_canonical.sh +125 -0
- package/ds-canonical/hooks/tests/test_check_solo_workflow.sh +201 -0
- package/ds-canonical/hooks/tests/test_check_spec_class_drift.sh +135 -0
- package/ds-canonical/hooks/tests/test_check_story_anatomy.sh.broken +197 -0
- package/ds-canonical/hooks/tests/test_check_story_category.sh.broken +187 -0
- package/ds-canonical/hooks/tests/test_check_story_compile_drift.sh +15 -0
- package/ds-canonical/hooks/tests/test_check_story_invariants.sh +209 -0
- package/ds-canonical/hooks/tests/test_check_story_name_jargon.sh.broken +53 -0
- package/ds-canonical/hooks/tests/test_check_story_slot_split.sh +156 -0
- package/ds-canonical/hooks/tests/test_check_substantive_edit_approval_preflight.sh +176 -0
- package/ds-canonical/hooks/tests/test_check_tab_lg_chrome_header_equal.sh +138 -0
- package/ds-canonical/hooks/tests/test_check_token_hygiene.sh +21 -0
- package/ds-canonical/hooks/tests/test_check_wrapper_primitive_schema_drift.sh +169 -0
- package/ds-canonical/hooks/tests/test_enforce_home_charter.sh +77 -0
- package/ds-canonical/hooks/tests/test_inject_pending_self_audit.sh +125 -0
- package/ds-canonical/hooks/tests/test_log_governance_fires.sh +10 -0
- package/ds-canonical/hooks/tests/test_log_skill_invokes.sh +7 -0
- package/ds-canonical/hooks/tests/test_post_edit_dispatcher.sh +108 -0
- package/ds-canonical/hooks/tests/test_session_start_governance_check.sh +143 -0
- package/ds-canonical/hooks/tests/test_stop_capture_metrics.sh +95 -0
- package/ds-canonical/hooks/tests/test_stop_governance_drift_check.sh.broken +125 -0
- package/ds-canonical/hooks/tests/test_stop_harvest_corrections.sh +10 -0
- package/ds-canonical/hooks/tests/test_stop_passive_logging.sh +100 -0
- package/ds-canonical/hooks/tests/test_stop_self_audit.sh +76 -0
- package/ds-canonical/hooks/tests/test_stop_tsc_sanity.sh +10 -0
- package/ds-canonical/references/README.md +43 -0
- package/ds-canonical/references/audit-coverage-vs-24-checklist.md +74 -0
- package/ds-canonical/references/build-ui-canonicals.md +69 -0
- package/ds-canonical/references/cva-patterns.md +41 -0
- package/ds-canonical/references/drag-canonical.md +331 -0
- package/ds-canonical/references/item-anatomy-recipe.md +225 -0
- package/ds-canonical/references/naming-conventions.md +56 -0
- package/ds-canonical/references/principle-dim-map.json +515 -0
- package/ds-canonical/references/props-naming.md +45 -0
- package/ds-canonical/references/spec-rules.md +58 -0
- package/ds-canonical/references/ssot-consultation.md +63 -0
- package/ds-canonical/references/ssot-index.md +40 -0
- package/ds-canonical/references/story-baseline-registry.json +79 -0
- package/ds-canonical/references/structural-token-retention.md +42 -0
- package/ds-canonical/references/tailwind-gotchas.md +87 -0
- package/ds-canonical/references/ui-dev-rules.md +60 -0
- package/ds-canonical/rules/README.md +34 -0
- package/ds-canonical/rules/meta-patterns.md +87 -0
- package/ds-canonical/rules/self-verify.md +53 -0
- package/ds-canonical/rules/spec-rules.md +25 -0
- package/ds-canonical/rules/story-rules.md +56 -0
- package/ds-canonical/rules/ui-development.md +87 -0
- package/ds-canonical/skills/README.md +88 -0
- package/ds-canonical/skills/bug-fix-rhythm/SKILL.md +181 -0
- package/ds-canonical/skills/code-quality-audit/SKILL.md +63 -0
- package/ds-canonical/skills/codex-collab/SKILL.md +249 -0
- package/ds-canonical/skills/codex-collab/references/brief-template.md +48 -0
- package/ds-canonical/skills/codex-collab/references/transport.md +58 -0
- package/ds-canonical/skills/codify-corrections/SKILL.md +184 -0
- package/ds-canonical/skills/codify-principle/SKILL.md +151 -0
- package/ds-canonical/skills/component-quality-gate/SKILL.md +102 -0
- package/ds-canonical/skills/component-quality-gate/references/checklist.md +79 -0
- package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +247 -0
- package/ds-canonical/skills/deep-audit-cross-codex/references/phase-a-workflow.md +123 -0
- package/ds-canonical/skills/deep-audit-cross-codex/references/phase-b-codex-brief.md +165 -0
- package/ds-canonical/skills/deep-audit-cross-codex/references/triage-rubric.md +91 -0
- package/ds-canonical/skills/delivery-handoff/SKILL.md +229 -0
- package/ds-canonical/skills/delivery-handoff/references/flow-diagram.md +180 -0
- package/ds-canonical/skills/delivery-handoff/references/handoff-template.md +177 -0
- package/ds-canonical/skills/delivery-handoff/references/inventory-checklist.md +196 -0
- package/ds-canonical/skills/design-system-audit/SKILL.md +343 -0
- package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +1260 -0
- package/ds-canonical/skills/design-system-audit/references/checkpoints.md +240 -0
- package/ds-canonical/skills/design-system-audit/references/historical-bugs.md +240 -0
- package/ds-canonical/skills/design-system-audit/references/principle-audit-protocol.md +364 -0
- package/ds-canonical/skills/design-system-audit/references/rule-placement.md +175 -0
- package/ds-canonical/skills/design-system-audit/references/spec-template.md +66 -0
- package/ds-canonical/skills/ensure-canonical/SKILL.md +196 -0
- package/ds-canonical/skills/governance-health/SKILL.md +146 -0
- package/ds-canonical/skills/knowledge-prune/SKILL.md +303 -0
- package/ds-canonical/skills/new-component/SKILL.md +170 -0
- package/ds-canonical/skills/new-component/references/new-component-checklist.md +85 -0
- package/ds-canonical/skills/performance-audit/SKILL.md +107 -0
- package/ds-canonical/skills/product-ui-audit/SKILL.md +230 -0
- package/ds-canonical/skills/product-ui-audit/references/audit-checks.md +246 -0
- package/ds-canonical/skills/product-ui-audit/references/common-misuses.md +329 -0
- package/ds-canonical/skills/product-ui-audit/references/report-template.md +159 -0
- package/ds-canonical/skills/propose-options/SKILL.md +177 -0
- package/ds-canonical/skills/prototype/SKILL.md +244 -0
- package/ds-canonical/skills/prototype/references/audit-checks.md +37 -0
- package/ds-canonical/skills/prototype/references/benchmark-sources.md +94 -0
- package/ds-canonical/skills/prototype/references/checkpoints.md +191 -0
- package/ds-canonical/skills/prototype/references/evaluation-matrix.md +141 -0
- package/ds-canonical/skills/prototype/references/ooux-template.md +198 -0
- package/ds-canonical/skills/prototype/references/proposal-template.md +229 -0
- package/ds-canonical/skills/scan-similar-bugs/SKILL.md +198 -0
- package/ds-canonical/skills/story-auto-compile-migrate/SKILL.md +159 -0
- package/ds-canonical/skills/story-writing/SKILL.md +122 -0
- package/ds-canonical/skills/story-writing/references/anatomy-standard.md +217 -0
- package/ds-canonical/skills/story-writing/references/category-templates.md +174 -0
- package/ds-canonical/skills/story-writing/references/example-selection.md +70 -0
- package/ds-canonical/skills/story-writing/references/self-check.md +20 -0
- package/ds-canonical/skills/ux-audit/SKILL.md +130 -0
- package/ds-canonical/skills/visual-audit/SKILL.md +245 -0
- package/ds-canonical/skills/visual-audit/output/.gitkeep +0 -0
- package/ds-canonical/skills/visual-audit/references/audit-architecture.md +100 -0
- package/ds-canonical/skills/visual-audit/references/visual-checklist.md +297 -0
- package/ds-canonical/skills/visual-audit/references/world-class-benchmarks.md +198 -0
- package/package.json +9 -5
- package/src/components/Accordion/accordion.spec.md +114 -0
- package/src/components/Alert/alert.spec.md +197 -0
- package/src/components/AppShell/app-shell.spec.md +331 -0
- package/src/components/AspectRatio/aspect-ratio.spec.md +134 -0
- package/src/components/Avatar/avatar.spec.md +329 -0
- package/src/components/Badge/badge.spec.md +380 -0
- package/src/components/Breadcrumb/breadcrumb.spec.md +257 -0
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +210 -0
- package/src/components/Button/button.spec.md +460 -0
- package/src/components/Calendar/calendar.spec.md +242 -0
- package/src/components/Carousel/carousel.spec.md +253 -0
- package/src/components/Chart/chart.spec.md +155 -0
- package/src/components/Checkbox/checkbox.spec.md +344 -0
- package/src/components/Chip/chip.spec.md +237 -0
- package/src/components/CircularProgress/circular-progress.spec.md +268 -0
- package/src/components/Coachmark/coachmark.spec.md +230 -0
- package/src/components/Combobox/combobox.spec.md +180 -0
- package/src/components/Command/command.spec.md +171 -0
- package/src/components/DataTable/data-table.spec.md +525 -0
- package/src/components/DateGrid/date-grid.spec.md +215 -0
- package/src/components/DatePicker/date-picker.spec.md +334 -0
- package/src/components/DescriptionList/description-list.spec.md +214 -0
- package/src/components/Dialog/dialog.spec.md +202 -0
- package/src/components/DropdownMenu/dropdown-menu.spec.md +250 -0
- package/src/components/Empty/empty.spec.md +214 -0
- package/src/components/Field/field-controls.spec.md +338 -0
- package/src/components/Field/field.spec.md +438 -0
- package/src/components/Field/form-validation.spec.md +152 -0
- package/src/components/FieldControlGroup/field-control-group.spec.md +176 -0
- package/src/components/FileItem/file-item.spec.md +467 -0
- package/src/components/FileUpload/file-upload.spec.md +123 -0
- package/src/components/FileViewer/file-viewer.spec.md +373 -0
- package/src/components/HoverCard/hover-card.spec.md +157 -0
- package/src/components/Input/input.spec.md +193 -0
- package/src/components/LinkInput/link-input.spec.md +130 -0
- package/src/components/Menu/menu-item.spec.md +290 -0
- package/src/components/NameCard/name-card.spec.md +171 -0
- package/src/components/Notice/notice.spec.md +149 -0
- package/src/components/NumberInput/number-input.spec.md +126 -0
- package/src/components/OverflowIndicator/overflow-indicator.spec.md +120 -0
- package/src/components/PeoplePicker/people-picker.spec.md +263 -0
- package/src/components/Popover/popover.spec.md +198 -0
- package/src/components/ProgressBar/progress-bar.spec.md +232 -0
- package/src/components/RadioGroup/radio-group.spec.md +141 -0
- package/src/components/Rating/rating.spec.md +208 -0
- package/src/components/ScrollArea/scroll-area.spec.md +145 -0
- package/src/components/SegmentedControl/segmented-control.spec.md +295 -0
- package/src/components/Select/select.spec.md +299 -0
- package/src/components/SelectMenu/select-menu.spec.md +220 -0
- package/src/components/SelectionControl/selection-item.spec.md +128 -0
- package/src/components/Separator/separator.spec.md +109 -0
- package/src/components/Sheet/sheet.spec.md +148 -0
- package/src/components/Sidebar/sidebar.spec.md +713 -0
- package/src/components/Skeleton/skeleton.spec.md +104 -0
- package/src/components/Slider/slider.spec.md +353 -0
- package/src/components/Steps/steps.spec.md +465 -0
- package/src/components/Switch/switch.spec.md +215 -0
- package/src/components/Tabs/tabs.spec.md +314 -0
- package/src/components/Tag/tag.spec.md +282 -0
- package/src/components/Textarea/textarea.spec.md +151 -0
- package/src/components/TimePicker/time-picker.spec.md +279 -0
- package/src/components/Toast/toast.spec.md +177 -0
- package/src/components/Tooltip/tooltip.spec.md +139 -0
- package/src/components/TreeView/tree-view.spec.md +374 -0
- package/src/patterns/action-bar/action-bar.spec.md +458 -0
- package/src/patterns/element-anatomy/element-anatomy.spec.md +215 -0
- package/src/patterns/element-anatomy/inline-action.spec.md +315 -0
- package/src/patterns/element-anatomy/item-anatomy.spec.md +1042 -0
- package/src/patterns/header-canonical/header-canonical.spec.md +285 -0
- package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +191 -0
- package/src/patterns/overlay-surface/overlay-surface.spec.md +428 -0
- package/src/patterns/resize-handle/resize-handle.spec.md +109 -0
- package/src/tokens/color/color.spec.md +804 -0
- package/src/tokens/density/density.spec.md +127 -0
- package/src/tokens/elevation/elevation.spec.md +81 -0
- package/src/tokens/layoutSpace/layoutSpace.spec.md +314 -0
- package/src/tokens/motion/motion.spec.md +97 -0
- package/src/tokens/opacity/opacity.spec.md +78 -0
- package/src/tokens/orphan-tokens.spec.md +117 -0
- package/src/tokens/radius/radius.spec.md +123 -0
- package/src/tokens/typography/typography.spec.md +202 -0
- package/src/tokens/uiSize/uiSize.spec.md +438 -0
- package/src/styles/preset.css +0 -31
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
# Tailwind token registry enforcement hook(2026-05-18 升級 per audit Dim 47 + codex P1-5):
|
|
4
|
+
# 原 hook 名 `check_opacity_token_usage.sh`(keep filename for settings.json registration),
|
|
5
|
+
# 實際 logic 已升級成 full Tailwind utility registry compliance(per Dim 47 SSOT)。
|
|
6
|
+
#
|
|
7
|
+
# SSOT source:`packages/design-system/src/tokens/utility-registry.json`(block list per category)。
|
|
8
|
+
# 對齊 Atlassian @atlaskit/tokens / Carbon @carbon/themes / Ant ConfigProvider / Polaris polaris-tokens
|
|
9
|
+
# token-first lint enforcement。
|
|
10
|
+
#
|
|
11
|
+
# 檢的 block category:
|
|
12
|
+
# - opacity:opacity-{5..95} numeric tier(原 logic,保留)
|
|
13
|
+
# - typography:text-{xs..9xl} / font-{thin|light|semibold|black} / leading-{N} numeric / tracking-{wide..widest|tighter|tight}
|
|
14
|
+
# - radius:rounded-{xl|2xl|3xl} / rounded(unscoped)
|
|
15
|
+
# - elevation:shadow-{sm|md|lg|xl|2xl|inner} / shadow(unscoped)
|
|
16
|
+
# - shadcn alias:bg-popover / text-muted-foreground / bg-accent 等
|
|
17
|
+
# - primitive 色名:bg-neutral-N / text-blue-N 等(越過 semantic 層)
|
|
18
|
+
#
|
|
19
|
+
# 例外:utility-registry.json `_meta.exceptions` 段定義的 anatomy stories / principles code blocks 豁免
|
|
20
|
+
# (本 hook 已 skip *.anatomy.stories.tsx / *.principles.stories.tsx 不檢)
|
|
21
|
+
#
|
|
22
|
+
# 修法:reuse semantic utility / token reference / var() bracket(詳 utility-registry.json `rationale_path`)。
|
|
23
|
+
|
|
24
|
+
source "$(dirname "$0")/_log-fire.sh" 2>/dev/null && log_hook_fire
|
|
25
|
+
|
|
26
|
+
INPUT=$(cat)
|
|
27
|
+
TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""')
|
|
28
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
|
|
29
|
+
|
|
30
|
+
case "$TOOL" in
|
|
31
|
+
Edit|Write|MultiEdit) ;;
|
|
32
|
+
*) exit 0 ;;
|
|
33
|
+
esac
|
|
34
|
+
|
|
35
|
+
# Scope:.tsx + .css(DS source);skip stories / tests / token spec 自家 / anatomy/principles per registry exception
|
|
36
|
+
case "$FILE_PATH" in
|
|
37
|
+
*.tsx|*.css) ;;
|
|
38
|
+
*) exit 0 ;;
|
|
39
|
+
esac
|
|
40
|
+
case "$FILE_PATH" in
|
|
41
|
+
# 註解:anatomy + principles stories per utility-registry.json `_meta.exceptions` 豁免
|
|
42
|
+
*.anatomy.stories.tsx|*.principles.stories.tsx) exit 0 ;;
|
|
43
|
+
*.stories.tsx|*.test.*|*.spec.tsx) exit 0 ;;
|
|
44
|
+
*tokens/opacity/*|*tokens/typography/*|*tokens/radius/*|*tokens/elevation/*|*tokens/color/*) exit 0 ;;
|
|
45
|
+
esac
|
|
46
|
+
|
|
47
|
+
NEW_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // .tool_input.new_string // ""')
|
|
48
|
+
[ -z "$NEW_CONTENT" ] && exit 0
|
|
49
|
+
|
|
50
|
+
# Resolve registry path(absolute via $CLAUDE_PROJECT_DIR or relative fallback)
|
|
51
|
+
REGISTRY="${CLAUDE_PROJECT_DIR:-}/packages/design-system/src/tokens/utility-registry.json"
|
|
52
|
+
if [ ! -f "$REGISTRY" ]; then
|
|
53
|
+
# Fallback: try relative from hook dir
|
|
54
|
+
REGISTRY="$(dirname "$0")/../../packages/design-system/src/tokens/utility-registry.json"
|
|
55
|
+
fi
|
|
56
|
+
if [ ! -f "$REGISTRY" ]; then
|
|
57
|
+
# 沒 registry → fall back 到原 opacity-only logic(避免 hook 啞掉)
|
|
58
|
+
HITS=$(echo "$NEW_CONTENT" | grep -oE "opacity-[0-9]+" | grep -vE "^opacity-(0|100)$" | sort -u || true)
|
|
59
|
+
if [ -n "$HITS" ]; then
|
|
60
|
+
echo "" >&2
|
|
61
|
+
echo "⚠️ M23 violation(opacity tier;registry 不可用,fallback mode):" >&2
|
|
62
|
+
echo "$HITS" | sed 's/^/ /' >&2
|
|
63
|
+
echo "" >&2
|
|
64
|
+
echo " reuse opacity-disabled / alpha 色階。詳 tokens/opacity/opacity.spec.md" >&2
|
|
65
|
+
fi
|
|
66
|
+
exit 0
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# Aggregate violations across all categories
|
|
70
|
+
VIOLATIONS=""
|
|
71
|
+
|
|
72
|
+
# 1) Opacity numeric tier(opacity-5..95,除 0/100/disabled)
|
|
73
|
+
HITS_OPACITY=$(echo "$NEW_CONTENT" | grep -oE "\bopacity-[0-9]+\b" | grep -vE "^opacity-(0|100)$" | sort -u || true)
|
|
74
|
+
if [ -n "$HITS_OPACITY" ]; then
|
|
75
|
+
VIOLATIONS="${VIOLATIONS}\n [opacity] $(echo "$HITS_OPACITY" | tr '\n' ' ')"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# 2) Typography raw size(text-xs..9xl)— skip semantic text-h*/text-body/text-caption/text-helper/text-label
|
|
79
|
+
HITS_TEXT=$(echo "$NEW_CONTENT" | grep -oE "\btext-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)\b" | sort -u || true)
|
|
80
|
+
if [ -n "$HITS_TEXT" ]; then
|
|
81
|
+
VIOLATIONS="${VIOLATIONS}\n [typography size] $(echo "$HITS_TEXT" | tr '\n' ' ')"
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# 3) Typography raw weight
|
|
85
|
+
HITS_FONT=$(echo "$NEW_CONTENT" | grep -oE "\bfont-(thin|extralight|light|semibold|extrabold|black)\b" | sort -u || true)
|
|
86
|
+
if [ -n "$HITS_FONT" ]; then
|
|
87
|
+
VIOLATIONS="${VIOLATIONS}\n [typography weight] $(echo "$HITS_FONT" | tr '\n' ' ')"
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# 4) Leading numeric(leading-N where N is digit;skip leading-compact/normal/none/tight/snug/relaxed/loose)
|
|
91
|
+
HITS_LEADING=$(echo "$NEW_CONTENT" | grep -oE "\bleading-[0-9]+\b" | sort -u || true)
|
|
92
|
+
if [ -n "$HITS_LEADING" ]; then
|
|
93
|
+
VIOLATIONS="${VIOLATIONS}\n [leading numeric] $(echo "$HITS_LEADING" | tr '\n' ' ')"
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# 5) Tracking raw(skip tracking-shortcut canonical token)
|
|
97
|
+
HITS_TRACKING=$(echo "$NEW_CONTENT" | grep -oE "\btracking-(tighter|tight|wide|wider|widest)\b" | sort -u || true)
|
|
98
|
+
if [ -n "$HITS_TRACKING" ]; then
|
|
99
|
+
VIOLATIONS="${VIOLATIONS}\n [tracking raw] $(echo "$HITS_TRACKING" | tr '\n' ' ')"
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# 6) Radius out-of-range
|
|
103
|
+
HITS_RADIUS=$(echo "$NEW_CONTENT" | grep -oE "\brounded-(xl|2xl|3xl)\b" | sort -u || true)
|
|
104
|
+
if [ -n "$HITS_RADIUS" ]; then
|
|
105
|
+
VIOLATIONS="${VIOLATIONS}\n [radius out-of-range] $(echo "$HITS_RADIUS" | tr '\n' ' ')"
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# 7) Shadow Tailwind size(shadow-sm/md/lg/xl/2xl/inner — DS 用 shadow-[var(--elevation-*)])
|
|
109
|
+
HITS_SHADOW=$(echo "$NEW_CONTENT" | grep -oE "\bshadow-(sm|md|lg|xl|2xl|inner)\b" | sort -u || true)
|
|
110
|
+
if [ -n "$HITS_SHADOW" ]; then
|
|
111
|
+
VIOLATIONS="${VIOLATIONS}\n [elevation size] $(echo "$HITS_SHADOW" | tr '\n' ' ')"
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
# 8) Shadcn compat alias(bg-popover / text-muted-foreground 等)
|
|
115
|
+
HITS_SHADCN=$(echo "$NEW_CONTENT" | grep -oE "\b(bg-popover|text-popover-foreground|text-muted-foreground|bg-accent|text-accent-foreground|bg-destructive|bg-background|text-background|border-input)\b" | sort -u || true)
|
|
116
|
+
if [ -n "$HITS_SHADCN" ]; then
|
|
117
|
+
VIOLATIONS="${VIOLATIONS}\n [shadcn alias] $(echo "$HITS_SHADCN" | tr '\n' ' ')"
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# 9) Primitive 色名作 utility(bg-neutral-N / text-blue-N 等)
|
|
121
|
+
HITS_PRIMITIVE=$(echo "$NEW_CONTENT" | grep -oE "\b(bg|text|border)-(neutral|blue|red|green|yellow|orange|purple|pink|cyan|teal|deep-orange|deep-purple|light-blue|light-green|lime|amber|indigo|brown|gray)-[0-9]+\b" | sort -u || true)
|
|
122
|
+
if [ -n "$HITS_PRIMITIVE" ]; then
|
|
123
|
+
VIOLATIONS="${VIOLATIONS}\n [primitive color as utility] $(echo "$HITS_PRIMITIVE" | tr '\n' ' ')"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
if [ -n "$VIOLATIONS" ]; then
|
|
127
|
+
cat >&2 <<'EOF_HEAD'
|
|
128
|
+
|
|
129
|
+
⚠️ M23 / Dim 47 violation:Tailwind utility 繞 SSOT registry。
|
|
130
|
+
EOF_HEAD
|
|
131
|
+
echo -e "$VIOLATIONS" >&2
|
|
132
|
+
cat >&2 <<'EOF_BODY'
|
|
133
|
+
|
|
134
|
+
SSOT:packages/design-system/src/tokens/utility-registry.json(block list per category)
|
|
135
|
+
修法 quick map:
|
|
136
|
+
text-xs..9xl → text-h1..h6 / text-body / text-caption / text-helper / text-label
|
|
137
|
+
font-thin/...black → font-normal / font-medium / font-bold
|
|
138
|
+
leading-{N} → leading-compact / leading-normal
|
|
139
|
+
tracking-* → tracking-shortcut(or codify role-specific semantic utility)
|
|
140
|
+
rounded-xl.. → rounded-xs..rounded-lg / rounded-full
|
|
141
|
+
shadow-sm.. → shadow-[var(--elevation-100)] / shadow-[var(--elevation-200)]
|
|
142
|
+
bg-popover .. → direct semantic token(--surface-raised / --fg-muted / --error 等)
|
|
143
|
+
bg-neutral-N → semantic utility(bg-surface 等)或 bg-[var(--color-neutral-N)]
|
|
144
|
+
|
|
145
|
+
詳 utility-registry.json `_meta.spec_sources` + M23「DS 內既有 canonical 優先」。
|
|
146
|
+
EOF_BODY
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
exit 0
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Pattern invariants unified hook(2026-05-08 cluster C consolidation)
|
|
3
|
+
#
|
|
4
|
+
# Merges 4 PreToolUse hooks(原各檔已 retire,合併入此):
|
|
5
|
+
# C.1 overlay panel scroll chain(原 check_overlay_panel_scroll_chain,P1 WARN stderr)
|
|
6
|
+
# C.2 inline-action canonical gap(原 check_inline_action_canonical_gap,P1 WARN stderr)
|
|
7
|
+
# C.3 primitive wrapper padding(原 check_primitive_wrapper_padding,P0 BLOCK exit 2)
|
|
8
|
+
# C.4 row slot handcraft(原 check_row_slot_handcraft,P0 BLOCK exit 2)
|
|
9
|
+
#
|
|
10
|
+
# Why merge:皆 element-anatomy / overlay-surface SSOT 消費紀律 invariant,共用 INPUT
|
|
11
|
+
# parsing + tsx filter,散裝是 M17 + Anthropic ≤ 15 hook best-practice 偏離。
|
|
12
|
+
#
|
|
13
|
+
# Exit precedence:BLOCK(2)> WARN-stderr(0)。每 rule 獨立 fire,worst 勝。
|
|
14
|
+
#
|
|
15
|
+
# Per-rule allowlist:
|
|
16
|
+
# C.1: `// @scroll-chain-allow: <reason>`(any line)
|
|
17
|
+
# C.2: `// @inline-action-gap-allow: <reason>`(any line)
|
|
18
|
+
# C.3: `// @primitive-padding-allow: <reason>`(檔案前 5 行)
|
|
19
|
+
# C.4: `// @row-slot-handcraft-allow: <reason>`(any line)
|
|
20
|
+
|
|
21
|
+
source "$(dirname "$0")/_log-fire.sh" 2>/dev/null && log_hook_fire
|
|
22
|
+
|
|
23
|
+
set -uo pipefail
|
|
24
|
+
|
|
25
|
+
INPUT=$(cat)
|
|
26
|
+
TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""')
|
|
27
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
|
|
28
|
+
|
|
29
|
+
case "$TOOL" in
|
|
30
|
+
Edit|Write|MultiEdit) ;;
|
|
31
|
+
*) exit 0 ;;
|
|
32
|
+
esac
|
|
33
|
+
|
|
34
|
+
case "$FILE_PATH" in
|
|
35
|
+
*.tsx) ;;
|
|
36
|
+
*) exit 0 ;;
|
|
37
|
+
esac
|
|
38
|
+
|
|
39
|
+
NEW_CONTENT=$(echo "$INPUT" | jq -r '
|
|
40
|
+
(.tool_input.content // "") + "\n" +
|
|
41
|
+
(.tool_input.new_string // "") + "\n" +
|
|
42
|
+
([.tool_input.edits[]? | .new_string] | join("\n"))
|
|
43
|
+
' 2>/dev/null || echo "")
|
|
44
|
+
|
|
45
|
+
[ -z "${NEW_CONTENT//[[:space:]]/}" ] && exit 0
|
|
46
|
+
|
|
47
|
+
WORST=0
|
|
48
|
+
record_worst() { local lvl=$1; [ "$lvl" -gt "$WORST" ] && WORST=$lvl; }
|
|
49
|
+
|
|
50
|
+
# ── C.1 overlay panel scroll chain(P1 WARN stderr only)──────────────────────
|
|
51
|
+
if ! echo "$NEW_CONTENT" | grep -q '@scroll-chain-allow' \
|
|
52
|
+
&& echo "$NEW_CONTENT" | grep -q '<SurfaceBody'; then
|
|
53
|
+
SUSPECT_C1=$(printf '%s' "$NEW_CONTENT" | awk '
|
|
54
|
+
/^[[:space:]]*<div[[:space:]]/ {
|
|
55
|
+
line = $0
|
|
56
|
+
while (index(line, ">") == 0 && (getline next_line) > 0) line = line " " next_line
|
|
57
|
+
if (line ~ /w-\[/ && (line !~ /flex[^"]*flex-col/ || line !~ /h-full/ || line !~ /min-h-0/)) {
|
|
58
|
+
for (k = 0; k < 30 && (getline next_line) > 0; k++) {
|
|
59
|
+
if (next_line ~ /<SurfaceBody/) {
|
|
60
|
+
print "[panel root w-[...] 缺 flex flex-col h-full min-h-0]: " substr(line, 1, 100)
|
|
61
|
+
break
|
|
62
|
+
}
|
|
63
|
+
if (next_line ~ /<\/div>/) break
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
')
|
|
68
|
+
if [ -n "$SUSPECT_C1" ]; then
|
|
69
|
+
cat >&2 <<EOF
|
|
70
|
+
|
|
71
|
+
┄┄┄ C.1 check_pattern_invariants — overlay scroll chain WARN ┄┄┄
|
|
72
|
+
|
|
73
|
+
[P1] ${FILE_PATH}
|
|
74
|
+
${SUSPECT_C1}
|
|
75
|
+
|
|
76
|
+
⚠️ M25 canonical:Popover/HoverCard/Dialog/Sheet viewport-aware scroll 要求 root → SurfaceBody
|
|
77
|
+
所有中間 wrapper forward \`flex flex-col h-full\`。斷鏈 → SurfaceBody flex-1 失效。
|
|
78
|
+
|
|
79
|
+
修法:wrapper className 加 \`flex flex-col h-full\`(最小組合 \`flex flex-col h-full min-h-0\`)
|
|
80
|
+
詳 patterns/overlay-surface/overlay-surface.spec.md「Viewport-aware scroll chain invariant」/ M25
|
|
81
|
+
例外:行尾 \`// @scroll-chain-allow: <reason>\`
|
|
82
|
+
|
|
83
|
+
EOF
|
|
84
|
+
fi
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# ── C.2 inline-action canonical gap(P1 WARN stderr only)─────────────────────
|
|
88
|
+
case "$FILE_PATH" in
|
|
89
|
+
*components/*.tsx|*patterns/*.tsx)
|
|
90
|
+
case "$FILE_PATH" in
|
|
91
|
+
*/item-anatomy.tsx|*.stories.tsx|*.test.*) ;; # SSOT/test skip
|
|
92
|
+
*)
|
|
93
|
+
if ! echo "$NEW_CONTENT" | grep -q '@inline-action-gap-allow' \
|
|
94
|
+
&& echo "$NEW_CONTENT" | grep -qE '<ItemInlineAction(Button)?\b|DropdownMenuTrigger.*ItemInlineAction'; then
|
|
95
|
+
WRONG_GAP=$(echo "$NEW_CONTENT" | grep -nE 'className=.*\bgap-(1|3|4|5|6|8|10|12)\b' | head -3 || true)
|
|
96
|
+
if [ -n "$WRONG_GAP" ]; then
|
|
97
|
+
cat >&2 <<EOF
|
|
98
|
+
|
|
99
|
+
┄┄┄ C.2 check_pattern_invariants — inline-action gap WARN ┄┄┄
|
|
100
|
+
|
|
101
|
+
[P1] ${FILE_PATH}
|
|
102
|
+
ItemInlineAction* consumer 用非 \`gap-2\` 的 gap class:
|
|
103
|
+
${WRONG_GAP}
|
|
104
|
+
|
|
105
|
+
⚠️ inline-action.spec.md:80:inline-action 跟 sibling gap 必 \`gap-2\`(8px)。
|
|
106
|
+
12px = --table-cell-px(cell L/R padding,不是 inline-action gap)。
|
|
107
|
+
|
|
108
|
+
修法:className gap → gap-2 / 加 \`// @inline-action-gap-allow: <reason>\` 行尾豁免
|
|
109
|
+
|
|
110
|
+
EOF
|
|
111
|
+
fi
|
|
112
|
+
fi
|
|
113
|
+
;;
|
|
114
|
+
esac
|
|
115
|
+
;;
|
|
116
|
+
esac
|
|
117
|
+
|
|
118
|
+
# ── C.3 primitive wrapper padding(P0 BLOCK exit 2)───────────────────────────
|
|
119
|
+
# File-level allowlist:檔頭前 5 行
|
|
120
|
+
ALLOW_C3=0
|
|
121
|
+
FIRST_LINES_NEW=$(printf '%s\n' "$NEW_CONTENT" | sed -n '1,5p')
|
|
122
|
+
echo "$FIRST_LINES_NEW" | grep -qE '//[[:space:]]*@primitive-padding-allow:' && ALLOW_C3=1
|
|
123
|
+
if [ -f "$FILE_PATH" ] && [ "$ALLOW_C3" = "0" ]; then
|
|
124
|
+
ON_DISK_FIRST=$(sed -n '1,5p' "$FILE_PATH" 2>/dev/null || true)
|
|
125
|
+
echo "$ON_DISK_FIRST" | grep -qE '//[[:space:]]*@primitive-padding-allow:' && ALLOW_C3=1
|
|
126
|
+
fi
|
|
127
|
+
if [ "$ALLOW_C3" = "0" ]; then
|
|
128
|
+
PRIMITIVES_REGEX='DateGrid|Calendar|Surface|SurfaceHeader|SurfaceBody|SurfaceFooter'
|
|
129
|
+
VIOLATIONS_C3=$(printf '%s' "$NEW_CONTENT" | perl -0777 -ne '
|
|
130
|
+
while (/<div\b[^>]*className\s*=\s*["\x27`][^"\x27`]*\bp-\d+[^"\x27`]*["\x27`][^>]*>(?:[^<]*<(?!\/div\b)){0,8}\s*<('"$PRIMITIVES_REGEX"')\b/gs) {
|
|
131
|
+
my $primitive = $1;
|
|
132
|
+
my $offset = $-[0];
|
|
133
|
+
my $before = substr($_, 0, $offset);
|
|
134
|
+
my $line = ($before =~ tr/\n//) + 1;
|
|
135
|
+
print "line $line: <div className=\"...p-X...\"> wrapping <$primitive>\n";
|
|
136
|
+
}
|
|
137
|
+
')
|
|
138
|
+
if [ -n "$VIOLATIONS_C3" ]; then
|
|
139
|
+
cat >&2 <<EOF
|
|
140
|
+
|
|
141
|
+
┄┄┄ C.3 check_pattern_invariants — primitive wrapper padding BLOCKER ┄┄┄
|
|
142
|
+
|
|
143
|
+
[P0] ${FILE_PATH}
|
|
144
|
+
${VIOLATIONS_C3}
|
|
145
|
+
|
|
146
|
+
SSOT primitive 自帶 outer padding,**consumer 不得另加 padding wrapper**:
|
|
147
|
+
- DateGrid / Calendar:root 自帶 p-3
|
|
148
|
+
- Surface{Header,Body,Footer}:各 segment 自帶 padding
|
|
149
|
+
|
|
150
|
+
修法:直接放 primitive,不包 padding div。
|
|
151
|
+
❌ <div className="p-2"><DateGrid /></div>
|
|
152
|
+
✅ <DateGrid />
|
|
153
|
+
|
|
154
|
+
整檔豁免:檔頭前 5 行加 \`// @primitive-padding-allow: <reason>\`(需 spec rationale)。
|
|
155
|
+
詳 mindset #2 + M1 SSOT 消費。
|
|
156
|
+
|
|
157
|
+
EOF
|
|
158
|
+
record_worst 2
|
|
159
|
+
fi
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
# ── C.4 row slot handcraft(P0 BLOCK exit 2)──────────────────────────────────
|
|
163
|
+
case "$FILE_PATH" in
|
|
164
|
+
*components/*.tsx|*patterns/*.tsx)
|
|
165
|
+
case "$FILE_PATH" in
|
|
166
|
+
*/item-anatomy.tsx|*/field-wrapper.tsx|*.stories.tsx|*.test.*|*.spec.tsx) ;; # SSOT/test skip
|
|
167
|
+
*)
|
|
168
|
+
if ! echo "$NEW_CONTENT" | grep -q '@row-slot-handcraft-allow' \
|
|
169
|
+
&& echo "$NEW_CONTENT" | grep -E 'h-\[1lh\][^"]*shrink-0[^"]*flex[^"]*items-center|shrink-0[^"]*h-\[1lh\][^"]*flex[^"]*items-center|flex[^"]*items-center[^"]*h-\[1lh\][^"]*shrink-0' >/dev/null; then
|
|
170
|
+
cat >&2 <<EOF
|
|
171
|
+
|
|
172
|
+
┄┄┄ C.4 check_pattern_invariants — row slot handcraft BLOCKER ┄┄┄
|
|
173
|
+
|
|
174
|
+
[P0] ${FILE_PATH}
|
|
175
|
+
偵測自刻 row-layout slot:\`<span class="h-[1lh] shrink-0 flex items-center...">\`
|
|
176
|
+
M1+M17 違反 — 該消費 L1 primitive。
|
|
177
|
+
|
|
178
|
+
⚠️ M19 canonical(2026-05-05 v8):row prefix/suffix slot 必走 patterns/element-anatomy:
|
|
179
|
+
import { ItemPrefix, ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'
|
|
180
|
+
<ItemPrefix><StartIcon /></ItemPrefix>
|
|
181
|
+
<ItemSuffix>{chevron}</ItemSuffix>
|
|
182
|
+
|
|
183
|
+
\`<ItemPrefix>\` / \`<ItemSuffix>\` 永遠 \`h-[1lh] shrink-0 flex items-center\`(item-anatomy.spec.md:175+190)。
|
|
184
|
+
例外:行尾 \`// @row-slot-handcraft-allow: <reason>\`
|
|
185
|
+
|
|
186
|
+
EOF
|
|
187
|
+
record_worst 2
|
|
188
|
+
fi
|
|
189
|
+
;;
|
|
190
|
+
esac
|
|
191
|
+
;;
|
|
192
|
+
esac
|
|
193
|
+
|
|
194
|
+
exit $WORST
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
# PreToolUse Edit/Write: PeoplePicker SSOT drift guard(2026-05-15 user verbatim「避免下次再度偏移」)
|
|
4
|
+
#
|
|
5
|
+
# 攔截:`people-picker.tsx` / `person-display.tsx` / `combobox.tsx` 動以下 PeoplePicker SSOT-bearing
|
|
6
|
+
# code 路徑時,要求 commit message / new content 包含 cite 對應 spec.md SSOT table row 的 marker。
|
|
7
|
+
#
|
|
8
|
+
# 規則 SSOT:`packages/design-system/src/components/PeoplePicker/people-picker.spec.md` §A-§F canonical table
|
|
9
|
+
# (Trigger display SSOT canonical table)。
|
|
10
|
+
#
|
|
11
|
+
# 警告(non-blocking):新內容含「動 SSOT-bearing API」但無 spec 引用 → stderr 警告,要求自查對應 row。
|
|
12
|
+
# Hard block 場景不適用(spec 偏移由 user 決定走 RFC 或直接修)。
|
|
13
|
+
|
|
14
|
+
source "$(dirname "$0")/_log-fire.sh" 2>/dev/null && log_hook_fire
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
INPUT=$(cat)
|
|
19
|
+
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
|
20
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
|
|
21
|
+
NEW_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty')
|
|
22
|
+
|
|
23
|
+
[ -z "$FILE_PATH" ] && exit 0
|
|
24
|
+
|
|
25
|
+
# Only check Edit/Write on PeoplePicker SSOT-bearing files
|
|
26
|
+
case "$FILE_PATH" in
|
|
27
|
+
*/PeoplePicker/people-picker.tsx|*/PeoplePicker/person-display.tsx|*/Combobox/combobox.tsx) ;;
|
|
28
|
+
*) exit 0 ;;
|
|
29
|
+
esac
|
|
30
|
+
|
|
31
|
+
# Patterns that touch SSOT-bearing render logic
|
|
32
|
+
SSOT_KEYWORDS='tagRenderer|overflowShape|tagWrapperClassName|selectedItemRenderer|MultiPersonDisplay|PersonAvatarTag|PersonDisplay|searchIn|searchPlaceholder|placeholder=\\{'
|
|
33
|
+
|
|
34
|
+
if echo "$NEW_CONTENT" | grep -qE "$SSOT_KEYWORDS"; then
|
|
35
|
+
# Check if new content cites spec.md SSOT table
|
|
36
|
+
if ! echo "$NEW_CONTENT" | grep -qE "(spec\\.md.*(§[A-F]|canonical table|Trigger display SSOT)|people-picker\\.spec\\.md.*L9[0-9])"; then
|
|
37
|
+
MSG="⚠️ PeoplePicker SSOT-bearing edit detected at ${FILE_PATH}
|
|
38
|
+
|
|
39
|
+
動到 (tagRenderer / overflowShape / tagWrapperClassName / placeholder logic / MultiPersonDisplay 等) — 這些是 PeoplePicker SSOT-bearing API,不是 Combobox / Select 純繼承。
|
|
40
|
+
|
|
41
|
+
必先 cite \`people-picker.spec.md\` §A-§F canonical table 對應 row,確認改動跟 SSOT 一致。違反 = 元件視覺漂移(2026-05-15 user verbatim「為什麼辯論之後會完全被 codex 帶走」+ 「避免下次再度偏移」)。
|
|
42
|
+
|
|
43
|
+
對應 SSOT row 速查:
|
|
44
|
+
§A 元件本質繼承表 - 動繼承關係 / 視覺自定義 → 此處
|
|
45
|
+
§B 單人 trigger 5 state - 動 single mode render → 此處
|
|
46
|
+
§C 多人 length=1 trigger 3 state - 動 length=1 降階單人視覺 → 此處
|
|
47
|
+
§D 多人 length≥2 trigger 4 state - 動 stack overlap / +N 圓形 / inline-search cursor → 此處
|
|
48
|
+
§E 共享 SSOT - 動 avatar inset 12px / PersonDisplay / placeholder ellipsis → 此處
|
|
49
|
+
§F Cell-edit ↔ Field-edit 一致性 - 動 cell-registry MultiPersonCell / form PeoplePicker → 此處"
|
|
50
|
+
|
|
51
|
+
ESCAPED=$(printf '%s' "$MSG" | jq -Rs .)
|
|
52
|
+
printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":%s}}\n' "$ESCAPED"
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
exit 0
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# check_pixel_quantified_audit.sh — M32(a) pixel-quantified verify mechanical guardrail
|
|
3
|
+
#
|
|
4
|
+
# Per M32(2026-05-12 user 抓「audit ALL PASS 但 user 看視覺仍 broken」連環事件 4 RC absorbed):
|
|
5
|
+
# audit script 必驗 `rect.top / .left / .height` numeric pixel position,**不**可只驗
|
|
6
|
+
# `getAttribute('data-state')` / `class.includes('items-start')` attribute-existence
|
|
7
|
+
# = false-positive trap(DOM-structure-pass ≠ visual-pass)。
|
|
8
|
+
#
|
|
9
|
+
# Mechanical rule:scripts/visual-audit-*.mjs(or *.js)若有 `getAttribute(` 但沒 paired
|
|
10
|
+
# `getBoundingClientRect(` 或 `offsetTop/.offsetHeight` 量化 measurement → warn。
|
|
11
|
+
#
|
|
12
|
+
# 對齊 Material X-DataGrid visual regression / AG Grid playwright pixel snapshot canonical。
|
|
13
|
+
|
|
14
|
+
source "$(dirname "$0")/_log-fire.sh" 2>/dev/null && log_hook_fire
|
|
15
|
+
|
|
16
|
+
set -uo pipefail
|
|
17
|
+
INPUT=$(cat 2>/dev/null || echo "{}")
|
|
18
|
+
TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null)
|
|
19
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""' 2>/dev/null)
|
|
20
|
+
EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // ""' 2>/dev/null)
|
|
21
|
+
|
|
22
|
+
case "$TOOL" in Edit|Write|MultiEdit) ;; *) exit 0 ;; esac
|
|
23
|
+
case "$FILE_PATH" in
|
|
24
|
+
*/scripts/visual-audit-*.mjs|*/scripts/visual-audit-*.js) ;;
|
|
25
|
+
*) exit 0 ;;
|
|
26
|
+
esac
|
|
27
|
+
[ "$EVENT" != "PostToolUse" ] && exit 0
|
|
28
|
+
[ ! -f "$FILE_PATH" ] && exit 0
|
|
29
|
+
|
|
30
|
+
# Allowlist
|
|
31
|
+
head -3 "$FILE_PATH" | grep -qE '//[[:space:]]*@pixel-quantified-allow:' && exit 0
|
|
32
|
+
|
|
33
|
+
# Count getAttribute calls vs getBoundingClientRect / offsetTop / offsetHeight calls
|
|
34
|
+
# 2026-05-23 bug fix:`grep -c ... 2>/dev/null || echo 0` 當 zero match 時 grep exit 1 + fallback echo,
|
|
35
|
+
# 產生 multi-line "0\n0" string,下游 [ "$X" -eq 0 ] arithmetic 報「integer expression expected」silent skip。
|
|
36
|
+
# 修法:grep 不用 fallback,直接保證輸出 numeric(strip 非數字)+ default 0。
|
|
37
|
+
ATTR_HITS=$(grep -cE 'getAttribute\(|\.hasAttribute\(' "$FILE_PATH" 2>/dev/null)
|
|
38
|
+
ATTR_HITS="${ATTR_HITS//[^0-9]/}"; ATTR_HITS=${ATTR_HITS:-0}
|
|
39
|
+
PIXEL_HITS=$(grep -cE 'getBoundingClientRect\(|\.offsetTop\b|\.offsetHeight\b|\.offsetLeft\b|\.offsetWidth\b' "$FILE_PATH" 2>/dev/null)
|
|
40
|
+
PIXEL_HITS="${PIXEL_HITS//[^0-9]/}"; PIXEL_HITS=${PIXEL_HITS:-0}
|
|
41
|
+
|
|
42
|
+
if [ "$ATTR_HITS" -gt 0 ] && [ "$PIXEL_HITS" -eq 0 ]; then
|
|
43
|
+
CTX="⚠️ M32(a) pixel-quantified verify gap:
|
|
44
|
+
${FILE_PATH}:
|
|
45
|
+
- getAttribute / hasAttribute calls: ${ATTR_HITS}
|
|
46
|
+
- getBoundingClientRect / offsetTop calls: 0
|
|
47
|
+
→ audit 驗 attribute 但沒驗 pixel position = M32 違反(DOM-pass ≠ visual-pass)。
|
|
48
|
+
加 numeric pixel measurement(rect.top / .left / .height)真實 verify 視覺,
|
|
49
|
+
或檔首加 // @pixel-quantified-allow: <reason>(structural-only audit 例如 schema check)。"
|
|
50
|
+
jq -n --arg ctx "$CTX" '{
|
|
51
|
+
hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: $ctx }
|
|
52
|
+
}'
|
|
53
|
+
fi
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# check_propose_plain_chinese.sh — Stop hook: scan AI 本 turn reply 含「要 user 決策」pattern 但用 jargon 違 propose-in-plain-chinese canonical。
|
|
3
|
+
#
|
|
4
|
+
# 對應 SSOT:.claude/memory/feedback_propose_in_plain_chinese.md
|
|
5
|
+
# + CLAUDE.md `# 自主執行 canonical`「SSOT-UI/UX 中文具體人話」明文
|
|
6
|
+
#
|
|
7
|
+
# User 原話 2026-05-15:「已經跟你說過任何要我決策的東西請講具體人話,為何又跟智障一樣講人聽不懂的話呢?」
|
|
8
|
+
#
|
|
9
|
+
# Mechanism:
|
|
10
|
+
# 1. Tail transcript 取 AI 本 turn 最後 assistant text content
|
|
11
|
+
# 2. Detect 決策 prompt pattern(「回 A / B / C」/「→ 選 A」/「等你拍板」/「user 決策」)
|
|
12
|
+
# 3. Count jargon density(「M[0-9]+」/「Dim [0-9]+」/「SSOT」/「@watch」/「frontmatter」/「stub」/etc)
|
|
13
|
+
# 4. Density > threshold + 含決策 prompt → warn(P1 soft inject)
|
|
14
|
+
#
|
|
15
|
+
# 不 BLOCK turn,只 inject context warning(避免 false-positive 中斷 work)
|
|
16
|
+
|
|
17
|
+
source "$(dirname "$0")/_log-fire.sh" 2>/dev/null && log_hook_fire
|
|
18
|
+
|
|
19
|
+
set -uo pipefail
|
|
20
|
+
INPUT=$(cat 2>/dev/null || echo "{}")
|
|
21
|
+
TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // ""' 2>/dev/null)
|
|
22
|
+
|
|
23
|
+
[ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ] && exit 0
|
|
24
|
+
|
|
25
|
+
# 找最新 user msg line 之後的 assistant text(本 turn AI reply)
|
|
26
|
+
LAST_USER_LINE=$(grep -n '"role":"user"' "$TRANSCRIPT_PATH" 2>/dev/null | tail -1 | cut -d: -f1)
|
|
27
|
+
[ -z "$LAST_USER_LINE" ] && exit 0
|
|
28
|
+
|
|
29
|
+
AI_REPLY_TEXT=$(tail -n +$((LAST_USER_LINE+1)) "$TRANSCRIPT_PATH" 2>/dev/null | \
|
|
30
|
+
jq -r 'select(.message.role=="assistant") | .message.content // empty | if type=="string" then . else (.[]? | select(.type=="text") | .text // empty) end' 2>/dev/null)
|
|
31
|
+
|
|
32
|
+
[ -z "$AI_REPLY_TEXT" ] && exit 0
|
|
33
|
+
|
|
34
|
+
# Decision prompt pattern(reply 結尾含 ASK)
|
|
35
|
+
DECISION_RE='(回 [A-Z]|→ ?選 [A-Z]|等你(拍板|決策|回|看)|user 決策|user 拍板|拍板.{0,5}決|決策.{0,5}選|一字回|回 OK)'
|
|
36
|
+
HAS_DECISION=$(echo "$AI_REPLY_TEXT" | grep -cE "$DECISION_RE" 2>/dev/null)
|
|
37
|
+
HAS_DECISION=${HAS_DECISION:-0}
|
|
38
|
+
|
|
39
|
+
[ "$HAS_DECISION" -eq 0 ] && exit 0
|
|
40
|
+
|
|
41
|
+
# GAP 4 fix(2026-05-18 M34 codify):升級 narrow 17-keyword fixed list 到
|
|
42
|
+
# Python unicode 中英夾雜 density detector(對齊 check_story_invariants.sh R5.5 fix template)。
|
|
43
|
+
# Spec wording「任何沒中譯的縮寫 / 內部代號 / hook 名」廣義,hook 過去窄 keyword list 漏:
|
|
44
|
+
# Earn-existence / compound-component / Polaris-aligned / wrapper-vs-primitive / Anchor preflight
|
|
45
|
+
# / *-canonical-allow / L42-58 / check_xxx.sh 等。
|
|
46
|
+
#
|
|
47
|
+
# 升級邏輯:
|
|
48
|
+
# (a) EXEMPT list:legitimate retained-English(framework/brand/DS API)
|
|
49
|
+
# (b) Density measure:jargon-word count / total Chinese-char count ratio
|
|
50
|
+
# (c) jargon = 英文 token 不在 EXEMPT(任何 alphanumeric word ≥ 3 char)
|
|
51
|
+
JARGON_COUNT=$(python3 -c "
|
|
52
|
+
import re, sys
|
|
53
|
+
text = sys.stdin.read()
|
|
54
|
+
EXEMPT = re.compile(r'\b(cva|Radix|Polaris|Material|Atlassian|Carbon|Ant|Apple|MUI|TanStack|shadcn|Recharts|cmdk|dnd-kit|TypeScript|JavaScript|API|UI|UX|Jira|Stripe|Notion|Figma|Linear|GitHub|Gmail|Dropbox|Slack|Spotify|Discord|Storybook|Tailwind|ARIA|WCAG|FAQ|HSL|CSS|HTML|DOM|DS|F[1-9]|TODO|FIXME|XXX|NOTE|README|MIT|JSON|YAML|TSX|CSS|HTTP|HTTPS|URL|UUID|REST|GraphQL|SDK|CDN|CI|CD|PR|RFC|MR|UI/UX|primary|secondary|tertiary|hover|focus|active|disabled|invalid|readonly|null|true|false|undefined|void)\b', re.I)
|
|
55
|
+
words = re.findall(r'\b[a-zA-Z][a-zA-Z\-_]{2,}\b', text)
|
|
56
|
+
jargon = [w for w in words if not EXEMPT.fullmatch(w)]
|
|
57
|
+
print(len(jargon))
|
|
58
|
+
" <<< "$AI_REPLY_TEXT" 2>/dev/null)
|
|
59
|
+
JARGON_COUNT=${JARGON_COUNT:-0}
|
|
60
|
+
|
|
61
|
+
# Threshold:含決策 prompt + jargon count ≥ 10 → warn(Python 計數較廣,threshold 提高)
|
|
62
|
+
THRESHOLD=10
|
|
63
|
+
|
|
64
|
+
if [ "$JARGON_COUNT" -ge "$THRESHOLD" ]; then
|
|
65
|
+
echo "🟡 check_propose_plain_chinese WARN:本 turn reply 含 user 決策 prompt + jargon 密度 ${JARGON_COUNT}(threshold ${THRESHOLD})" >&2
|
|
66
|
+
echo "" >&2
|
|
67
|
+
echo "→ 違反 propose-in-plain-chinese canonical(memory/feedback_propose_in_plain_chinese.md SSOT)" >&2
|
|
68
|
+
echo "→ User 原話「請講具體人話,為何又跟智障一樣講人聽不懂的話」" >&2
|
|
69
|
+
echo "→ Rewrite reply:必含 3 段(發生什麼 / 影響什麼 / 各選項 outcome — 全中文具體,禁 jargon)" >&2
|
|
70
|
+
echo "" >&2
|
|
71
|
+
# Soft warn,不 BLOCK turn(exit 0)— 讓 AI 看到 stderr 後 self-correct
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
exit 0
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
# PreToolUse Edit/Write: enforce M18 Q0「Pre-ASK self-verify problem 真存在」.
|
|
4
|
+
#
|
|
5
|
+
# 2026-05-18 user-mandated codify after Sheet/inline-action/SurfaceBody 三題誤判事件:
|
|
6
|
+
# Claude propose 給 user 拍板前必先 grep DS-wide + Read 相關 spec.md 確認 problem 真存在,
|
|
7
|
+
# 沒 grep 就斷言「N 元件缺 X / 該 migrate」= 撤回 propose。
|
|
8
|
+
#
|
|
9
|
+
# Detection:Edit/Write content 含「propose / 請拍板 / 等你拍板 / 待你拍板 /
|
|
10
|
+
# 決策 N / 我推 A/B/C / 選項」keywords,**但 content 內無 file:line cite 證據**
|
|
11
|
+
# → P1 warn(stderr exit 0,Claude 自決撤回 OR 在 reply 補 cite)。
|
|
12
|
+
#
|
|
13
|
+
# SSOT: `.claude/skills/propose-options/SKILL.md` Q0 段 + meta-patterns.md M18。
|
|
14
|
+
|
|
15
|
+
source "$(dirname "$0")/_log-fire.sh" 2>/dev/null && log_hook_fire
|
|
16
|
+
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
|
|
19
|
+
INPUT=$(cat)
|
|
20
|
+
TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""')
|
|
21
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
|
|
22
|
+
|
|
23
|
+
case "$TOOL" in
|
|
24
|
+
Edit|Write|MultiEdit) ;;
|
|
25
|
+
*) exit 0 ;;
|
|
26
|
+
esac
|
|
27
|
+
|
|
28
|
+
# Only fires on planning docs / commit-style summaries / reply-style markdown
|
|
29
|
+
# (避 false positive on production code / 一般 spec edit)
|
|
30
|
+
case "$FILE_PATH" in
|
|
31
|
+
*/planning/*.md|*/reports/*.md|*/handoff/*.md) ;;
|
|
32
|
+
*) exit 0 ;;
|
|
33
|
+
esac
|
|
34
|
+
|
|
35
|
+
NEW_CONTENT=$(echo "$INPUT" | jq -r '
|
|
36
|
+
(.tool_input.content // "") + "\n" +
|
|
37
|
+
(.tool_input.new_string // "") + "\n" +
|
|
38
|
+
([.tool_input.edits[]? | .new_string] | join("\n"))
|
|
39
|
+
' 2>/dev/null || echo "")
|
|
40
|
+
|
|
41
|
+
[ -z "${NEW_CONTENT//[[:space:]]/}" ] && exit 0
|
|
42
|
+
|
|
43
|
+
# Detect propose keywords
|
|
44
|
+
HAS_PROPOSE=$(echo "$NEW_CONTENT" | grep -cE '(請拍板|等你拍板|待你拍板|決策 [0-9N]|我推 [ABC]|選項[::]|propose for user|你決定|讓我判斷決策)' || true)
|
|
45
|
+
|
|
46
|
+
[ "$HAS_PROPOSE" = "0" ] && exit 0
|
|
47
|
+
|
|
48
|
+
# Allowed escape:檔頭 `<!-- @propose-pre-verified -->` 例外
|
|
49
|
+
if echo "$NEW_CONTENT" | head -3 | grep -qE '@propose-pre-verified'; then
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# Check for file:line cite evidence
|
|
54
|
+
HAS_CITE=$(echo "$NEW_CONTENT" | grep -cE '`[a-z][a-z0-9/-]+\.(tsx|ts|css|md):[0-9]+`|`[a-z][a-z0-9/-]+\.(tsx|ts|css|md)`|`packages/design-system/src/' || true)
|
|
55
|
+
|
|
56
|
+
if [ "$HAS_CITE" = "0" ]; then
|
|
57
|
+
printf '⚠️ PRE-ASK SELF-VERIFY GAP(M18 Q0,P1 soft):\n' >&2
|
|
58
|
+
printf ' File: %s\n' "$FILE_PATH" >&2
|
|
59
|
+
printf ' 偵測到 propose 給 user 拍板 keyword,但 content 無 file:line cite 證據。\n' >&2
|
|
60
|
+
printf '\n M18 Q0 強制檢查:\n' >&2
|
|
61
|
+
printf ' 1. grep 既有 code DS-wide 確認 problem 真存在 (file:line)\n' >&2
|
|
62
|
+
printf ' 2. Read 相關 spec.md 找 canonical cite\n' >&2
|
|
63
|
+
printf ' 3. Read consumer usage 確認該 pattern 已在 N 處 work fine\n' >&2
|
|
64
|
+
printf ' 4. propose 給 user 前列具體 file:line 證據\n' >&2
|
|
65
|
+
printf '\n 錨例:2026-05-18 Sheet/inline-action/SurfaceBody 三題誤判 propose 浪費 user 時間\n' >&2
|
|
66
|
+
printf ' SSOT: .claude/skills/propose-options/SKILL.md Q0 段\n' >&2
|
|
67
|
+
printf ' Escape: 檔頭加 <!-- @propose-pre-verified --> 若已 verify(rationale 必明示)\n' >&2
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
exit 0
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
# PreToolUse Edit/Write: Select All ordering SSOT drift guard(2026-05-16 user verbatim
|
|
4
|
+
# 「以後遇到此類設計都有相同邏輯不會偏移」)
|
|
5
|
+
#
|
|
6
|
+
# 攔截 packages/design-system/src/** edit 時,如果新內容含「Select All」 / 「checkAll」 / 「全選」 等 bulk-select
|
|
7
|
+
# 動作 + 自寫 ordering logic(`onValueChange?.(options.map(...))` / `[...prev, ...]` 等),
|
|
8
|
+
# 要求消費 `@/design-system/lib/multi-select-ordering` SSOT primitive 而非自刻。
|
|
9
|
+
#
|
|
10
|
+
# 規則 SSOT:`packages/design-system/src/lib/multi-select-ordering.ts`(applySelectAll / clearSelection)。
|
|
11
|
+
# 對應 Ant Transfer + Table rowSelection canonical(Layer A + Codex M31 Round 4 共識)。
|
|
12
|
+
#
|
|
13
|
+
# Soft warn(stderr,non-blocking)— 給開發者警示但不擋 ship。Edge case 真需自刻 → 在
|
|
14
|
+
# new content 加 `// @select-all-canonical-allow: <reason>` allowlist escape。
|
|
15
|
+
|
|
16
|
+
source "$(dirname "$0")/_log-fire.sh" 2>/dev/null && log_hook_fire
|
|
17
|
+
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
INPUT=$(cat)
|
|
21
|
+
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
|
22
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
|
|
23
|
+
NEW_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty')
|
|
24
|
+
|
|
25
|
+
[ -z "$FILE_PATH" ] && exit 0
|
|
26
|
+
[[ "$TOOL" != "Edit" && "$TOOL" != "Write" ]] && exit 0
|
|
27
|
+
[[ "$FILE_PATH" != *packages/design-system/src/* ]] && exit 0
|
|
28
|
+
# 本 SSOT primitive 檔自己不查
|
|
29
|
+
[[ "$FILE_PATH" == *multi-select-ordering.ts ]] && exit 0
|
|
30
|
+
# Allowlist
|
|
31
|
+
echo "$NEW_CONTENT" | grep -q "@select-all-canonical-allow" && exit 0
|
|
32
|
+
|
|
33
|
+
# Detect Select-All-like pattern:
|
|
34
|
+
# - function name contains SelectAll / CheckAll
|
|
35
|
+
# - `[...prev`-style append with `options.map`-style source values
|
|
36
|
+
# - 顯式「全部」 button click → onValueChange
|
|
37
|
+
HAS_SELECTALL_HANDLER=$(echo "$NEW_CONTENT" | grep -cE "(handleSelectAll|onSelectAll|onCheckAll|handleCheckAll|全選|checkAll\s*=|selectAll\s*=)" || true)
|
|
38
|
+
|
|
39
|
+
if [ "$HAS_SELECTALL_HANDLER" -gt 0 ]; then
|
|
40
|
+
USES_SSOT=$(echo "$NEW_CONTENT" | grep -cE "(applySelectAll|multi-select-ordering)" || true)
|
|
41
|
+
if [ "$USES_SSOT" -eq 0 ]; then
|
|
42
|
+
cat >&2 <<EOF
|
|
43
|
+
⚠️ Select All ordering SSOT drift warning
|
|
44
|
+
File: $FILE_PATH
|
|
45
|
+
|
|
46
|
+
偵測「Select All」/「checkAll」/「全選」 handler 但無 import \`applySelectAll\` from
|
|
47
|
+
\`@/design-system/lib/multi-select-ordering\`。
|
|
48
|
+
|
|
49
|
+
SSOT canonical(per Ant Transfer + Table rowSelection,Layer A + Codex M31 Round 4 共識):
|
|
50
|
+
preserve existing user click order + append unselected options in source order(dedup)
|
|
51
|
+
→ \`applySelectAll(selectedValues, allOptionValues)\`
|
|
52
|
+
|
|
53
|
+
若刻意自刻 → 加 \`// @select-all-canonical-allow: <reason>\` 在 new content 中。
|
|
54
|
+
EOF
|
|
55
|
+
fi
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
exit 0
|