@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,205 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Field family unified invariant hook(2026-05-08 cluster A consolidation)
|
|
3
|
+
#
|
|
4
|
+
# Merges 4 PreToolUse hooks(原各檔已 retire,合併入此)— 共 4 條 sub-rules:
|
|
5
|
+
# A.1 naked row-mode propagation(原 check_naked_row_mode_propagation,P0 BLOCKER)
|
|
6
|
+
# A.2 FieldControlGroup wrapper direct child(原 check_field_control_group_direct_child,P1 WARN)
|
|
7
|
+
# A.3 Field state ring SSOT(原 check_field_state_token_consume 3 sub-rules,P0 BLOCKER)
|
|
8
|
+
# A.4 disabled placeholder color(原 check_disabled_placeholder_color,P1 stderr only)
|
|
9
|
+
#
|
|
10
|
+
# Why merge:皆 Field 家族 invariant,共用 INPUT parsing + Edit/Write filter pattern,
|
|
11
|
+
# 分散在 4 個 hook 是「散裝 SSOT」(M17 + Anthropic ≤ 15 hook best practice 違反)。
|
|
12
|
+
#
|
|
13
|
+
# Exit code precedence:BLOCK(2)> WARN(1)> INFO(0)。每 rule 可獨立觸發,worst 勝。
|
|
14
|
+
#
|
|
15
|
+
# Per-rule allowlist(各自獨立):
|
|
16
|
+
# A.1: `// @naked-row-mode-allow: <reason>`
|
|
17
|
+
# A.2: `// @fcg-wrapper-allow: <reason>` 或檔頭
|
|
18
|
+
# A.3: `// @field-state-ring-allow: <reason>`
|
|
19
|
+
# A.4: `// @disabled-color-allow: <reason>`
|
|
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
|
+
# Tool filter — 只 Edit/Write/MultiEdit 跑
|
|
30
|
+
case "$TOOL" in
|
|
31
|
+
Edit|Write|MultiEdit) ;;
|
|
32
|
+
*) exit 0 ;;
|
|
33
|
+
esac
|
|
34
|
+
|
|
35
|
+
# 不是 .tsx / .ts 直接過(各 rule 內部還會再 narrow)
|
|
36
|
+
case "$FILE_PATH" in
|
|
37
|
+
*.tsx|*.ts) ;;
|
|
38
|
+
*) exit 0 ;;
|
|
39
|
+
esac
|
|
40
|
+
|
|
41
|
+
# 讀 merged content(舊檔 + 新 edit 拼起)— A.1 / A.3 需要整檔判 naked variant 存在性
|
|
42
|
+
FILE_CONTENT=""
|
|
43
|
+
if [ -f "$FILE_PATH" ]; then
|
|
44
|
+
FILE_CONTENT=$(cat "$FILE_PATH")
|
|
45
|
+
fi
|
|
46
|
+
NEW_CONTENT=$(echo "$INPUT" | jq -r '
|
|
47
|
+
(.tool_input.content // "") + "\n" +
|
|
48
|
+
(.tool_input.new_string // "") + "\n" +
|
|
49
|
+
([.tool_input.edits[]? | .new_string] | join("\n"))
|
|
50
|
+
' 2>/dev/null || echo "")
|
|
51
|
+
|
|
52
|
+
# A.2 / A.4 只看 NEW_CONTENT(diff-level signal),A.1 / A.3 看 MERGED
|
|
53
|
+
MERGED_CONTENT="${FILE_CONTENT}
|
|
54
|
+
${NEW_CONTENT}"
|
|
55
|
+
|
|
56
|
+
[ -z "${MERGED_CONTENT//[[:space:]]/}" ] && exit 0
|
|
57
|
+
|
|
58
|
+
WORST=0
|
|
59
|
+
record_worst() { local lvl=$1; [ "$lvl" -gt "$WORST" ] && WORST=$lvl; }
|
|
60
|
+
|
|
61
|
+
# ── A.1 naked row-mode propagation(P0 BLOCKER)─────────────────────────────────
|
|
62
|
+
case "$FILE_PATH" in
|
|
63
|
+
*components/*.tsx)
|
|
64
|
+
case "$FILE_PATH" in
|
|
65
|
+
*/field-wrapper.tsx|*/textarea.tsx) ;; # SSOT host skip
|
|
66
|
+
*)
|
|
67
|
+
if ! echo "$MERGED_CONTENT" | grep -q '@naked-row-mode-allow' \
|
|
68
|
+
&& echo "$MERGED_CONTENT" | grep -E "variant:\s*['\"]naked['\"]|variant=\{?['\"]naked['\"]" >/dev/null \
|
|
69
|
+
&& echo "$MERGED_CONTENT" | grep -E "(inline-flex|flex)[^\"'\`]*items-center" >/dev/null \
|
|
70
|
+
&& ! echo "$MERGED_CONTENT" | grep -q "nakedCellRowModeAlign"; then
|
|
71
|
+
cat >&2 <<EOF
|
|
72
|
+
|
|
73
|
+
┄┄┄ A.1 check_field_family_invariants — naked row-mode propagation BLOCKER ┄┄┄
|
|
74
|
+
|
|
75
|
+
[P0] ${FILE_PATH}
|
|
76
|
+
偵測到此檔消費 \`variant="naked"\` + 內部 wrapper hardcode \`items-center\`,
|
|
77
|
+
但**未** import / apply \`nakedCellRowModeAlign\` SSOT。
|
|
78
|
+
|
|
79
|
+
⚠️ M19 canonical:naked variant 元件所有內部 wrapper 必 propagate host cell
|
|
80
|
+
\`data-row-mode\`(autoRow→items-start / fixed→items-center)。
|
|
81
|
+
|
|
82
|
+
修法:
|
|
83
|
+
1. import { nakedCellRowModeAlign } from '@/design-system/components/Field/field-wrapper'
|
|
84
|
+
2. wrapper className 加上 SSOT(eg \`cn('flex-1 min-w-0 inline-flex items-center', nakedCellRowModeAlign)\`)
|
|
85
|
+
3. 例外:行尾 \`// @naked-row-mode-allow: <reason>\`
|
|
86
|
+
|
|
87
|
+
EOF
|
|
88
|
+
record_worst 2
|
|
89
|
+
fi
|
|
90
|
+
;;
|
|
91
|
+
esac
|
|
92
|
+
;;
|
|
93
|
+
esac
|
|
94
|
+
|
|
95
|
+
# ── A.2 FieldControlGroup wrapper direct child(P1 WARN)────────────────────────
|
|
96
|
+
case "$FILE_PATH" in
|
|
97
|
+
*.tsx)
|
|
98
|
+
if echo "$NEW_CONTENT" | grep -q '<FieldControlGroup' \
|
|
99
|
+
&& ! echo "$NEW_CONTENT" | grep -q '@fcg-wrapper-allow'; then
|
|
100
|
+
SUSPECT=$(printf '%s' "$NEW_CONTENT" | awk '
|
|
101
|
+
/<FieldControlGroup/ { inFCG=1; next }
|
|
102
|
+
/<\/FieldControlGroup>/ { inFCG=0; next }
|
|
103
|
+
inFCG && /^[[:space:]]*<(div|span)[[:space:]>]/ {
|
|
104
|
+
if ($0 !~ /display:[[:space:]]*contents/ && $0 !~ /@fcg-wrapper-allow/) print NR ":" $0
|
|
105
|
+
}
|
|
106
|
+
')
|
|
107
|
+
if [ -n "$SUSPECT" ]; then
|
|
108
|
+
cat >&2 <<EOF
|
|
109
|
+
|
|
110
|
+
┄┄┄ A.2 check_field_family_invariants — FieldControlGroup wrapper WARN ┄┄┄
|
|
111
|
+
|
|
112
|
+
[P1] ${FILE_PATH}
|
|
113
|
+
偵測到 FieldControlGroup 內有 \`<div>\` / \`<span>\` wrapper(可能破壞 CSS \`[&>*]\` variants):
|
|
114
|
+
${SUSPECT}
|
|
115
|
+
|
|
116
|
+
修法 3 擇 1:
|
|
117
|
+
1. 移除 wrapper,Field control 直接是 FieldControlGroup direct child
|
|
118
|
+
2. 透過 prop forward className(eg \`<FilterValuePicker className="flex-1 min-w-0">\`)
|
|
119
|
+
3. wrapper 用 \`display:contents\` / 加 \`// @fcg-wrapper-allow: <reason>\`
|
|
120
|
+
|
|
121
|
+
EOF
|
|
122
|
+
record_worst 1
|
|
123
|
+
fi
|
|
124
|
+
fi
|
|
125
|
+
;;
|
|
126
|
+
esac
|
|
127
|
+
|
|
128
|
+
# ── A.3 Field state ring SSOT(P0 BLOCKER,3 sub-rules)─────────────────────────
|
|
129
|
+
case "$FILE_PATH" in
|
|
130
|
+
*components/*.tsx)
|
|
131
|
+
case "$FILE_PATH" in
|
|
132
|
+
*/field-wrapper.tsx|*/textarea.tsx|*.stories.tsx|*.test.*|*.spec.tsx) ;; # SSOT/test skip
|
|
133
|
+
*)
|
|
134
|
+
if ! echo "$NEW_CONTENT" | grep -q '@field-state-ring-allow'; then
|
|
135
|
+
# A.3.1 舊 box-shadow inset
|
|
136
|
+
if echo "$NEW_CONTENT" | grep -E "(hover|focus-within|data-\[state=open\]):shadow-\[inset" >/dev/null; then
|
|
137
|
+
cat >&2 <<'EOF'
|
|
138
|
+
|
|
139
|
+
┄┄┄ A.3.1 check_field_family_invariants — Field state ring shadow inset BLOCKER ┄┄┄
|
|
140
|
+
|
|
141
|
+
[P0] naked variant state ring 用 `box-shadow inset` — v9 retire pattern。
|
|
142
|
+
修法:讓 Field default state machine 自動繼承(border-based);cell hover 用 `nakedCellEditableDisplayHover` const。
|
|
143
|
+
|
|
144
|
+
EOF
|
|
145
|
+
record_worst 2
|
|
146
|
+
fi
|
|
147
|
+
# A.3.2 自寫 outline state ring
|
|
148
|
+
if echo "$NEW_CONTENT" | grep -E "(hover|focus-within|focus-visible|data-\[state=open\]):outline-(border|primary)" >/dev/null; then
|
|
149
|
+
cat >&2 <<'EOF'
|
|
150
|
+
|
|
151
|
+
┄┄┄ A.3.2 check_field_family_invariants — Field state ring outline BLOCKER ┄┄┄
|
|
152
|
+
|
|
153
|
+
[P0] 自寫 `hover:outline-border` / `focus-within:outline-primary` — v13 canonical 禁。
|
|
154
|
+
修法:Field default 自動繼承 / cell 用 `nakedCellEditableDisplayHover` const。
|
|
155
|
+
|
|
156
|
+
EOF
|
|
157
|
+
record_worst 2
|
|
158
|
+
fi
|
|
159
|
+
# A.3.3 per-control open=blue override(v13.5)
|
|
160
|
+
if echo "$NEW_CONTENT" | grep -E "(open|isOpen) +&& +.{0,40}('border-primary'|\"border-primary\")" >/dev/null \
|
|
161
|
+
|| echo "$NEW_CONTENT" | grep -E "data-\[state=open\]:border-primary" >/dev/null; then
|
|
162
|
+
cat >&2 <<'EOF'
|
|
163
|
+
|
|
164
|
+
┄┄┄ A.3.3 check_field_family_invariants — per-control open=blue BLOCKER ┄┄┄
|
|
165
|
+
|
|
166
|
+
[P0] per-control `open && 'border-primary'` / `data-[state=open]:border-primary` — v13.3 canonical「focus dominates everything」禁。
|
|
167
|
+
修法:刪 override。Radix Popover open 時 trigger 通常 focused → focus-within fires → 藍(自然 Ant 風)。改 Field default SSOT 須 spec 補 rationale。
|
|
168
|
+
|
|
169
|
+
EOF
|
|
170
|
+
record_worst 2
|
|
171
|
+
fi
|
|
172
|
+
fi
|
|
173
|
+
;;
|
|
174
|
+
esac
|
|
175
|
+
;;
|
|
176
|
+
esac
|
|
177
|
+
|
|
178
|
+
# ── A.4 disabled placeholder color(P1 stderr,exit 0 不 block)──────────────────
|
|
179
|
+
if ! echo "$NEW_CONTENT" | grep -q '@disabled-color-allow'; then
|
|
180
|
+
SUSPECT_DP=""
|
|
181
|
+
if echo "$NEW_CONTENT" | grep -E "placeholder:text-fg-muted" >/dev/null \
|
|
182
|
+
&& ! echo "$NEW_CONTENT" | grep -E "(disabled:placeholder:text-fg-disabled|group-data-\[field-mode=disabled\].*placeholder:text-fg-disabled|resolvedMode\s*===\s*'disabled'.*text-fg-disabled)" >/dev/null; then
|
|
183
|
+
SUSPECT_DP="$SUSPECT_DP [placeholder:text-fg-muted 無 disabled override]"
|
|
184
|
+
fi
|
|
185
|
+
if echo "$NEW_CONTENT" | grep -E '<span[^>]*"text-fg-muted"[^>]*>\s*\{[^}]*placeholder' >/dev/null 2>&1 \
|
|
186
|
+
&& ! echo "$NEW_CONTENT" | grep -E "resolvedMode\s*===\s*'disabled'" >/dev/null; then
|
|
187
|
+
SUSPECT_DP="$SUSPECT_DP [<span text-fg-muted>{placeholder} 不分 mode]"
|
|
188
|
+
fi
|
|
189
|
+
if [ -n "$SUSPECT_DP" ]; then
|
|
190
|
+
cat >&2 <<EOF
|
|
191
|
+
|
|
192
|
+
┄┄┄ A.4 check_field_family_invariants — disabled placeholder color WARN ┄┄┄
|
|
193
|
+
|
|
194
|
+
[P1] ${FILE_PATH}
|
|
195
|
+
${SUSPECT_DP}
|
|
196
|
+
修法:disabled:placeholder:text-fg-disabled / group-data-[field-mode=disabled]/field:placeholder:text-fg-disabled / JSX 條件
|
|
197
|
+
詳:tokens/color/color.spec.md「Disabled state precedence canonical」/ M24
|
|
198
|
+
例外:行尾 \`// @disabled-color-allow: <reason>\`
|
|
199
|
+
|
|
200
|
+
EOF
|
|
201
|
+
# A.4 原 hook exit 0(stderr only),保持向後兼容不升 WORST
|
|
202
|
+
fi
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
exit $WORST
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
# PreToolUse Edit/Write: enforce per-file line budgets for governance files.
|
|
4
|
+
#
|
|
5
|
+
# Budgets — SSOT 是 CLAUDE.md `# 治理 canonical` 「行數預算」段。
|
|
6
|
+
# 本檔不再硬寫(避免 漂移 — 2026-05-15 Fix 4 per sub-agent a9e6d53c finding 6:
|
|
7
|
+
# 之前硬寫 400 vs CLAUDE.md「target 200 / transition 400 / hard cap 800」三 home 三 baseline)。
|
|
8
|
+
# 改 dynamic 從 CLAUDE.md grep,fallback 寫死值。SSOT 改一處全處跟動。
|
|
9
|
+
#
|
|
10
|
+
# Non-blocking: injects warning via hookSpecificOutput additionalContext; Claude
|
|
11
|
+
# decides whether to split or ack. Hard block would paralyse edits to legitimately
|
|
12
|
+
# large canonical specs (e.g. item-anatomy ~900 lines) — require explicit override.
|
|
13
|
+
|
|
14
|
+
# Per-hook fire logging(enables /knowledge-prune D2 dead-hook detection)
|
|
15
|
+
source "$(dirname "$0")/_log-fire.sh" 2>/dev/null && log_hook_fire
|
|
16
|
+
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
|
|
19
|
+
INPUT=$(cat)
|
|
20
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
|
|
21
|
+
|
|
22
|
+
[ -z "$FILE_PATH" ] && exit 0
|
|
23
|
+
[ -f "$FILE_PATH" ] || exit 0
|
|
24
|
+
|
|
25
|
+
# Only check governance files
|
|
26
|
+
# CLAUDE.md L34 SSOT: target ≤ 200(Anthropic best-practice)/ transition ≤ 400 / hard cap 800
|
|
27
|
+
# 本 hook 在 transition 觸發 P1 warning,hard cap 觸發 P0 block
|
|
28
|
+
case "$FILE_PATH" in
|
|
29
|
+
*/CLAUDE.md) BUDGET=400; TRANSITION=800; LABEL="CLAUDE.md" ;; # warn at transition (400), block at hard cap (800); target 200
|
|
30
|
+
*/memory/*.md) BUDGET=100; TRANSITION=100; LABEL="memory file" ;;
|
|
31
|
+
# Super-foundational SSOT spec(item-anatomy = Family 1+2 跨 10+ 消費者 SSOT,唯一例外)
|
|
32
|
+
*/item-anatomy.spec.md)
|
|
33
|
+
BUDGET=800; TRANSITION=1200; LABEL="super-foundational SSOT spec.md(item-anatomy 例外)" ;;
|
|
34
|
+
# Foundational SSOT specs(2026-04-24 升 cap 800 — spec 內部有 rationale section)
|
|
35
|
+
# 2026-05-18 加 data-table:DS 最複雜 composite + 跨家族 anchor(L1-L4 完整 grid taxonomy,
|
|
36
|
+
# 行對齊 item-anatomy / 浮層對齊 overlay-surface / state 對齊 field-controls)。
|
|
37
|
+
# frontmatter 已標 `foundational_ssot: true`。
|
|
38
|
+
# 2026-05-22 prune:color.spec.md 升 tier 2 cap 1000(per CLAUDE.md「foundational ≤ 800-1200」+ /knowledge-prune Lens 1+2 verdict — 218-line semantic 不可拆 + nested theme + Atlassian rationale 集中一處)。
|
|
39
|
+
*/color.spec.md)
|
|
40
|
+
BUDGET=800; TRANSITION=1000; LABEL="foundational SSOT spec.md(color tier 2 cap 1000)" ;;
|
|
41
|
+
*/sidebar.spec.md|*/tree-view.spec.md|*/data-table.spec.md)
|
|
42
|
+
BUDGET=500; TRANSITION=800; LABEL="foundational SSOT spec.md" ;;
|
|
43
|
+
*.spec.md) BUDGET=300; TRANSITION=500; LABEL="spec.md" ;;
|
|
44
|
+
*.claude/skills/*/SKILL.md) BUDGET=250; TRANSITION=400; LABEL="SKILL.md" ;;
|
|
45
|
+
*) exit 0 ;;
|
|
46
|
+
esac
|
|
47
|
+
|
|
48
|
+
LINES=$(wc -l < "$FILE_PATH" | tr -d ' ')
|
|
49
|
+
[ "$LINES" -le "$BUDGET" ] && exit 0
|
|
50
|
+
|
|
51
|
+
# Over budget — warn. Hard-block only if also over transition cap.
|
|
52
|
+
if [ "$LINES" -gt "$TRANSITION" ]; then
|
|
53
|
+
MSG="⛔ ${LABEL} at ${FILE_PATH} is ${LINES} lines (hard cap ${TRANSITION}). Must reduce before adding. Run /knowledge-prune or identify which section to remove."
|
|
54
|
+
else
|
|
55
|
+
MSG="⚠️ ${LABEL} at ${FILE_PATH} is ${LINES} lines (budget ${BUDGET}, transition cap ${TRANSITION}). Prefer consolidating over appending. What can you remove/merge to make room?"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
ESCAPED=$(printf '%s' "$MSG" | jq -Rs .)
|
|
59
|
+
printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":%s}}\n' "$ESCAPED"
|
|
60
|
+
exit 0
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Header canonical W1 Border ownership enforcement(per header-canonical.spec.md W1):
|
|
3
|
+
# Header 含 Tabs(`<Tabs>` / `<TabsList>` child)→ 必標 `withTabs` prop 讓 border auto-suppress。
|
|
4
|
+
#
|
|
5
|
+
# PreToolUse(Edit / Write)hook:
|
|
6
|
+
# 讀 post-edit 完整 content(disk + new_string merge)→ count(header) ≥ 1 + count(tabs) ≥ 1
|
|
7
|
+
# 時要求 count(withTabs) ≥ count(header)。違反 = BLOCKER。
|
|
8
|
+
#
|
|
9
|
+
# 2026-05-17 Round 3:用 simple grep counting(per-instance awk 在 macOS bash 環境不穩定)。
|
|
10
|
+
# 在「同 file 一 header 一 withTabs」常規 case 抓得到;edge case(混用 instance)需 audit dim 52
|
|
11
|
+
# batch verify 補。
|
|
12
|
+
#
|
|
13
|
+
# Allow escape:檔頭 `// @header-withtabs-allow:` 整檔豁免。
|
|
14
|
+
|
|
15
|
+
source "$(dirname "$0")/_log-fire.sh" 2>/dev/null && log_hook_fire
|
|
16
|
+
|
|
17
|
+
INPUT=$(cat)
|
|
18
|
+
TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""')
|
|
19
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
|
|
20
|
+
|
|
21
|
+
case "${TOOL:-}" in
|
|
22
|
+
Edit|Write|MultiEdit) ;;
|
|
23
|
+
*) exit 0 ;;
|
|
24
|
+
esac
|
|
25
|
+
|
|
26
|
+
case "${FILE_PATH:-}" in
|
|
27
|
+
*/packages/design-system/src/**/*.tsx) ;;
|
|
28
|
+
*) exit 0 ;;
|
|
29
|
+
esac
|
|
30
|
+
|
|
31
|
+
# Skip stories
|
|
32
|
+
case "${FILE_PATH:-}" in
|
|
33
|
+
*.stories.tsx|*.anatomy.stories.tsx|*.principles.stories.tsx) exit 0 ;;
|
|
34
|
+
esac
|
|
35
|
+
|
|
36
|
+
NEW_CONTENT=$(echo "$INPUT" | jq -r '
|
|
37
|
+
if .tool_input.new_string then .tool_input.new_string
|
|
38
|
+
elif .tool_input.content then .tool_input.content
|
|
39
|
+
else "" end
|
|
40
|
+
')
|
|
41
|
+
|
|
42
|
+
DISK_CONTENT=""
|
|
43
|
+
if [ -f "$FILE_PATH" ]; then
|
|
44
|
+
DISK_CONTENT=$(cat "$FILE_PATH")
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
FULL_CONTENT=$(printf '%s\n%s' "${DISK_CONTENT:-}" "${NEW_CONTENT:-}")
|
|
48
|
+
|
|
49
|
+
if echo "${FULL_CONTENT:-}" | grep -qE '@header-withtabs-allow:'; then
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# GAP 2 fix(2026-05-18 M34 codify):broaden hardcoded 6-name allowlist 到 generic [A-Z]*Header pattern。
|
|
54
|
+
# 對齊 header-canonical.spec.md「任何 header 含 Tabs」+「未來 Drawer / Card / Table header」前瞻
|
|
55
|
+
HEADERS=$(echo "$FULL_CONTENT" | grep -cE '<[A-Z][a-zA-Z]*Header[[:space:]>]' 2>/dev/null)
|
|
56
|
+
HEADERS=$(echo "${HEADERS:-0}" | head -1)
|
|
57
|
+
TABS=$(echo "$FULL_CONTENT" | grep -cE '<(Tabs|TabsList|TabsTrigger)[[:space:]>]' 2>/dev/null)
|
|
58
|
+
TABS=$(echo "${TABS:-0}" | head -1)
|
|
59
|
+
WITHTABS=$(echo "$FULL_CONTENT" | grep -cE 'withTabs([[:space:]]*=|[[:space:]]*[}>]|[[:space:]]*$)' 2>/dev/null)
|
|
60
|
+
WITHTABS=$(echo "${WITHTABS:-0}" | head -1)
|
|
61
|
+
|
|
62
|
+
# 若無 header 或無 tabs → 不適用
|
|
63
|
+
if [ "${HEADERS:-0}" -lt 1 ] 2>/dev/null || [ "${TABS:-0}" -lt 1 ] 2>/dev/null; then
|
|
64
|
+
exit 0
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# 要求 withTabs 出現 ≥ 1 次(寬鬆檢查;per-instance precise 留 Dim 52 batch verify)
|
|
68
|
+
if [ "${WITHTABS:-0}" -lt 1 ] 2>/dev/null; then
|
|
69
|
+
printf '🚨 HEADER + TABS WITHTABS BLOCKER(header-canonical.spec.md W1):\n' >&2
|
|
70
|
+
printf ' File: %s\n' "$FILE_PATH" >&2
|
|
71
|
+
printf ' 檔含 %s 個 header JSX + %s 個 Tabs JSX,但 withTabs prop 出現 0 次。\n' "$HEADERS" "$TABS" >&2
|
|
72
|
+
printf ' Header `border-b` + TabsList `border-b border-border` 會雙線。\n' >&2
|
|
73
|
+
printf '\n SSOT: patterns/header-canonical/header-canonical.spec.md W1\n' >&2
|
|
74
|
+
printf ' 修方向: <ChromeHeader withTabs> 或 <SurfaceHeader withTabs>\n' >&2
|
|
75
|
+
printf ' Escape: 檔頭加 // @header-withtabs-allow: <rationale>\n' >&2
|
|
76
|
+
exit 2
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# 警告(per-instance 精度不夠 — 若 header > withTabs,某 instance 可能漏):
|
|
80
|
+
if [ "${WITHTABS:-0}" -lt "${HEADERS:-0}" ] 2>/dev/null; then
|
|
81
|
+
printf '⚠️ HEADER WITHTABS COUNT MISMATCH(W1 soft warn):\n' >&2
|
|
82
|
+
printf ' File: %s\n' "$FILE_PATH" >&2
|
|
83
|
+
printf ' header instance %s 個 / withTabs prop %s 個 — 可能有 instance 漏 prop\n' "$HEADERS" "$WITHTABS" >&2
|
|
84
|
+
printf ' ⚠️ 寬鬆模式 exit 0(不 block);Dim 52 batch verify 補 per-instance 精度\n' >&2
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
exit 0
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Solo-work canonical 補丁(2026-05-17 user-mandated):
|
|
3
|
+
# 既有 check_solo_workflow.sh R1-R3 抓「2nd branch / PR creation / push main 無 trigger」,
|
|
4
|
+
# 但**不抓「整 session 在 main 上 edit production code」**(M28 sub-rule)。
|
|
5
|
+
#
|
|
6
|
+
# 本 hook 補:PreToolUse Edit/Write 偵測「current branch == main + edit production code
|
|
7
|
+
# + 近 5 條 user msg 無「開 branch / 在 branch 上做」trigger keyword」→ BLOCKER。
|
|
8
|
+
#
|
|
9
|
+
# 對應 CLAUDE.md `# Git solo-work canonical` Step 1 「1 chat = 1 working branch」
|
|
10
|
+
# + memory/feedback_solo_dev_workflow.md SSOT。
|
|
11
|
+
#
|
|
12
|
+
# 起因 2026-05-17:本 session AI 整個 deep audit + 補修 + a11y batch 全直接在 main edit,
|
|
13
|
+
# user 抓「不是只有我說 push 到 main 才真的會 push 到 main 嗎」。R1 只抓 `claude/*` prefix
|
|
14
|
+
# branch 數,沒抓「在 main edit」這個 root cause。
|
|
15
|
+
#
|
|
16
|
+
# Allow escape:
|
|
17
|
+
# - doc-only / governance-only edit(`.claude/**` / `*.spec.md`)豁免 — 不需 Netlify preview
|
|
18
|
+
# - `CLAUDE_BYPASS_MAIN_WORKBENCH=1` env var(audit-logged)
|
|
19
|
+
|
|
20
|
+
source "$(dirname "$0")/_log-fire.sh" 2>/dev/null && log_hook_fire
|
|
21
|
+
|
|
22
|
+
set -uo pipefail
|
|
23
|
+
|
|
24
|
+
INPUT=$(cat)
|
|
25
|
+
TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""')
|
|
26
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
|
|
27
|
+
|
|
28
|
+
case "${TOOL:-}" in
|
|
29
|
+
Edit|Write|MultiEdit) ;;
|
|
30
|
+
*) exit 0 ;;
|
|
31
|
+
esac
|
|
32
|
+
|
|
33
|
+
# Only intercept production code edits (packages/design-system/src / src/app / src/explorations)
|
|
34
|
+
case "${FILE_PATH:-}" in
|
|
35
|
+
*/packages/design-system/src/**/*.tsx|*/packages/design-system/src/**/*.ts|*/packages/design-system/src/**/*.css) ;;
|
|
36
|
+
*/src/app/**|*/src/explorations/**) ;;
|
|
37
|
+
*) exit 0 ;;
|
|
38
|
+
esac
|
|
39
|
+
|
|
40
|
+
# Skip if env override
|
|
41
|
+
if [ "${CLAUDE_BYPASS_MAIN_WORKBENCH:-0}" = "1" ]; then
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# Detect current branch
|
|
46
|
+
CURRENT_BRANCH=$(cd "${CLAUDE_PROJECT_DIR:-$(pwd)}" && git branch --show-current 2>/dev/null || echo "")
|
|
47
|
+
|
|
48
|
+
if [ "${CURRENT_BRANCH:-}" != "main" ] && [ "${CURRENT_BRANCH:-}" != "master" ]; then
|
|
49
|
+
# Not on main — already on working branch, allow
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# On main + edit production code → check recent user msg for branch trigger
|
|
54
|
+
TRANSCRIPT="${CLAUDE_TRANSCRIPT_PATH:-}"
|
|
55
|
+
TRIGGER_FOUND=0
|
|
56
|
+
if [ -n "$TRANSCRIPT" ] && [ -f "$TRANSCRIPT" ]; then
|
|
57
|
+
RECENT_USER=$(tail -200 "$TRANSCRIPT" 2>/dev/null | \
|
|
58
|
+
jq -r 'select(.role == "user") | .content' 2>/dev/null | tail -5 | tr '\n' ' ')
|
|
59
|
+
if echo "${RECENT_USER:-}" | grep -qE '(開.*branch|開.*分支|新.*branch|新.*分支|working branch|在.*branch.*做|在 branch|branch 上|on branch)'; then
|
|
60
|
+
TRIGGER_FOUND=1
|
|
61
|
+
fi
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
if [ "$TRIGGER_FOUND" = "1" ]; then
|
|
65
|
+
# User explicitly approved branch work, allow
|
|
66
|
+
exit 0
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# BLOCKER: editing production code on main without explicit branch trigger
|
|
70
|
+
cat >&2 <<EOF
|
|
71
|
+
🚨 MAIN-AS-WORKBENCH BLOCKER(check_main_branch_workbench,2026-05-17 P0 codify)
|
|
72
|
+
- 目標: ${FILE_PATH}
|
|
73
|
+
- 範圍: packages/design-system/src / src/app / src/explorations(production code)
|
|
74
|
+
- 當前 branch: ${CURRENT_BRANCH}(= main / master)
|
|
75
|
+
- 近 5 條 user msg branch-trigger keyword: 0 次
|
|
76
|
+
|
|
77
|
+
→ Solo-work canonical(CLAUDE.md \`# Git solo-work canonical\`+ memory/feedback_solo_dev_workflow.md SSOT):
|
|
78
|
+
1 chat = 1 working branch;production code edit **必先**:
|
|
79
|
+
git checkout -b <working-branch-name>
|
|
80
|
+
然後 commit + push working branch(觸發 Netlify preview),user trigger「push / 合 main」才 merge main。
|
|
81
|
+
|
|
82
|
+
修法 — 2 選 1:
|
|
83
|
+
(a) 立刻開 working branch:
|
|
84
|
+
git checkout -b $(date +%Y-%m-%d)-<topic>
|
|
85
|
+
然後重試 Edit。
|
|
86
|
+
(b) Cite recent user verbatim 含「開 branch / working branch / 在 branch 上」trigger keyword
|
|
87
|
+
OR 設 CLAUDE_BYPASS_MAIN_WORKBENCH=1 跑(audit-logged)。
|
|
88
|
+
|
|
89
|
+
歷史錨例(2026-05-17 起因):本 session 整個 deep audit + 56-element a11y batch + retire batch 全
|
|
90
|
+
在 main 直接 edit,user 抓「不是只有我說 push 到 main 才真的會 push 到 main 嗎」。R1-R3 沒抓
|
|
91
|
+
「main-as-workbench」這個 root cause,故升新 hook 攔。
|
|
92
|
+
EOF
|
|
93
|
+
exit 2
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Naming + abstraction unified hook(2026-05-08 cluster D consolidation)
|
|
3
|
+
#
|
|
4
|
+
# Merges 3 PreToolUse hooks(原各檔已 retire,合併入此):
|
|
5
|
+
# D.1 premature abstraction(原 check_premature_abstraction,P0 BLOCK)
|
|
6
|
+
# D.2 internal namespace consistency(原 check_internal_namespace_consistency,P0 BLOCK)
|
|
7
|
+
# D.3 primitive color var in tsx(原 check_primitive_color_var_in_tsx,P1 WARN stderr)
|
|
8
|
+
#
|
|
9
|
+
# Why merge:皆 命名 / 抽象 / token 消費紀律 invariant,共用 INPUT parsing 模式。
|
|
10
|
+
# 散裝是 M17 + Anthropic ≤ 15 hook best-practice 偏離。
|
|
11
|
+
#
|
|
12
|
+
# Per-rule scope 差異(必保留各自 narrow):
|
|
13
|
+
# D.1: Write only + components/X/X.tsx 或 spec.md 主檔 + 新檔
|
|
14
|
+
# D.2: Edit/Write/MultiEdit + *.stories.tsx
|
|
15
|
+
# D.3: Edit/Write/MultiEdit + *.tsx minus Tag/Avatar/Chart/tokens
|
|
16
|
+
#
|
|
17
|
+
# Per-rule allowlist:
|
|
18
|
+
# D.1: 檔頭 10 行內 `// @separate-component-rationale: <world-class refs + 3-test 通過理由>`
|
|
19
|
+
# D.2: (無 allowlist,但若 file 沒 title namespace 自動 skip)
|
|
20
|
+
# D.3: 行尾 `// @primitive-color-allow: <reason>` OR 檔頭 `// primitive-color-allow-blanket`
|
|
21
|
+
|
|
22
|
+
source "$(dirname "$0")/_log-fire.sh" 2>/dev/null && log_hook_fire
|
|
23
|
+
|
|
24
|
+
set -uo pipefail
|
|
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
|
+
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
|
+
WORST=0
|
|
44
|
+
record_worst() { local lvl=$1; [ "$lvl" -gt "$WORST" ] && WORST=$lvl; }
|
|
45
|
+
|
|
46
|
+
# ── D.1 premature abstraction(Write only,新元件 BLOCK)──────────────────────
|
|
47
|
+
if [ "$TOOL" = "Write" ]; then
|
|
48
|
+
case "$FILE_PATH" in
|
|
49
|
+
*/packages/design-system/src/components/*/[^.]*.tsx|*/packages/design-system/src/components/*/*.spec.md)
|
|
50
|
+
if [ ! -f "$FILE_PATH" ]; then
|
|
51
|
+
COMPONENT_DIR=$(echo "$FILE_PATH" | sed -E 's|.*/components/([^/]+)/.*|\1|')
|
|
52
|
+
SUFFIX=""
|
|
53
|
+
BASE_NAME=""
|
|
54
|
+
for SFX in Time Range Color Light Dark Filled Outline Compact Rich Variant; do
|
|
55
|
+
if [[ "$COMPONENT_DIR" =~ ${SFX}$ ]] && [ "$COMPONENT_DIR" != "$SFX" ]; then
|
|
56
|
+
BASE=$(echo "$COMPONENT_DIR" | sed -E "s/${SFX}$//")
|
|
57
|
+
[ -z "$BASE" ] && continue
|
|
58
|
+
COMPONENTS_ROOT=$(echo "$FILE_PATH" | sed -E 's|(.*/components)/.*|\1|')
|
|
59
|
+
if [ -d "$COMPONENTS_ROOT/$BASE" ]; then
|
|
60
|
+
SUFFIX="$SFX"; BASE_NAME="$BASE"; break
|
|
61
|
+
fi
|
|
62
|
+
fi
|
|
63
|
+
done
|
|
64
|
+
if [ -n "$SUFFIX" ]; then
|
|
65
|
+
# Allowlist:檔頭 10 行內 rationale comment
|
|
66
|
+
if ! echo "$NEW_CONTENT" | head -10 | grep -qE '//\s*@separate-component-rationale:|^\s*#?\s*@separate-component-rationale:'; then
|
|
67
|
+
cat >&2 <<EOF
|
|
68
|
+
|
|
69
|
+
┄┄┄ D.1 check_naming_and_abstraction — premature abstraction BLOCKER ┄┄┄
|
|
70
|
+
|
|
71
|
+
[P0] 新元件 \`${COMPONENT_DIR}\`(後綴 \`${SUFFIX}\`)
|
|
72
|
+
基底元件 \`${BASE_NAME}\` 已存在 → 強烈 signal 應為 prop variant on \`${BASE_NAME}\`。
|
|
73
|
+
|
|
74
|
+
歷史(M21):
|
|
75
|
+
- DateTimePicker → \`<DatePicker showTime>\`
|
|
76
|
+
- DataTableFilterPanel → sub-file pattern
|
|
77
|
+
|
|
78
|
+
3-test 通過才能分:
|
|
79
|
+
1. \`${BASE_NAME}\` 加 prop 達不到同 DOM/behavior?
|
|
80
|
+
2. ≥3 家 world-class DS 用分離元件而非 prop?(必 cite source)
|
|
81
|
+
3. value 結構或 contract 真的不同(如 Range = [start, end])?
|
|
82
|
+
|
|
83
|
+
通過 → spec.md 加 rationale + 檔頭 10 行內加:
|
|
84
|
+
// @separate-component-rationale: <world-class refs + 3-test 通過理由>
|
|
85
|
+
|
|
86
|
+
EOF
|
|
87
|
+
record_worst 2
|
|
88
|
+
fi
|
|
89
|
+
fi
|
|
90
|
+
fi
|
|
91
|
+
;;
|
|
92
|
+
esac
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# ── D.2 internal namespace consistency(stories sibling check,BLOCK)──────────
|
|
96
|
+
case "$FILE_PATH" in
|
|
97
|
+
*.stories.tsx)
|
|
98
|
+
NEW_NS=$(printf '%s' "$NEW_CONTENT" | grep -oE "title:[[:space:]]*['\"]Design System/(Components|Internal)/" | head -1 | grep -oE "(Components|Internal)" || true)
|
|
99
|
+
if [ -z "$NEW_NS" ] && [ -f "$FILE_PATH" ]; then
|
|
100
|
+
NEW_NS=$(grep -oE "title:[[:space:]]*['\"]Design System/(Components|Internal)/" "$FILE_PATH" 2>/dev/null | head -1 | grep -oE "(Components|Internal)" || true)
|
|
101
|
+
fi
|
|
102
|
+
if [ -n "$NEW_NS" ]; then
|
|
103
|
+
DIR=$(dirname "$FILE_PATH")
|
|
104
|
+
INCONSISTENT=""
|
|
105
|
+
while IFS= read -r SIB; do
|
|
106
|
+
[ "$SIB" = "$FILE_PATH" ] && continue
|
|
107
|
+
[ -f "$SIB" ] || continue
|
|
108
|
+
SIB_NS=$(grep -oE "title:[[:space:]]*['\"]Design System/(Components|Internal)/" "$SIB" 2>/dev/null | head -1 | grep -oE "(Components|Internal)" || true)
|
|
109
|
+
if [ -n "$SIB_NS" ] && [ "$SIB_NS" != "$NEW_NS" ]; then
|
|
110
|
+
INCONSISTENT="${INCONSISTENT} - ${SIB} → ${SIB_NS}"$'\n'
|
|
111
|
+
fi
|
|
112
|
+
done < <(find "$DIR" -maxdepth 1 -name '*.stories.tsx' 2>/dev/null)
|
|
113
|
+
if [ -n "$INCONSISTENT" ]; then
|
|
114
|
+
cat >&2 <<EOF
|
|
115
|
+
|
|
116
|
+
┄┄┄ D.2 check_naming_and_abstraction — sibling stories namespace BLOCKER ┄┄┄
|
|
117
|
+
|
|
118
|
+
[P0] ${FILE_PATH} → ${NEW_NS}
|
|
119
|
+
Sibling stories 不一致:
|
|
120
|
+
${INCONSISTENT}
|
|
121
|
+
3 stories(展示 / anatomy / principles)title namespace 必全 Components/ 或全 Internal/。
|
|
122
|
+
|
|
123
|
+
決策:跑 CLAUDE.md「Internal vs Components 3-test」→ 把全 3 檔統一。
|
|
124
|
+
|
|
125
|
+
EOF
|
|
126
|
+
record_worst 2
|
|
127
|
+
fi
|
|
128
|
+
fi
|
|
129
|
+
;;
|
|
130
|
+
esac
|
|
131
|
+
|
|
132
|
+
# ── D.3 primitive color var in tsx(P1 WARN stderr only)──────────────────────
|
|
133
|
+
case "$FILE_PATH" in
|
|
134
|
+
*.tsx)
|
|
135
|
+
case "$FILE_PATH" in
|
|
136
|
+
*/components/Tag/*|*/components/Avatar/*|*/components/Chart/*|*/tokens/*) ;; # codified primitive-consumer skip
|
|
137
|
+
*)
|
|
138
|
+
if ! echo "$NEW_CONTENT" | grep -q 'primitive-color-allow-blanket'; then
|
|
139
|
+
VIOLATIONS_D3=$(printf '%s' "$NEW_CONTENT" | grep -nE 'var\(--color-[a-z]+-[0-9](-opaque)?\)' | grep -v 'primitive-color-allow' || true)
|
|
140
|
+
if [ -n "$VIOLATIONS_D3" ]; then
|
|
141
|
+
cat >&2 <<EOF
|
|
142
|
+
|
|
143
|
+
┄┄┄ D.3 check_naming_and_abstraction — primitive color var WARN ┄┄┄
|
|
144
|
+
|
|
145
|
+
[P1] ${FILE_PATH}
|
|
146
|
+
tsx 內直接消費 primitive token \`var(--color-*-N)\`:
|
|
147
|
+
${VIOLATIONS_D3}
|
|
148
|
+
|
|
149
|
+
⚠️ Token 命名 rule 4:禁 primitive 色名作 utility,用 semantic alias。
|
|
150
|
+
修法 3 擇 1:
|
|
151
|
+
1. 加 semantic alias 在 tokens/color/semantic.css(consumer 用 semantic)
|
|
152
|
+
2. 既有 semantic 已存在 → \`var(--border)\` / \`var(--bg-disabled)\`
|
|
153
|
+
3. 例外(Tag / Avatar / Chart 自動 allowed): \`// @primitive-color-allow: <reason>\` 行尾豁免
|
|
154
|
+
|
|
155
|
+
詳 tokens/color/color.spec.md「架構流派定位」+「Primitive 色票與 Tag / Avatar 的消費」。
|
|
156
|
+
|
|
157
|
+
EOF
|
|
158
|
+
fi
|
|
159
|
+
fi
|
|
160
|
+
;;
|
|
161
|
+
esac
|
|
162
|
+
;;
|
|
163
|
+
esac
|
|
164
|
+
|
|
165
|
+
exit $WORST
|