@hustle-together/api-dev-tools 3.2.0 → 3.5.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 (45) hide show
  1. package/README.md +712 -377
  2. package/commands/api-create.md +300 -149
  3. package/demo/hustle-together/blog/gemini-vs-claude-widgets.html +1 -1
  4. package/demo/hustle-together/blog/interview-driven-api-development.html +1 -1
  5. package/demo/hustle-together/blog/tdd-for-ai.html +1 -1
  6. package/demo/hustle-together/index.html +2 -2
  7. package/demo/workflow-demo-v3.5-backup.html +5008 -0
  8. package/demo/workflow-demo.html +5137 -3805
  9. package/hooks/enforce-deep-research.py +6 -1
  10. package/hooks/enforce-disambiguation.py +7 -1
  11. package/hooks/enforce-documentation.py +6 -1
  12. package/hooks/enforce-environment.py +5 -1
  13. package/hooks/enforce-interview.py +5 -1
  14. package/hooks/enforce-refactor.py +3 -1
  15. package/hooks/enforce-schema.py +0 -0
  16. package/hooks/enforce-scope.py +5 -1
  17. package/hooks/enforce-tdd-red.py +5 -1
  18. package/hooks/enforce-verify.py +0 -0
  19. package/hooks/track-tool-use.py +167 -0
  20. package/hooks/verify-implementation.py +0 -0
  21. package/package.json +1 -1
  22. package/templates/api-dev-state.json +24 -0
  23. package/demo/audio/audio-sync.js +0 -295
  24. package/demo/audio/generate-all-narrations.js +0 -581
  25. package/demo/audio/generate-narration.js +0 -486
  26. package/demo/audio/generate-voice-previews.js +0 -140
  27. package/demo/audio/narration-adam-timing.json +0 -4675
  28. package/demo/audio/narration-adam.mp3 +0 -0
  29. package/demo/audio/narration-creature-timing.json +0 -4675
  30. package/demo/audio/narration-creature.mp3 +0 -0
  31. package/demo/audio/narration-gaming-timing.json +0 -4675
  32. package/demo/audio/narration-gaming.mp3 +0 -0
  33. package/demo/audio/narration-hope-timing.json +0 -4675
  34. package/demo/audio/narration-hope.mp3 +0 -0
  35. package/demo/audio/narration-mark-timing.json +0 -4675
  36. package/demo/audio/narration-mark.mp3 +0 -0
  37. package/demo/audio/narration-timing.json +0 -3614
  38. package/demo/audio/narration-timing.sample.json +0 -48
  39. package/demo/audio/narration.mp3 +0 -0
  40. package/demo/audio/previews/manifest.json +0 -30
  41. package/demo/audio/previews/preview-creature.mp3 +0 -0
  42. package/demo/audio/previews/preview-gaming.mp3 +0 -0
  43. package/demo/audio/previews/preview-hope.mp3 +0 -0
  44. package/demo/audio/previews/preview-mark.mp3 +0 -0
  45. package/demo/audio/voices-manifest.json +0 -50
