@hustle-together/api-dev-tools 3.6.5 → 3.10.0

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 (72) hide show
  1. package/README.md +5599 -258
  2. package/bin/cli.js +395 -20
  3. package/commands/README.md +459 -71
  4. package/commands/hustle-api-continue.md +158 -0
  5. package/commands/{api-create.md → hustle-api-create.md} +35 -15
  6. package/commands/{api-env.md → hustle-api-env.md} +4 -4
  7. package/commands/{api-interview.md → hustle-api-interview.md} +1 -1
  8. package/commands/{api-research.md → hustle-api-research.md} +3 -3
  9. package/commands/hustle-api-sessions.md +149 -0
  10. package/commands/{api-status.md → hustle-api-status.md} +16 -16
  11. package/commands/{api-verify.md → hustle-api-verify.md} +2 -2
  12. package/commands/hustle-combine.md +763 -0
  13. package/commands/hustle-ui-create-page.md +933 -0
  14. package/commands/hustle-ui-create.md +825 -0
  15. package/hooks/api-workflow-check.py +545 -21
  16. package/hooks/cache-research.py +337 -0
  17. package/hooks/check-api-routes.py +168 -0
  18. package/hooks/check-playwright-setup.py +103 -0
  19. package/hooks/check-storybook-setup.py +81 -0
  20. package/hooks/detect-interruption.py +165 -0
  21. package/hooks/enforce-a11y-audit.py +202 -0
  22. package/hooks/enforce-brand-guide.py +241 -0
  23. package/hooks/enforce-documentation.py +60 -8
  24. package/hooks/enforce-freshness.py +184 -0
  25. package/hooks/enforce-page-components.py +186 -0
  26. package/hooks/enforce-page-data-schema.py +155 -0
  27. package/hooks/enforce-questions-sourced.py +146 -0
  28. package/hooks/enforce-schema-from-interview.py +248 -0
  29. package/hooks/enforce-ui-disambiguation.py +108 -0
  30. package/hooks/enforce-ui-interview.py +130 -0
  31. package/hooks/generate-manifest-entry.py +1161 -0
  32. package/hooks/session-logger.py +297 -0
  33. package/hooks/session-startup.py +160 -15
  34. package/hooks/track-scope-coverage.py +220 -0
  35. package/hooks/track-tool-use.py +81 -1
  36. package/hooks/update-api-showcase.py +149 -0
  37. package/hooks/update-registry.py +352 -0
  38. package/hooks/update-ui-showcase.py +212 -0
  39. package/package.json +8 -3
  40. package/templates/BRAND_GUIDE.md +299 -0
  41. package/templates/CLAUDE-SECTION.md +56 -24
  42. package/templates/SPEC.json +640 -0
  43. package/templates/api-dev-state.json +217 -161
  44. package/templates/api-showcase/_components/APICard.tsx +153 -0
  45. package/templates/api-showcase/_components/APIModal.tsx +375 -0
  46. package/templates/api-showcase/_components/APIShowcase.tsx +231 -0
  47. package/templates/api-showcase/_components/APITester.tsx +522 -0
  48. package/templates/api-showcase/page.tsx +41 -0
  49. package/templates/component/Component.stories.tsx +172 -0
  50. package/templates/component/Component.test.tsx +237 -0
  51. package/templates/component/Component.tsx +86 -0
  52. package/templates/component/Component.types.ts +55 -0
  53. package/templates/component/index.ts +15 -0
  54. package/templates/dev-tools/_components/DevToolsLanding.tsx +320 -0
  55. package/templates/dev-tools/page.tsx +10 -0
  56. package/templates/page/page.e2e.test.ts +218 -0
  57. package/templates/page/page.tsx +42 -0
  58. package/templates/performance-budgets.json +58 -0
  59. package/templates/registry.json +13 -0
  60. package/templates/settings.json +90 -0
  61. package/templates/shared/HeroHeader.tsx +261 -0
  62. package/templates/shared/index.ts +1 -0
  63. package/templates/ui-showcase/_components/PreviewCard.tsx +315 -0
  64. package/templates/ui-showcase/_components/PreviewModal.tsx +676 -0
  65. package/templates/ui-showcase/_components/UIShowcase.tsx +262 -0
  66. package/templates/ui-showcase/page.tsx +26 -0
  67. package/demo/hustle-together/blog/gemini-vs-claude-widgets.html +0 -959
  68. package/demo/hustle-together/blog/interview-driven-api-development.html +0 -1146
  69. package/demo/hustle-together/blog/tdd-for-ai.html +0 -982
  70. package/demo/hustle-together/index.html +0 -1312
  71. package/demo/workflow-demo-v3.5-backup.html +0 -5008
  72. package/demo/workflow-demo.html +0 -6202
