@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,69 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PostToolUse hook for Edit/Write on component .tsx:
|
|
3
|
+
# When a change touches cva() defaultVariants, grep for 3-way consistency across
|
|
4
|
+
# spec.md + docblock + anatomy.stories.tsx and warn if any disagree on the default.
|
|
5
|
+
#
|
|
6
|
+
# Prevents the SegmentedControl bug class: cva says 'md', spec/docblock/anatomy say
|
|
7
|
+
# 'sm ★default' — silently drifts until audit catches it.
|
|
8
|
+
#
|
|
9
|
+
# Exit: 0 + stdout JSON additionalContext (non-blocking warning)
|
|
10
|
+
|
|
11
|
+
# Per-hook fire logging(enables /knowledge-prune D2 dead-hook detection)
|
|
12
|
+
source "$(dirname "$0")/../_log-fire.sh" 2>/dev/null && log_hook_fire
|
|
13
|
+
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
INPUT=$(cat)
|
|
17
|
+
TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""')
|
|
18
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
|
|
19
|
+
|
|
20
|
+
case "$TOOL" in
|
|
21
|
+
Edit|Write|MultiEdit) ;;
|
|
22
|
+
*) exit 0 ;;
|
|
23
|
+
esac
|
|
24
|
+
|
|
25
|
+
# Only target component .tsx files (not stories, not specs)
|
|
26
|
+
if ! echo "$FILE_PATH" | grep -qE 'packages/design-system/src/components/[^/]+/[^/]+\.tsx$'; then
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
echo "$FILE_PATH" | grep -qE '\.stories\.tsx$' && exit 0
|
|
30
|
+
|
|
31
|
+
# Only fire if the file actually contains defaultVariants (post-edit state)
|
|
32
|
+
[ -f "$FILE_PATH" ] || exit 0
|
|
33
|
+
grep -q "defaultVariants" "$FILE_PATH" || exit 0
|
|
34
|
+
|
|
35
|
+
# Diff-aware: only fire if this edit actually touched cva / defaultVariants content.
|
|
36
|
+
# Extract old_string + new_string (Edit / MultiEdit) or content (Write) — if none
|
|
37
|
+
# mention cva(, defaultVariants, or variants: , skip noise.
|
|
38
|
+
DIFF_TEXT=$(echo "$INPUT" | jq -r '
|
|
39
|
+
(.tool_input.old_string // "") + "\n" +
|
|
40
|
+
(.tool_input.new_string // "") + "\n" +
|
|
41
|
+
(.tool_input.content // "") + "\n" +
|
|
42
|
+
([.tool_input.edits[]? | (.old_string + "\n" + .new_string + "\n")] | join(""))
|
|
43
|
+
' 2>/dev/null || echo "")
|
|
44
|
+
if [ -n "$DIFF_TEXT" ] && ! echo "$DIFF_TEXT" | grep -qE 'cva\(|defaultVariants|variants:[[:space:]]*\{'; then
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
COMP_DIR=$(dirname "$FILE_PATH")
|
|
49
|
+
COMP_NAME=$(basename "$COMP_DIR")
|
|
50
|
+
SPEC_FILE="$COMP_DIR/$(basename "$FILE_PATH" .tsx).spec.md"
|
|
51
|
+
ANATOMY_FILE="$COMP_DIR/$(basename "$FILE_PATH" .tsx).anatomy.stories.tsx"
|
|
52
|
+
|
|
53
|
+
# Extract default values from each source (best-effort grep; signal not proof)
|
|
54
|
+
CVA_DEFAULTS=$(grep -A5 "defaultVariants" "$FILE_PATH" 2>/dev/null | grep -E "(size|variant|state):" | head -3 | tr -d ' "' | sed 's/,$//' || echo "")
|
|
55
|
+
SPEC_DEFAULTS=""
|
|
56
|
+
[ -f "$SPEC_FILE" ] && SPEC_DEFAULTS=$(grep -E "★|預設|default" "$SPEC_FILE" 2>/dev/null | head -5 || echo "")
|
|
57
|
+
ANATOMY_DEFAULTS=""
|
|
58
|
+
[ -f "$ANATOMY_FILE" ] && ANATOMY_DEFAULTS=$(grep -E "★|default|defaultVariants" "$ANATOMY_FILE" 2>/dev/null | head -5 || echo "")
|
|
59
|
+
|
|
60
|
+
WARNING="⚠️ $COMP_NAME cva/defaultVariants touched — verify 3-way sync: tsx cva ↔ spec.md default markers ↔ anatomy SIZE_SPECS. Ref: SegmentedControl drift bug. Grep \"★|預設|default\" in $COMP_DIR/."
|
|
61
|
+
|
|
62
|
+
# Escape for JSON via jq
|
|
63
|
+
ESCAPED=$(printf '%s' "$WARNING" | jq -Rs .)
|
|
64
|
+
|
|
65
|
+
cat <<EOJSON
|
|
66
|
+
{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":$ESCAPED}}
|
|
67
|
+
EOJSON
|
|
68
|
+
|
|
69
|
+
exit 0
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# _governance_coverage_check.sh — PostToolUse helper:
|
|
3
|
+
# 偵測 governance file edit(meta-patterns / spec.md frontmatter / hook / SKILL / rules)
|
|
4
|
+
# → 跑 audit-preflight.mjs --check 比 baseline 是否新增 coverage gap
|
|
5
|
+
# → 有 gap → stderr inject 警告:user 必補 audit dim or 撤原則
|
|
6
|
+
#
|
|
7
|
+
# 對應 SSOT:
|
|
8
|
+
# - memory/feedback_audit_preflight_全盤查.md(2026-05-15 codified)
|
|
9
|
+
# - design-system-audit/SKILL.md Phase 0.5 invariant
|
|
10
|
+
# - user 原話「確保現在和未來都會自動涵蓋,當有新的準則就務必更新設計系統進階稽核的內容」
|
|
11
|
+
#
|
|
12
|
+
# 由 post_edit_dispatcher.sh orchestrate,non-standalone hook(不算 hook count)
|
|
13
|
+
|
|
14
|
+
source "$(dirname "$0")/../_log-fire.sh" 2>/dev/null && log_hook_fire
|
|
15
|
+
|
|
16
|
+
FILE_PATH=$(jq -r '.tool_input.file_path // empty')
|
|
17
|
+
[ -z "$FILE_PATH" ] && exit 0
|
|
18
|
+
|
|
19
|
+
# Governance file scope:M-rule / spec.md frontmatter / hook check / SKILL / rules
|
|
20
|
+
GOV_RE='(meta-patterns\.md|/spec\.md$|check_.*\.sh$|/SKILL\.md$|\.claude/rules/.*\.md$|CLAUDE\.md$)'
|
|
21
|
+
echo "$FILE_PATH" | grep -qE "$GOV_RE" || exit 0
|
|
22
|
+
|
|
23
|
+
# Pre-check audit-preflight 存在
|
|
24
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
25
|
+
PREFLIGHT="$PROJECT_DIR/scripts/audit-preflight.mjs"
|
|
26
|
+
[ -f "$PREFLIGHT" ] || exit 0
|
|
27
|
+
|
|
28
|
+
# 跑 preflight check mode(silent;exit 1 if gap)
|
|
29
|
+
PREFLIGHT_OUT=$(cd "$PROJECT_DIR" && node scripts/audit-preflight.mjs --check 2>&1)
|
|
30
|
+
PREFLIGHT_EXIT=$?
|
|
31
|
+
|
|
32
|
+
if [ "$PREFLIGHT_EXIT" -eq 1 ]; then
|
|
33
|
+
TODAY=$(date +%Y-%m-%d)
|
|
34
|
+
LOG_FILE="$PROJECT_DIR/.claude/logs/audit-preflight-${TODAY}.json"
|
|
35
|
+
GAP_COUNT=0
|
|
36
|
+
GAP_SAMPLE=""
|
|
37
|
+
if [ -f "$LOG_FILE" ]; then
|
|
38
|
+
GAP_COUNT=$(jq -r '.coverageGaps // 0' "$LOG_FILE" 2>/dev/null || echo 0)
|
|
39
|
+
GAP_SAMPLE=$(jq -r '.gaps[:3] | .[]' "$LOG_FILE" 2>/dev/null | head -3 | tr '\n' ',' | sed 's/,$//')
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
ADDITIONAL_CONTEXT="⚠️ Governance coverage gap(audit-preflight): ${FILE_PATH##*/} edit 後 ${GAP_COUNT} 原則無 audit dim cover(包含: ${GAP_SAMPLE})。若新加入的原則 → 必補 audit dim 進 design-system-audit/SKILL.md。若既有 false-positive(heuristic 不準)→ sub-agent semantic verify。SSOT: memory/feedback_audit_preflight_全盤查.md"
|
|
43
|
+
|
|
44
|
+
jq -n --arg ctx "$ADDITIONAL_CONTEXT" '{
|
|
45
|
+
hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: $ctx }
|
|
46
|
+
}'
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
exit 0
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PostToolUse hook: flag hardcoded user-facing strings in DS component tsx
|
|
3
|
+
# (CJK ≥ 3 chars or English ≥ 8 chars that look like sentence-case labels).
|
|
4
|
+
#
|
|
5
|
+
# Rationale(CLAUDE.md # Internationalization / 24-checklist #13 gap):
|
|
6
|
+
# DS primitive 的文案若 hardcode,consumer 換語言 / A/B test label 時要 forkDS。
|
|
7
|
+
# 正確路線:prop 接收 + `loadingText` / `emptyText` 等 slot,或走 i18n 層。
|
|
8
|
+
#
|
|
9
|
+
# Scope:
|
|
10
|
+
# - ONLY packages/design-system/src/components/**/*.tsx(primitives)
|
|
11
|
+
# - ONLY patterns/ 如果出現 user-facing string
|
|
12
|
+
# - SKIP stories.tsx(stories 用真實 Jira/Stripe 範例 = 刻意)
|
|
13
|
+
# - SKIP spec.md / anatomy
|
|
14
|
+
#
|
|
15
|
+
# Whitelist(不 flag):
|
|
16
|
+
# - 註解 // /* */
|
|
17
|
+
# - `aria-label` / `title` 等 a11y prop 若無對應 prop 可接收 → 仍 flag(warn 改成 prop)
|
|
18
|
+
# - 標示 `// i18n-allow: {rationale}` 該行白名單
|
|
19
|
+
#
|
|
20
|
+
# WARN-style, 不 block.
|
|
21
|
+
|
|
22
|
+
# Per-hook fire logging(enables /knowledge-prune D2 dead-hook detection)
|
|
23
|
+
source "$(dirname "$0")/../_log-fire.sh" 2>/dev/null && log_hook_fire
|
|
24
|
+
|
|
25
|
+
FILE_PATH=$(jq -r '.tool_input.file_path // empty')
|
|
26
|
+
|
|
27
|
+
# Scope: only design-system components/patterns .tsx
|
|
28
|
+
if ! echo "$FILE_PATH" | grep -qE 'packages/design-system/src/(components|patterns)/.*\.tsx$'; then
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
if echo "$FILE_PATH" | grep -qE '\.(stories|principles|anatomy\.stories)\.tsx$'; then
|
|
32
|
+
exit 0
|
|
33
|
+
fi
|
|
34
|
+
if [ ! -f "$FILE_PATH" ]; then
|
|
35
|
+
exit 0
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# Detect hardcoded user-facing CJK / English labels via perl oneliner
|
|
39
|
+
# (macOS-portable: perl is always present; grep -P / pcregrep / rg 不一定有)
|
|
40
|
+
#
|
|
41
|
+
# Perl logic(per line):
|
|
42
|
+
# 1. 跳過 `// ...` 單行註解(但不影響行內其他 content 判斷 — 我們直接跳整行簡化)
|
|
43
|
+
# 2. 跳過 `/* ... */` 一行註解
|
|
44
|
+
# 3. 跳過該行含 `i18n-allow` 白名單標記
|
|
45
|
+
# 4. 對剩餘行偵測 JSX 文字節點 `>...<` 或字串字面量 `"..."` / `'...'`
|
|
46
|
+
# 內含 ≥ 3 個 CJK 字元(\p{Han} / \p{Hiragana} / \p{Katakana} / \p{Hangul})
|
|
47
|
+
# 5. English:narrow detection,僅當 JSX 文字節點或屬性值含 ≥ 2 個 sentence-case 詞
|
|
48
|
+
#
|
|
49
|
+
# Output: "line_num:matched_snippet"(每 file 最多 5 條)
|
|
50
|
+
|
|
51
|
+
VIOLATIONS=""
|
|
52
|
+
|
|
53
|
+
CJK_HITS=$(perl -CSD -ne '
|
|
54
|
+
BEGIN {
|
|
55
|
+
our $jsdoc = 0; our $console_open = 0; our $jsx_comment = 0;
|
|
56
|
+
our $allow_pending = 0; our $allow_depth = 0;
|
|
57
|
+
}
|
|
58
|
+
# ── block-level i18n-allow:上一行有 `// i18n-allow-block` 或
|
|
59
|
+
# `// i18n-allow`(單獨 comment 行)→ 標記下一個 `{...}` 區塊整個 skip ──
|
|
60
|
+
if (m{^\s*(?://|/\*+)\s*i18n-allow(-block)?\b}) { $allow_pending = 1; next }
|
|
61
|
+
if ($allow_pending && m{[\{\[]}) {
|
|
62
|
+
$allow_pending = 0;
|
|
63
|
+
my $opens = () = /[\{\[]/g; my $closes = () = /[\}\]]/g;
|
|
64
|
+
$allow_depth = $opens - $closes;
|
|
65
|
+
next if $allow_depth > 0; # only enter block if net positive opens
|
|
66
|
+
}
|
|
67
|
+
if ($allow_depth > 0) {
|
|
68
|
+
my $opens = () = /[\{\[]/g; my $closes = () = /[\}\]]/g;
|
|
69
|
+
$allow_depth += $opens - $closes;
|
|
70
|
+
next;
|
|
71
|
+
}
|
|
72
|
+
# ── JSDoc block(多行 /** ... */)── stateful ──
|
|
73
|
+
if (!$jsdoc && m{/\*\*}) { $jsdoc = 1 }
|
|
74
|
+
if ($jsdoc) {
|
|
75
|
+
if (m{\*/}) { $jsdoc = 0 }
|
|
76
|
+
next;
|
|
77
|
+
}
|
|
78
|
+
# ── JSX block comment(多行 {/* ... */})── stateful ──
|
|
79
|
+
if (!$jsx_comment && m{\{/\*}) { $jsx_comment = 1 }
|
|
80
|
+
if ($jsx_comment) {
|
|
81
|
+
if (m{\*/\s*\}}) { $jsx_comment = 0 }
|
|
82
|
+
next;
|
|
83
|
+
}
|
|
84
|
+
# ── Line-level 註解 skip ──────────────
|
|
85
|
+
next if m{^\s*//}; # single-line comment
|
|
86
|
+
next if m{^\s*/\*.*\*/\s*$}; # single-line block comment
|
|
87
|
+
next if m{^\s*\*}; # stray JSDoc line
|
|
88
|
+
next if m{i18n-allow};
|
|
89
|
+
# ── strip trailing `//` comments(naïve:不處理 URL 等 string 內 `//`)─
|
|
90
|
+
# Fix FP:`code() // CJK comment` 被誤匹配 comment 內的 CJK
|
|
91
|
+
# 注意:不能用 `[^\x27"]` 限制,comment 內可能有 `"` 被 CJK 夾住
|
|
92
|
+
# 簡單策略 — `//` 後到 EOL 全 strip(string 內的 `//` 罕見 → 可接受 FP trade)
|
|
93
|
+
$_ =~ s{\s+//.*$}{};
|
|
94
|
+
# ── dev-only console.* / throw new Error() multi-line — stateful ─
|
|
95
|
+
# 這些是 DS internal dev / developer-facing diagnostics,consumer 看不到
|
|
96
|
+
if (!$console_open && m{(console\.(warn|error|log|info|debug)|throw\s+new\s+(Error|TypeError|RangeError))\s*\(}) {
|
|
97
|
+
$console_open = 1;
|
|
98
|
+
if (m{\)\s*[;,]?\s*$}) { $console_open = 0 }
|
|
99
|
+
next;
|
|
100
|
+
}
|
|
101
|
+
if ($console_open) {
|
|
102
|
+
if (m{\)\s*[;,]?\s*$}) { $console_open = 0 }
|
|
103
|
+
next;
|
|
104
|
+
}
|
|
105
|
+
# ── DS-internal metadata keys(designer-facing,非 end-user-facing)──
|
|
106
|
+
# 典型:buttonMeta / componentMeta / story args 的 `purpose:` / `description:` / `rationale:` / `when:` / `dont:` / `note:` / `text:`(stories)
|
|
107
|
+
# 匹配「行內任何位置出現 `{metadata_key}: "..."`」:如 `primary: { purpose: "..." }` 的 purpose
|
|
108
|
+
next if m{\b(purpose|description|rationale|when|dont|note|tip|detail|explanation|summary)\s*:\s*[\x27"]};
|
|
109
|
+
# ── JSX text node or string literal with CJK ≥ 3 ─
|
|
110
|
+
if (m{([>"\047])([^<"\047]*?[\p{Han}\p{Hiragana}\p{Katakana}\p{Hangul}]{3,}[^<"\047]*?)([<"\047])}) {
|
|
111
|
+
my $snippet = $2;
|
|
112
|
+
$snippet =~ s/^\s+|\s+$//g;
|
|
113
|
+
$snippet = substr($snippet, 0, 80) . "…" if length($snippet) > 80;
|
|
114
|
+
print "$.:$snippet\n";
|
|
115
|
+
}
|
|
116
|
+
' "$FILE_PATH" 2>/dev/null | head -5)
|
|
117
|
+
|
|
118
|
+
if [ -n "$CJK_HITS" ]; then
|
|
119
|
+
VIOLATIONS="${VIOLATIONS}\n⚠️ Hardcoded CJK strings in DS primitive(consumer 換語言要 fork 元件):\n${CJK_HITS}\n 修法:改成 prop 接收(e.g. \`loadingText / emptyText / label\` slot)。若確實 DS 層預設(如 internal debug),在該行末加 \`// i18n-allow: {rationale}\` 白名單。"
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# English sentence-case labels — narrow detection
|
|
123
|
+
ENG_HITS=$(perl -CSD -ne '
|
|
124
|
+
BEGIN { our $allow_pending = 0; our $allow_depth = 0; }
|
|
125
|
+
# ── block-level i18n-allow(同 CJK block)─
|
|
126
|
+
if (m{^\s*(?://|/\*+)\s*i18n-allow(-block)?\b}) { $allow_pending = 1; next }
|
|
127
|
+
if ($allow_pending && m{[\{\[]}) {
|
|
128
|
+
$allow_pending = 0;
|
|
129
|
+
my $opens = () = /[\{\[]/g; my $closes = () = /[\}\]]/g;
|
|
130
|
+
$allow_depth = $opens - $closes;
|
|
131
|
+
next if $allow_depth > 0; # only enter block if net positive opens
|
|
132
|
+
}
|
|
133
|
+
if ($allow_depth > 0) {
|
|
134
|
+
my $opens = () = /[\{\[]/g; my $closes = () = /[\}\]]/g;
|
|
135
|
+
$allow_depth += $opens - $closes;
|
|
136
|
+
next;
|
|
137
|
+
}
|
|
138
|
+
# ── 註解 skip(line-level)─────────────────────
|
|
139
|
+
next if m{^\s*//};
|
|
140
|
+
next if m{^\s*/\*.*\*/\s*$};
|
|
141
|
+
next if m{^\s*\*};
|
|
142
|
+
next if m{^\s*/\*\*?\s*$};
|
|
143
|
+
next if m{i18n-allow};
|
|
144
|
+
next if m{console\.(warn|error|log|info|debug)\s*\(};
|
|
145
|
+
next if m{className=|aria-label=|data-\w+=|href=|key=};
|
|
146
|
+
if (m{>([A-Z][a-z]+ [a-z]+(?: [a-z]+)+)<} || m{[\"\047]([A-Z][a-z]+ [a-z]+(?: [a-z]+)+)[\"\047]}) {
|
|
147
|
+
print "$.:$1\n";
|
|
148
|
+
}
|
|
149
|
+
' "$FILE_PATH" 2>/dev/null | head -3)
|
|
150
|
+
|
|
151
|
+
if [ -n "$ENG_HITS" ]; then
|
|
152
|
+
VIOLATIONS="${VIOLATIONS}\n⚠️ Hardcoded English sentence-case labels:\n${ENG_HITS}\n 若是 user-facing label,改 prop 接收;若是 internal,加 \`// i18n-allow\` 白名單。"
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
# ── Emit warning if any violation ────────────────────────────────────────────
|
|
156
|
+
if [ -n "$VIOLATIONS" ]; then
|
|
157
|
+
ESCAPED=$(printf "%b" "$VIOLATIONS" | jq -Rs .)
|
|
158
|
+
cat <<EOJSON
|
|
159
|
+
{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"i18n hygiene 檢查(24-checklist #13 gap):${ESCAPED}\n\nDS primitive 應 prop-driven,hardcoded 文案讓 consumer 換語言時要 fork。"}}
|
|
160
|
+
EOJSON
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
exit 0
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PostToolUse hook: detect layoutSpace canonical violations in stories / showcase tsx.
|
|
3
|
+
#
|
|
4
|
+
# Catches typical block-vs-inline gap mistakes per layoutSpace.spec.md:
|
|
5
|
+
# 1. Hardcoded mt-N / mb-N / pt-N / pb-N (4/6/8) instead of tokens — should use --layout-space-*
|
|
6
|
+
# 2. gap-tight on a flex column form with all-inline elements (should be loose per rule 3)
|
|
7
|
+
# 3. block element (DataTable / Textarea / Editor) followed by mt-tight on next sibling
|
|
8
|
+
# (block → 容器底 / chrome band 應該 loose,規則 4)
|
|
9
|
+
#
|
|
10
|
+
# Scope: design-system stories.tsx + app/ + explorations/ (consumer-side layout).
|
|
11
|
+
# WARN-style (not BLOCK): emit additionalContext so AI reads and can fix.
|
|
12
|
+
|
|
13
|
+
# Per-hook fire logging
|
|
14
|
+
source "$(dirname "$0")/../_log-fire.sh" 2>/dev/null && log_hook_fire
|
|
15
|
+
|
|
16
|
+
FILE_PATH=$(jq -r '.tool_input.file_path // empty')
|
|
17
|
+
|
|
18
|
+
# Scope: only stories.tsx + app code (not core component .tsx — they own their own internal padding)
|
|
19
|
+
if ! echo "$FILE_PATH" | grep -qE '(\.stories\.tsx$|src/app/.*\.tsx$|src/explorations/.*\.tsx$)'; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
if [ ! -f "$FILE_PATH" ]; then
|
|
23
|
+
exit 0
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
VIOLATIONS=""
|
|
27
|
+
|
|
28
|
+
# ── Check 1: hardcoded mt-N / mb-N adjacent to <DataTable / Textarea / Editor> ──
|
|
29
|
+
# Block elements need loose bottom/top gap; tight is rule violation.
|
|
30
|
+
# Pattern: <DataTable...>...</...> followed by <Alert / BulkActionBar / div with mt-tight>
|
|
31
|
+
BLOCK_TIGHT_HITS=$(grep -nE "(mt|mb)-\[var\(--layout-space-tight\)\]" "$FILE_PATH" 2>/dev/null | head -5)
|
|
32
|
+
if [ -n "$BLOCK_TIGHT_HITS" ]; then
|
|
33
|
+
# Only flag if file ALSO contains block elements (DataTable / Textarea / Editor / CodeBlock)
|
|
34
|
+
if grep -qE '<(DataTable|Textarea|Editor|CodeBlock)' "$FILE_PATH" 2>/dev/null; then
|
|
35
|
+
VIOLATIONS="${VIOLATIONS}\n⚠️ tight margin near block element(可能違反規則 4:block → 容器底 / chrome band = loose):\n${BLOCK_TIGHT_HITS}\n → 確認該位置:block 旁邊應 loose;只有 block ↔ block 緊鄰且都在 form 內才用 tight。"
|
|
36
|
+
fi
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# ── Check 3: gap-tight on flex column form with all-inline (likely violation)──
|
|
40
|
+
# 規則 3:inline ↔ inline = loose,不是 tight
|
|
41
|
+
GAP_TIGHT_FLEX=$(grep -nE 'flex-col\s+gap-\[var\(--layout-space-tight\)\]' "$FILE_PATH" 2>/dev/null | head -3)
|
|
42
|
+
if [ -n "$GAP_TIGHT_FLEX" ]; then
|
|
43
|
+
VIOLATIONS="${VIOLATIONS}\n⚠️ flex-col gap-tight(若內容全是 inline 元件就違反規則 3:inline ↔ inline 應 loose):\n${GAP_TIGHT_FLEX}\n → 確認 form 內含 block(Table/Textarea/Editor)?有 → 保留 tight 合規;全 inline → 改 loose。"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
if [ -n "$VIOLATIONS" ]; then
|
|
47
|
+
ADDITIONAL_CONTEXT=$(printf '⚠️ layoutSpace canonical 提醒(tokens/layoutSpace/layoutSpace.spec.md):%b' "$VIOLATIONS")
|
|
48
|
+
jq -n --arg ctx "$ADDITIONAL_CONTEXT" '{
|
|
49
|
+
hookSpecificOutput: {
|
|
50
|
+
hookEventName: "PostToolUse",
|
|
51
|
+
additionalContext: $ctx
|
|
52
|
+
}
|
|
53
|
+
}'
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
exit 0
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PostToolUse hook: detect hand-crafted overlay chrome violating mindset #2.
|
|
3
|
+
#
|
|
4
|
+
# Catches: <div className="...px-[var(--layout-space-loose)]...border-(b|t) border-divider">
|
|
5
|
+
# in stories.tsx / DataTable helper.tsx / app code — pattern means consumer self-rendered
|
|
6
|
+
# overlay header/footer chrome instead of consuming SurfaceHeader / SurfaceBody / SurfaceFooter
|
|
7
|
+
# (or PopoverHeader / DialogHeader / SheetHeader).
|
|
8
|
+
#
|
|
9
|
+
# Why block: canonical primitives bundle padding + border + close X (Popover) + autofocus +
|
|
10
|
+
# typography (PopoverTitle text-body font-medium). Self-rendering bypasses these = mindset #2
|
|
11
|
+
# violation + silently breaks alignment / close X 一致性。
|
|
12
|
+
#
|
|
13
|
+
# 對齊 patterns/overlay-surface/overlay-surface.spec.md「Consumer rule」+
|
|
14
|
+
# components/Popover/popover.tsx:72「所有 PopoverHeader 一律附右上 X」canonical。
|
|
15
|
+
#
|
|
16
|
+
# Escape hatch:add `// overlay-handcraft-allow: <reason>` on prev/same line for intentional cases
|
|
17
|
+
# (e.g. non-overlay panel that just borrows the layout-space token).
|
|
18
|
+
#
|
|
19
|
+
# WARN-style (additionalContext) — AI reads + fixes next iteration.
|
|
20
|
+
|
|
21
|
+
# Per-hook fire logging
|
|
22
|
+
source "$(dirname "$0")/../_log-fire.sh" 2>/dev/null && log_hook_fire
|
|
23
|
+
|
|
24
|
+
FILE_PATH=$(jq -r '.tool_input.file_path // empty')
|
|
25
|
+
|
|
26
|
+
# Scope: tsx files (stories, components, patterns, app, explorations)
|
|
27
|
+
if ! echo "$FILE_PATH" | grep -qE '\.tsx$'; then
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
# Skip spec.md / anatomy stories (different context)
|
|
31
|
+
if echo "$FILE_PATH" | grep -qE '(\.spec\.md$|\.anatomy\.stories\.tsx$|\.principles\.stories\.tsx$)'; then
|
|
32
|
+
exit 0
|
|
33
|
+
fi
|
|
34
|
+
if [ ! -f "$FILE_PATH" ]; then
|
|
35
|
+
exit 0
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# Pattern: <div className="...px-[var(--layout-space-loose)]...border-(b|t) border-divider...">
|
|
39
|
+
PATTERN='<div className="[^"]*px-\[var\(--layout-space-loose\)\][^"]*border-(b|t) border-divider'
|
|
40
|
+
HITS=$(grep -nE "$PATTERN" "$FILE_PATH" 2>/dev/null | head -5)
|
|
41
|
+
|
|
42
|
+
if [ -n "$HITS" ]; then
|
|
43
|
+
# filter out lines with allowlist comment
|
|
44
|
+
FILTERED=""
|
|
45
|
+
while IFS= read -r line; do
|
|
46
|
+
line_num=$(echo "$line" | cut -d: -f1)
|
|
47
|
+
[ -z "$line_num" ] && continue
|
|
48
|
+
# check current line + previous line for allowlist
|
|
49
|
+
prev_line=$(sed -n "$((line_num-1))p" "$FILE_PATH" 2>/dev/null)
|
|
50
|
+
cur_line=$(sed -n "${line_num}p" "$FILE_PATH" 2>/dev/null)
|
|
51
|
+
if echo "$prev_line $cur_line" | grep -q 'overlay-handcraft-allow:'; then
|
|
52
|
+
continue
|
|
53
|
+
fi
|
|
54
|
+
FILTERED="${FILTERED}${line}\n"
|
|
55
|
+
done <<< "$HITS"
|
|
56
|
+
|
|
57
|
+
if [ -n "$FILTERED" ]; then
|
|
58
|
+
VIOLATIONS="${VIOLATIONS}\n⚠️ Hand-crafted overlay chrome detected(自刻 overlay 結構違 mindset #2):\n${FILTERED}\n → 改用 primitive:\n Popover content → PopoverHeader / PopoverBody / PopoverFooter / PopoverTitle\n Dialog content → DialogHeader / DialogBody / DialogFooter / DialogTitle\n Generic overlay panel → SurfaceHeader / SurfaceBody / SurfaceFooter (overlay-surface)\n Why:canonical 自帶 padding token + border + close X(Popover)+ autofocus + title typography。\n Escape hatch:加 \`// overlay-handcraft-allow: <reason>\` 在同/前行。"
|
|
59
|
+
fi
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# ── Check 2.5: Raw row handcraft (px-loose + py + rounded-md + hover:bg-neutral-hover) ──
|
|
63
|
+
# Pattern:`<div className="...flex...gap-2 px-loose py-1.5 rounded-md hover:bg-neutral-hover">`
|
|
64
|
+
# = 自刻 MenuItem-like row 違反 mindset #2(MenuItem primitive 自帶這些 + size canonical + a11y)
|
|
65
|
+
ROW_PATTERN='<div className="[^"]*flex[^"]*gap-[12][^"]*px-\[var\(--layout-space-loose\)\][^"]*hover:bg-neutral-hover[^"]*rounded'
|
|
66
|
+
ROW_HITS=$(grep -nE "$ROW_PATTERN" "$FILE_PATH" 2>/dev/null | head -3)
|
|
67
|
+
if [ -n "$ROW_HITS" ] && ! grep -qE 'menu-item-handcraft-allow:' "$FILE_PATH" 2>/dev/null; then
|
|
68
|
+
VIOLATIONS="${VIOLATIONS}\n⚠️ 自刻 row(MenuItem-like)違反 mindset #2:\n${ROW_HITS}\n → 改用 <MenuItem startIcon={...} endContent={...} disabled={...}>label</MenuItem>\n Why:MenuItem 自帶 SelectionItem py 公式 + size canonical + a11y(role=option, aria-disabled, aria-selected) + cursor-not-allowed disabled。\n Escape hatch:加 \`// menu-item-handcraft-allow: <reason>\` 在檔頭。"
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
# ── Check 2: Raw <Checkbox> count > 1 not in <CheckboxGroup> ──
|
|
72
|
+
# 同 root cause(自刻 SelectionItem 包 Checkbox 而非消費 CheckboxGroup primitive)。
|
|
73
|
+
# 對齊 checkbox.spec.md「群組模式(CheckboxGroup)」canonical line 225:
|
|
74
|
+
# 多選 Checkbox 必包 <CheckboxGroup>(zero-gap canonical + Context 隔離 + a11y group)。
|
|
75
|
+
CB_COUNT=$(grep -c '<Checkbox\b' "$FILE_PATH" 2>/dev/null | head -1 || echo 0)
|
|
76
|
+
CBG_COUNT=$(grep -c '<CheckboxGroup\b' "$FILE_PATH" 2>/dev/null | head -1 || echo 0)
|
|
77
|
+
CB_COUNT=${CB_COUNT:-0}
|
|
78
|
+
CBG_COUNT=${CBG_COUNT:-0}
|
|
79
|
+
if [ "$CB_COUNT" -ge 2 ] && [ "$CBG_COUNT" -eq 0 ]; then
|
|
80
|
+
CB_HITS=$(grep -nE '<Checkbox\b' "$FILE_PATH" 2>/dev/null | head -3)
|
|
81
|
+
# allowlist: same-line or prev-line has // checkbox-group-allow:
|
|
82
|
+
if ! grep -qE 'checkbox-group-allow:' "$FILE_PATH" 2>/dev/null; then
|
|
83
|
+
VIOLATIONS="${VIOLATIONS}\n⚠️ 多個 raw <Checkbox> 未包 <CheckboxGroup>(${CB_COUNT} hits)— 違反 checkbox.spec.md 群組 canonical:\n${CB_HITS}\n → 改用 <CheckboxGroup><Checkbox label=\"...\" />...</CheckboxGroup>\n Why:CheckboxGroup 自帶 zero-gap canonical(SelectionItem py 公式)+ Context 隔離 fieldCtx + a11y group;raw 自刻 wrapper 違 mindset #2。\n Escape hatch:加 \`// checkbox-group-allow: <reason>\` 在檔頭。"
|
|
84
|
+
fi
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# ── Check 5: Same-row consistency 違反(同 row 混 ItemInlineActionButton + Button iconOnly)──
|
|
88
|
+
# 對齊 inline-action.spec.md L152「Same-row consistency rule:同 action row 所有 icon action 必同一類」。
|
|
89
|
+
# Pattern:同檔出現 <ItemInlineActionButton 與 <Button.*iconOnly,且非 menu primitive impl(menu 內 Button 為合法 chrome)。
|
|
90
|
+
HAS_INLINE=$(grep -c '<ItemInlineActionButton' "$FILE_PATH" 2>/dev/null | head -1)
|
|
91
|
+
HAS_BTN_ICON=$(grep -cE '<Button[^>]*iconOnly' "$FILE_PATH" 2>/dev/null | head -1)
|
|
92
|
+
HAS_INLINE=${HAS_INLINE:-0}
|
|
93
|
+
HAS_BTN_ICON=${HAS_BTN_ICON:-0}
|
|
94
|
+
IS_MENU_PRIMITIVE2=$(echo "$FILE_PATH" | grep -cE '(DropdownMenu|SelectMenu|Combobox|Menu)/.*\.tsx$' | head -1)
|
|
95
|
+
IS_MENU_PRIMITIVE2=${IS_MENU_PRIMITIVE2:-0}
|
|
96
|
+
if [ "$HAS_INLINE" -ge 1 ] && [ "$HAS_BTN_ICON" -ge 1 ] && [ "$IS_MENU_PRIMITIVE2" -eq 0 ]; then
|
|
97
|
+
if ! grep -qE 'same-row-mixed-allow:' "$FILE_PATH" 2>/dev/null; then
|
|
98
|
+
VIOLATIONS="${VIOLATIONS}\n⚠️ 同檔混用 <ItemInlineActionButton>(${HAS_INLINE}) + <Button.*iconOnly>(${HAS_BTN_ICON}):\n → 違反 inline-action.spec.md L152 Same-row consistency rule(同 row icon action 必同一類)。\n → Box size 不一致(InlineAction 16+18 vs Button text sm 28)會 gap 斷裂。\n → 修法:row 內 icon action 全 ItemInlineActionButton(對齊 size=md / 16+18 hover bg)。\n Escape hatch:加 \`// same-row-mixed-allow: <reason>\` 在檔頭(若 chrome corner action group 跟 row 不同 row,可分開)。"
|
|
99
|
+
fi
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# ── Check 4: Panel-style Popover + MenuItem co-occur(2026-04-29) ──
|
|
103
|
+
# Pattern:同檔出現 <PopoverHeader> 且 <MenuItem>(不是 DropdownMenu / SelectMenu primitive 自身)
|
|
104
|
+
# → panel-style popover 不該硬塞 menu specialization。MenuItem 預設 `px-3` 不對齊 panel chrome
|
|
105
|
+
# `loose`,且 startIcon 色彩無 override → 該用視覺 primitive(ItemPrefix/ItemLabel/ItemSuffix)自組。
|
|
106
|
+
HAS_POP_HEADER=$(grep -c '<PopoverHeader' "$FILE_PATH" 2>/dev/null | head -1)
|
|
107
|
+
HAS_MENU_ITEM=$(grep -c '<MenuItem\b' "$FILE_PATH" 2>/dev/null | head -1)
|
|
108
|
+
HAS_POP_HEADER=${HAS_POP_HEADER:-0}
|
|
109
|
+
HAS_MENU_ITEM=${HAS_MENU_ITEM:-0}
|
|
110
|
+
# Skip:DropdownMenu/SelectMenu primitive impl 本身(他們 import MenuItem 是合法 menu-style)
|
|
111
|
+
IS_MENU_PRIMITIVE=$(echo "$FILE_PATH" | grep -cE '(DropdownMenu|SelectMenu|Combobox)/.*\.tsx$' | head -1)
|
|
112
|
+
IS_MENU_PRIMITIVE=${IS_MENU_PRIMITIVE:-0}
|
|
113
|
+
if [ "$HAS_POP_HEADER" -ge 1 ] && [ "$HAS_MENU_ITEM" -ge 1 ] && [ "$IS_MENU_PRIMITIVE" -eq 0 ]; then
|
|
114
|
+
if ! grep -qE 'panel-menuitem-allow:' "$FILE_PATH" 2>/dev/null; then
|
|
115
|
+
VIOLATIONS="${VIOLATIONS}\n⚠️ Panel-style Popover(<PopoverHeader>)+ <MenuItem> 同檔(${HAS_MENU_ITEM} hits):\n → MenuItem 是 menu specialization(px-3 menu-style + icon 色繼承),不適 panel chrome loose。\n → 改用視覺 primitive 自組:ItemPrefix + ItemLabel + ItemSuffix(item-anatomy.tsx)+ ROW_PADDING_BY_SIZE.md\n Why:panel-style 需對齊 chrome \`loose\` + utility chrome icon color(neutral-7 / fg-muted),MenuItem 兩處 workaround = 錯 layer。\n Escape hatch:加 \`// panel-menuitem-allow: <reason>\` 在檔頭。"
|
|
116
|
+
fi
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# ── Check 6: Overlay body 重新引入 flush / 等價 boolean variant(2026-05-01)──
|
|
120
|
+
# 對齊 patterns/overlay-surface/overlay-surface.spec.md「List-as-region in overlay body」。
|
|
121
|
+
# 2026-05-01 移除 flush(rationale:Material/Atlassian/Mantine/shadcn 主流不做 universal LayoutBody flush;
|
|
122
|
+
# variant 不解決底層脆弱 — 加 1 row search/banner 就破功)。
|
|
123
|
+
# 防止未來 silent re-introduction:DialogBody / SheetBody / PopoverBody tsx 內出現
|
|
124
|
+
# `flush?: boolean` / `flush = false` / `naked?: boolean` / `bare?: boolean` / `noPadding?: boolean` 同類 boolean 變體。
|
|
125
|
+
# Scope:`components/(Dialog|Sheet|Popover)/*.tsx`(非 stories)。
|
|
126
|
+
IS_OVERLAY_BODY_TSX=$(echo "$FILE_PATH" | grep -cE 'components/(Dialog|Sheet|Popover)/[^/]+\.tsx$' | head -1)
|
|
127
|
+
IS_OVERLAY_BODY_TSX=${IS_OVERLAY_BODY_TSX:-0}
|
|
128
|
+
if [ "$IS_OVERLAY_BODY_TSX" -ge 1 ] && ! echo "$FILE_PATH" | grep -qE '\.stories\.tsx$'; then
|
|
129
|
+
STRIPPED_PROPS_PATTERN='(flush|naked|bare|stripped|unpadded|noPadding|paddingless)\??:\s*boolean'
|
|
130
|
+
STRIPPED_HITS=$(grep -nE "$STRIPPED_PROPS_PATTERN" "$FILE_PATH" 2>/dev/null | head -3)
|
|
131
|
+
if [ -n "$STRIPPED_HITS" ] && ! grep -qE 'overlay-body-stripped-variant-allow:' "$FILE_PATH" 2>/dev/null; then
|
|
132
|
+
VIOLATIONS="${VIOLATIONS}\n⚠️ Overlay body 重新引入 stripped-padding boolean variant(2026-05-01 已移除):\n${STRIPPED_HITS}\n → list-as-region in overlay body canonical = consumer 用 className override:\n <DialogBody className=\"!px-0 !pt-0 !pb-0\"><div className=\"py-2\">{items}</div></DialogBody>\n Why removed:Material/Atlassian/Mantine/shadcn 主流不做 universal LayoutBody flush;\n variant 不解決底層脆弱(加 1 row search/banner 就破功)+ 把 1 surface decision 拆兩 API。\n 詳 overlay-surface.spec.md「List-as-region in overlay body」+ memory feedback_layout_v6_canonical.md\n Escape hatch:加 \`// overlay-body-stripped-variant-allow: <reason>\` 在檔頭(必含 ≥3 家世界級對照 + multi-row hold 保證)。"
|
|
133
|
+
fi
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
if [ -n "$VIOLATIONS" ]; then
|
|
137
|
+
ESCAPED=$(printf "%b" "$VIOLATIONS" | jq -Rs .)
|
|
138
|
+
cat <<EOJSON
|
|
139
|
+
{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"Primitive consumption 檢查發現違規:${ESCAPED}"}}
|
|
140
|
+
EOJSON
|
|
141
|
+
fi
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PostToolUse hook: catch sparse PersonData literals that violate name-card.spec「DS-wide 預設展示一致 canonical」.
|
|
3
|
+
#
|
|
4
|
+
# Per name-card.spec.md:
|
|
5
|
+
# 所有 PersonData 來源(table seller / picker member / dialog reviewer)展示 NameCard 時
|
|
6
|
+
# description / status / statusMessage / fields 4 個 section 預設都應提供
|
|
7
|
+
# sparse `{ name, avatarUrl }` 資料 = bug,違反一致呈現
|
|
8
|
+
#
|
|
9
|
+
# Detects literal `{ name: '...', avatar(Url)?: '...' }` with NO other PersonData fields.
|
|
10
|
+
|
|
11
|
+
source "$(dirname "$0")/../_log-fire.sh" 2>/dev/null && log_hook_fire
|
|
12
|
+
|
|
13
|
+
FILE_PATH=$(jq -r '.tool_input.file_path // empty')
|
|
14
|
+
|
|
15
|
+
if ! echo "$FILE_PATH" | grep -qE '\.(tsx|stories\.tsx)$'; then
|
|
16
|
+
exit 0
|
|
17
|
+
fi
|
|
18
|
+
if [ ! -f "$FILE_PATH" ]; then
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# Skip the canonical sources(NameCard / Avatar / person-display 自身)
|
|
23
|
+
if echo "$FILE_PATH" | grep -qE '(NameCard|Avatar|person-display|avatar\.spec)'; then
|
|
24
|
+
exit 0
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Match `{ name: '...', avatarUrl?: '...' }` with no other PersonData fields on same line.
|
|
28
|
+
# A "rich" PersonData usually spans multiple lines OR has description/status/statusMessage/fields.
|
|
29
|
+
# Heuristic: single-line literal with only name + avatarUrl(no other PersonData props on same line).
|
|
30
|
+
SPARSE_HITS=$(grep -nE "\{\s*name:\s*['\"][^'\"]+['\"],\s*avatar(Url)?:\s*['\"][^'\"]+['\"]\s*\}" "$FILE_PATH" 2>/dev/null | head -5)
|
|
31
|
+
|
|
32
|
+
if [ -n "$SPARSE_HITS" ]; then
|
|
33
|
+
ADDITIONAL_CONTEXT=$(printf 'Sparse PersonData literal(違反 name-card.spec「DS-wide 預設展示一致 canonical」):\n%s\n → 補上 description / status / statusMessage / fields 4 sections,讓 NameCard hover 展示資訊量一致' "$SPARSE_HITS")
|
|
34
|
+
jq -n --arg ctx "$ADDITIONAL_CONTEXT" '{
|
|
35
|
+
hookSpecificOutput: {
|
|
36
|
+
hookEventName: "PostToolUse",
|
|
37
|
+
additionalContext: $ctx
|
|
38
|
+
}
|
|
39
|
+
}'
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
exit 0
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PreToolUse Edit/Write: 改過 tsx componentMeta / spec frontmatter 時,
|
|
3
|
+
# 自動跑 compile-stories --check 偵測 drift。
|
|
4
|
+
#
|
|
5
|
+
# 對齊 Story Auto-Compile Phase 4 — stories canonical 從 spec+tsx 機械產,
|
|
6
|
+
# key 不齊 = canonical drift,該修。本 hook 是 write-time early warning
|
|
7
|
+
# (git commit 前),讓 AI / user 立刻看到 drift 而非 audit 時才發現。
|
|
8
|
+
|
|
9
|
+
# Per-hook fire logging(enables /knowledge-prune D2 dead-hook detection)
|
|
10
|
+
source "$(dirname "$0")/../_log-fire.sh" 2>/dev/null && log_hook_fire
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
INPUT=$(cat)
|
|
15
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
|
|
16
|
+
|
|
17
|
+
[ -z "$FILE_PATH" ] && exit 0
|
|
18
|
+
|
|
19
|
+
# Only trigger on component tsx / spec.md writes
|
|
20
|
+
case "$FILE_PATH" in
|
|
21
|
+
*/design-system/components/*.tsx) ;;
|
|
22
|
+
*/design-system/components/*.spec.md) ;;
|
|
23
|
+
*) exit 0 ;;
|
|
24
|
+
esac
|
|
25
|
+
|
|
26
|
+
# Extract component name from path
|
|
27
|
+
COMP_NAME=$(echo "$FILE_PATH" | grep -oE 'components/[^/]+/' | head -1 | sed 's|components/||' | sed 's|/||')
|
|
28
|
+
[ -z "$COMP_NAME" ] && exit 0
|
|
29
|
+
|
|
30
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
31
|
+
COMPILE_SCRIPT="$PROJECT_DIR/scripts/compile-stories.mjs"
|
|
32
|
+
|
|
33
|
+
# Skip if compile script not yet built(graceful for pre-Phase-4 state)
|
|
34
|
+
[ ! -f "$COMPILE_SCRIPT" ] && exit 0
|
|
35
|
+
|
|
36
|
+
# Run drift check(quiet mode)
|
|
37
|
+
cd "$PROJECT_DIR" || exit 0
|
|
38
|
+
OUTPUT=$(node "$COMPILE_SCRIPT" "$COMP_NAME" --check 2>&1 || true)
|
|
39
|
+
|
|
40
|
+
# Check if drift detected(exit 1 or has 「spec/tsx canonical drift」 in output)
|
|
41
|
+
if echo "$OUTPUT" | grep -qE "spec/tsx canonical drift|spec-only|tsx-only"; then
|
|
42
|
+
MSG="📐 Story canonical drift detected in ${COMP_NAME}:\n\n${OUTPUT}\n\nFix tsx componentMeta export 或 spec frontmatter 讓 keys 對齊。"
|
|
43
|
+
ESCAPED=$(printf '%s' "$MSG" | jq -Rs .)
|
|
44
|
+
printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":%s}}\n' "$ESCAPED"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# Skip graceful(migration 未做的元件)不報
|
|
48
|
+
exit 0
|