@hustle-together/api-dev-tools 1.9.0 → 2.0.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.
@@ -0,0 +1,982 @@
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>TDD is Dead, Long Live TDD - 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.12.2/gsap.min.js"></script>
11
+ <style>
12
+ * { margin: 0; padding: 0; box-sizing: border-box; }
13
+
14
+ :root {
15
+ --bg: #0a0a0a;
16
+ --bg-secondary: #111;
17
+ --bg-tertiary: #1a1a1a;
18
+ --text: #e0e0e0;
19
+ --text-muted: #888;
20
+ --text-dim: #444;
21
+ --accent: #fff;
22
+ --accent-red: #BA0C2F;
23
+ --accent-red-glow: rgba(186, 12, 47, 0.4);
24
+ --border: #333;
25
+ --glow: rgba(255, 255, 255, 0.3);
26
+ --red: #ef4444;
27
+ --green: #22c55e;
28
+ --blue: #3b82f6;
29
+ }
30
+
31
+ [data-theme="light"] {
32
+ --bg: #f5f5f5;
33
+ --bg-secondary: #fff;
34
+ --bg-tertiary: #e5e5e5;
35
+ --text: #1a1a1a;
36
+ --text-muted: #666;
37
+ --text-dim: #aaa;
38
+ --accent: #000;
39
+ --accent-red: #BA0C2F;
40
+ --accent-red-glow: rgba(186, 12, 47, 0.3);
41
+ --border: #ccc;
42
+ --glow: rgba(0, 0, 0, 0.15);
43
+ }
44
+
45
+ body {
46
+ background: var(--bg);
47
+ color: var(--text);
48
+ font-family: 'JetBrains Mono', 'Courier New', monospace;
49
+ line-height: 1.8;
50
+ }
51
+
52
+ nav {
53
+ position: fixed;
54
+ top: 0;
55
+ left: 0;
56
+ right: 0;
57
+ z-index: 1000;
58
+ padding: 20px 40px;
59
+ display: flex;
60
+ justify-content: space-between;
61
+ align-items: center;
62
+ background: var(--bg);
63
+ border-bottom: 1px dashed var(--border);
64
+ }
65
+
66
+ .logo {
67
+ font-size: 1.1rem;
68
+ font-family: 'Fredoka', sans-serif;
69
+ font-weight: 700;
70
+ letter-spacing: 2px;
71
+ text-decoration: none;
72
+ color: var(--text);
73
+ display: flex;
74
+ align-items: center;
75
+ gap: 10px;
76
+ }
77
+
78
+ .logo-icon {
79
+ width: 28px;
80
+ height: 28px;
81
+ border: 2px solid var(--text);
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ font-size: 0.7rem;
86
+ }
87
+
88
+ .nav-links {
89
+ display: flex;
90
+ gap: 25px;
91
+ align-items: center;
92
+ }
93
+
94
+ .nav-links a {
95
+ color: var(--text-muted);
96
+ text-decoration: none;
97
+ font-size: 0.8rem;
98
+ letter-spacing: 1px;
99
+ }
100
+
101
+ .nav-links a:hover { color: var(--accent-red); }
102
+
103
+ .theme-toggle {
104
+ background: transparent;
105
+ border: 1px dashed var(--border);
106
+ color: var(--text);
107
+ padding: 6px 12px;
108
+ cursor: pointer;
109
+ font-family: inherit;
110
+ font-size: 0.75rem;
111
+ }
112
+
113
+ .theme-toggle:hover { background: var(--text); color: var(--bg); }
114
+
115
+ main {
116
+ max-width: 900px;
117
+ margin: 0 auto;
118
+ padding: 120px 40px 80px;
119
+ }
120
+
121
+ .article-header {
122
+ margin-bottom: 50px;
123
+ opacity: 0;
124
+ }
125
+
126
+ .article-meta {
127
+ display: flex;
128
+ gap: 20px;
129
+ margin-bottom: 20px;
130
+ flex-wrap: wrap;
131
+ }
132
+
133
+ .article-tag {
134
+ font-size: 0.7rem;
135
+ letter-spacing: 2px;
136
+ color: var(--accent-red);
137
+ border: 1px dashed var(--accent-red);
138
+ padding: 5px 12px;
139
+ text-transform: uppercase;
140
+ }
141
+
142
+ .article-date { font-size: 0.8rem; color: var(--text-dim); }
143
+
144
+ .article-title {
145
+ font-size: 2.8rem;
146
+ font-family: 'Fredoka', sans-serif;
147
+ font-weight: 700;
148
+ line-height: 1.2;
149
+ margin-bottom: 25px;
150
+ }
151
+
152
+ .article-excerpt {
153
+ font-size: 1.1rem;
154
+ color: var(--text-muted);
155
+ line-height: 1.8;
156
+ border-left: 3px solid var(--border);
157
+ padding-left: 20px;
158
+ }
159
+
160
+ .audio-player {
161
+ background: var(--bg-secondary);
162
+ border: 1px dashed var(--border);
163
+ padding: 25px;
164
+ margin: 40px 0;
165
+ opacity: 0;
166
+ }
167
+
168
+ .audio-header {
169
+ display: flex;
170
+ align-items: center;
171
+ gap: 15px;
172
+ margin-bottom: 20px;
173
+ }
174
+
175
+ .audio-icon {
176
+ width: 50px;
177
+ height: 50px;
178
+ border: 2px solid var(--text);
179
+ display: flex;
180
+ align-items: center;
181
+ justify-content: center;
182
+ font-size: 1.2rem;
183
+ }
184
+
185
+ .audio-info h4 { font-size: 0.9rem; font-weight: 600; margin-bottom: 5px; }
186
+ .audio-info p { font-size: 0.75rem; color: var(--text-muted); }
187
+
188
+ .audio-controls { display: flex; align-items: center; gap: 20px; }
189
+
190
+ .play-btn {
191
+ width: 50px;
192
+ height: 50px;
193
+ border: 2px solid var(--text);
194
+ background: transparent;
195
+ color: var(--text);
196
+ cursor: pointer;
197
+ font-size: 1.2rem;
198
+ display: flex;
199
+ align-items: center;
200
+ justify-content: center;
201
+ }
202
+
203
+ .play-btn:hover { background: var(--accent-red); color: #fff; border-color: var(--accent-red); }
204
+
205
+ .progress-bar { flex: 1; height: 4px; background: var(--border); cursor: pointer; }
206
+ .progress-fill { height: 100%; background: var(--accent-red); width: 0%; }
207
+ .audio-time { font-size: 0.8rem; color: var(--text-muted); min-width: 90px; text-align: right; }
208
+
209
+ /* TDD Cycle Visualization */
210
+ .tdd-cycle {
211
+ display: flex;
212
+ justify-content: center;
213
+ gap: 30px;
214
+ margin: 50px 0;
215
+ flex-wrap: wrap;
216
+ opacity: 0;
217
+ }
218
+
219
+ .tdd-phase {
220
+ width: 180px;
221
+ padding: 30px 20px;
222
+ border: 2px solid var(--border);
223
+ text-align: center;
224
+ position: relative;
225
+ transition: all 0.3s;
226
+ }
227
+
228
+ .tdd-phase:hover {
229
+ transform: translateY(-5px);
230
+ box-shadow: 0 10px 30px var(--glow);
231
+ }
232
+
233
+ .tdd-phase.red { border-color: var(--red); }
234
+ .tdd-phase.green { border-color: var(--green); }
235
+ .tdd-phase.blue { border-color: var(--blue); }
236
+
237
+ .tdd-phase-icon {
238
+ width: 50px;
239
+ height: 50px;
240
+ margin: 0 auto 15px;
241
+ display: flex;
242
+ align-items: center;
243
+ justify-content: center;
244
+ font-size: 1.5rem;
245
+ }
246
+
247
+ .tdd-phase.red .tdd-phase-icon { border: 2px solid var(--red); color: var(--red); }
248
+ .tdd-phase.green .tdd-phase-icon { border: 2px solid var(--green); color: var(--green); }
249
+ .tdd-phase.blue .tdd-phase-icon { border: 2px solid var(--blue); color: var(--blue); }
250
+
251
+ .tdd-phase-name {
252
+ font-size: 1.1rem;
253
+ font-family: 'Fredoka', sans-serif;
254
+ margin-bottom: 8px;
255
+ }
256
+
257
+ .tdd-phase.red .tdd-phase-name { color: var(--red); }
258
+ .tdd-phase.green .tdd-phase-name { color: var(--green); }
259
+ .tdd-phase.blue .tdd-phase-name { color: var(--blue); }
260
+
261
+ .tdd-phase-desc {
262
+ font-size: 0.8rem;
263
+ color: var(--text-muted);
264
+ line-height: 1.5;
265
+ }
266
+
267
+ .tdd-arrow {
268
+ display: flex;
269
+ align-items: center;
270
+ font-size: 1.5rem;
271
+ color: var(--text-dim);
272
+ }
273
+
274
+ .featured-media {
275
+ margin: 40px 0;
276
+ border: 1px dashed var(--border);
277
+ opacity: 0;
278
+ }
279
+
280
+ .media-container {
281
+ aspect-ratio: 16/9;
282
+ background: var(--bg-tertiary);
283
+ display: flex;
284
+ align-items: center;
285
+ justify-content: center;
286
+ }
287
+
288
+ .media-placeholder {
289
+ text-align: center;
290
+ color: var(--text-dim);
291
+ }
292
+
293
+ .media-placeholder .icon {
294
+ font-size: 4rem;
295
+ margin-bottom: 15px;
296
+ display: block;
297
+ }
298
+
299
+ .media-caption {
300
+ padding: 15px;
301
+ font-size: 0.8rem;
302
+ color: var(--text-muted);
303
+ border-top: 1px dashed var(--border);
304
+ }
305
+
306
+ .article-content { opacity: 0; }
307
+
308
+ .article-content h2 {
309
+ font-size: 1.6rem;
310
+ font-family: 'Fredoka', sans-serif;
311
+ margin: 50px 0 20px;
312
+ }
313
+
314
+ .article-content h3 { font-size: 1.2rem; margin: 35px 0 15px; }
315
+ .article-content p { margin-bottom: 20px; font-size: 1rem; }
316
+ .article-content ul, .article-content ol { margin: 20px 0 20px 30px; }
317
+ .article-content li { margin-bottom: 10px; }
318
+
319
+ .article-content code {
320
+ background: var(--bg-secondary);
321
+ padding: 2px 8px;
322
+ border: 1px dashed var(--border);
323
+ font-size: 0.9em;
324
+ }
325
+
326
+ .article-content pre {
327
+ background: var(--bg-secondary);
328
+ border: 1px dashed var(--border);
329
+ padding: 20px;
330
+ margin: 25px 0;
331
+ overflow-x: auto;
332
+ font-size: 0.85rem;
333
+ line-height: 1.6;
334
+ }
335
+
336
+ .article-content blockquote {
337
+ border-left: 3px solid var(--text);
338
+ padding-left: 20px;
339
+ margin: 30px 0;
340
+ font-style: italic;
341
+ color: var(--text-muted);
342
+ }
343
+
344
+ .callout {
345
+ background: var(--bg-secondary);
346
+ border: 1px dashed var(--border);
347
+ border-left: 3px solid var(--text);
348
+ padding: 25px;
349
+ margin: 30px 0;
350
+ }
351
+
352
+ .callout-title {
353
+ font-size: 0.8rem;
354
+ letter-spacing: 2px;
355
+ text-transform: uppercase;
356
+ margin-bottom: 10px;
357
+ display: flex;
358
+ align-items: center;
359
+ gap: 10px;
360
+ }
361
+
362
+ .callout-title::before {
363
+ content: '!';
364
+ width: 20px;
365
+ height: 20px;
366
+ border: 1px dashed var(--text);
367
+ display: flex;
368
+ align-items: center;
369
+ justify-content: center;
370
+ font-size: 0.75rem;
371
+ }
372
+
373
+ .command-showcase {
374
+ background: var(--bg-secondary);
375
+ border: 1px dashed var(--border);
376
+ margin: 40px 0;
377
+ opacity: 0;
378
+ }
379
+
380
+ .command-showcase-header {
381
+ padding: 15px 20px;
382
+ border-bottom: 1px dashed var(--border);
383
+ display: flex;
384
+ align-items: center;
385
+ gap: 10px;
386
+ font-size: 0.8rem;
387
+ color: var(--text-muted);
388
+ }
389
+
390
+ .command-item {
391
+ padding: 20px;
392
+ border-bottom: 1px dashed var(--border);
393
+ display: grid;
394
+ grid-template-columns: 120px 1fr;
395
+ gap: 20px;
396
+ align-items: start;
397
+ }
398
+
399
+ .command-item:last-child { border-bottom: none; }
400
+
401
+ .command-name {
402
+ font-weight: 600;
403
+ font-size: 0.95rem;
404
+ }
405
+
406
+ .command-name.red { color: var(--red); }
407
+ .command-name.green { color: var(--green); }
408
+ .command-name.blue { color: var(--blue); }
409
+
410
+ .command-desc {
411
+ font-size: 0.85rem;
412
+ color: var(--text-muted);
413
+ line-height: 1.6;
414
+ }
415
+
416
+ .ai-chat {
417
+ background: var(--bg-secondary);
418
+ border: 1px dashed var(--border);
419
+ margin: 50px 0;
420
+ opacity: 0;
421
+ }
422
+
423
+ .ai-chat-header {
424
+ padding: 20px;
425
+ border-bottom: 1px dashed var(--border);
426
+ display: flex;
427
+ align-items: center;
428
+ gap: 15px;
429
+ }
430
+
431
+ .ai-chat-icon {
432
+ width: 40px;
433
+ height: 40px;
434
+ border: 2px solid var(--text);
435
+ display: flex;
436
+ align-items: center;
437
+ justify-content: center;
438
+ }
439
+
440
+ .ai-chat-title h4 { font-size: 0.9rem; font-weight: 600; }
441
+ .ai-chat-title p { font-size: 0.75rem; color: var(--text-muted); }
442
+
443
+ .ai-chat-body {
444
+ padding: 20px;
445
+ max-height: 400px;
446
+ overflow-y: auto;
447
+ }
448
+
449
+ .chat-message {
450
+ margin-bottom: 20px;
451
+ display: flex;
452
+ gap: 12px;
453
+ }
454
+
455
+ .chat-message.user { flex-direction: row-reverse; }
456
+
457
+ .chat-avatar {
458
+ width: 30px;
459
+ height: 30px;
460
+ border: 1px dashed var(--border);
461
+ display: flex;
462
+ align-items: center;
463
+ justify-content: center;
464
+ font-size: 0.7rem;
465
+ flex-shrink: 0;
466
+ }
467
+
468
+ .chat-bubble {
469
+ background: var(--bg-tertiary);
470
+ border: 1px dashed var(--border);
471
+ padding: 12px 16px;
472
+ max-width: 80%;
473
+ font-size: 0.9rem;
474
+ line-height: 1.6;
475
+ }
476
+
477
+ .chat-message.user .chat-bubble { background: var(--bg); }
478
+
479
+ .quick-questions { padding: 0 20px 20px; }
480
+
481
+ .quick-questions-label {
482
+ font-size: 0.7rem;
483
+ letter-spacing: 2px;
484
+ color: var(--text-muted);
485
+ margin-bottom: 12px;
486
+ text-transform: uppercase;
487
+ }
488
+
489
+ .quick-btns { display: flex; flex-wrap: wrap; gap: 10px; }
490
+
491
+ .quick-btn {
492
+ background: transparent;
493
+ border: 1px dashed var(--border);
494
+ color: var(--text);
495
+ padding: 8px 16px;
496
+ font-family: inherit;
497
+ font-size: 0.8rem;
498
+ cursor: pointer;
499
+ }
500
+
501
+ .quick-btn:hover { background: var(--accent-red); color: #fff; }
502
+
503
+ .ai-chat-input {
504
+ display: flex;
505
+ gap: 10px;
506
+ padding: 15px 20px;
507
+ border-top: 1px dashed var(--border);
508
+ }
509
+
510
+ .ai-chat-input input {
511
+ flex: 1;
512
+ background: transparent;
513
+ border: 1px dashed var(--border);
514
+ color: var(--text);
515
+ padding: 12px 15px;
516
+ font-family: inherit;
517
+ font-size: 0.9rem;
518
+ }
519
+
520
+ .ai-chat-input input::placeholder { color: var(--text-dim); }
521
+ .ai-chat-input input:focus { outline: none; border-color: var(--text); }
522
+
523
+ .ai-chat-input button {
524
+ background: var(--accent-red);
525
+ color: #fff;
526
+ border: none;
527
+ padding: 12px 20px;
528
+ font-family: inherit;
529
+ font-size: 0.8rem;
530
+ cursor: pointer;
531
+ }
532
+
533
+ .feedback-section {
534
+ background: var(--bg-secondary);
535
+ border: 1px dashed var(--border);
536
+ padding: 30px;
537
+ margin: 50px 0;
538
+ text-align: center;
539
+ opacity: 0;
540
+ }
541
+
542
+ .feedback-title { font-size: 1.1rem; margin-bottom: 8px; }
543
+ .feedback-subtitle { font-size: 0.85rem; color: var(--text-muted); margin-bottom: 25px; }
544
+
545
+ .feedback-buttons {
546
+ display: flex;
547
+ gap: 15px;
548
+ justify-content: center;
549
+ flex-wrap: wrap;
550
+ }
551
+
552
+ .feedback-btn {
553
+ background: transparent;
554
+ border: 2px solid var(--border);
555
+ color: var(--text);
556
+ padding: 15px 30px;
557
+ font-family: inherit;
558
+ font-size: 0.9rem;
559
+ cursor: pointer;
560
+ display: flex;
561
+ align-items: center;
562
+ gap: 10px;
563
+ }
564
+
565
+ .feedback-btn:hover { border-color: var(--accent-red); box-shadow: 0 0 20px var(--accent-red-glow); }
566
+ .feedback-btn.active { background: var(--accent-red); color: #fff; }
567
+ .feedback-btn .icon { font-size: 1.2rem; }
568
+
569
+ .feedback-count { font-size: 0.75rem; color: var(--text-muted); margin-top: 20px; }
570
+
571
+ .related-posts { margin: 60px 0; }
572
+
573
+ .related-posts h3 {
574
+ font-size: 1.2rem;
575
+ font-family: 'Fredoka', sans-serif;
576
+ margin-bottom: 25px;
577
+ letter-spacing: 2px;
578
+ }
579
+
580
+ .related-grid {
581
+ display: grid;
582
+ grid-template-columns: repeat(2, 1fr);
583
+ gap: 25px;
584
+ }
585
+
586
+ .related-card {
587
+ border: 1px dashed var(--border);
588
+ padding: 25px;
589
+ text-decoration: none;
590
+ color: var(--text);
591
+ transition: all 0.3s;
592
+ opacity: 0;
593
+ }
594
+
595
+ .related-card:hover { border-color: var(--accent-red); box-shadow: 0 0 30px var(--accent-red-glow); }
596
+
597
+ .related-card-tag {
598
+ font-size: 0.65rem;
599
+ letter-spacing: 2px;
600
+ color: var(--accent-red);
601
+ text-transform: uppercase;
602
+ margin-bottom: 12px;
603
+ }
604
+
605
+ .related-card-title {
606
+ font-size: 1rem;
607
+ font-family: 'Fredoka', sans-serif;
608
+ font-weight: 600;
609
+ line-height: 1.4;
610
+ }
611
+
612
+ footer {
613
+ border-top: 1px dashed var(--border);
614
+ padding: 40px;
615
+ text-align: center;
616
+ margin-top: 60px;
617
+ }
618
+
619
+ .footer-logo {
620
+ font-size: 1.2rem;
621
+ font-family: 'Fredoka', sans-serif;
622
+ font-weight: 700;
623
+ letter-spacing: 2px;
624
+ margin-bottom: 20px;
625
+ }
626
+
627
+ .footer-links {
628
+ display: flex;
629
+ gap: 25px;
630
+ justify-content: center;
631
+ margin-bottom: 20px;
632
+ }
633
+
634
+ .footer-links a {
635
+ color: var(--text-muted);
636
+ text-decoration: none;
637
+ font-size: 0.8rem;
638
+ }
639
+
640
+ .footer-links a:hover { color: var(--accent-red); }
641
+ .footer-copy { color: var(--text-dim); font-size: 0.75rem; }
642
+
643
+ @media (max-width: 768px) {
644
+ main { padding: 100px 20px 60px; }
645
+ .article-title { font-size: 2rem; }
646
+ .tdd-cycle { flex-direction: column; align-items: center; }
647
+ .tdd-arrow { transform: rotate(90deg); }
648
+ .command-item { grid-template-columns: 1fr; }
649
+ .related-grid { grid-template-columns: 1fr; }
650
+ nav { padding: 15px 20px; }
651
+ .nav-links { display: none; }
652
+ }
653
+ </style>
654
+ </head>
655
+ <body>
656
+ <nav>
657
+ <a href="../index.html" class="logo">
658
+ <div class="logo-icon">HT</div>
659
+ HUSTLE TOGETHER
660
+ </a>
661
+ <div class="nav-links">
662
+ <a href="../index.html">HOME</a>
663
+ <a href="../index.html#tools">TOOLS</a>
664
+ <a href="../index.html#blog">BLOG</a>
665
+ <button class="theme-toggle" id="themeToggle">[ LIGHT ]</button>
666
+ </div>
667
+ </nav>
668
+
669
+ <main>
670
+ <header class="article-header">
671
+ <div class="article-meta">
672
+ <span class="article-tag">WORKFLOW</span>
673
+ <span class="article-date">December 1, 2025</span>
674
+ </div>
675
+ <h1 class="article-title">TDD is Dead, Long Live TDD</h1>
676
+ <p class="article-excerpt">
677
+ The /red /green /refactor cycle isn't just for humans anymore. Here's how to
678
+ enforce test-driven development with AI assistants - and why it makes
679
+ Claude Code dramatically more reliable.
680
+ </p>
681
+ </header>
682
+
683
+ <div class="audio-player">
684
+ <div class="audio-header">
685
+ <div class="audio-icon">[>]</div>
686
+ <div class="audio-info">
687
+ <h4>Listen to this article</h4>
688
+ <p>AI-generated audio - 7 min read</p>
689
+ </div>
690
+ </div>
691
+ <div class="audio-controls">
692
+ <button class="play-btn" id="playBtn">[>]</button>
693
+ <div class="progress-bar">
694
+ <div class="progress-fill" id="progressFill"></div>
695
+ </div>
696
+ <span class="audio-time">0:00 / 7:03</span>
697
+ </div>
698
+ </div>
699
+
700
+ <div class="tdd-cycle">
701
+ <div class="tdd-phase red">
702
+ <div class="tdd-phase-icon">[X]</div>
703
+ <div class="tdd-phase-name">/red</div>
704
+ <div class="tdd-phase-desc">Write ONE failing test that defines success</div>
705
+ </div>
706
+ <div class="tdd-arrow">-></div>
707
+ <div class="tdd-phase green">
708
+ <div class="tdd-phase-icon">[+]</div>
709
+ <div class="tdd-phase-name">/green</div>
710
+ <div class="tdd-phase-desc">Write minimal code to make the test pass</div>
711
+ </div>
712
+ <div class="tdd-arrow">-></div>
713
+ <div class="tdd-phase blue">
714
+ <div class="tdd-phase-icon">[*]</div>
715
+ <div class="tdd-phase-name">/refactor</div>
716
+ <div class="tdd-phase-desc">Clean up while keeping tests green</div>
717
+ </div>
718
+ </div>
719
+
720
+ <div class="featured-media">
721
+ <div class="media-container">
722
+ <div class="media-placeholder">
723
+ <span class="icon">[/]</span>
724
+ <p>LIVE TDD CYCLE DEMO</p>
725
+ </div>
726
+ </div>
727
+ <p class="media-caption">Watch the /red /green /refactor cycle in action with Claude Code</p>
728
+ </div>
729
+
730
+ <article class="article-content">
731
+ <h2>Why TDD Matters More With AI</h2>
732
+
733
+ <p>Traditional arguments for TDD focus on design benefits and confidence in refactoring. With AI code generation, there's an even more important reason: <strong>verification</strong>.</p>
734
+
735
+ <p>AI models hallucinate. They make confident mistakes. They claim things work when they don't. Tests are the only way to verify that generated code actually does what it's supposed to do.</p>
736
+
737
+ <div class="callout">
738
+ <div class="callout-title">The Core Problem</div>
739
+ <p>Claude Code will happily write 500 lines of API integration code that looks perfect but uses parameters that don't exist. Without tests, you won't know until production.</p>
740
+ </div>
741
+
742
+ <h2>The Commands That Changed Everything</h2>
743
+
744
+ <p>We adopted the TDD commands from <a href="https://github.com/wbern/claude-instructions" style="color: var(--text);">wbern/claude-instructions</a> and adapted them for API development:</p>
745
+
746
+ <div class="command-showcase">
747
+ <div class="command-showcase-header">[SLASH COMMANDS]</div>
748
+ <div class="command-item">
749
+ <div class="command-name red">/red</div>
750
+ <div class="command-desc">Write ONE failing test. Just one. This defines what "success" looks like before any implementation code exists. Claude must see the test fail before proceeding.</div>
751
+ </div>
752
+ <div class="command-item">
753
+ <div class="command-name green">/green</div>
754
+ <div class="command-desc">Write the minimal code to make the test pass. No extra features. No "while I'm here" improvements. Just enough to turn red to green.</div>
755
+ </div>
756
+ <div class="command-item">
757
+ <div class="command-name blue">/refactor</div>
758
+ <div class="command-desc">Clean up the code while keeping all tests passing. Extract functions, rename variables, improve structure. But if a test fails, stop immediately.</div>
759
+ </div>
760
+ <div class="command-item">
761
+ <div class="command-name">/cycle</div>
762
+ <div class="command-desc">Run all three phases in sequence. Perfect for small features: "Add email validation to the user preferences API"</div>
763
+ </div>
764
+ </div>
765
+
766
+ <h2>The Enforcement Problem</h2>
767
+
768
+ <p>Commands alone aren't enough. Claude will try to be "helpful" by skipping steps. It will write tests and implementation together. It will mark tests as passing when they're actually skipped.</p>
769
+
770
+ <p>That's why we built Python hooks that intercept Claude's actions:</p>
771
+
772
+ <pre>// This gets BLOCKED:
773
+ Claude: "I'll write the tests and implementation together..."
774
+ Hook: ❌ DENIED - Must see failing test first
775
+
776
+ // This is ALLOWED:
777
+ Claude: "Let me write a test that expects X..."
778
+ *test runs and fails*
779
+ Claude: "Now let me implement the minimal code..."
780
+ Hook: ✅ ALLOWED - Following TDD workflow</pre>
781
+
782
+ <h2>Refactoring Legacy Code Safely</h2>
783
+
784
+ <p>TDD isn't just for new features. It's the safest way to refactor existing code. The process:</p>
785
+
786
+ <ol>
787
+ <li><strong>Characterization Test:</strong> Write a test that passes for the CURRENT behavior (even if that behavior is wrong)</li>
788
+ <li><strong>Safe Refactor:</strong> Rewrite the code structure while keeping the test green</li>
789
+ <li><strong>Fix Logic:</strong> Update the test to expect CORRECT behavior, then make it pass</li>
790
+ </ol>
791
+
792
+ <pre>/red write a test that passes for CURRENT behavior of @messy-file.ts
793
+ /refactor rewrite @messy-file.ts to use modern syntax
794
+ /green fix the test to expect CORRECT behavior</pre>
795
+
796
+ <p>This sequence prevents the classic refactoring disaster: changing code structure AND behavior at the same time, with no way to know which broke what.</p>
797
+
798
+ <h2>The Results</h2>
799
+
800
+ <p>Since adopting enforced TDD with our API development workflow:</p>
801
+
802
+ <ul>
803
+ <li><strong>Zero production bugs</strong> from hallucinated API parameters</li>
804
+ <li><strong>3x fewer iterations</strong> to get endpoints working</li>
805
+ <li><strong>Comprehensive test coverage</strong> without manual effort</li>
806
+ <li><strong>Confident refactoring</strong> with safety nets in place</li>
807
+ </ul>
808
+
809
+ <div class="callout">
810
+ <div class="callout-title">The Bottom Line</div>
811
+ <p>TDD isn't dead. It's more important than ever. AI can write code faster than humans, but that speed is worthless if the code doesn't work. Tests are the verification layer that makes AI-generated code trustworthy.</p>
812
+ </div>
813
+
814
+ <h2>Get Started</h2>
815
+
816
+ <pre>npx @hustle-together/api-dev-tools</pre>
817
+
818
+ <p>The TDD commands are included automatically. Start your next feature with <code>/red</code> and experience the difference.</p>
819
+ </article>
820
+
821
+ <div class="ai-chat">
822
+ <div class="ai-chat-header">
823
+ <div class="ai-chat-icon">[?]</div>
824
+ <div class="ai-chat-title">
825
+ <h4>Quick Answers</h4>
826
+ <p>Ask about TDD with AI</p>
827
+ </div>
828
+ </div>
829
+ <div class="ai-chat-body">
830
+ <div class="chat-message">
831
+ <div class="chat-avatar">AI</div>
832
+ <div class="chat-bubble">
833
+ Want to know more about enforcing TDD with Claude Code? I can explain the workflow, the hooks, or how to adapt it for your project.
834
+ </div>
835
+ </div>
836
+ </div>
837
+ <div class="quick-questions">
838
+ <div class="quick-questions-label">Quick Questions</div>
839
+ <div class="quick-btns">
840
+ <button class="quick-btn" data-question="What if my tests keep failing?">Tests keep failing?</button>
841
+ <button class="quick-btn" data-question="How do I test external APIs?">Test external APIs?</button>
842
+ <button class="quick-btn" data-question="Can I skip refactor phase?">Skip refactor?</button>
843
+ </div>
844
+ </div>
845
+ <div class="ai-chat-input">
846
+ <input type="text" placeholder="Ask about TDD..." id="chatInput">
847
+ <button id="chatSend">SEND</button>
848
+ </div>
849
+ </div>
850
+
851
+ <div class="feedback-section">
852
+ <h3 class="feedback-title">Did this change how you think about TDD?</h3>
853
+ <p class="feedback-subtitle">Tests are the new documentation</p>
854
+ <div class="feedback-buttons">
855
+ <button class="feedback-btn" data-reaction="very-nice">
856
+ <span class="icon">[+]</span>
857
+ Very Nice
858
+ </button>
859
+ <button class="feedback-btn" data-reaction="helpful">
860
+ <span class="icon">[*]</span>
861
+ Helpful
862
+ </button>
863
+ <button class="feedback-btn" data-reaction="skeptical">
864
+ <span class="icon">[?]</span>
865
+ Still Skeptical
866
+ </button>
867
+ </div>
868
+ <p class="feedback-count">56 developers found this helpful</p>
869
+ </div>
870
+
871
+ <div class="related-posts">
872
+ <h3>RELATED POSTS</h3>
873
+ <div class="related-grid">
874
+ <a href="interview-driven-api-development.html" class="related-card">
875
+ <div class="related-card-tag">DEV TOOLS</div>
876
+ <h4 class="related-card-title">Why We Built Interview-Driven API Development</h4>
877
+ </a>
878
+ <a href="gemini-vs-claude-widgets.html" class="related-card">
879
+ <div class="related-card-tag">AI</div>
880
+ <h4 class="related-card-title">Gemini Pro 2.5 vs Claude for Widget Generation</h4>
881
+ </a>
882
+ </div>
883
+ </div>
884
+ </main>
885
+
886
+ <footer>
887
+ <div class="footer-logo">HUSTLE TOGETHER</div>
888
+ <div class="footer-links">
889
+ <a href="https://github.com/hustle-together" target="_blank">GITHUB</a>
890
+ <a href="https://www.npmjs.com/org/hustle-together" target="_blank">NPM</a>
891
+ <a href="mailto:hello@hustletogether.dev">CONTACT</a>
892
+ </div>
893
+ <div class="footer-copy">2025 Hustle Together. Experiment. Build. Share.</div>
894
+ </footer>
895
+
896
+ <script>
897
+ const themeToggle = document.getElementById('themeToggle');
898
+ let isDark = true;
899
+ themeToggle.addEventListener('click', () => {
900
+ isDark = !isDark;
901
+ document.documentElement.setAttribute('data-theme', isDark ? '' : 'light');
902
+ themeToggle.textContent = isDark ? '[ LIGHT ]' : '[ DARK ]';
903
+ });
904
+
905
+ gsap.to('.article-header', { opacity: 1, duration: 0.8, delay: 0.3 });
906
+ gsap.to('.audio-player', { opacity: 1, duration: 0.6, delay: 0.5 });
907
+ gsap.to('.tdd-cycle', { opacity: 1, duration: 0.6, delay: 0.7 });
908
+ gsap.to('.featured-media', { opacity: 1, duration: 0.6, delay: 0.9 });
909
+ gsap.to('.article-content', { opacity: 1, duration: 0.8, delay: 1.1 });
910
+ gsap.to('.command-showcase', { opacity: 1, duration: 0.6, delay: 1.3 });
911
+ gsap.to('.ai-chat', { opacity: 1, duration: 0.6, delay: 1.5 });
912
+ gsap.to('.feedback-section', { opacity: 1, duration: 0.6, delay: 1.7 });
913
+ gsap.to('.related-card', { opacity: 1, duration: 0.5, stagger: 0.15, delay: 1.9 });
914
+
915
+ const playBtn = document.getElementById('playBtn');
916
+ const progressFill = document.getElementById('progressFill');
917
+ let isPlaying = false, progress = 0, playInterval;
918
+
919
+ playBtn.addEventListener('click', () => {
920
+ isPlaying = !isPlaying;
921
+ playBtn.textContent = isPlaying ? '[||]' : '[>]';
922
+ if (isPlaying) {
923
+ playInterval = setInterval(() => {
924
+ progress += 0.22;
925
+ if (progress >= 100) { progress = 0; isPlaying = false; playBtn.textContent = '[>]'; clearInterval(playInterval); }
926
+ progressFill.style.width = progress + '%';
927
+ }, 100);
928
+ } else { clearInterval(playInterval); }
929
+ });
930
+
931
+ const quickBtns = document.querySelectorAll('.quick-btn');
932
+ const chatBody = document.querySelector('.ai-chat-body');
933
+ const answers = {
934
+ "What if my tests keep failing?": "That's actually the point! Failing tests show you what's wrong. Debug the test first (is it testing the right thing?), then debug the code. Never skip a failing test.",
935
+ "How do I test external APIs?": "Mock them! Use MSW (Mock Service Worker) or similar tools. Your tests shouldn't hit real APIs - they should verify YOUR code handles responses correctly.",
936
+ "Can I skip refactor phase?": "You can, but you'll accumulate tech debt. The refactor phase is when you clean up the 'just make it work' code. Skip it too often and your codebase becomes unmaintainable."
937
+ };
938
+
939
+ quickBtns.forEach(btn => {
940
+ btn.addEventListener('click', () => {
941
+ const q = btn.dataset.question;
942
+ chatBody.innerHTML += `<div class="chat-message user"><div class="chat-avatar">U</div><div class="chat-bubble">${q}</div></div>`;
943
+ setTimeout(() => {
944
+ chatBody.innerHTML += `<div class="chat-message"><div class="chat-avatar">AI</div><div class="chat-bubble">${answers[q] || "Good question!"}</div></div>`;
945
+ chatBody.scrollTop = chatBody.scrollHeight;
946
+ }, 500);
947
+ chatBody.scrollTop = chatBody.scrollHeight;
948
+ });
949
+ });
950
+
951
+ const chatInput = document.getElementById('chatInput');
952
+ const chatSend = document.getElementById('chatSend');
953
+ chatSend.addEventListener('click', sendMessage);
954
+ chatInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') sendMessage(); });
955
+
956
+ function sendMessage() {
957
+ const msg = chatInput.value.trim();
958
+ if (!msg) return;
959
+ chatBody.innerHTML += `<div class="chat-message user"><div class="chat-avatar">U</div><div class="chat-bubble">${msg}</div></div>`;
960
+ chatInput.value = '';
961
+ setTimeout(() => {
962
+ chatBody.innerHTML += `<div class="chat-message"><div class="chat-avatar">AI</div><div class="chat-bubble">This demo shows how we'd add conversational Q&A to each article. Great for deeper dives!</div></div>`;
963
+ chatBody.scrollTop = chatBody.scrollHeight;
964
+ }, 500);
965
+ chatBody.scrollTop = chatBody.scrollHeight;
966
+ }
967
+
968
+ const feedbackBtns = document.querySelectorAll('.feedback-btn');
969
+ const feedbackCount = document.querySelector('.feedback-count');
970
+ let count = 56;
971
+ feedbackBtns.forEach(btn => {
972
+ btn.addEventListener('click', () => {
973
+ feedbackBtns.forEach(b => b.classList.remove('active'));
974
+ btn.classList.add('active');
975
+ count++;
976
+ feedbackCount.textContent = `${count} developers found this helpful`;
977
+ gsap.to(btn, { scale: 1.1, duration: 0.1, yoyo: true, repeat: 1 });
978
+ });
979
+ });
980
+ </script>
981
+ </body>
982
+ </html>