@qijenchen/design-system 0.1.0-beta.32 → 0.1.0-beta.34

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.
Files changed (57) hide show
  1. package/CLAUDE.md +5 -7
  2. package/README.md +2 -2
  3. package/ds-canonical/hooks/auto_regen_ds_barrel.sh +58 -0
  4. package/ds-canonical/hooks/check_addon_subdir_ship.sh +76 -0
  5. package/ds-canonical/hooks/check_chrome_header_avatar_canonical.sh +84 -0
  6. package/ds-canonical/hooks/check_codex_brief_invariants.sh +40 -8
  7. package/ds-canonical/hooks/check_consumer_app_story_title.sh +81 -0
  8. package/ds-canonical/hooks/check_consumer_ds_primitive_misuse.sh +106 -0
  9. package/ds-canonical/hooks/check_consumer_no_ds_catalog.sh +98 -0
  10. package/ds-canonical/hooks/check_consumer_story_baseline.sh +76 -0
  11. package/ds-canonical/hooks/check_data_table_size_num_to_meta_width.sh +68 -0
  12. package/ds-canonical/hooks/check_ds_anchor_preflight.sh +132 -0
  13. package/ds-canonical/hooks/check_escape_marker_abuse.sh +86 -0
  14. package/ds-canonical/hooks/check_fork_user_plugin_install.sh +75 -0
  15. package/ds-canonical/hooks/check_full_story_visual_interaction_sweep.sh +71 -0
  16. package/ds-canonical/hooks/check_item_list_gap.sh +54 -0
  17. package/ds-canonical/hooks/check_layout_space_magic_numbers.sh +84 -0
  18. package/ds-canonical/hooks/check_orphan_ds_css.sh +66 -0
  19. package/ds-canonical/hooks/check_overlay_open_focus_escape_probe.sh +75 -0
  20. package/ds-canonical/hooks/check_plugin_freshness.sh +68 -0
  21. package/ds-canonical/hooks/check_post_main_ssot_propagate.sh +88 -0
  22. package/ds-canonical/hooks/check_propose_cite_required.sh +88 -0
  23. package/ds-canonical/hooks/check_propose_without_benchmark.sh +63 -0
  24. package/ds-canonical/hooks/check_sidebar_menu_button_implicit_wrap.sh +84 -0
  25. package/ds-canonical/hooks/check_storybook_addon_preset_cjs.sh +82 -0
  26. package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +7 -2
  27. package/ds-canonical/hooks/check_tailwind_wildcard_in_docs.sh +68 -0
  28. package/ds-canonical/hooks/chrome_header_dispatcher.sh +36 -0
  29. package/ds-canonical/hooks/inject_deploy_url_after_push.sh +178 -0
  30. package/ds-canonical/hooks/inject_pending_self_audit.sh +45 -1
  31. package/ds-canonical/hooks/{check_app_shell_primary_header_consistency.sh → lib/_app_shell_primary_header_consistency.sh} +1 -1
  32. package/ds-canonical/hooks/lib/_approval_re.sh +1 -1
  33. package/ds-canonical/hooks/{check_chrome_header_handcraft.sh → lib/_chrome_header_handcraft.sh} +1 -1
  34. package/ds-canonical/hooks/{check_header_with_tabs_border.sh → lib/_header_with_tabs_border.sh} +1 -1
  35. package/ds-canonical/hooks/{check_tab_lg_chrome_header_equal.sh → lib/_tab_lg_chrome_header_equal.sh} +1 -1
  36. package/ds-canonical/hooks/session_start_governance_check.sh +16 -3
  37. package/ds-canonical/hooks/stop_self_audit.sh +28 -3
  38. package/ds-canonical/hooks/tests/test_check_app_shell_primary_header_consistency.sh +1 -1
  39. package/ds-canonical/hooks/tests/test_check_chrome_header_handcraft.sh +1 -1
  40. package/ds-canonical/hooks/tests/test_check_data_table_size_num_to_meta_width.sh +52 -0
  41. package/ds-canonical/hooks/tests/test_check_ds_anchor_preflight.sh +104 -0
  42. package/ds-canonical/hooks/tests/test_check_fork_user_plugin_install.sh +45 -0
  43. package/ds-canonical/hooks/tests/test_check_header_with_tabs_border.sh +1 -1
  44. package/ds-canonical/hooks/tests/test_check_item_list_gap.sh +44 -0
  45. package/ds-canonical/hooks/tests/test_check_propose_without_benchmark.sh +55 -0
  46. package/ds-canonical/hooks/tests/test_check_tab_lg_chrome_header_equal.sh +1 -1
  47. package/ds-canonical/hooks/tests/test_session_start_governance_check.sh +3 -3
  48. package/ds-canonical/references/composition-fidelity.md +101 -0
  49. package/ds-canonical/references/scenario-definition.md +146 -0
  50. package/ds-canonical/rules/meta-patterns.md +1 -1
  51. package/ds-canonical/rules/story-rules.md +11 -2
  52. package/ds-canonical/skills/codex-collab/references/brief-template.md +1 -1
  53. package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +61 -6
  54. package/ds-canonical/skills/design-system-audit/SKILL.md +28 -1
  55. package/ds-canonical/skills/knowledge-prune/SKILL.md +12 -0
  56. package/ds-story-manifest.json +1 -1
  57. package/package.json +1 -1
