@hustle-together/api-dev-tools 3.12.16 → 4.5.3

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 (180) hide show
  1. package/.claude/adr-requests/.gitkeep +10 -0
  2. package/.claude/agents/adr-researcher.md +109 -0
  3. package/.claude/agents/visual-analyzer.md +183 -0
  4. package/.claude/api-dev-state.json +10 -0
  5. package/.claude/documentation-audit.json +114 -0
  6. package/.claude/registry.json +289 -0
  7. package/.claude/settings.json +45 -1
  8. package/.claude/settings.local.json +1 -7
  9. package/.claude/workflow-logs/None.json +49 -0
  10. package/.claude/workflow-logs/session-20251230-143727.json +106 -0
  11. package/.skills/adr-deep-research/SKILL.md +351 -0
  12. package/.skills/api-create/SKILL.md +34 -20
  13. package/.skills/api-research/SKILL.md +130 -0
  14. package/.skills/docs-update/SKILL.md +205 -0
  15. package/.skills/hustle-brand/SKILL.md +368 -0
  16. package/.skills/hustle-build/SKILL.md +365 -38
  17. package/.skills/parallel-spawn/SKILL.md +212 -0
  18. package/.skills/ralph-continue/SKILL.md +151 -0
  19. package/.skills/ralph-loop/SKILL.md +341 -0
  20. package/.skills/ralph-status/SKILL.md +87 -0
  21. package/.skills/refactor/SKILL.md +59 -0
  22. package/.skills/shadcn/SKILL.md +522 -0
  23. package/.skills/test-all/SKILL.md +210 -0
  24. package/.skills/test-builds/SKILL.md +208 -0
  25. package/.skills/test-debug/SKILL.md +212 -0
  26. package/.skills/test-e2e/SKILL.md +168 -0
  27. package/.skills/test-review/SKILL.md +707 -0
  28. package/.skills/test-unit/SKILL.md +143 -0
  29. package/.skills/test-visual/SKILL.md +301 -0
  30. package/.skills/token-report/SKILL.md +132 -0
  31. package/CHANGELOG.md +488 -0
  32. package/README.md +346 -53
  33. package/bin/cli.js +359 -123
  34. package/hooks/__pycache__/api-workflow-check.cpython-314.pyc +0 -0
  35. package/hooks/__pycache__/auto-answer.cpython-314.pyc +0 -0
  36. package/hooks/__pycache__/cache-research.cpython-314.pyc +0 -0
  37. package/hooks/__pycache__/check-api-routes.cpython-314.pyc +0 -0
  38. package/hooks/__pycache__/check-playwright-setup.cpython-314.pyc +0 -0
  39. package/hooks/__pycache__/check-storybook-setup.cpython-314.pyc +0 -0
  40. package/hooks/__pycache__/check-update.cpython-314.pyc +0 -0
  41. package/hooks/__pycache__/completion-promise-detector.cpython-314.pyc +0 -0
  42. package/hooks/__pycache__/context-capacity-warning.cpython-314.pyc +0 -0
  43. package/hooks/__pycache__/detect-interruption.cpython-314.pyc +0 -0
  44. package/hooks/__pycache__/docs-update-check.cpython-314.pyc +0 -0
  45. package/hooks/__pycache__/enforce-a11y-audit.cpython-314.pyc +0 -0
  46. package/hooks/__pycache__/enforce-brand-guide.cpython-314.pyc +0 -0
  47. package/hooks/__pycache__/enforce-component-type-confirm.cpython-314.pyc +0 -0
  48. package/hooks/__pycache__/enforce-deep-research.cpython-314.pyc +0 -0
  49. package/hooks/__pycache__/enforce-disambiguation.cpython-314.pyc +0 -0
  50. package/hooks/__pycache__/enforce-documentation.cpython-314.pyc +0 -0
  51. package/hooks/__pycache__/enforce-dry-run.cpython-314.pyc +0 -0
  52. package/hooks/__pycache__/enforce-environment.cpython-314.pyc +0 -0
  53. package/hooks/__pycache__/enforce-external-research.cpython-314.pyc +0 -0
  54. package/hooks/__pycache__/enforce-freshness.cpython-314.pyc +0 -0
  55. package/hooks/__pycache__/enforce-interview.cpython-314.pyc +0 -0
  56. package/hooks/__pycache__/enforce-page-components.cpython-314.pyc +0 -0
  57. package/hooks/__pycache__/enforce-page-data-schema.cpython-314.pyc +0 -0
  58. package/hooks/__pycache__/enforce-questions-sourced.cpython-314.pyc +0 -0
  59. package/hooks/__pycache__/enforce-refactor.cpython-314.pyc +0 -0
  60. package/hooks/__pycache__/enforce-research.cpython-314.pyc +0 -0
  61. package/hooks/__pycache__/enforce-schema-from-interview.cpython-314.pyc +0 -0
  62. package/hooks/__pycache__/enforce-schema.cpython-314.pyc +0 -0
  63. package/hooks/__pycache__/enforce-scope.cpython-314.pyc +0 -0
  64. package/hooks/__pycache__/enforce-tdd-red.cpython-314.pyc +0 -0
  65. package/hooks/__pycache__/enforce-ui-disambiguation.cpython-314.pyc +0 -0
  66. package/hooks/__pycache__/enforce-ui-interview.cpython-314.pyc +0 -0
  67. package/hooks/__pycache__/enforce-verify.cpython-314.pyc +0 -0
  68. package/hooks/__pycache__/generate-adr-options.cpython-314.pyc +0 -0
  69. package/hooks/__pycache__/generate-manifest-entry.cpython-314.pyc +0 -0
  70. package/hooks/__pycache__/hook_utils.cpython-314.pyc +0 -0
  71. package/hooks/__pycache__/notify-input-needed.cpython-314.pyc +0 -0
  72. package/hooks/__pycache__/notify-phase-complete.cpython-314.pyc +0 -0
  73. package/hooks/__pycache__/ntfy-on-question.cpython-314.pyc +0 -0
  74. package/hooks/__pycache__/orchestrator-completion.cpython-314.pyc +0 -0
  75. package/hooks/__pycache__/orchestrator-handoff.cpython-314.pyc +0 -0
  76. package/hooks/__pycache__/orchestrator-session-startup.cpython-314.pyc +0 -0
  77. package/hooks/__pycache__/parallel-orchestrator.cpython-314.pyc +0 -0
  78. package/hooks/__pycache__/periodic-reground.cpython-314.pyc +0 -0
  79. package/hooks/__pycache__/project-document-prompt.cpython-314.pyc +0 -0
  80. package/hooks/__pycache__/remote-question-proxy.cpython-314.pyc +0 -0
  81. package/hooks/__pycache__/remote-question-server.cpython-314.pyc +0 -0
  82. package/hooks/__pycache__/run-code-review.cpython-314.pyc +0 -0
  83. package/hooks/__pycache__/run-visual-qa.cpython-314.pyc +0 -0
  84. package/hooks/__pycache__/session-logger.cpython-314.pyc +0 -0
  85. package/hooks/__pycache__/session-startup.cpython-314.pyc +0 -0
  86. package/hooks/__pycache__/track-scope-coverage.cpython-314.pyc +0 -0
  87. package/hooks/__pycache__/track-token-usage.cpython-314.pyc +0 -0
  88. package/hooks/__pycache__/track-tool-use.cpython-314.pyc +0 -0
  89. package/hooks/__pycache__/update-adr-decision.cpython-314.pyc +0 -0
  90. package/hooks/__pycache__/update-api-showcase.cpython-314.pyc +0 -0
  91. package/hooks/__pycache__/update-registry.cpython-314.pyc +0 -0
  92. package/hooks/__pycache__/update-ui-showcase.cpython-314.pyc +0 -0
  93. package/hooks/__pycache__/verify-after-green.cpython-314.pyc +0 -0
  94. package/hooks/__pycache__/verify-implementation.cpython-314.pyc +0 -0
  95. package/hooks/api-workflow-check.py +34 -0
  96. package/hooks/auto-answer.py +97 -20
  97. package/{.claude/hooks → hooks}/completion-promise-detector.py +0 -0
  98. package/{.claude/hooks → hooks}/context-capacity-warning.py +0 -0
  99. package/{.claude/hooks → hooks}/docs-update-check.py +0 -0
  100. package/{.claude/hooks → hooks}/enforce-dry-run.py +0 -0
  101. package/hooks/enforce-external-research.py +25 -0
  102. package/hooks/enforce-interview.py +20 -0
  103. package/{.claude/hooks → hooks}/generate-adr-options.py +0 -0
  104. package/{.claude/hooks → hooks}/hook_utils.py +0 -0
  105. package/hooks/ntfy-on-question.py +15 -2
  106. package/hooks/orchestrator-handoff.py +81 -3
  107. package/{.claude/hooks → hooks}/parallel-orchestrator.py +0 -0
  108. package/hooks/periodic-reground.py +40 -0
  109. package/{.claude/hooks → hooks}/remote-question-server.py +0 -0
  110. package/hooks/run-code-review.py +176 -29
  111. package/{.claude/hooks → hooks}/run-visual-qa.py +0 -0
  112. package/hooks/session-logger.py +27 -1
  113. package/hooks/session-startup.py +113 -0
  114. package/{.claude/hooks → hooks}/update-adr-decision.py +0 -0
  115. package/package.json +1 -1
  116. package/templates/.skills/hustle-interview/SKILL.md +174 -0
  117. package/templates/adr-viewer/_components/ADRViewer.tsx +326 -0
  118. package/templates/api-dev-state.json +33 -1
  119. package/templates/brand-page/page.tsx +645 -0
  120. package/templates/component/Component.visual.spec.ts +30 -24
  121. package/templates/eslint-plugin-zod-schema/index.js +446 -0
  122. package/templates/eslint-plugin-zod-schema/package.json +26 -0
  123. package/templates/github-workflows/security.yml +274 -0
  124. package/templates/hustle-build-defaults.json +53 -1
  125. package/templates/page/page.e2e.test.ts +30 -26
  126. package/templates/performance-budgets.json +63 -5
  127. package/templates/registry.json +279 -3
  128. package/templates/review-dashboard/page.tsx +510 -0
  129. package/templates/settings.json +74 -7
  130. package/templates/ui-showcase/_components/UIShowcase.tsx +47 -0
  131. package/templates/ui-showcase/_components/VisualTestingDashboard.tsx +579 -0
  132. package/.claude/commands/hustle-combine.md +0 -1089
  133. package/.claude/commands/hustle-ui-create-page.md +0 -1078
  134. package/.claude/commands/hustle-ui-create.md +0 -1058
  135. package/.claude/hooks/auto-answer.py +0 -305
  136. package/.claude/hooks/cache-research.py +0 -337
  137. package/.claude/hooks/check-api-routes.py +0 -168
  138. package/.claude/hooks/check-playwright-setup.py +0 -103
  139. package/.claude/hooks/check-storybook-setup.py +0 -81
  140. package/.claude/hooks/check-update.py +0 -132
  141. package/.claude/hooks/detect-interruption.py +0 -165
  142. package/.claude/hooks/enforce-a11y-audit.py +0 -202
  143. package/.claude/hooks/enforce-brand-guide.py +0 -241
  144. package/.claude/hooks/enforce-component-type-confirm.py +0 -97
  145. package/.claude/hooks/enforce-freshness.py +0 -184
  146. package/.claude/hooks/enforce-page-components.py +0 -186
  147. package/.claude/hooks/enforce-page-data-schema.py +0 -155
  148. package/.claude/hooks/enforce-questions-sourced.py +0 -146
  149. package/.claude/hooks/enforce-schema-from-interview.py +0 -248
  150. package/.claude/hooks/enforce-ui-disambiguation.py +0 -108
  151. package/.claude/hooks/enforce-ui-interview.py +0 -130
  152. package/.claude/hooks/generate-manifest-entry.py +0 -1161
  153. package/.claude/hooks/lib/__init__.py +0 -1
  154. package/.claude/hooks/lib/greptile.py +0 -355
  155. package/.claude/hooks/lib/ntfy.py +0 -209
  156. package/.claude/hooks/notify-input-needed.py +0 -73
  157. package/.claude/hooks/notify-phase-complete.py +0 -90
  158. package/.claude/hooks/ntfy-on-question.py +0 -240
  159. package/.claude/hooks/orchestrator-completion.py +0 -313
  160. package/.claude/hooks/orchestrator-handoff.py +0 -267
  161. package/.claude/hooks/orchestrator-session-startup.py +0 -146
  162. package/.claude/hooks/run-code-review.py +0 -393
  163. package/.claude/hooks/session-logger.py +0 -323
  164. package/.claude/hooks/test-orchestrator-reground.py +0 -248
  165. package/.claude/hooks/track-scope-coverage.py +0 -220
  166. package/.claude/hooks/track-token-usage.py +0 -121
  167. package/.claude/hooks/update-api-showcase.py +0 -161
  168. package/.claude/hooks/update-registry.py +0 -352
  169. package/.claude/hooks/update-ui-showcase.py +0 -224
  170. package/.claude/test-auto-answer-bot.py +0 -183
  171. package/.claude/test-completion-detector.py +0 -263
  172. package/.claude/test-orchestrator-state.json +0 -20
  173. package/.claude/test-orchestrator.sh +0 -271
  174. /package/{.claude/commands → commands}/hustle-build.md +0 -0
  175. /package/{.claude/hooks → hooks}/lib/__pycache__/__init__.cpython-314.pyc +0 -0
  176. /package/{.claude/hooks → hooks}/lib/__pycache__/greptile.cpython-314.pyc +0 -0
  177. /package/{.claude/hooks → hooks}/lib/__pycache__/ntfy.cpython-314.pyc +0 -0
  178. /package/{.claude/hooks → hooks}/project-document-prompt.py +0 -0
  179. /package/{.claude/hooks → hooks}/remote-question-proxy.py +0 -0
  180. /package/{.claude/hooks → hooks}/update-testing-checklist.py +0 -0