@@ -22,6 +22,10 @@
22
22
  {
23
23
  "type": "command",
24
24
  "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-startup.py"
25
+ },
26
+ {
27
+ "type": "command",
28
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/detect-interruption.py"
25
29
  }
26
30
  ]
27
31
  }
@@ -44,6 +48,10 @@
44
48
  "type": "command",
45
49
  "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-disambiguation.py"
46
50
  },
51
+ {
52
+ "type": "command",
53
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-ui-disambiguation.py"
54
+ },
47
55
  {
48
56
  "type": "command",
49
57
  "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-scope.py"
@@ -56,6 +64,10 @@
56
64
  "type": "command",
57
65
  "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-interview.py"
58
66
  },
67
+ {
68
+ "type": "command",
69
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-ui-interview.py"
70
+ },
59
71
  {
60
72
  "type": "command",
61
73
  "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-deep-research.py"
@@ -87,6 +99,47 @@
87
99
  {
88
100
  "type": "command",
89
101
  "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-documentation.py"
102
+ },
103
+ {
104
+ "type": "command",
105
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-schema-from-interview.py"
106
+ },
107
+ {
108
+ "type": "command",
109
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-freshness.py"
110
+ },
111
+ {
112
+ "type": "command",
113
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-brand-guide.py"
114
+ },
115
+ {
116
+ "type": "command",
117
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/check-storybook-setup.py"
118
+ },
119
+ {
120
+ "type": "command",
121
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/check-playwright-setup.py"
122
+ },
123
+ {
124
+ "type": "command",
125
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/check-api-routes.py"
126
+ },
127
+ {
128
+ "type": "command",
129
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-page-components.py"
130
+ },
131
+ {
132
+ "type": "command",
133
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-page-data-schema.py"
134
+ }
135
+ ]
136
+ },
137
+ {
138
+ "matcher": "AskUserQuestion",
139
+ "hooks": [
140
+ {
141
+ "type": "command",
142
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-questions-sourced.py"
90
143
  }
91
144
  ]
92
145
  }
@@ -102,6 +155,10 @@
102
155
  {
103
156
  "type": "command",
104
157
  "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/periodic-reground.py"
158
+ },
159
+ {
160
+ "type": "command",
161
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/track-scope-coverage.py"
105
162
  }
106
163
  ]
107
164
  },
@@ -113,6 +170,35 @@
113
170
  "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/verify-after-green.py"
114
171
  }
115
172
  ]
173
+ },
174
+ {
175
+ "matcher": "Write|Edit",
176
+ "hooks": [
177
+ {
178
+ "type": "command",
179
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/cache-research.py"
180
+ },
181
+ {
182
+ "type": "command",
183
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/generate-manifest-entry.py"
184
+ },
185
+ {
186
+ "type": "command",
187
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/update-registry.py"
188
+ },
189
+ {
190
+ "type": "command",
191
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/update-api-showcase.py"
192
+ },
193
+ {
194
+ "type": "command",
195
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/update-ui-showcase.py"
196
+ },
197
+ {
198
+ "type": "command",
199
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-a11y-audit.py"
200
+ }
201
+ ]
116
202
  }
117
203
  ],
118
204
  "Stop": [
@@ -121,6 +207,10 @@
121
207
  {
122
208
  "type": "command",
123
209
  "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/api-workflow-check.py"
210
+ },
211
+ {
212
+ "type": "command",
213
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-logger.py"
124
214
  }
125
215
  ]
126
216
  }