@@ -0,0 +1,76 @@
1
+ #!/bin/bash
2
+ # check_consumer_story_baseline.sh — P0 BLOCKER
3
+ #
4
+ # Consumer storybook files wrapping high-risk DS primitives MUST declare
5
+ # `// @story-baseline: <DS-story-path>#<exportName>` marker
6
+ # (per M31 codex synthesis 2026-05-27).
7
+ #
8
+ # Anchor 2026-05-27 codex M31 v1:「Consumer 若 wrap 高風險 DS primitive,
9
+ # 檔頭必須有 @story-baseline:,並由 CI 對 DS canonical story 做 visual diff」.
10
+ #
11
+ # High-risk DS primitives(per codex list):
12
+ # DataTable / Dialog / Sheet / Popover / DropdownMenu / Tooltip / HoverCard /
13
+ # LinkInput / RadioGroup / CircularProgress / AppShell / Sidebar
14
+ #
15
+ # Triggers on consumer apps/**/*.stories.tsx edit. Blocks if file uses any of
16
+ # these primitives but lacks @story-baseline: marker.
17
+ #
18
+ # Escape:`// @story-baseline-allow: <rationale>` for legitimate exceptions
19
+ # (eg. behavior-only test stories, exception per ds-story-manifest.json).
20
+
21
+ source "$(dirname "$0")/_log-fire.sh" 2>/dev/null && log_hook_fire
22
+
23
+ set -uo pipefail
24
+
25
+ INPUT=$(cat 2>/dev/null || echo "{}")
26
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null)
27
+
28
+ case "${TOOL:-}" in
29
+ Edit|Write|MultiEdit) ;;
30
+ *) exit 0 ;;
31
+ esac
32
+
33
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""' 2>/dev/null)
34
+ # Only check consumer storybook files
35
+ if ! echo "$FILE" | grep -qE '/(apps|consumer)/.*\.stories\.tsx$'; then exit 0; fi
36
+ if echo "$FILE" | grep -qE 'packages/design-system/src/'; then exit 0; fi
37
+
38
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // ""' 2>/dev/null)
39
+ [ -z "$CONTENT" ] && exit 0
40
+
41
+ # Escape clauses
42
+ if echo "$CONTENT" | grep -qE '@story-baseline-allow:|@consumer-catalog-allow:'; then exit 0; fi
43
+
44
+ # High-risk DS primitives requiring baseline marker
45
+ HIGH_RISK_PRIMITIVES='DataTable|Dialog|Sheet|Popover|DropdownMenu|Tooltip|HoverCard|LinkInput|RadioGroup|CircularProgress|AppShell|Sidebar'
46
+
47
+ # Detect usage
48
+ USED=$(echo "$CONTENT" | grep -oE "<DS\.($HIGH_RISK_PRIMITIVES)\\b" | sort -u | head -10)
49
+
50
+ if [ -z "$USED" ]; then exit 0; fi
51
+
52
+ # Check for @story-baseline: marker
53
+ if echo "$CONTENT" | grep -qE '@story-baseline:[[:space:]]*\S'; then exit 0; fi
54
+
55
+ cat >&2 << EOF
56
+ 🚨 CONSUMER-STORY-BASELINE BLOCKER(P0,2026-05-27 M31 codex synthesis)
57
+
58
+ Consumer file $FILE 用高風險 DS primitive 但無 \`// @story-baseline:\` marker:
59
+ $(echo "$USED" | sed 's/^/ /')
60
+
61
+ per M31 codex synthesis SSOT:「Consumer wrap 高風險 DS primitive 必 @story-baseline:
62
+ marker,由 CI 對 DS canonical story 做 visual diff」.
63
+
64
+ High-risk list:DataTable / Dialog / Sheet / Popover / DropdownMenu / Tooltip /
65
+ HoverCard / LinkInput / RadioGroup / CircularProgress / AppShell / Sidebar.
66
+
67
+ 修法 2 選 1:
68
+ (a) 加 marker(檔頭或 story body):
69
+ // @story-baseline: @qijenchen/design-system/components/<Name>/<name>.stories.tsx#<ExportName>
70
+ 例:// @story-baseline: @qijenchen/design-system/components/Sidebar/sidebar.stories.tsx#IconCollapse
71
+ (b) Escape:\`// @story-baseline-allow: <rationale>\` 顯式 documented exception
72
+ (eg. pure behavior test / per ds-story-manifest.json exception list)
73
+
74
+ 完整 mapping → packages/design-system/ds-story-manifest.json(DS package ship)
75
+ EOF
76
+ exit 2
@@ -0,0 +1,68 @@
1
+ #!/bin/bash
2
+ # check_data_table_size_num_to_meta_width.sh — M23(c) mechanical enforcement(2026-05-26 backfill).
3
+ #
4
+ # Purpose:PreToolUse Edit/Write — 偵測 DataTable column 定義使用 TanStack `size: <number>`
5
+ # (px width)而非 DS canonical `meta: { width: 'sm'|'md'|'lg' }` density-aware token。
6
+ # 違反 M23(c) framework prop name namespace conflict(TanStack `size` 是 px 數字,
7
+ # DS `size` 是 density variant)→ soft warn 要求 wrap-and-rename。
8
+ #
9
+ # 對齊 meta-patterns.md M23(c) + data-table.spec.md L?「Column width canonical」段。
10
+
11
+ source "$(dirname "$0")/_log-fire.sh" 2>/dev/null && log_hook_fire
12
+
13
+ set -uo pipefail
14
+ INPUT=$(cat 2>/dev/null || echo "{}")
15
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null)
16
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""' 2>/dev/null)
17
+ EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // ""' 2>/dev/null)
18
+ NEW=$(echo "$INPUT" | jq -r '.tool_input.content // .tool_input.new_string // ""' 2>/dev/null)
19
+
20
+ [ "$EVENT" != "PreToolUse" ] && exit 0
21
+ case "$TOOL" in Edit|Write|MultiEdit) ;; *) exit 0 ;; esac
22
+ case "$FILE_PATH" in *.tsx|*.ts) ;; *) exit 0 ;; esac
23
+ case "$FILE_PATH" in *.stories.tsx|*.test.*) exit 0 ;; esac
24
+
25
+ # DataTable 上下文:column 定義 OR DataTable import OR ColumnDef 型別
26
+ HAS_DATATABLE_CTX=0
27
+ if echo "$NEW" | grep -qE '(ColumnDef|columns:|DataTable|@tanstack/react-table)'; then
28
+ HAS_DATATABLE_CTX=1
29
+ fi
30
+
31
+ [ "$HAS_DATATABLE_CTX" = "0" ] && exit 0
32
+
33
+ # Detect raw size: <number> in column def(TanStack 用法)
34
+ RAW_SIZE_HITS=$(echo "$NEW" | grep -cE '\bsize:\s*[0-9]+' 2>/dev/null)
35
+ RAW_SIZE_HITS=${RAW_SIZE_HITS:-0}
36
+
37
+ [ "$RAW_SIZE_HITS" -eq 0 ] && exit 0
38
+
39
+ # 若同時用 meta: { width 也 OK(混用允許 transition)
40
+ if echo "$NEW" | grep -qE 'meta:\s*\{[^}]*width'; then
41
+ # mixed — let it pass(transition state)
42
+ exit 0
43
+ fi
44
+
45
+ REL=${FILE_PATH#*/my-project/}
46
+
47
+ cat >&2 <<EOF
48
+ ⚠️ M23(c) framework prop namespace conflict — DataTable column \`size: <number>\`
49
+
50
+ 📁 File: $REL
51
+ 🔍 偵測:$RAW_SIZE_HITS 個 \`size: <number>\` (TanStack px API)在 ColumnDef 上下文,但無 \`meta: { width: 'sm'|'md'|'lg' }\` DS density-aware variant。
52
+
53
+ M23(c)要求:
54
+ - TanStack \`size\` prop = px 數字(framework spec)
55
+ - DS \`size\` prop = density variant 'sm'|'md'|'lg'(DS spec)
56
+ - 同名不同義 → wrap-and-rename:DataTable column 用 \`meta.width: 'sm'|'md'|'lg'\`,DS internal 再 map 成 TanStack \`size\` px
57
+
58
+ 修法:
59
+ // ❌ Avoid
60
+ { accessorKey: 'name', size: 280 }
61
+
62
+ // ✅ Use DS density-aware
63
+ { accessorKey: 'name', meta: { width: 'md' } }
64
+
65
+ 對應 canonical:meta-patterns.md M23(c) + components/DataTable/data-table.spec.md「Column width SSOT」。
66
+ EOF
67
+
68
+ exit 0
@@ -0,0 +1,132 @@
1
+ #!/bin/bash
2
+ # check_ds_anchor_preflight.sh — M29 mechanical enforcement(2026-05-26 backfill per user verbatim
3
+ # 「該程式化的都沒程式化,導致你他媽那麼容易便宜」+「未來其他人 fork 用其他元件也偏移」)。
4
+ #
5
+ # Purpose: PreToolUse Edit/Write/MultiEdit 偵測 production tsx wrap DS primitive
6
+ # (`<Sidebar>` / `<AppShell>` / `<DataTable>` / `<Dialog>` / `<Field>` 等)
7
+ # 沒同 turn 跑過 Grep / Read DS canonical baseline → 軟 BLOCKER 提醒 propose 前必出 3-column owner table。
8
+ #
9
+ # Scope:同 check_substantive_edit_approval_preflight.sh extended scope
10
+ # - packages/design-system/src/**.tsx (DS internal)
11
+ # - apps/**.tsx (consumer fork-user code) ← M29 gap absorption(2026-05-26)
12
+ # - node_modules/@qijenchen/design-system/** (禁改)
13
+ #
14
+ # Excluded: *.stories.tsx (story_invariants R7/R8 already cover) / *.test.tsx / *.spec.md.
15
+ #
16
+ # Why mechanical:M29 anchor preflight 在 meta-patterns.md 喊很久,5 個文件 reference but file 0,
17
+ # 結果 2026-05-26 product-workspace App.tsx mock-drift 沒被攔 → user 抓「肌肉沒長出來」。
18
+ # 本 hook 將「propose / write 視覺結構 前必 grep DS spec.md / canonical story」從 mindset 升 mechanical。
19
+ #
20
+ # Detection:scan transcript 過去 ~30 turns:
21
+ # (a) 有 Grep / Read tool call hit `packages/design-system/src/**/*.spec.md` OR
22
+ # `**/*.stories.tsx`(canonical baseline) → PASS
23
+ # (b) 無 → soft BLOCKER inject context 提醒 grep canonical 出 3-column owner table。
24
+ #
25
+ # 對齊 meta-patterns.md M29 「視覺/結構 propose 前必 grep DS spec.md 找 owner SSOT(M29)— 出 3-column 表」+
26
+ # .claude/rules/self-verify.md Pre-edit phase「M29 3-column owner table」。
27
+
28
+ source "$(dirname "$0")/_log-fire.sh" 2>/dev/null && log_hook_fire
29
+
30
+ set -uo pipefail
31
+ INPUT=$(cat 2>/dev/null || echo "{}")
32
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null)
33
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""' 2>/dev/null)
34
+ TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // ""' 2>/dev/null)
35
+ EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // ""' 2>/dev/null)
36
+
37
+ [ "$EVENT" != "PreToolUse" ] && exit 0
38
+ case "$TOOL" in Edit|Write|MultiEdit) ;; *) exit 0 ;; esac
39
+
40
+ # Scope:DS production code + consumer fork-user app code(不含 stories / spec / test)
41
+ case "$FILE_PATH" in
42
+ */packages/design-system/src/*.tsx) ;;
43
+ */apps/*.tsx) ;;
44
+ */node_modules/@qijenchen/design-system/*.tsx) ;;
45
+ *) exit 0 ;;
46
+ esac
47
+
48
+ case "$FILE_PATH" in
49
+ *.stories.tsx|*.test.tsx|*.spec.md|*.spec.ts) exit 0 ;;
50
+ esac
51
+
52
+ # Extract content being written/edited
53
+ NEW_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // .tool_input.new_string // ""' 2>/dev/null)
54
+
55
+ # DS primitive 名單(wrap 這些就觸發 anchor preflight)
56
+ DS_PRIMITIVES_RE='<(Sidebar|AppShell|DataTable|Dialog|Sheet|Popover|DropdownMenu|Field|FieldControlGroup|MenuItem|ItemAvatar|ItemLabel|ItemIcon|SegmentedControl|Tabs|TabsList|TabsTrigger|Combobox|Select|DatePicker|TimePicker|TreeView|Tooltip|Coachmark|FileViewer|ScrollArea|Avatar|Badge|Button|ChromeHeader|SurfaceHeader|SurfaceBody|SurfaceFooter|OverlaySurface|NameCard|Toast|FileUpload|DescriptionList|Chart|BulkActionBar|ActionBar|Carousel|Breadcrumb)\b'
57
+
58
+ # 沒 wrap DS primitive → 跳過(可能是純 utility / hook code)
59
+ if ! echo "$NEW_CONTENT" | grep -qE "$DS_PRIMITIVES_RE"; then
60
+ exit 0
61
+ fi
62
+
63
+ [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ] && exit 0
64
+
65
+ # Scan last ~30 turns(~600 lines transcript)— 找 Grep/Read 對 DS spec.md / canonical story 的 trace
66
+ RECENT_TRANSCRIPT=$(tail -600 "$TRANSCRIPT_PATH" 2>/dev/null)
67
+
68
+ # Pass condition(a):有 Grep tool call pattern hit spec.md / stories.tsx
69
+ HAS_CANONICAL_READ=$(echo "$RECENT_TRANSCRIPT" | \
70
+ jq -r 'select(.message.content != null) |
71
+ .message.content // empty |
72
+ if type == "array" then
73
+ (.[]? | select(.type == "tool_use") |
74
+ select(.name == "Grep" or .name == "Read" or .name == "Glob") |
75
+ .input | tostring)
76
+ else empty
77
+ end' 2>/dev/null | \
78
+ grep -cE 'packages/design-system/src/.*\.(spec\.md|stories\.tsx)|stories\.tsx#' 2>/dev/null)
79
+ HAS_CANONICAL_READ=${HAS_CANONICAL_READ:-0}
80
+
81
+ # Pass condition(b):AI 已 written 3-column owner table OR cite `@story-baseline:` marker
82
+ HAS_3COL_OR_MARKER=$(echo "$RECENT_TRANSCRIPT" | \
83
+ grep -cE '@story-baseline:|owner spec|canonical sentence|3-column' 2>/dev/null)
84
+ HAS_3COL_OR_MARKER=${HAS_3COL_OR_MARKER:-0}
85
+
86
+ # Pass: 有任一 canonical read OR 3-column marker → silent
87
+ if [ "$HAS_CANONICAL_READ" -gt 0 ] || [ "$HAS_3COL_OR_MARKER" -gt 0 ]; then
88
+ exit 0
89
+ fi
90
+
91
+ # Override env var(audit-logged)
92
+ if [ "${CLAUDE_BYPASS_DS_ANCHOR:-0}" = "1" ]; then
93
+ mkdir -p "$(dirname "$0")/../logs" 2>/dev/null
94
+ printf '{"ts":"%s","event":"ds-anchor-bypass","file":"%s","tool":"%s"}\n' \
95
+ "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$FILE_PATH" "$TOOL" >> "$(dirname "$0")/../logs/governance-bypass.jsonl" 2>/dev/null
96
+ exit 0
97
+ fi
98
+
99
+ REL_PATH=${FILE_PATH#*/my-project/}
100
+
101
+ # Soft BLOCKER(對齊 check_substantive_edit_approval_preflight.sh hybrid pattern)
102
+ cat >&2 <<EOF
103
+ 🚨 M29 DS Anchor Preflight — visual/structural edit 偵測
104
+
105
+ 📁 File: $REL_PATH
106
+ 🔧 Tool: $TOOL
107
+ 🧩 偵測到 DS primitive 在 new content:wrap pattern
108
+
109
+ ⚠️ 過去 ~30 turns 無 Grep/Read 對 \`packages/design-system/src/**/spec.md\` 或
110
+ \`*.stories.tsx\` baseline 的 trace,且未見 \`@story-baseline:\` marker 或 3-column owner table。
111
+
112
+ → 這違反 M29 anchor preflight invariant + story-rules.md「Production-grade composition fidelity」。
113
+ → 對 fork-user 後果:憑記憶寫 simplified mock(像 2026-05-26 App.tsx SidebarTrigger 漏 / collapsible 漏 / startIcon 漏)。
114
+
115
+ 修法 — 2 選 1(對齊 check_substantive_edit_approval_preflight.sh hybrid):
116
+ (a) 先 Grep / Read DS canonical:
117
+ - \`packages/design-system/src/<Component>/*.spec.md\`(找 owner SSOT)
118
+ - \`packages/design-system/src/<Component>/*.stories.tsx\`(找完整佈局 baseline)
119
+ 然後 inline 寫 3-column owner table(\`owner spec\` / \`canonical sentence\` / \`conflicting code\`)
120
+ 或加 \`// @story-baseline: <path>#<StoryName>\` marker 在檔頭。
121
+ (b) Bypass:\`CLAUDE_BYPASS_DS_ANCHOR=1\` env var(audit-logged in governance-bypass.jsonl)
122
+ 僅當你**真的**已 Read canonical 但 transcript scan miss 時用,not 規則繞道。
123
+
124
+ 對應 canonical:
125
+ - \`.claude/rules/meta-patterns.md\` M29
126
+ - \`.claude/rules/self-verify.md\` Pre-edit phase
127
+ - \`.claude/references/ssot-index.md\` Step 0.1 high-risk interface owner mapping
128
+ EOF
129
+
130
+ # Soft warn (exit 0):inject context 讓 AI 自決,Stop hook backstop。
131
+ # 對齊 check_substantive_edit_approval_preflight.sh hybrid pattern(soft pre + hard stop)。
132
+ exit 0
@@ -0,0 +1,86 @@
1
+ #!/bin/bash
2
+ # check_escape_marker_abuse.sh — P0 BLOCKER (codify warning)
3
+ #
4
+ # 偵測 consumer code(.tsx/.stories.tsx)濫用 escape markers 跳 SSOT enforcement.
5
+ # Per user 2026-05-27 verbatim「不亂加 escape markers — 加就跳 enforcement」.
6
+ #
7
+ # Escape markers exist for真 exceptions(per-line documented rationale)。但 fork
8
+ # user 若大量加 escape markers (eg. ≥3 同一 file) = 違反 escape philosophy → BLOCK.
9
+ #
10
+ # Detected markers:
11
+ # - @ds-misuse-allow: (check_consumer_ds_primitive_misuse.sh escape)
12
+ # - @story-baseline-allow:(check_consumer_story_baseline.sh escape)
13
+ # - @consumer-catalog-allow:(check_consumer_no_ds_catalog.sh escape)
14
+ # - @overlay-open-skip: (check_overlay_open_focus_escape_probe.sh escape)
15
+ # - @template-customized (template canonical sync opt-out)
16
+ # - @layout-space-magic-ok:(check_layout_space_magic_numbers.sh escape)
17
+ # - @story-trait-allow: (story-baseline / catalog escape)
18
+ # - @story-trait-rationale:(check_story_invariants.sh R3 escape)
19
+ # - @story-split-rationale:(check_story_invariants.sh R2 escape)
20
+ # - @story-name-canonical-allow:(check_story_invariants.sh R4 escape)
21
+ # - @propose-cite-skip: (check_propose_cite_required.sh escape)
22
+ # - @anatomy-exempt: (story-rules escape)
23
+ # - @anatomy-exempt-next:(story-rules per-line escape)
24
+ # - @benchmark-unverified: (M22 cite)
25
+ # - @benchmark-citation-allow / @benchmark-unverified-blanket (M22 file-level cite escapes)
26
+ #
27
+ # Threshold:≥3 distinct markers OR ≥5 total occurrences in same file → BLOCK
28
+ # Forces fork user to either (a) fix root cause OR (b) refactor properly OR
29
+ # (c) explicitly cite reason in commit message via env override.
30
+
31
+ source "$(dirname "$0")/_log-fire.sh" 2>/dev/null && log_hook_fire
32
+
33
+ set -uo pipefail
34
+
35
+ INPUT=$(cat 2>/dev/null || echo "{}")
36
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null)
37
+
38
+ case "${TOOL:-}" in
39
+ Edit|Write|MultiEdit) ;;
40
+ *) exit 0 ;;
41
+ esac
42
+
43
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""' 2>/dev/null)
44
+ # Only check consumer apps tsx (not DS source — DS has legitimate exceptions per spec)
45
+ if ! echo "$FILE" | grep -qE '/(apps|consumer)/.*\.(tsx|ts)$'; then exit 0; fi
46
+ if echo "$FILE" | grep -qE 'packages/design-system/src/'; then exit 0; fi
47
+
48
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // ""' 2>/dev/null)
49
+ [ -z "$CONTENT" ] && exit 0
50
+
51
+ # Global escape — meta-skip(env override OR explicit comment)
52
+ if [ "${CLAUDE_BYPASS_ESCAPE_MARKER_AUDIT:-0}" = "1" ]; then exit 0; fi
53
+
54
+ # Count distinct markers + total occurrences
55
+ MARKER_RE='@(ds-misuse-allow|story-baseline-allow|consumer-catalog-allow|overlay-open-skip|template-customized|layout-space-magic-ok|story-trait-allow|story-trait-rationale|story-split-rationale|story-name-canonical-allow|propose-cite-skip|anatomy-exempt|anatomy-exempt-next|benchmark-unverified|benchmark-citation-allow|benchmark-unverified-blanket)'
56
+ MARKERS_FOUND=$(echo "$CONTENT" | grep -oE "$MARKER_RE" | sort -u)
57
+ DISTINCT_COUNT=$(echo "$MARKERS_FOUND" | grep -c . || echo 0)
58
+ TOTAL_COUNT=$(echo "$CONTENT" | grep -oE "$MARKER_RE" | wc -l | tr -d ' ')
59
+
60
+ # Threshold: ≥3 distinct types OR ≥5 total
61
+ if [ "$DISTINCT_COUNT" -ge 3 ] || [ "$TOTAL_COUNT" -ge 5 ]; then
62
+ cat >&2 << EOF
63
+ 🚨 ESCAPE-MARKER-ABUSE BLOCKER(P0,user 2026-05-27 verbatim「不亂加 escape markers — 加就跳 enforcement」)
64
+
65
+ File $FILE:
66
+ Distinct escape markers: $DISTINCT_COUNT(threshold ≥3)
67
+ Total occurrences: $TOTAL_COUNT(threshold ≥5)
68
+
69
+ Markers detected:
70
+ $(echo "$MARKERS_FOUND" | sed 's/^/ /')
71
+
72
+ Escape markers 設計為「rare per-line documented exception」,不該 routine 加。
73
+ Fork user file 大量加 marker = 違反 SSOT 哲學 — 應該:
74
+
75
+ 修法 3 選 1:
76
+ (a) **重構 code** 走 DS canonical pattern(消除根因,不繞)
77
+ (b) **拆 file**:1 個 escape 對應 1 個 specific case,分散到不同 file
78
+ (c) **Override env**(極罕見,documented in commit msg):
79
+ CLAUDE_BYPASS_ESCAPE_MARKER_AUDIT=1 git commit -m "<rationale>"
80
+
81
+ per check_consumer_*.sh hooks SSOT — escape 是 emergency exit,不是 daily tool.
82
+ EOF
83
+ exit 2
84
+ fi
85
+
86
+ exit 0
@@ -0,0 +1,75 @@
1
+ #!/bin/bash
2
+ # check_fork_user_plugin_install.sh — SessionStart enforce plugin install on fork-user repos.
3
+ #
4
+ # 2026-05-26 backfill per user verbatim「我們做那麼多 plugin 不就是要避免這件事?結果還避不了?」
5
+ #
6
+ # Purpose:當 session 跑在「consumer fork-user repo」(non-DS repo,但 package.json
7
+ # 含 @qijenchen/design-system dep)+ 偵測未安裝 design-system plugin → context inject
8
+ # 強制提示用 `/plugin marketplace add github:ajenchen/design-system → /plugin install`。
9
+ #
10
+ # Detection logic:
11
+ # (a) cwd 不是 DS repo(無 packages/design-system/ folder)
12
+ # (b) package.json 含 `"@qijenchen/design-system"` dep
13
+ # (c) `.claude/plugins/design-system@qijenchen-ds/` OR plugin marketplace symlink 不存在
14
+ # → 三題全 YES = consumer fork-user 沒裝 plugin → 強制提示
15
+
16
+ source "$(dirname "$0")/_log-fire.sh" 2>/dev/null && log_hook_fire
17
+
18
+ set -uo pipefail
19
+ INPUT=$(cat 2>/dev/null || echo "{}")
20
+ EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // ""' 2>/dev/null)
21
+
22
+ [ "$EVENT" != "SessionStart" ] && exit 0
23
+
24
+ CWD=$(pwd)
25
+
26
+ # (a) 不是 DS repo:無 packages/design-system/src
27
+ [ -d "$CWD/packages/design-system/src" ] && exit 0
28
+
29
+ # (b) package.json 含 DS dep?
30
+ [ ! -f "$CWD/package.json" ] && exit 0
31
+ if ! grep -q '"@qijenchen/design-system"' "$CWD/package.json" 2>/dev/null; then
32
+ exit 0
33
+ fi
34
+
35
+ # (c) Plugin 已安裝?Anthropic plugin install 可能使用 <plugin> 或
36
+ # <plugin>@<marketplace> directory name;accept both.
37
+ PLUGIN_INSTALLED=0
38
+ [ -d "$HOME/.claude/plugins/design-system" ] && PLUGIN_INSTALLED=1
39
+ [ -d "$HOME/.claude/plugins/design-system@qijenchen-ds" ] && PLUGIN_INSTALLED=1
40
+ [ -d "$CWD/.claude/plugins/design-system" ] && PLUGIN_INSTALLED=1
41
+ [ -d "$CWD/.claude/plugins/design-system@qijenchen-ds" ] && PLUGIN_INSTALLED=1
42
+ # 也接受 plugin marketplace cache(claude code 自動產生)
43
+ [ -d "$HOME/.claude/marketplaces"/*/design-system/ ] 2>/dev/null && PLUGIN_INSTALLED=1
44
+ [ -d "$HOME/.claude/marketplaces"/*/design-system@qijenchen-ds/ ] 2>/dev/null && PLUGIN_INSTALLED=1
45
+
46
+ if [ "$PLUGIN_INSTALLED" = "1" ]; then
47
+ exit 0
48
+ fi
49
+
50
+ # All YES → fork-user 沒裝 plugin → context inject(SessionStart additional context)
51
+ cat <<EOF
52
+ 🚨 Fork-user plugin not installed — DS governance hooks 不會 fire,憑記憶寫 mock 不會被攔。
53
+
54
+ 偵測:
55
+ cwd = $CWD
56
+ package.json 含 @qijenchen/design-system dep ✅
57
+ ~/.claude/plugins/design-system@qijenchen-ds/ 或 .claude/plugins/design-system@qijenchen-ds/ 不存在 ❌
58
+
59
+ → 後果(2026-05-26 anchor event):憑記憶寫 App.tsx mock(漏 SidebarTrigger / collapsible / startIcon /
60
+ tooltip / SidebarFooter)= production-grade fork-user 跑版 anti-pattern。
61
+
62
+ 修法:**session 開始第一件事必跑**
63
+ 1. \`/plugin marketplace add github:ajenchen/design-system\`
64
+ 2. \`/plugin install design-system@qijenchen-ds\`(or 對應 plugin name per marketplace.json)
65
+
66
+ 跑完 plugin install 後:
67
+ - 59 個 DS governance hooks 自動 fire(M29 anchor preflight / approval-preflight / story_invariants / inject_deploy_url_after_push 等;count snapshot 2026-05-29)
68
+ - DS canonical / rules / skills 從 ~/.claude/plugins/ 可 cross-load
69
+ - Fork-user 寫 App.tsx 時憑記憶寫 mock 會被 mechanical BLOCKER 攔
70
+
71
+ 對應 canonical:
72
+ - product-workspace CLAUDE.md「Fork-and-go onboarding」step 2-3
73
+ - .claude-plugin/marketplace.json
74
+ EOF
75
+ exit 0
@@ -0,0 +1,71 @@
1
+ #!/bin/bash
2
+ # check_full_story_visual_interaction_sweep.sh — P0 BLOCKER
3
+ #
4
+ # Codex M31 P0 finding 2026-05-27: ds-story-manifest.json 是 62-component / 916-story
5
+ # SSOT,但無 hook 攔 audit reports 漏 manifest story IDs / screenshots / metrics / probe.
6
+ # User 2026-05-27 verbatim「不准抽樣 全 visual + interaction 全跑」.
7
+ #
8
+ # Triggers when audit report JSON is edited/written:
9
+ # - /tmp/codex-*-sweep/audit-report.json
10
+ # - /tmp/claude-*-sweep/audit-report.json
11
+ # - .claude/snapshots/*audit-report.json
12
+ # Verifies storyResults.length === manifest.totalStories (916) — block if sample.
13
+ #
14
+ # Escape: report frontmatter `"_sampling_allowed": "<rationale>"`(極罕見).
15
+
16
+ source "$(dirname "$0")/_log-fire.sh" 2>/dev/null && log_hook_fire
17
+
18
+ set -uo pipefail
19
+
20
+ INPUT=$(cat 2>/dev/null || echo "{}")
21
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null)
22
+
23
+ case "${TOOL:-}" in
24
+ Write|Edit|MultiEdit) ;;
25
+ *) exit 0 ;;
26
+ esac
27
+
28
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""' 2>/dev/null)
29
+ if ! echo "$FILE" | grep -qE '(sweep|audit-report)\.json$'; then exit 0; fi
30
+
31
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // ""' 2>/dev/null)
32
+ [ -z "$CONTENT" ] && exit 0
33
+
34
+ # Check escape
35
+ if echo "$CONTENT" | grep -q '_sampling_allowed'; then exit 0; fi
36
+
37
+ # Parse story count vs manifest (multi-field fallback)
38
+ STORY_COUNT=$(echo "$CONTENT" | jq -r '.storyResults // .results // [] | length' 2>/dev/null)
39
+ if [ -z "$STORY_COUNT" ] || [ "$STORY_COUNT" = "0" ] || [ "$STORY_COUNT" = "null" ]; then
40
+ STORY_COUNT=$(echo "$CONTENT" | jq -r '.storySummary.total // .summary.total // .total // 0' 2>/dev/null)
41
+ fi
42
+ STORY_COUNT=${STORY_COUNT:-0}
43
+
44
+ MANIFEST_PATH="${CLAUDE_PROJECT_DIR:-.}/packages/design-system/ds-story-manifest.json"
45
+ if [ ! -f "$MANIFEST_PATH" ]; then exit 0; fi
46
+ MANIFEST_TOTAL=$(jq -r '.totalStories // 0' "$MANIFEST_PATH" 2>/dev/null)
47
+ [ -z "$MANIFEST_TOTAL" ] || [ "$MANIFEST_TOTAL" = "0" ] && exit 0
48
+ [ "$STORY_COUNT" = "0" ] && exit 0
49
+ # numeric compare
50
+ if ! [[ "$STORY_COUNT" =~ ^[0-9]+$ ]]; then exit 0; fi
51
+
52
+ if [ "$STORY_COUNT" -lt "$MANIFEST_TOTAL" ]; then
53
+ cat >&2 << EOF
54
+ 🚨 FULL-STORY-SWEEP BLOCKER(P0,codex M31 finding 2026-05-27 + user「不准抽樣」)
55
+
56
+ Audit report $FILE:
57
+ storyResults: $STORY_COUNT
58
+ manifest.totalStories: $MANIFEST_TOTAL
59
+ 缺失: $((MANIFEST_TOTAL - STORY_COUNT)) stories
60
+
61
+ per ds-story-manifest.json SSOT(scripts/gen-ds-story-manifest.mjs:59)+ user 2026-05-27
62
+ 「請你不要給我抽樣,因為你一抽樣就是會有東西壞掉,務必要全部檢查」directive.
63
+
64
+ 修法 2 選 1:
65
+ (a) Re-run sweep covering all $MANIFEST_TOTAL stories
66
+ (b) Escape:report 含 \`"_sampling_allowed": "<rationale>"\`(極罕見)
67
+ EOF
68
+ exit 2
69
+ fi
70
+
71
+ exit 0
@@ -0,0 +1,54 @@
1
+ #!/bin/bash
2
+ # check_item_list_gap.sh — M16 mechanical enforcement(2026-05-26 backfill).
3
+ #
4
+ # Purpose:PreToolUse Edit/Write — 偵測 production tsx 含 standalone card/pill 渲染
5
+ # pattern(`<FileItem>` / `<MenuItem rich>` / 卡片型 standalone)without gap-N or space-y-N
6
+ # parent → soft warn requires explicit gap canonical per item-anatomy.spec.md。
7
+ #
8
+ # 3 條公式(per M16):
9
+ # - 同類 standalone 卡片 → 必 gap
10
+ # - 同類 permanent flush(連續貼齊)→ 0 gap OK,但必 codify in spec
11
+ # - 混合語言 → 必取最保守 gap
12
+
13
+ source "$(dirname "$0")/_log-fire.sh" 2>/dev/null && log_hook_fire
14
+
15
+ set -uo pipefail
16
+ INPUT=$(cat 2>/dev/null || echo "{}")
17
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null)
18
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""' 2>/dev/null)
19
+ EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // ""' 2>/dev/null)
20
+ NEW=$(echo "$INPUT" | jq -r '.tool_input.content // .tool_input.new_string // ""' 2>/dev/null)
21
+
22
+ [ "$EVENT" != "PreToolUse" ] && exit 0
23
+ case "$TOOL" in Edit|Write|MultiEdit) ;; *) exit 0 ;; esac
24
+ case "$FILE_PATH" in *.tsx) ;; *) exit 0 ;; esac
25
+ case "$FILE_PATH" in *.stories.tsx|*.test.tsx) exit 0 ;; esac
26
+
27
+ # Heuristic:multiple <FileItem> / <MenuItem variant="rich"> / standalone card pattern + parent 無 gap-N
28
+ # 2026-05-26 fix:grep -c 計行數不計次,用 -o count 真實 occurrence
29
+ STANDALONE_RE='<(FileItem|MenuItem[^>]+variant=["'\'']rich["'\''])'
30
+ COUNT=$(echo "$NEW" | grep -oE "$STANDALONE_RE" 2>/dev/null | wc -l | tr -d ' ')
31
+ COUNT=${COUNT:-0}
32
+
33
+ [ "$COUNT" -lt 2 ] && exit 0
34
+
35
+ # Has gap-N or space-y-N near the standalone pattern?
36
+ if echo "$NEW" | grep -qE '(gap-[0-9]|space-y-[0-9])'; then
37
+ exit 0
38
+ fi
39
+
40
+ cat >&2 <<EOF
41
+ ⚠️ M16 Item-list gap canonical
42
+
43
+ → 偵測到 $COUNT 個 standalone item(FileItem / MenuItem rich)未見 gap-N / space-y-N parent class。
44
+
45
+ M16 要求(per item-anatomy.spec.md):
46
+ - 同類 standalone → 必 gap(常見 gap-2 / gap-3)
47
+ - 同類 permanent flush(連續貼齊)→ 0 gap OK,但必 spec codify
48
+ - 混合語言 → 必取最保守 gap
49
+
50
+ 修法:parent container 加 \`gap-2\` / \`space-y-2\` 或 codify 連續貼齊 in 元件 spec。
51
+ 對應 canonical:meta-patterns.md M16 + patterns/element-anatomy/item-anatomy.spec.md。
52
+ EOF
53
+
54
+ exit 0
@@ -0,0 +1,84 @@
1
+ #!/bin/bash
2
+ # check_layout_space_magic_numbers.sh — P0 BLOCKER
3
+ #
4
+ # 偵測 consumer / DS app code 用 Tailwind spacing magic numbers
5
+ # (`p-4` / `px-6` / `py-2` / `gap-3` 等)而非 layoutSpace token
6
+ # (`p-[var(--layout-space-N) N∈{loose,tight}]` / `gap-[var(--layout-space-N) N∈{loose,tight}]`)。
7
+ # 2026-05-27 user verbatim「機械無強制就不會做?那為何不全部 ssot 都要強制吻合?」
8
+ # 永久 codify — SSOT canonical 必 P0 BLOCKER,不分級。
9
+ #
10
+ # Anchor:user 質疑「content 自動繼承 layoutSpace SSOT 嗎?」
11
+ # - app-shell.spec.md:205 明文 `<main>` landmark padding=0 (intentional)
12
+ # - app-shell.spec.md:207-212 consumer 必遵循 layoutSpace.spec.md 6 條規則 + 親疏 3 級
13
+ #
14
+ # PostToolUse Edit/Write detect magic Tailwind spacing → P0 BLOCKER exit 2
15
+ # 強制改 token OR 加 `// @layout-space-magic-ok: <rationale>` escape comment(per-line)。
16
+
17
+ source "$(dirname "$0")/_log-fire.sh" 2>/dev/null && log_hook_fire
18
+
19
+ set -uo pipefail
20
+
21
+ INPUT=$(cat 2>/dev/null || echo "{}")
22
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null)
23
+
24
+ case "${TOOL:-}" in
25
+ Edit|Write|MultiEdit) ;;
26
+ *) exit 0 ;;
27
+ esac
28
+
29
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.notebook_path // ""' 2>/dev/null)
30
+
31
+ # Only check .tsx / .ts in app code
32
+ if ! echo "$FILE" | grep -qE '\.(tsx|ts)$'; then exit 0; fi
33
+ # Skip DS source (DS components have their own spacing logic via cva)
34
+ if echo "$FILE" | grep -qE 'packages/design-system/src/|node_modules/'; then exit 0; fi
35
+
36
+ # Get new content
37
+ NEW_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // ""' 2>/dev/null)
38
+ [ -z "$NEW_CONTENT" ] && exit 0
39
+
40
+ # Escape clause:single line escape comment per line of magic number usage
41
+ # `// @layout-space-magic-ok: <rationale>` immediately above the line OR on same line
42
+ ESCAPE_MARKER='@layout-space-magic-ok:'
43
+
44
+ # Detect magic spacing classes (Tailwind class strings only — NOT JSX props like size={24})
45
+ # Match per line so we can check per-line escape comments
46
+ MAGIC_LINES=$(echo "$NEW_CONTENT" | grep -nE '\b(p|px|py|pt|pb|pl|pr|gap|space-x|space-y|m|mx|my|mt|mb|ml|mr)-(0\.5|[1-9][0-9]?(\.[0-9])?)\b')
47
+
48
+ if [ -z "$MAGIC_LINES" ]; then
49
+ exit 0
50
+ fi
51
+
52
+ # Filter out lines with escape marker on same line OR preceding line
53
+ UNJUSTIFIED=""
54
+ while IFS= read -r line; do
55
+ if echo "$line" | grep -qF "$ESCAPE_MARKER"; then continue; fi
56
+ UNJUSTIFIED="${UNJUSTIFIED}${line}\n"
57
+ done <<< "$MAGIC_LINES"
58
+
59
+ if [ -z "$UNJUSTIFIED" ]; then
60
+ exit 0
61
+ fi
62
+
63
+ cat >&2 << EOF
64
+ 🚨 LAYOUT-SPACE-MAGIC-NUMBER BLOCKER(P0,2026-05-27 user verbatim「機械無強制就不會做?
65
+ 為何不全部 ssot 都要強制吻合?」永久 codify)
66
+
67
+ Detected Tailwind spacing magic numbers in $FILE without escape:
68
+ $(echo -e "$UNJUSTIFIED" | sed 's/^/ /' | head -10)
69
+
70
+ per app-shell.spec.md L205-219 + layoutSpace.spec.md SSOT:consumer content 必遵循
71
+ layoutSpace 6 條規則 + 親疏 3 級,**禁** 硬寫 Tailwind magic numbers。改用:
72
+ p-[var(--layout-space-loose)] /* 16px 規則 1A/1B chrome / wrap */
73
+ p-[var(--layout-space-tight)] /* 12px 規則 3 親 gap */
74
+ gap-[var(--layout-space-distant)] /* 24px 規則 3 疏 gap */
75
+ space-y-[var(--layout-space-distant)]
76
+
77
+ 修法 2 選 1:
78
+ (a) 改 token:換成 var(--layout-space-N) N∈{loose,tight} family per 6 規則 + 親疏 3 級
79
+ (b) Escape:在該 line 加 \`// @layout-space-magic-ok: <rationale>\` 顯式 documented
80
+ (eg.「\`gap-1\` 是 4px stack icon — non-spacing context,not consumer layout」)
81
+
82
+ 完整 6 條規則 → packages/design-system/src/tokens/layoutSpace/layoutSpace.spec.md
83
+ EOF
84
+ exit 2