@@ -1,295 +0,0 @@
1
- /**
2
- * Audio Sync Controller for Workflow Demo
3
- *
4
- * This module syncs audio narration with GSAP animations.
5
- * It uses word-level timestamps to trigger highlights at the right moment.
6
- *
7
- * Usage:
8
- * const sync = new AudioSyncController('audio/narration.mp3', 'audio/narration-timing.json');
9
- * await sync.init();
10
- * sync.play();
11
- */
12
-
13
- class AudioSyncController {
14
- constructor(audioUrl, timingUrl) {
15
- this.audioUrl = audioUrl;
16
- this.timingUrl = timingUrl;
17
- this.audio = null;
18
- this.timing = null;
19
- this.currentSection = null;
20
- this.highlightedElements = new Set();
21
- this.isPlaying = false;
22
- this.onSectionChange = null;
23
- this.onHighlight = null;
24
- this.onWordSpoken = null;
25
- }
26
-
27
- /**
28
- * Initialize the controller by loading audio and timing data
29
- */
30
- async init() {
31
- // Load timing data
32
- const response = await fetch(this.timingUrl);
33
- this.timing = await response.json();
34
-
35
- // Create audio element
36
- this.audio = new Audio(this.audioUrl);
37
- this.audio.preload = 'auto';
38
-
39
- // Set up event listeners
40
- this.audio.addEventListener('timeupdate', () => this.onTimeUpdate());
41
- this.audio.addEventListener('ended', () => this.onEnded());
42
- this.audio.addEventListener('play', () => { this.isPlaying = true; });
43
- this.audio.addEventListener('pause', () => { this.isPlaying = false; });
44
-
45
- // Wait for audio to be ready
46
- return new Promise((resolve, reject) => {
47
- this.audio.addEventListener('canplaythrough', () => resolve(), { once: true });
48
- this.audio.addEventListener('error', (e) => reject(e), { once: true });
49
- });
50
- }
51
-
52
- /**
53
- * Get the current playback time
54
- */
55
- get currentTime() {
56
- return this.audio ? this.audio.currentTime : 0;
57
- }
58
-
59
- /**
60
- * Get the total duration
61
- */
62
- get duration() {
63
- return this.audio ? this.audio.duration : 0;
64
- }
65
-
66
- /**
67
- * Play the audio
68
- */
69
- play() {
70
- if (this.audio) {
71
- this.audio.play();
72
- }
73
- }
74
-
75
- /**
76
- * Pause the audio
77
- */
78
- pause() {
79
- if (this.audio) {
80
- this.audio.pause();
81
- }
82
- }
83
-
84
- /**
85
- * Toggle play/pause
86
- */
87
- toggle() {
88
- if (this.isPlaying) {
89
- this.pause();
90
- } else {
91
- this.play();
92
- }
93
- }
94
-
95
- /**
96
- * Seek to a specific time
97
- */
98
- seek(time) {
99
- if (this.audio) {
100
- this.audio.currentTime = time;
101
- }
102
- }
103
-
104
- /**
105
- * Seek to a specific section
106
- */
107
- seekToSection(sectionId) {
108
- const section = this.timing.sections.find(s => s.id === sectionId);
109
- if (section) {
110
- this.seek(section.timestamp);
111
- }
112
- }
113
-
114
- /**
115
- * Handle time updates from the audio element
116
- */
117
- onTimeUpdate() {
118
- const currentTime = this.audio.currentTime;
119
-
120
- // Check for section changes
121
- const newSection = this.getCurrentSection(currentTime);
122
- if (newSection !== this.currentSection) {
123
- this.currentSection = newSection;
124
- this.clearHighlights();
125
- if (this.onSectionChange) {
126
- this.onSectionChange(newSection);
127
- }
128
- // Scroll to section
129
- this.scrollToSection(newSection);
130
- }
131
-
132
- // Check for highlights that should be active
133
- this.updateHighlights(currentTime);
134
-
135
- // Check for words being spoken
136
- if (this.onWordSpoken) {
137
- const currentWord = this.getCurrentWord(currentTime);
138
- if (currentWord) {
139
- this.onWordSpoken(currentWord);
140
- }
141
- }
142
- }
143
-
144
- /**
145
- * Get the current section based on time
146
- */
147
- getCurrentSection(time) {
148
- let current = null;
149
- for (const section of this.timing.sections) {
150
- if (section.timestamp <= time) {
151
- current = section.id;
152
- } else {
153
- break;
154
- }
155
- }
156
- return current;
157
- }
158
-
159
- /**
160
- * Get the current word being spoken
161
- */
162
- getCurrentWord(time) {
163
- for (const word of this.timing.words) {
164
- if (time >= word.start && time <= word.end) {
165
- return word;
166
- }
167
- }
168
- return null;
169
- }
170
-
171
- /**
172
- * Update highlights based on current time
173
- */
174
- updateHighlights(time) {
175
- // Get highlights that should be active (within 3 seconds of their timestamp)
176
- const HIGHLIGHT_DURATION = 3; // seconds
177
-
178
- for (const highlight of this.timing.highlights) {
179
- const isActive = time >= highlight.timestamp &&
180
- time < highlight.timestamp + HIGHLIGHT_DURATION;
181
-
182
- const wasHighlighted = this.highlightedElements.has(highlight.selector);
183
-
184
- if (isActive && !wasHighlighted) {
185
- // Add highlight
186
- this.applyHighlight(highlight.selector);
187
- this.highlightedElements.add(highlight.selector);
188
- if (this.onHighlight) {
189
- this.onHighlight(highlight.selector, true);
190
- }
191
- } else if (!isActive && wasHighlighted) {
192
- // Remove highlight
193
- this.removeHighlight(highlight.selector);
194
- this.highlightedElements.delete(highlight.selector);
195
- if (this.onHighlight) {
196
- this.onHighlight(highlight.selector, false);
197
- }
198
- }
199
- }
200
- }
201
-
202
- /**
203
- * Apply highlight animation to an element
204
- */
205
- applyHighlight(selector) {
206
- const elements = document.querySelectorAll(selector);
207
- elements.forEach(el => {
208
- // Add highlight class
209
- el.classList.add('audio-highlighted');
210
-
211
- // Use GSAP for smooth animation if available
212
- if (typeof gsap !== 'undefined') {
213
- gsap.to(el, {
214
- boxShadow: '0 0 30px var(--accent-red-glow), 0 0 60px var(--accent-red-glow)',
215
- borderColor: 'var(--accent-red)',
216
- scale: 1.02,
217
- duration: 0.3,
218
- ease: 'power2.out'
219
- });
220
- }
221
- });
222
- }
223
-
224
- /**
225
- * Remove highlight from an element
226
- */
227
- removeHighlight(selector) {
228
- const elements = document.querySelectorAll(selector);
229
- elements.forEach(el => {
230
- el.classList.remove('audio-highlighted');
231
-
232
- if (typeof gsap !== 'undefined') {
233
- gsap.to(el, {
234
- boxShadow: 'none',
235
- borderColor: 'var(--grey)',
236
- scale: 1,
237
- duration: 0.3,
238
- ease: 'power2.out'
239
- });
240
- }
241
- });
242
- }
243
-
244
- /**
245
- * Clear all highlights
246
- */
247
- clearHighlights() {
248
- for (const selector of this.highlightedElements) {
249
- this.removeHighlight(selector);
250
- }
251
- this.highlightedElements.clear();
252
- }
253
-
254
- /**
255
- * Scroll to a section
256
- */
257
- scrollToSection(sectionId) {
258
- const element = document.getElementById(sectionId);
259
- if (element) {
260
- element.scrollIntoView({ behavior: 'smooth', block: 'start' });
261
- }
262
- }
263
-
264
- /**
265
- * Handle audio ended
266
- */
267
- onEnded() {
268
- this.isPlaying = false;
269
- this.clearHighlights();
270
- if (this.onSectionChange) {
271
- this.onSectionChange(null);
272
- }
273
- }
274
-
275
- /**
276
- * Get all section timestamps for building a progress bar
277
- */
278
- getSectionMarkers() {
279
- return this.timing.sections.map(s => ({
280
- id: s.id,
281
- timestamp: s.timestamp,
282
- percentage: (s.timestamp / this.duration) * 100
283
- }));
284
- }
285
- }
286
-
287
- // Export for use in browser
288
- if (typeof window !== 'undefined') {
289
- window.AudioSyncController = AudioSyncController;
290
- }
291
-
292
- // Export for Node.js (if needed for testing)
293
- if (typeof module !== 'undefined') {
294
- module.exports = AudioSyncController;
295
- }