@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
@@ -1,1146 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Why We Built Interview-Driven API Development - Hustle Together Blog</title>
7
- <link rel="preconnect" href="https://fonts.googleapis.com">
8
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;700&family=Fredoka:wght@600;700&display=swap" rel="stylesheet">
10
- <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.14.1/gsap.min.js"></script>
11
- <style>
12
- * {
13
- margin: 0;
14
- padding: 0;
15
- box-sizing: border-box;
16
- }
17
-
18
- :root {
19
- --bg: #0a0a0a;
20
- --bg-secondary: #121212;
21
- --bg-tertiary: #1a1a1a;
22
- --bg-card: #1a1a1a;
23
- --text: #e8e8e8;
24
- --text-muted: #999;
25
- --text-dim: #555;
26
- --accent: #fff;
27
- --accent-red: #BA0C2F;
28
- --accent-red-glow: rgba(186, 12, 47, 0.4);
29
- --border: #333;
30
- --border-strong: #444;
31
- --glow: rgba(255, 255, 255, 0.3);
32
- --success: #4ade80;
33
- --warning: #fbbf24;
34
- }
35
-
36
- [data-theme="light"] {
37
- --bg: #f0f0f0;
38
- --bg-secondary: #fff;
39
- --bg-tertiary: #f5f5f5;
40
- --bg-card: #fafafa;
41
- --text: #1a1a1a;
42
- --text-muted: #666;
43
- --text-dim: #aaa;
44
- --accent: #000;
45
- --accent-red: #BA0C2F;
46
- --accent-red-glow: rgba(186, 12, 47, 0.3);
47
- --border: #ddd;
48
- --border-strong: #ccc;
49
- --glow: rgba(0, 0, 0, 0.15);
50
- }
51
-
52
- body {
53
- background: var(--bg);
54
- color: var(--text);
55
- font-family: 'JetBrains Mono', 'Courier New', monospace;
56
- line-height: 1.8;
57
- overflow-x: hidden;
58
- }
59
-
60
- /* Navigation - boxy style */
61
- nav {
62
- position: fixed;
63
- top: 0;
64
- left: 0;
65
- right: 0;
66
- z-index: 1000;
67
- padding: 15px 40px;
68
- display: flex;
69
- justify-content: space-between;
70
- align-items: center;
71
- background: var(--bg);
72
- border-bottom: 2px solid var(--border);
73
- }
74
-
75
- .logo {
76
- font-size: 1.1rem;
77
- font-family: 'Fredoka', sans-serif;
78
- font-weight: 700;
79
- letter-spacing: 2px;
80
- text-decoration: none;
81
- color: var(--text);
82
- display: flex;
83
- align-items: center;
84
- gap: 10px;
85
- }
86
-
87
- .logo-icon {
88
- width: 30px;
89
- height: 30px;
90
- border: 2px solid var(--accent-red);
91
- background: var(--accent-red);
92
- color: #fff;
93
- display: flex;
94
- align-items: center;
95
- justify-content: center;
96
- font-size: 0.75rem;
97
- font-weight: bold;
98
- }
99
-
100
- .nav-links {
101
- display: flex;
102
- gap: 25px;
103
- align-items: center;
104
- }
105
-
106
- .nav-links a {
107
- color: var(--text-muted);
108
- text-decoration: none;
109
- font-size: 0.75rem;
110
- letter-spacing: 1px;
111
- text-transform: uppercase;
112
- padding: 8px 12px;
113
- border: 2px solid transparent;
114
- transition: all 0.2s;
115
- }
116
-
117
- .nav-links a:hover {
118
- color: var(--accent-red);
119
- border-color: var(--accent-red);
120
- }
121
-
122
- .theme-toggle {
123
- background: var(--bg-card);
124
- border: 2px solid var(--border);
125
- color: var(--text);
126
- padding: 8px 14px;
127
- cursor: pointer;
128
- font-family: inherit;
129
- font-size: 0.7rem;
130
- text-transform: uppercase;
131
- letter-spacing: 1px;
132
- transition: all 0.2s;
133
- }
134
-
135
- .theme-toggle:hover {
136
- background: var(--accent-red);
137
- color: #fff;
138
- border-color: var(--accent-red);
139
- }
140
-
141
- /* Main Content */
142
- main {
143
- max-width: 900px;
144
- margin: 0 auto;
145
- padding: 120px 40px 80px;
146
- }
147
-
148
- /* Article Header */
149
- .article-header {
150
- margin-bottom: 50px;
151
- opacity: 0;
152
- }
153
-
154
- .article-meta {
155
- display: flex;
156
- gap: 20px;
157
- margin-bottom: 20px;
158
- flex-wrap: wrap;
159
- }
160
-
161
- .article-tag {
162
- font-size: 0.7rem;
163
- letter-spacing: 2px;
164
- color: var(--accent-red);
165
- border: 1px dashed var(--accent-red);
166
- padding: 5px 12px;
167
- text-transform: uppercase;
168
- }
169
-
170
- .article-date {
171
- font-size: 0.8rem;
172
- color: var(--text-dim);
173
- }
174
-
175
- .article-title {
176
- font-size: 2.8rem;
177
- font-family: 'Fredoka', sans-serif;
178
- font-weight: 700;
179
- line-height: 1.2;
180
- margin-bottom: 25px;
181
- }
182
-
183
- .article-excerpt {
184
- font-size: 1.1rem;
185
- color: var(--text-muted);
186
- line-height: 1.8;
187
- border-left: 3px solid var(--border);
188
- padding-left: 20px;
189
- }
190
-
191
- /* Audio Player */
192
- .audio-player {
193
- background: var(--bg-secondary);
194
- border: 1px dashed var(--border);
195
- padding: 25px;
196
- margin: 40px 0;
197
- opacity: 0;
198
- }
199
-
200
- .audio-header {
201
- display: flex;
202
- align-items: center;
203
- gap: 15px;
204
- margin-bottom: 20px;
205
- }
206
-
207
- .audio-icon {
208
- width: 50px;
209
- height: 50px;
210
- border: 2px solid var(--text);
211
- display: flex;
212
- align-items: center;
213
- justify-content: center;
214
- font-size: 1.2rem;
215
- }
216
-
217
- .audio-info h4 {
218
- font-size: 0.9rem;
219
- font-weight: 600;
220
- margin-bottom: 5px;
221
- }
222
-
223
- .audio-info p {
224
- font-size: 0.75rem;
225
- color: var(--text-muted);
226
- }
227
-
228
- .audio-controls {
229
- display: flex;
230
- align-items: center;
231
- gap: 20px;
232
- }
233
-
234
- .play-btn {
235
- width: 50px;
236
- height: 50px;
237
- border: 2px solid var(--text);
238
- background: transparent;
239
- color: var(--text);
240
- cursor: pointer;
241
- font-size: 1.2rem;
242
- display: flex;
243
- align-items: center;
244
- justify-content: center;
245
- transition: all 0.3s;
246
- }
247
-
248
- .play-btn:hover {
249
- background: var(--accent-red);
250
- color: #fff;
251
- border-color: var(--accent-red);
252
- }
253
-
254
- .progress-bar {
255
- flex: 1;
256
- height: 4px;
257
- background: var(--border);
258
- position: relative;
259
- cursor: pointer;
260
- }
261
-
262
- .progress-fill {
263
- height: 100%;
264
- background: var(--accent-red);
265
- width: 0%;
266
- transition: width 0.1s;
267
- }
268
-
269
- .audio-time {
270
- font-size: 0.8rem;
271
- color: var(--text-muted);
272
- min-width: 90px;
273
- text-align: right;
274
- }
275
-
276
- /* Featured Video/Image */
277
- .featured-media {
278
- margin: 40px 0;
279
- border: 1px dashed var(--border);
280
- opacity: 0;
281
- }
282
-
283
- .media-container {
284
- aspect-ratio: 16/9;
285
- background: var(--bg-tertiary);
286
- display: flex;
287
- align-items: center;
288
- justify-content: center;
289
- position: relative;
290
- overflow: hidden;
291
- }
292
-
293
- .media-placeholder {
294
- text-align: center;
295
- color: var(--text-dim);
296
- }
297
-
298
- .media-placeholder .icon {
299
- font-size: 4rem;
300
- margin-bottom: 15px;
301
- display: block;
302
- }
303
-
304
- .media-caption {
305
- padding: 15px;
306
- font-size: 0.8rem;
307
- color: var(--text-muted);
308
- border-top: 1px dashed var(--border);
309
- }
310
-
311
- /* Article Content */
312
- .article-content {
313
- opacity: 0;
314
- }
315
-
316
- .article-content h2 {
317
- font-size: 1.6rem;
318
- font-family: 'Fredoka', sans-serif;
319
- margin: 50px 0 20px;
320
- }
321
-
322
- .article-content h3 {
323
- font-size: 1.2rem;
324
- margin: 35px 0 15px;
325
- color: var(--text);
326
- }
327
-
328
- .article-content p {
329
- margin-bottom: 20px;
330
- font-size: 1rem;
331
- }
332
-
333
- .article-content ul, .article-content ol {
334
- margin: 20px 0 20px 30px;
335
- }
336
-
337
- .article-content li {
338
- margin-bottom: 10px;
339
- }
340
-
341
- .article-content code {
342
- background: var(--bg-secondary);
343
- padding: 2px 8px;
344
- border: 1px dashed var(--border);
345
- font-size: 0.9em;
346
- }
347
-
348
- .article-content pre {
349
- background: var(--bg-secondary);
350
- border: 1px dashed var(--border);
351
- padding: 20px;
352
- margin: 25px 0;
353
- overflow-x: auto;
354
- font-size: 0.85rem;
355
- line-height: 1.6;
356
- }
357
-
358
- .article-content blockquote {
359
- border-left: 3px solid var(--text);
360
- padding-left: 20px;
361
- margin: 30px 0;
362
- font-style: italic;
363
- color: var(--text-muted);
364
- }
365
-
366
- /* Callout Box */
367
- .callout {
368
- background: var(--bg-secondary);
369
- border: 1px dashed var(--border);
370
- border-left: 3px solid var(--text);
371
- padding: 25px;
372
- margin: 30px 0;
373
- }
374
-
375
- .callout-title {
376
- font-size: 0.8rem;
377
- letter-spacing: 2px;
378
- text-transform: uppercase;
379
- margin-bottom: 10px;
380
- display: flex;
381
- align-items: center;
382
- gap: 10px;
383
- }
384
-
385
- .callout-title::before {
386
- content: '!';
387
- width: 20px;
388
- height: 20px;
389
- border: 1px dashed var(--text);
390
- display: flex;
391
- align-items: center;
392
- justify-content: center;
393
- font-size: 0.75rem;
394
- }
395
-
396
- /* AI Chat Widget */
397
- .ai-chat {
398
- background: var(--bg-secondary);
399
- border: 1px dashed var(--border);
400
- margin: 50px 0;
401
- opacity: 0;
402
- }
403
-
404
- .ai-chat-header {
405
- padding: 20px;
406
- border-bottom: 1px dashed var(--border);
407
- display: flex;
408
- align-items: center;
409
- gap: 15px;
410
- }
411
-
412
- .ai-chat-icon {
413
- width: 40px;
414
- height: 40px;
415
- border: 2px solid var(--text);
416
- display: flex;
417
- align-items: center;
418
- justify-content: center;
419
- font-size: 1rem;
420
- }
421
-
422
- .ai-chat-title h4 {
423
- font-size: 0.9rem;
424
- font-weight: 600;
425
- }
426
-
427
- .ai-chat-title p {
428
- font-size: 0.75rem;
429
- color: var(--text-muted);
430
- }
431
-
432
- .ai-chat-body {
433
- padding: 20px;
434
- max-height: 400px;
435
- overflow-y: auto;
436
- }
437
-
438
- .chat-message {
439
- margin-bottom: 20px;
440
- display: flex;
441
- gap: 12px;
442
- }
443
-
444
- .chat-message.user {
445
- flex-direction: row-reverse;
446
- }
447
-
448
- .chat-avatar {
449
- width: 30px;
450
- height: 30px;
451
- border: 1px dashed var(--border);
452
- display: flex;
453
- align-items: center;
454
- justify-content: center;
455
- font-size: 0.7rem;
456
- flex-shrink: 0;
457
- }
458
-
459
- .chat-bubble {
460
- background: var(--bg-tertiary);
461
- border: 1px dashed var(--border);
462
- padding: 12px 16px;
463
- max-width: 80%;
464
- font-size: 0.9rem;
465
- line-height: 1.6;
466
- }
467
-
468
- .chat-message.user .chat-bubble {
469
- background: var(--bg);
470
- }
471
-
472
- .quick-questions {
473
- padding: 0 20px 20px;
474
- }
475
-
476
- .quick-questions-label {
477
- font-size: 0.7rem;
478
- letter-spacing: 2px;
479
- color: var(--text-muted);
480
- margin-bottom: 12px;
481
- text-transform: uppercase;
482
- }
483
-
484
- .quick-btns {
485
- display: flex;
486
- flex-wrap: wrap;
487
- gap: 10px;
488
- }
489
-
490
- .quick-btn {
491
- background: transparent;
492
- border: 1px dashed var(--border);
493
- color: var(--text);
494
- padding: 8px 16px;
495
- font-family: inherit;
496
- font-size: 0.8rem;
497
- cursor: pointer;
498
- transition: all 0.3s;
499
- }
500
-
501
- .quick-btn:hover {
502
- background: var(--accent-red);
503
- color: #fff;
504
- border-color: var(--accent-red);
505
- }
506
-
507
- .ai-chat-input {
508
- display: flex;
509
- gap: 10px;
510
- padding: 15px 20px;
511
- border-top: 1px dashed var(--border);
512
- }
513
-
514
- .ai-chat-input input {
515
- flex: 1;
516
- background: transparent;
517
- border: 1px dashed var(--border);
518
- color: var(--text);
519
- padding: 12px 15px;
520
- font-family: inherit;
521
- font-size: 0.9rem;
522
- }
523
-
524
- .ai-chat-input input::placeholder {
525
- color: var(--text-dim);
526
- }
527
-
528
- .ai-chat-input input:focus {
529
- outline: none;
530
- border-color: var(--text);
531
- }
532
-
533
- .ai-chat-input button {
534
- background: var(--accent-red);
535
- color: #fff;
536
- border: none;
537
- padding: 12px 20px;
538
- font-family: inherit;
539
- font-size: 0.8rem;
540
- cursor: pointer;
541
- letter-spacing: 1px;
542
- transition: all 0.3s;
543
- }
544
-
545
- .ai-chat-input button:hover {
546
- opacity: 0.8;
547
- }
548
-
549
- /* Feedback Section */
550
- .feedback-section {
551
- background: var(--bg-secondary);
552
- border: 1px dashed var(--border);
553
- padding: 30px;
554
- margin: 50px 0;
555
- text-align: center;
556
- opacity: 0;
557
- }
558
-
559
- .feedback-title {
560
- font-size: 1.1rem;
561
- margin-bottom: 8px;
562
- }
563
-
564
- .feedback-subtitle {
565
- font-size: 0.85rem;
566
- color: var(--text-muted);
567
- margin-bottom: 25px;
568
- }
569
-
570
- .feedback-buttons {
571
- display: flex;
572
- gap: 15px;
573
- justify-content: center;
574
- flex-wrap: wrap;
575
- }
576
-
577
- .feedback-btn {
578
- background: transparent;
579
- border: 2px solid var(--border);
580
- color: var(--text);
581
- padding: 15px 30px;
582
- font-family: inherit;
583
- font-size: 0.9rem;
584
- cursor: pointer;
585
- transition: all 0.3s;
586
- display: flex;
587
- align-items: center;
588
- gap: 10px;
589
- }
590
-
591
- .feedback-btn:hover {
592
- border-color: var(--accent-red);
593
- box-shadow: 0 0 20px var(--accent-red-glow);
594
- }
595
-
596
- .feedback-btn.active {
597
- background: var(--accent-red);
598
- color: #fff;
599
- border-color: var(--accent-red);
600
- }
601
-
602
- .feedback-btn .icon {
603
- font-size: 1.2rem;
604
- }
605
-
606
- .feedback-count {
607
- font-size: 0.75rem;
608
- color: var(--text-muted);
609
- margin-top: 20px;
610
- }
611
-
612
- /* Author Section */
613
- .author-section {
614
- display: flex;
615
- gap: 25px;
616
- align-items: center;
617
- padding: 30px;
618
- background: var(--bg-secondary);
619
- border: 1px dashed var(--border);
620
- margin: 50px 0;
621
- opacity: 0;
622
- }
623
-
624
- .author-avatar {
625
- width: 80px;
626
- height: 80px;
627
- border: 2px solid var(--text);
628
- display: flex;
629
- align-items: center;
630
- justify-content: center;
631
- font-size: 1.5rem;
632
- flex-shrink: 0;
633
- }
634
-
635
- .author-info h4 {
636
- font-size: 1.1rem;
637
- margin-bottom: 8px;
638
- }
639
-
640
- .author-info p {
641
- font-size: 0.9rem;
642
- color: var(--text-muted);
643
- line-height: 1.6;
644
- }
645
-
646
- /* Related Posts */
647
- .related-posts {
648
- margin: 60px 0;
649
- }
650
-
651
- .related-posts h3 {
652
- font-size: 1.2rem;
653
- font-family: 'Fredoka', sans-serif;
654
- margin-bottom: 25px;
655
- letter-spacing: 2px;
656
- }
657
-
658
- .related-grid {
659
- display: grid;
660
- grid-template-columns: repeat(2, 1fr);
661
- gap: 25px;
662
- }
663
-
664
- .related-card {
665
- border: 1px dashed var(--border);
666
- padding: 25px;
667
- text-decoration: none;
668
- color: var(--text);
669
- transition: all 0.3s;
670
- opacity: 0;
671
- }
672
-
673
- .related-card:hover {
674
- border-color: var(--accent-red);
675
- box-shadow: 0 0 30px var(--accent-red-glow);
676
- }
677
-
678
- .related-card-tag {
679
- font-size: 0.65rem;
680
- letter-spacing: 2px;
681
- color: var(--accent-red);
682
- text-transform: uppercase;
683
- margin-bottom: 12px;
684
- }
685
-
686
- .related-card-title {
687
- font-size: 1rem;
688
- font-family: 'Fredoka', sans-serif;
689
- font-weight: 600;
690
- line-height: 1.4;
691
- }
692
-
693
- /* Footer */
694
- footer {
695
- border-top: 1px dashed var(--border);
696
- padding: 40px;
697
- text-align: center;
698
- margin-top: 60px;
699
- }
700
-
701
- .footer-logo {
702
- font-size: 1.2rem;
703
- font-family: 'Fredoka', sans-serif;
704
- font-weight: 700;
705
- letter-spacing: 2px;
706
- margin-bottom: 20px;
707
- }
708
-
709
- .footer-links {
710
- display: flex;
711
- gap: 25px;
712
- justify-content: center;
713
- margin-bottom: 20px;
714
- }
715
-
716
- .footer-links a {
717
- color: var(--text-muted);
718
- text-decoration: none;
719
- font-size: 0.8rem;
720
- transition: color 0.3s;
721
- }
722
-
723
- .footer-links a:hover {
724
- color: var(--accent-red);
725
- }
726
-
727
- .footer-copy {
728
- color: var(--text-dim);
729
- font-size: 0.75rem;
730
- }
731
-
732
- /* Responsive */
733
- @media (max-width: 768px) {
734
- main {
735
- padding: 100px 20px 60px;
736
- }
737
-
738
- .article-title {
739
- font-size: 2rem;
740
- }
741
-
742
- .audio-controls {
743
- flex-wrap: wrap;
744
- }
745
-
746
- .related-grid {
747
- grid-template-columns: 1fr;
748
- }
749
-
750
- .author-section {
751
- flex-direction: column;
752
- text-align: center;
753
- }
754
-
755
- nav {
756
- padding: 15px 20px;
757
- }
758
-
759
- .nav-links {
760
- display: none;
761
- }
762
- }
763
- </style>
764
- </head>
765
- <body>
766
- <!-- Navigation -->
767
- <nav>
768
- <a href="../index.html" class="logo">
769
- <div class="logo-icon">HT</div>
770
- HUSTLE TOGETHER
771
- </a>
772
- <div class="nav-links">
773
- <a href="../index.html">HOME</a>
774
- <a href="../index.html#tools">TOOLS</a>
775
- <a href="../index.html#blog">BLOG</a>
776
- <button class="theme-toggle" id="themeToggle">[ LIGHT ]</button>
777
- </div>
778
- </nav>
779
-
780
- <main>
781
- <!-- Article Header -->
782
- <header class="article-header">
783
- <div class="article-meta">
784
- <span class="article-tag">DEV TOOLS</span>
785
- <span class="article-date">December 7, 2025</span>
786
- </div>
787
- <h1 class="article-title">Why We Built Interview-Driven API Development</h1>
788
- <p class="article-excerpt">
789
- Claude Code is incredible, but it has a problem: it skips steps. It hallucinates APIs.
790
- It marks things "done" without checking. We fixed that with Python hooks that enforce
791
- a 10-phase workflow. Here's why research-first beats code-first every time.
792
- </p>
793
- </header>
794
-
795
- <!-- Audio Player -->
796
- <div class="audio-player">
797
- <div class="audio-header">
798
- <div class="audio-icon">[>]</div>
799
- <div class="audio-info">
800
- <h4>Listen to this article</h4>
801
- <p>AI-generated audio narration - 8 min read</p>
802
- </div>
803
- </div>
804
- <div class="audio-controls">
805
- <button class="play-btn" id="playBtn">[>]</button>
806
- <div class="progress-bar">
807
- <div class="progress-fill" id="progressFill"></div>
808
- </div>
809
- <span class="audio-time">0:00 / 8:24</span>
810
- </div>
811
- </div>
812
-
813
- <!-- Featured Media -->
814
- <div class="featured-media">
815
- <div class="media-container">
816
- <div class="media-placeholder">
817
- <span class="icon">[*]</span>
818
- <p>WORKFLOW VISUALIZATION</p>
819
- </div>
820
- </div>
821
- <p class="media-caption">The 10-phase interview-driven workflow enforced by Python hooks</p>
822
- </div>
823
-
824
- <!-- Article Content -->
825
- <article class="article-content">
826
- <h2>The Problem With AI Code Assistants</h2>
827
-
828
- <p>We love Claude Code. It's genuinely incredible at understanding context, writing clean code, and solving complex problems. But there's a pattern we kept seeing over and over:</p>
829
-
830
- <ol>
831
- <li><strong>Ask Claude to implement an API endpoint</strong></li>
832
- <li><strong>Claude writes the code immediately</strong> (without researching the actual API)</li>
833
- <li><strong>The code looks perfect</strong> but uses hallucinated parameters</li>
834
- <li><strong>Tests fail</strong> (or worse, Claude skips them)</li>
835
- <li><strong>"Done!"</strong> says Claude, marking the task complete</li>
836
- </ol>
837
-
838
- <p>This isn't Claude's fault - it's how most AI assistants work. They're trained to be helpful, which means they want to give you answers fast. But with API development, fast answers are often wrong answers.</p>
839
-
840
- <div class="callout">
841
- <div class="callout-title">The Core Insight</div>
842
- <p>The best developers don't start coding - they start researching. They read the docs. They look at examples. They understand the problem before writing a single line of code. We needed to teach Claude this workflow.</p>
843
- </div>
844
-
845
- <h2>Five Gaps We Had to Close</h2>
846
-
847
- <h3>Gap 1: AI Paraphrasing Instead of Quoting</h3>
848
- <p>When Claude researches something, it tends to paraphrase what it found rather than using exact terms. This matters because API parameters are case-sensitive, specific strings that must match exactly.</p>
849
-
850
- <pre>// What Claude might say:
851
- "The endpoint supports standard authentication"
852
-
853
- // What the docs actually say:
854
- "Use X-Api-Key header with Bearer token"</pre>
855
-
856
- <h3>Gap 2: Claiming Updates Without Verification</h3>
857
- <p>Claude would say "I've updated the file" without showing a git diff or verifying the change actually happened. Our hooks now require proof.</p>
858
-
859
- <h3>Gap 3: Skipped Tests Accepted As-Is</h3>
860
- <p>When tests fail, Claude often marks them as "skipped" and moves on. But skipped tests aren't passing tests - they're potential bugs hiding in your codebase.</p>
861
-
862
- <h3>Gap 4: Tasks Marked Complete Without Verification</h3>
863
- <p>The workflow wasn't actually done just because Claude said it was. We needed checkpoints that verify each phase is truly complete.</p>
864
-
865
- <h3>Gap 5: Environment Variable Mismatches</h3>
866
- <p>Test environments and production environments often use different API keys and configurations. Claude would test with one and deploy with another.</p>
867
-
868
- <h2>The Solution: Python Hooks</h2>
869
-
870
- <p>Claude Code has a hook system that lets you intercept tool calls. We built three Python hooks that enforce our workflow:</p>
871
-
872
- <pre>hooks/
873
- enforce-interview.py # Blocks writing until interview is complete
874
- track-tool-use.py # Logs all research activity
875
- api-workflow-check.py # Prevents "Stop" until phases are done</pre>
876
-
877
- <p>These hooks create a 10-phase workflow that Claude must follow:</p>
878
-
879
- <ol>
880
- <li><strong>Scope</strong> - Define what we're building</li>
881
- <li><strong>Research Initial</strong> - Find official documentation</li>
882
- <li><strong>Interview</strong> - Ask the user questions</li>
883
- <li><strong>Research Deep</strong> - Dive into edge cases</li>
884
- <li><strong>Schema Creation</strong> - Define TypeScript interfaces</li>
885
- <li><strong>Environment Check</strong> - Verify API keys exist</li>
886
- <li><strong>TDD Red</strong> - Write failing tests first</li>
887
- <li><strong>TDD Green</strong> - Make tests pass</li>
888
- <li><strong>TDD Refactor</strong> - Clean up the code</li>
889
- <li><strong>Documentation</strong> - Update all the docs</li>
890
- </ol>
891
-
892
- <h2>Why Interview-Driven?</h2>
893
-
894
- <p>The interview phase is the key innovation. Instead of Claude making assumptions, it's forced to ask you questions with multiple-choice options:</p>
895
-
896
- <pre>"Which AI provider should this endpoint support?"
897
- 1. OpenAI (GPT-4o)
898
- 2. Anthropic (Claude)
899
- 3. Google (Gemini)
900
- 4. All of the above
901
- 5. Type something else...</pre>
902
-
903
- <p>The options come from the research phase, so they're real choices - not hallucinated possibilities. And your answers are tracked in a state file, so Claude can reference them during implementation.</p>
904
-
905
- <div class="callout">
906
- <div class="callout-title">Key Benefit</div>
907
- <p>When you answer "Anthropic", Claude won't accidentally implement OpenAI instead. The decision is recorded and enforced throughout the workflow.</p>
908
- </div>
909
-
910
- <h2>Getting Started</h2>
911
-
912
- <pre>npx @hustle-together/api-dev-tools</pre>
913
-
914
- <p>That's it. The installer sets up everything: commands, hooks, state file, and MCP server recommendations. Start your next API endpoint with <code>/api-create endpoint-name</code> and experience the difference.</p>
915
-
916
- </article>
917
-
918
- <!-- AI Chat Widget -->
919
- <div class="ai-chat">
920
- <div class="ai-chat-header">
921
- <div class="ai-chat-icon">[?]</div>
922
- <div class="ai-chat-title">
923
- <h4>Quick Answers</h4>
924
- <p>Ask questions about this article</p>
925
- </div>
926
- </div>
927
- <div class="ai-chat-body">
928
- <div class="chat-message">
929
- <div class="chat-avatar">AI</div>
930
- <div class="chat-bubble">
931
- Hi! I can answer questions about interview-driven API development, the hook system, or how to get started. What would you like to know?
932
- </div>
933
- </div>
934
- </div>
935
- <div class="quick-questions">
936
- <div class="quick-questions-label">Quick Questions</div>
937
- <div class="quick-btns">
938
- <button class="quick-btn" data-question="How do the Python hooks work?">How do hooks work?</button>
939
- <button class="quick-btn" data-question="What's the difference between research phases?">Research phases?</button>
940
- <button class="quick-btn" data-question="Can I customize the workflow?">Customize workflow?</button>
941
- </div>
942
- </div>
943
- <div class="ai-chat-input">
944
- <input type="text" placeholder="Ask a question about the article..." id="chatInput">
945
- <button id="chatSend">SEND</button>
946
- </div>
947
- </div>
948
-
949
- <!-- Feedback Section -->
950
- <div class="feedback-section">
951
- <h3 class="feedback-title">Did you find this article helpful?</h3>
952
- <p class="feedback-subtitle">Your feedback helps us create better content</p>
953
- <div class="feedback-buttons">
954
- <button class="feedback-btn" data-reaction="very-nice">
955
- <span class="icon">[+]</span>
956
- Very Nice
957
- </button>
958
- <button class="feedback-btn" data-reaction="helpful">
959
- <span class="icon">[*]</span>
960
- Helpful
961
- </button>
962
- <button class="feedback-btn" data-reaction="needs-more">
963
- <span class="icon">[?]</span>
964
- Needs More
965
- </button>
966
- </div>
967
- <p class="feedback-count">42 people found this helpful</p>
968
- </div>
969
-
970
- <!-- Author Section -->
971
- <div class="author-section">
972
- <div class="author-avatar">[HT]</div>
973
- <div class="author-info">
974
- <h4>Hustle Together Team</h4>
975
- <p>We're a small team obsessed with building tools that actually work. We experiment, we iterate, and we share what we learn. No corporate BS - just useful stuff for developers.</p>
976
- </div>
977
- </div>
978
-
979
- <!-- Related Posts -->
980
- <div class="related-posts">
981
- <h3>RELATED POSTS</h3>
982
- <div class="related-grid">
983
- <a href="gemini-vs-claude-widgets.html" class="related-card">
984
- <div class="related-card-tag">AI</div>
985
- <h4 class="related-card-title">Gemini Pro 2.5 vs Claude for Widget Generation</h4>
986
- </a>
987
- <a href="tdd-for-ai.html" class="related-card">
988
- <div class="related-card-tag">WORKFLOW</div>
989
- <h4 class="related-card-title">TDD is Dead, Long Live TDD</h4>
990
- </a>
991
- </div>
992
- </div>
993
- </main>
994
-
995
- <!-- Footer -->
996
- <footer>
997
- <div class="footer-logo">HUSTLE TOGETHER</div>
998
- <div class="footer-links">
999
- <a href="https://github.com/hustle-together" target="_blank">GITHUB</a>
1000
- <a href="https://www.npmjs.com/org/hustle-together" target="_blank">NPM</a>
1001
- <a href="mailto:hello@hustletogether.dev">CONTACT</a>
1002
- </div>
1003
- <div class="footer-copy">
1004
- 2025 Hustle Together. Experiment. Build. Share.
1005
- </div>
1006
- </footer>
1007
-
1008
- <script>
1009
- // Theme Toggle
1010
- const themeToggle = document.getElementById('themeToggle');
1011
- let isDark = true;
1012
-
1013
- themeToggle.addEventListener('click', () => {
1014
- isDark = !isDark;
1015
- document.documentElement.setAttribute('data-theme', isDark ? '' : 'light');
1016
- themeToggle.textContent = isDark ? '[ LIGHT ]' : '[ DARK ]';
1017
- });
1018
-
1019
- // GSAP Animations
1020
- gsap.to('.article-header', { opacity: 1, duration: 0.8, delay: 0.3 });
1021
- gsap.to('.audio-player', { opacity: 1, duration: 0.6, delay: 0.5 });
1022
- gsap.to('.featured-media', { opacity: 1, duration: 0.6, delay: 0.7 });
1023
- gsap.to('.article-content', { opacity: 1, duration: 0.8, delay: 0.9 });
1024
- gsap.to('.ai-chat', { opacity: 1, duration: 0.6, delay: 1.1 });
1025
- gsap.to('.feedback-section', { opacity: 1, duration: 0.6, delay: 1.3 });
1026
- gsap.to('.author-section', { opacity: 1, duration: 0.6, delay: 1.5 });
1027
- gsap.to('.related-card', { opacity: 1, duration: 0.5, stagger: 0.15, delay: 1.7 });
1028
-
1029
- // Audio Player Simulation
1030
- const playBtn = document.getElementById('playBtn');
1031
- const progressFill = document.getElementById('progressFill');
1032
- let isPlaying = false;
1033
- let progress = 0;
1034
- let playInterval;
1035
-
1036
- playBtn.addEventListener('click', () => {
1037
- isPlaying = !isPlaying;
1038
- playBtn.textContent = isPlaying ? '[||]' : '[>]';
1039
-
1040
- if (isPlaying) {
1041
- playInterval = setInterval(() => {
1042
- progress += 0.2;
1043
- if (progress >= 100) {
1044
- progress = 0;
1045
- isPlaying = false;
1046
- playBtn.textContent = '[>]';
1047
- clearInterval(playInterval);
1048
- }
1049
- progressFill.style.width = progress + '%';
1050
- }, 100);
1051
- } else {
1052
- clearInterval(playInterval);
1053
- }
1054
- });
1055
-
1056
- // Quick Questions
1057
- const quickBtns = document.querySelectorAll('.quick-btn');
1058
- const chatBody = document.querySelector('.ai-chat-body');
1059
-
1060
- const answers = {
1061
- "How do the Python hooks work?": "The Python hooks intercept Claude's tool calls. When Claude tries to write a file, enforce-interview.py checks if the interview is complete. When Claude uses research tools, track-tool-use.py logs the activity. When Claude tries to stop, api-workflow-check.py verifies all phases are done.",
1062
- "What's the difference between research phases?": "Research Initial happens BEFORE the interview - it discovers what's possible. Research Deep happens AFTER the interview - it dives into the specific choices the user made. Both use Context7 and WebSearch to get real documentation.",
1063
- "Can I customize the workflow?": "Yes! The hooks read from api-dev-state.json which you can modify. You can also edit the hook files directly to change requirements, add phases, or adjust the minimum question count."
1064
- };
1065
-
1066
- quickBtns.forEach(btn => {
1067
- btn.addEventListener('click', () => {
1068
- const question = btn.dataset.question;
1069
-
1070
- // Add user message
1071
- chatBody.innerHTML += `
1072
- <div class="chat-message user">
1073
- <div class="chat-avatar">U</div>
1074
- <div class="chat-bubble">${question}</div>
1075
- </div>
1076
- `;
1077
-
1078
- // Add AI response after delay
1079
- setTimeout(() => {
1080
- chatBody.innerHTML += `
1081
- <div class="chat-message">
1082
- <div class="chat-avatar">AI</div>
1083
- <div class="chat-bubble">${answers[question] || "I don't have a specific answer for that, but feel free to ask something else!"}</div>
1084
- </div>
1085
- `;
1086
- chatBody.scrollTop = chatBody.scrollHeight;
1087
- }, 500);
1088
-
1089
- chatBody.scrollTop = chatBody.scrollHeight;
1090
- });
1091
- });
1092
-
1093
- // Chat Input
1094
- const chatInput = document.getElementById('chatInput');
1095
- const chatSend = document.getElementById('chatSend');
1096
-
1097
- chatSend.addEventListener('click', sendMessage);
1098
- chatInput.addEventListener('keypress', (e) => {
1099
- if (e.key === 'Enter') sendMessage();
1100
- });
1101
-
1102
- function sendMessage() {
1103
- const message = chatInput.value.trim();
1104
- if (!message) return;
1105
-
1106
- chatBody.innerHTML += `
1107
- <div class="chat-message user">
1108
- <div class="chat-avatar">U</div>
1109
- <div class="chat-bubble">${message}</div>
1110
- </div>
1111
- `;
1112
-
1113
- chatInput.value = '';
1114
-
1115
- setTimeout(() => {
1116
- chatBody.innerHTML += `
1117
- <div class="chat-message">
1118
- <div class="chat-avatar">AI</div>
1119
- <div class="chat-bubble">Great question! This is a demo chat - in the full version, this would connect to an AI model to answer your questions about the article in detail.</div>
1120
- </div>
1121
- `;
1122
- chatBody.scrollTop = chatBody.scrollHeight;
1123
- }, 500);
1124
-
1125
- chatBody.scrollTop = chatBody.scrollHeight;
1126
- }
1127
-
1128
- // Feedback Buttons
1129
- const feedbackBtns = document.querySelectorAll('.feedback-btn');
1130
- const feedbackCount = document.querySelector('.feedback-count');
1131
- let currentCount = 42;
1132
-
1133
- feedbackBtns.forEach(btn => {
1134
- btn.addEventListener('click', () => {
1135
- feedbackBtns.forEach(b => b.classList.remove('active'));
1136
- btn.classList.add('active');
1137
- currentCount++;
1138
- feedbackCount.textContent = `${currentCount} people found this helpful`;
1139
-
1140
- // Animation
1141
- gsap.to(btn, { scale: 1.1, duration: 0.1, yoyo: true, repeat: 1 });
1142
- });
1143
- });
1144
- </script>
1145
- </body>
1146
- </html>