@@ -1,202 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Hook: PostToolUse for Write/Edit
4
- Purpose: Trigger accessibility audit after UI component/page implementation
5
-
6
- This hook runs after Phase 9 (TDD GREEN) for UI workflows. It notifies Claude
7
- to run axe-core audit on Storybook stories or pages to verify WCAG compliance.
8
-
9
- Version: 3.10.0
10
-
11
- Returns:
12
- - {"continue": true} - Always continues
13
- - May include "notify" with accessibility check reminder
14
- - May include "additionalContext" with accessibility guidelines
15
- """
16
- import json
17
- import sys
18
- from pathlib import Path
19
-
20
- # State file is in .claude/ directory (sibling to hooks/)
21
- STATE_FILE = Path(__file__).parent.parent / "api-dev-state.json"
22
-
23
- # WCAG 2.1 Level AA Quick Reference
24
- WCAG_AA_CHECKLIST = [
25
- "Color contrast: 4.5:1 for normal text, 3:1 for large text",
26
- "Focus visible: All interactive elements show focus state",
27
- "Keyboard nav: All functionality accessible via keyboard",
28
- "Labels: All form inputs have associated labels",
29
- "Alt text: All images have meaningful alt text",
30
- "Headings: Proper heading hierarchy (h1-h6)",
31
- "Touch targets: Min 44x44px for touch targets",
32
- "Error messages: Clear error identification and suggestions",
33
- ]
34
-
35
-
36
- def get_workflow_type(state):
37
- """Detect the workflow type from state."""
38
- workflow = state.get("workflow", "")
39
- if workflow:
40
- return workflow
41
-
42
- if state.get("ui_config"):
43
- mode = state.get("ui_config", {}).get("mode", "")
44
- return f"ui-create-{mode}" if mode else "ui-create-component"
45
-
46
- return "api-create"
47
-
48
-
49
- def get_active_element(state):
50
- """Get active element name and data."""
51
- if "elements" in state and "active_element" in state:
52
- active = state.get("active_element")
53
- if active and active in state["elements"]:
54
- return active, state["elements"][active]
55
- return None, None
56
-
57
- active = state.get("active_element")
58
- if active:
59
- return active, state
60
-
61
- return None, None
62
-
63
-
64
- def is_verify_phase(phases):
65
- """Check if we're in or just completed the verify phase."""
66
- verify = phases.get("verify", {})
67
- tdd_green = phases.get("tdd_green", {})
68
-
69
- # After green, before or during verify
70
- return (
71
- tdd_green.get("status") == "complete" and
72
- verify.get("status") in ["not_started", "in_progress"]
73
- )
74
-
75
-
76
- def get_accessibility_level(state, element_data):
77
- """Get the accessibility level requirement."""
78
- ui_config = state.get("ui_config", {})
79
- if not ui_config and element_data:
80
- ui_config = element_data.get("ui_config", {})
81
-
82
- return ui_config.get("accessibility_level", "AA")
83
-
84
-
85
- def generate_a11y_commands(element_name, workflow_type):
86
- """Generate accessibility testing commands."""
87
- commands = []
88
-
89
- if "component" in workflow_type:
90
- commands.extend([
91
- f"# Storybook accessibility check",
92
- f"pnpm storybook --ci",
93
- f"# Then run axe in browser or:",
94
- f"pnpm dlx @storybook/test-runner --url http://localhost:6006",
95
- f"",
96
- f"# Or manual axe-core check:",
97
- f"pnpm dlx @axe-core/cli http://localhost:6006/?path=/story/{element_name.lower()}--default"
98
- ])
99
- else:
100
- commands.extend([
101
- f"# Page accessibility check",
102
- f"pnpm dev",
103
- f"# Then in another terminal:",
104
- f"pnpm dlx @axe-core/cli http://localhost:3000/{element_name}",
105
- f"",
106
- f"# Or use Playwright accessibility tests:",
107
- f"pnpm test:e2e --grep 'accessibility'"
108
- ])
109
-
110
- return "\n".join(commands)
111
-
112
-
113
- def main():
114
- # Read hook input from stdin
115
- try:
116
- input_data = json.load(sys.stdin)
117
- except json.JSONDecodeError:
118
- print(json.dumps({"continue": True}))
119
- sys.exit(0)
120
-
121
- tool_name = input_data.get("tool_name", "")
122
-
123
- # Only process Write/Edit operations
124
- if tool_name not in ["Write", "Edit"]:
125
- print(json.dumps({"continue": True}))
126
- sys.exit(0)
127
-
128
- # Check if state file exists
129
- if not STATE_FILE.exists():
130
- print(json.dumps({"continue": True}))
131
- sys.exit(0)
132
-
133
- # Load state
134
- try:
135
- state = json.loads(STATE_FILE.read_text())
136
- except json.JSONDecodeError:
137
- print(json.dumps({"continue": True}))
138
- sys.exit(0)
139
-
140
- workflow_type = get_workflow_type(state)
141
-
142
- # Only apply for UI workflows
143
- if not workflow_type.startswith("ui-create"):
144
- print(json.dumps({"continue": True}))
145
- sys.exit(0)
146
-
147
- # Get active element
148
- element_name, element_data = get_active_element(state)
149
- if not element_name or not element_data:
150
- print(json.dumps({"continue": True}))
151
- sys.exit(0)
152
-
153
- phases = element_data.get("phases", {}) if element_data else state.get("phases", {})
154
-
155
- # Check if we should trigger a11y audit (after TDD Green)
156
- if not is_verify_phase(phases):
157
- print(json.dumps({"continue": True}))
158
- sys.exit(0)
159
-
160
- # Get accessibility level
161
- a11y_level = get_accessibility_level(state, element_data)
162
-
163
- # Generate audit commands
164
- commands = generate_a11y_commands(element_name, workflow_type)
165
-
166
- # Build accessibility context
167
- checklist = "\n".join([f" - {item}" for item in WCAG_AA_CHECKLIST])
168
-
169
- context = f"""
170
- ## Accessibility Audit Required (WCAG 2.1 {a11y_level})
171
-
172
- The TDD Green phase is complete. Before marking verify as complete, run an accessibility audit.
173
-
174
- ### Quick Commands
175
- ```bash
176
- {commands}
177
- ```
178
-
179
- ### WCAG 2.1 {a11y_level} Checklist
180
- {checklist}
181
-
182
- ### 4-Step Verification for UI
183
- 1. **Responsive**: Test at 320px, 768px, 1024px, 1440px
184
- 2. **Data Binding**: Verify all data sources load correctly
185
- 3. **Tests**: All unit/e2e tests pass
186
- 4. **Accessibility**: Run axe-core, fix any violations
187
-
188
- If violations are found, fix them before completing the verify phase.
189
- """
190
-
191
- output = {
192
- "continue": True,
193
- "notify": f"Accessibility audit required for {element_name} (WCAG 2.1 {a11y_level})",
194
- "additionalContext": context
195
- }
196
-
197
- print(json.dumps(output))
198
- sys.exit(0)
199
-
200
-
201
- if __name__ == "__main__":
202
- main()
@@ -1,241 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Hook: PreToolUse for Write/Edit
4
- Purpose: Inject brand guide content and validate color compliance during UI implementation
5
-
6
- This hook runs before writing component/page files. When use_brand_guide=true
7
- in the state, it logs the brand guide summary to remind Claude to apply
8
- consistent branding and validates that only approved colors are used.
9
-
10
- Version: 3.10.0
11
-
12
- Returns:
13
- - {"continue": true} - Always continues (notifies on violations)
14
- - May include "notify" with brand guide summary or color violations
15
- """
16
- import json
17
- import sys
18
- import re
19
- from pathlib import Path
20
-
21
- # State file is in .claude/ directory (sibling to hooks/)
22
- STATE_FILE = Path(__file__).parent.parent / "api-dev-state.json"
23
- BRAND_GUIDE_FILE = Path(__file__).parent.parent / "BRAND_GUIDE.md"
24
-
25
-
26
- def extract_brand_colors(content):
27
- """Extract all brand colors from brand guide markdown.
28
-
29
- Returns a set of allowed colors (hex values, CSS variables, Tailwind classes).
30
- """
31
- allowed_colors = set()
32
-
33
- # Extract hex colors from brand guide
34
- hex_pattern = r'#[0-9A-Fa-f]{3,8}'
35
- for match in re.finditer(hex_pattern, content):
36
- allowed_colors.add(match.group(0).upper())
37
-
38
- # Extract CSS variable names
39
- css_var_pattern = r'var\(--([a-zA-Z0-9-]+)\)'
40
- for match in re.finditer(css_var_pattern, content):
41
- allowed_colors.add(f"--{match.group(1)}")
42
-
43
- # Extract Tailwind color classes mentioned in brand guide
44
- tailwind_pattern = r'(?:bg|text|border|ring)-([a-zA-Z]+-[0-9]+|[a-zA-Z]+)'
45
- for match in re.finditer(tailwind_pattern, content):
46
- allowed_colors.add(match.group(0))
47
-
48
- # Always allow these common values
49
- allowed_colors.update([
50
- 'transparent', 'inherit', 'currentColor', 'current',
51
- 'white', 'black', 'bg-white', 'bg-black', 'text-white', 'text-black',
52
- 'bg-transparent', 'border-transparent',
53
- # Common utility colors
54
- 'bg-background', 'text-foreground', 'border-border',
55
- 'bg-primary', 'text-primary', 'border-primary',
56
- 'bg-secondary', 'text-secondary', 'border-secondary',
57
- 'bg-accent', 'text-accent', 'border-accent',
58
- 'bg-muted', 'text-muted', 'border-muted',
59
- 'bg-destructive', 'text-destructive', 'border-destructive',
60
- ])
61
-
62
- return allowed_colors
63
-
64
-
65
- def extract_colors_from_code(code_content):
66
- """Extract colors used in component code.
67
-
68
- Returns a list of color usages found.
69
- """
70
- used_colors = []
71
-
72
- # Find hex colors
73
- hex_pattern = r'#[0-9A-Fa-f]{3,8}'
74
- for match in re.finditer(hex_pattern, code_content):
75
- used_colors.append(('hex', match.group(0).upper()))
76
-
77
- # Find Tailwind color classes (excluding allowed dynamic patterns)
78
- tailwind_pattern = r'(?:bg|text|border|ring|from|to|via)-([a-zA-Z]+-[0-9]+)'
79
- for match in re.finditer(tailwind_pattern, code_content):
80
- # Skip if it's a dynamic value like bg-[#xxx]
81
- full_match = match.group(0)
82
- if '[' not in full_match:
83
- used_colors.append(('tailwind', full_match))
84
-
85
- # Find inline style colors
86
- style_pattern = r'(?:color|backgroundColor|borderColor):\s*["\']([^"\']+)["\']'
87
- for match in re.finditer(style_pattern, code_content):
88
- value = match.group(1)
89
- if value.startswith('#'):
90
- used_colors.append(('style', value.upper()))
91
-
92
- return used_colors
93
-
94
-
95
- def validate_color_compliance(code_content, allowed_colors):
96
- """Check if code uses only brand-approved colors.
97
-
98
- Returns list of violations found.
99
- """
100
- violations = []
101
- used_colors = extract_colors_from_code(code_content)
102
-
103
- for color_type, color_value in used_colors:
104
- # Check if color is allowed
105
- is_allowed = False
106
-
107
- if color_type == 'hex':
108
- is_allowed = color_value in allowed_colors
109
- elif color_type == 'tailwind':
110
- is_allowed = color_value in allowed_colors or color_value.split('-')[0] in ['bg', 'text', 'border']
111
- elif color_type == 'style':
112
- is_allowed = color_value in allowed_colors
113
-
114
- if not is_allowed:
115
- # Check against all allowed colors more loosely
116
- if color_value not in allowed_colors:
117
- violations.append(f"{color_type}: {color_value}")
118
-
119
- return violations
120
-
121
-
122
- def extract_brand_summary(content):
123
- """Extract key brand values from brand guide markdown."""
124
- summary = []
125
-
126
- lines = content.split("\n")
127
- current_section = ""
128
-
129
- for line in lines:
130
- line = line.strip()
131
-
132
- # Track section
133
- if line.startswith("## "):
134
- current_section = line[3:].lower()
135
- continue
136
-
137
- # Extract key values
138
- if line.startswith("- **") and ":" in line:
139
- # Parse "- **Key:** Value" format
140
- try:
141
- key_part = line.split(":**")[0].replace("- **", "")
142
- value_part = line.split(":**")[1].strip()
143
-
144
- # Only include primary brand values
145
- if current_section == "colors" and key_part in ["Primary", "Accent", "Background"]:
146
- summary.append(f"{key_part}: {value_part}")
147
- elif current_section == "typography" and key_part in ["Headings", "Body"]:
148
- summary.append(f"{key_part}: {value_part}")
149
- elif current_section == "component styling" and key_part in ["Border Radius", "Focus Ring"]:
150
- summary.append(f"{key_part}: {value_part}")
151
- except IndexError:
152
- continue
153
-
154
- return summary
155
-
156
-
157
- def main():
158
- # Read hook input from stdin
159
- try:
160
- input_data = json.load(sys.stdin)
161
- except json.JSONDecodeError:
162
- print(json.dumps({"continue": True}))
163
- sys.exit(0)
164
-
165
- tool_name = input_data.get("tool_name", "")
166
- tool_input = input_data.get("tool_input", {})
167
-
168
- # Only check Write/Edit operations
169
- if tool_name not in ["Write", "Edit"]:
170
- print(json.dumps({"continue": True}))
171
- sys.exit(0)
172
-
173
- # Check if targeting component or page files
174
- file_path = tool_input.get("file_path", "")
175
- is_component = "/components/" in file_path and file_path.endswith(".tsx")
176
- is_page = "/app/" in file_path and "page.tsx" in file_path
177
-
178
- if not is_component and not is_page:
179
- print(json.dumps({"continue": True}))
180
- sys.exit(0)
181
-
182
- # Check if state file exists
183
- if not STATE_FILE.exists():
184
- print(json.dumps({"continue": True}))
185
- sys.exit(0)
186
-
187
- # Load state
188
- try:
189
- state = json.loads(STATE_FILE.read_text())
190
- except json.JSONDecodeError:
191
- print(json.dumps({"continue": True}))
192
- sys.exit(0)
193
-
194
- workflow = state.get("workflow", "")
195
-
196
- # Only apply for UI workflows
197
- if workflow not in ["ui-create-component", "ui-create-page"]:
198
- print(json.dumps({"continue": True}))
199
- sys.exit(0)
200
-
201
- # Check if brand guide is enabled
202
- ui_config = state.get("ui_config", {})
203
- use_brand_guide = ui_config.get("use_brand_guide", False)
204
-
205
- if not use_brand_guide:
206
- print(json.dumps({"continue": True}))
207
- sys.exit(0)
208
-
209
- # Check if brand guide file exists
210
- if not BRAND_GUIDE_FILE.exists():
211
- print(json.dumps({"continue": True}))
212
- sys.exit(0)
213
-
214
- # Extract brand summary
215
- brand_content = BRAND_GUIDE_FILE.read_text()
216
- summary = extract_brand_summary(brand_content)
217
-
218
- # For Edit operations, check color compliance
219
- tool_input = input_data.get("tool_input", {})
220
- if tool_name == "Edit":
221
- new_content = tool_input.get("new_string", "")
222
- if new_content:
223
- allowed_colors = extract_brand_colors(brand_content)
224
- violations = validate_color_compliance(new_content, allowed_colors)
225
-
226
- if violations:
227
- notify_msg = f"⚠️ Brand color check: {len(violations)} potential non-brand colors: " + ", ".join(violations[:3])
228
- print(json.dumps({"continue": True, "notify": notify_msg}))
229
- sys.exit(0)
230
-
231
- if summary:
232
- notify_msg = "Applying brand guide: " + " | ".join(summary[:5])
233
- print(json.dumps({"continue": True, "notify": notify_msg}))
234
- else:
235
- print(json.dumps({"continue": True}))
236
-
237
- sys.exit(0)
238
-
239
-
240
- if __name__ == "__main__":
241
- main()
@@ -1,97 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Hook: PreToolUse for Write/Edit
4
- Purpose: Ensure user has confirmed component type before proceeding
5
-
6
- This hook checks that:
7
- 1. AI has suggested a component type (basic/complex)
8
- 2. User has explicitly confirmed the type
9
- 3. The confirmation is recorded in state
10
-
11
- If not confirmed, blocks the write and reminds to get user confirmation.
12
-
13
- Version: 3.10.0
14
- """
15
- import json
16
- import sys
17
- from pathlib import Path
18
-
19
-
20
- def main():
21
- # Read hook input from stdin
22
- try:
23
- input_data = json.load(sys.stdin)
24
- except json.JSONDecodeError:
25
- print(json.dumps({"continue": True}))
26
- sys.exit(0)
27
-
28
- tool_name = input_data.get("tool_name", "")
29
- tool_input = input_data.get("tool_input", {})
30
-
31
- # Only check Write/Edit operations
32
- if tool_name not in ["Write", "Edit"]:
33
- print(json.dumps({"continue": True}))
34
- sys.exit(0)
35
-
36
- # Check if writing to a component file
37
- file_path = tool_input.get("file_path", "")
38
- if "/components/" not in file_path:
39
- print(json.dumps({"continue": True}))
40
- sys.exit(0)
41
-
42
- # Check for state file
43
- cwd = Path.cwd()
44
- state_file = cwd / ".claude" / "api-dev-state.json"
45
-
46
- if not state_file.exists():
47
- print(json.dumps({"continue": True}))
48
- sys.exit(0)
49
-
50
- try:
51
- state = json.loads(state_file.read_text())
52
- except (json.JSONDecodeError, IOError):
53
- print(json.dumps({"continue": True}))
54
- sys.exit(0)
55
-
56
- # Check if this is a UI workflow
57
- workflow = state.get("workflow", "")
58
- if "ui-create" not in workflow:
59
- print(json.dumps({"continue": True}))
60
- sys.exit(0)
61
-
62
- # Check for component type confirmation
63
- ui_config = state.get("ui_config", {})
64
- user_confirmed = ui_config.get("user_confirmed", False)
65
- component_type = ui_config.get("component_type", "")
66
- ai_suggested = ui_config.get("ai_suggested", "")
67
-
68
- if not user_confirmed:
69
- print(json.dumps({
70
- "continue": False,
71
- "reason": (
72
- "⚠️ Component type not confirmed by user.\n\n"
73
- "Before writing component files, you must:\n\n"
74
- "1. Analyze the component and suggest a type (Basic or Complex)\n"
75
- "2. Present your suggestion to the user\n"
76
- "3. Get explicit confirmation\n"
77
- "4. Update state with user_confirmed: true\n\n"
78
- "Example state update:\n"
79
- "{\n"
80
- ' "ui_config": {\n'
81
- f' "component_type": "{component_type or "basic"}",\n'
82
- f' "ai_suggested": "{ai_suggested or "basic"}",\n'
83
- ' "user_confirmed": true\n'
84
- " }\n"
85
- "}\n\n"
86
- "Use AskUserQuestion to get confirmation."
87
- )
88
- }))
89
- sys.exit(0)
90
-
91
- # All checks passed
92
- print(json.dumps({"continue": True}))
93
- sys.exit(0)
94
-
95
-
96
- if __name__ == "__main__":
97
- main()