@@ -0,0 +1,261 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useRef, useState } from 'react';
4
+
5
+ interface HeroHeaderProps {
6
+ title: string;
7
+ description: React.ReactNode;
8
+ badge?: string;
9
+ }
10
+
11
+ /**
12
+ * HeroHeader Component
13
+ *
14
+ * Animated 3D perspective grid hero section with Hustle Together branding.
15
+ * Features:
16
+ * - Canvas-based 3D grid animation
17
+ * - Hustle red (#BA0C2F) accent highlights
18
+ * - Dark/light mode support
19
+ * - Left-aligned responsive layout
20
+ *
21
+ * Created with Hustle Dev Tools (v3.9.2)
22
+ */
23
+ export function HeroHeader({ title, description, badge }: HeroHeaderProps) {
24
+ const canvasRef = useRef<HTMLCanvasElement>(null);
25
+ const headerRef = useRef<HTMLDivElement>(null);
26
+ const [isDark, setIsDark] = useState(false);
27
+
28
+ // Detect dark mode
29
+ useEffect(() => {
30
+ const checkDarkMode = () => {
31
+ setIsDark(
32
+ document.documentElement.classList.contains('dark') ||
33
+ document.documentElement.getAttribute('data-theme') === 'dark'
34
+ );
35
+ };
36
+
37
+ checkDarkMode();
38
+
39
+ // Watch for theme changes
40
+ const observer = new MutationObserver(checkDarkMode);
41
+ observer.observe(document.documentElement, {
42
+ attributes: true,
43
+ attributeFilter: ['class', 'data-theme'],
44
+ });
45
+
46
+ return () => observer.disconnect();
47
+ }, []);
48
+
49
+ // Grid animation
50
+ useEffect(() => {
51
+ const canvas = canvasRef.current;
52
+ const header = headerRef.current;
53
+ if (!canvas || !header) return;
54
+
55
+ const ctx = canvas.getContext('2d');
56
+ if (!ctx) return;
57
+
58
+ let animationId: number;
59
+ let offset = 0;
60
+
61
+ // Grid configuration
62
+ const speed = 0.2;
63
+ const tileSize = 60;
64
+ const gridWidth = 60;
65
+ const gridDepth = 30;
66
+ const horizonY = -150;
67
+ const fov = 350;
68
+ const camHeight = 200;
69
+ const zNear = 20;
70
+
71
+ interface GridCell {
72
+ active: boolean;
73
+ alpha: number;
74
+ color: string;
75
+ }
76
+
77
+ let gridRows: GridCell[][] = [];
78
+
79
+ const createRow = (): GridCell[] => {
80
+ const cells: GridCell[] = [];
81
+ for (let c = 0; c < gridWidth; c++) {
82
+ cells.push({ active: false, alpha: 0, color: 'rgba(186, 12, 47' });
83
+ }
84
+ return cells;
85
+ };
86
+
87
+ // Initialize rows
88
+ for (let r = 0; r < gridDepth + 5; r++) {
89
+ gridRows.push(createRow());
90
+ }
91
+
92
+ const resize = () => {
93
+ canvas.width = header.clientWidth;
94
+ canvas.height = header.clientHeight;
95
+ };
96
+
97
+ const project = (
98
+ x: number,
99
+ z: number
100
+ ): { x: number; y: number; scale: number } | null => {
101
+ if (z <= 0) return null;
102
+ const scale = fov / z;
103
+ const px = x * scale + canvas.width / 2;
104
+ const py = camHeight * scale + canvas.height / 2 + horizonY;
105
+ return { x: px, y: py, scale };
106
+ };
107
+
108
+ const updateGridState = () => {
109
+ const secondaryColor = isDark ? 'rgba(60, 60, 60' : 'rgba(30, 30, 30';
110
+
111
+ if (Math.random() > 0.92) {
112
+ const r = Math.floor(Math.random() * (gridRows.length - 5)) + 2;
113
+ const c = Math.floor(Math.random() * gridWidth);
114
+ const cell = gridRows[r][c];
115
+ if (!cell.active) {
116
+ cell.active = true;
117
+ cell.alpha = 1.0;
118
+ cell.color = Math.random() > 0.7 ? secondaryColor : 'rgba(186, 12, 47';
119
+ }
120
+ }
121
+
122
+ gridRows.forEach((row) =>
123
+ row.forEach((cell) => {
124
+ if (cell.active) {
125
+ cell.alpha -= 0.005;
126
+ if (cell.alpha <= 0) {
127
+ cell.active = false;
128
+ cell.alpha = 0;
129
+ }
130
+ }
131
+ })
132
+ );
133
+ };
134
+
135
+ const draw = () => {
136
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
137
+ offset += speed;
138
+
139
+ if (offset >= tileSize) {
140
+ offset -= tileSize;
141
+ gridRows.shift();
142
+ gridRows.push(createRow());
143
+ }
144
+
145
+ updateGridState();
146
+ ctx.lineWidth = 1;
147
+
148
+ const lineColor = isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)';
149
+
150
+ // Draw rows (back to front)
151
+ for (let r = gridRows.length - 1; r >= 0; r--) {
152
+ const zNearLine = r * tileSize - offset + zNear;
153
+ const zFarLine = (r + 1) * tileSize - offset + zNear;
154
+ if (zNearLine <= 0) continue;
155
+
156
+ for (let c = 0; c < gridWidth; c++) {
157
+ const cell = gridRows[r][c];
158
+ if (cell.active && cell.alpha > 0.05) {
159
+ const cOffset = c - gridWidth / 2;
160
+ const xL = cOffset * tileSize;
161
+ const xR = (cOffset + 1) * tileSize;
162
+
163
+ const p1 = project(xL, zFarLine);
164
+ const p2 = project(xR, zFarLine);
165
+ const p3 = project(xR, zNearLine);
166
+ const p4 = project(xL, zNearLine);
167
+
168
+ if (p1 && p2 && p3 && p4) {
169
+ ctx.fillStyle = `${cell.color}, ${cell.alpha})`;
170
+ ctx.beginPath();
171
+ ctx.moveTo(p1.x, p1.y);
172
+ ctx.lineTo(p2.x, p2.y);
173
+ ctx.lineTo(p3.x, p3.y);
174
+ ctx.lineTo(p4.x, p4.y);
175
+ ctx.fill();
176
+ }
177
+ }
178
+ }
179
+
180
+ // Horizontal grid lines
181
+ const xL = (-gridWidth / 2) * tileSize;
182
+ const xR = (gridWidth / 2) * tileSize;
183
+ const pL = project(xL, zFarLine);
184
+ const pR = project(xR, zFarLine);
185
+ if (pL && pR) {
186
+ ctx.strokeStyle = lineColor;
187
+ ctx.beginPath();
188
+ ctx.moveTo(pL.x, pL.y);
189
+ ctx.lineTo(pR.x, pR.y);
190
+ ctx.stroke();
191
+ }
192
+ }
193
+
194
+ // Vertical grid lines
195
+ const zMax = gridRows.length * tileSize + zNear;
196
+ for (let c = 0; c <= gridWidth; c++) {
197
+ const cOffset = c - gridWidth / 2;
198
+ const x = cOffset * tileSize;
199
+ const pStart = project(x, zNear);
200
+ const pEnd = project(x, zMax);
201
+ if (pStart && pEnd) {
202
+ ctx.strokeStyle = lineColor;
203
+ ctx.beginPath();
204
+ ctx.moveTo(pStart.x, pStart.y);
205
+ ctx.lineTo(pEnd.x, pEnd.y);
206
+ ctx.stroke();
207
+ }
208
+ }
209
+
210
+ // Fog / fade gradient
211
+ const fadeColor = isDark ? '0,0,0' : '255,255,255';
212
+ const g = ctx.createLinearGradient(0, 0, 0, canvas.height / 1.5);
213
+ g.addColorStop(0, `rgba(${fadeColor}, 1)`);
214
+ g.addColorStop(0.3, `rgba(${fadeColor}, 0.8)`);
215
+ g.addColorStop(1, `rgba(${fadeColor}, 0)`);
216
+ ctx.fillStyle = g;
217
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
218
+
219
+ animationId = requestAnimationFrame(draw);
220
+ };
221
+
222
+ resize();
223
+ window.addEventListener('resize', resize);
224
+ draw();
225
+
226
+ return () => {
227
+ window.removeEventListener('resize', resize);
228
+ cancelAnimationFrame(animationId);
229
+ };
230
+ }, [isDark]);
231
+
232
+ return (
233
+ <header
234
+ ref={headerRef}
235
+ className="relative flex h-[300px] w-full flex-col items-center justify-center overflow-hidden border-b-2 border-black text-left dark:border-gray-600"
236
+ >
237
+ {/* Animated Grid Canvas */}
238
+ <canvas
239
+ ref={canvasRef}
240
+ className="pointer-events-none absolute inset-0 z-0 opacity-80 dark:opacity-60 dark:blur-[0.5px]"
241
+ />
242
+
243
+ {/* Content - Uses same container as main content for alignment */}
244
+ <div className="container relative z-10 mx-auto px-4">
245
+ {badge && (
246
+ <span className="mb-4 inline-block border-2 border-[#BA0C2F] bg-[#BA0C2F]/10 px-3 py-1 text-sm font-bold text-[#BA0C2F]">
247
+ {badge}
248
+ </span>
249
+ )}
250
+ <h1 className="mb-5 text-4xl font-extrabold leading-tight tracking-tight text-gray-900 dark:text-gray-100 md:text-5xl lg:text-6xl">
251
+ {title}
252
+ </h1>
253
+ <p className="max-w-2xl text-lg leading-relaxed text-gray-600 dark:text-gray-400">
254
+ {description}
255
+ </p>
256
+ </div>
257
+ </header>
258
+ );
259
+ }
260
+
261
+ export default HeroHeader;
@@ -0,0 +1 @@
1
+ export { HeroHeader } from './HeroHeader';