@hustle-together/api-dev-tools 3.12.3 → 4.5.1

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 (159) hide show
  1. package/.claude/adr-requests/.gitkeep +10 -0
  2. package/.claude/agents/adr-researcher.md +109 -0
  3. package/.claude/agents/visual-analyzer.md +183 -0
  4. package/.claude/api-dev-state.json +7 -463
  5. package/.claude/documentation-audit.json +114 -0
  6. package/.claude/registry.json +289 -0
  7. package/.claude/settings.json +45 -1
  8. package/.claude/workflow-logs/None.json +49 -0
  9. package/.claude/workflow-logs/session-20251230-143727.json +106 -0
  10. package/.skills/adr-deep-research/SKILL.md +351 -0
  11. package/.skills/api-create/SKILL.md +116 -17
  12. package/.skills/api-research/SKILL.md +130 -0
  13. package/.skills/docs-sync/SKILL.md +260 -0
  14. package/.skills/docs-update/SKILL.md +205 -0
  15. package/.skills/hustle-brand/SKILL.md +368 -0
  16. package/.skills/hustle-build/SKILL.md +786 -0
  17. package/.skills/hustle-build-review/SKILL.md +518 -0
  18. package/.skills/parallel-spawn/SKILL.md +212 -0
  19. package/.skills/ralph-continue/SKILL.md +151 -0
  20. package/.skills/ralph-loop/SKILL.md +341 -0
  21. package/.skills/ralph-status/SKILL.md +87 -0
  22. package/.skills/refactor/SKILL.md +59 -0
  23. package/.skills/shadcn/SKILL.md +522 -0
  24. package/.skills/test-all/SKILL.md +210 -0
  25. package/.skills/test-builds/SKILL.md +208 -0
  26. package/.skills/test-debug/SKILL.md +212 -0
  27. package/.skills/test-e2e/SKILL.md +168 -0
  28. package/.skills/test-review/SKILL.md +707 -0
  29. package/.skills/test-unit/SKILL.md +143 -0
  30. package/.skills/test-visual/SKILL.md +301 -0
  31. package/.skills/token-report/SKILL.md +132 -0
  32. package/CHANGELOG.md +575 -0
  33. package/README.md +426 -56
  34. package/bin/cli.js +1538 -88
  35. package/commands/hustle-api-create.md +22 -0
  36. package/commands/hustle-build.md +259 -0
  37. package/commands/hustle-combine.md +81 -2
  38. package/commands/hustle-ui-create-page.md +84 -2
  39. package/commands/hustle-ui-create.md +82 -2
  40. package/hooks/__pycache__/api-workflow-check.cpython-314.pyc +0 -0
  41. package/hooks/__pycache__/auto-answer.cpython-314.pyc +0 -0
  42. package/hooks/__pycache__/cache-research.cpython-314.pyc +0 -0
  43. package/hooks/__pycache__/check-api-routes.cpython-314.pyc +0 -0
  44. package/hooks/__pycache__/check-playwright-setup.cpython-314.pyc +0 -0
  45. package/hooks/__pycache__/check-storybook-setup.cpython-314.pyc +0 -0
  46. package/hooks/__pycache__/check-update.cpython-314.pyc +0 -0
  47. package/hooks/__pycache__/completion-promise-detector.cpython-314.pyc +0 -0
  48. package/hooks/__pycache__/context-capacity-warning.cpython-314.pyc +0 -0
  49. package/hooks/__pycache__/detect-interruption.cpython-314.pyc +0 -0
  50. package/hooks/__pycache__/docs-update-check.cpython-314.pyc +0 -0
  51. package/hooks/__pycache__/enforce-a11y-audit.cpython-314.pyc +0 -0
  52. package/hooks/__pycache__/enforce-brand-guide.cpython-314.pyc +0 -0
  53. package/hooks/__pycache__/enforce-component-type-confirm.cpython-314.pyc +0 -0
  54. package/hooks/__pycache__/enforce-deep-research.cpython-314.pyc +0 -0
  55. package/hooks/__pycache__/enforce-disambiguation.cpython-314.pyc +0 -0
  56. package/hooks/__pycache__/enforce-documentation.cpython-314.pyc +0 -0
  57. package/hooks/__pycache__/enforce-dry-run.cpython-314.pyc +0 -0
  58. package/hooks/__pycache__/enforce-environment.cpython-314.pyc +0 -0
  59. package/hooks/__pycache__/enforce-external-research.cpython-314.pyc +0 -0
  60. package/hooks/__pycache__/enforce-freshness.cpython-314.pyc +0 -0
  61. package/hooks/__pycache__/enforce-interview.cpython-314.pyc +0 -0
  62. package/hooks/__pycache__/enforce-page-components.cpython-314.pyc +0 -0
  63. package/hooks/__pycache__/enforce-page-data-schema.cpython-314.pyc +0 -0
  64. package/hooks/__pycache__/enforce-questions-sourced.cpython-314.pyc +0 -0
  65. package/hooks/__pycache__/enforce-refactor.cpython-314.pyc +0 -0
  66. package/hooks/__pycache__/enforce-research.cpython-314.pyc +0 -0
  67. package/hooks/__pycache__/enforce-schema-from-interview.cpython-314.pyc +0 -0
  68. package/hooks/__pycache__/enforce-schema.cpython-314.pyc +0 -0
  69. package/hooks/__pycache__/enforce-scope.cpython-314.pyc +0 -0
  70. package/hooks/__pycache__/enforce-tdd-red.cpython-314.pyc +0 -0
  71. package/hooks/__pycache__/enforce-ui-disambiguation.cpython-314.pyc +0 -0
  72. package/hooks/__pycache__/enforce-ui-interview.cpython-314.pyc +0 -0
  73. package/hooks/__pycache__/enforce-verify.cpython-314.pyc +0 -0
  74. package/hooks/__pycache__/generate-adr-options.cpython-314.pyc +0 -0
  75. package/hooks/__pycache__/generate-manifest-entry.cpython-314.pyc +0 -0
  76. package/hooks/__pycache__/hook_utils.cpython-314.pyc +0 -0
  77. package/hooks/__pycache__/notify-input-needed.cpython-314.pyc +0 -0
  78. package/hooks/__pycache__/notify-phase-complete.cpython-314.pyc +0 -0
  79. package/hooks/__pycache__/ntfy-on-question.cpython-314.pyc +0 -0
  80. package/hooks/__pycache__/orchestrator-completion.cpython-314.pyc +0 -0
  81. package/hooks/__pycache__/orchestrator-handoff.cpython-314.pyc +0 -0
  82. package/hooks/__pycache__/orchestrator-session-startup.cpython-314.pyc +0 -0
  83. package/hooks/__pycache__/parallel-orchestrator.cpython-314.pyc +0 -0
  84. package/hooks/__pycache__/periodic-reground.cpython-314.pyc +0 -0
  85. package/hooks/__pycache__/project-document-prompt.cpython-314.pyc +0 -0
  86. package/hooks/__pycache__/remote-question-proxy.cpython-314.pyc +0 -0
  87. package/hooks/__pycache__/remote-question-server.cpython-314.pyc +0 -0
  88. package/hooks/__pycache__/run-code-review.cpython-314.pyc +0 -0
  89. package/hooks/__pycache__/run-visual-qa.cpython-314.pyc +0 -0
  90. package/hooks/__pycache__/session-logger.cpython-314.pyc +0 -0
  91. package/hooks/__pycache__/session-startup.cpython-314.pyc +0 -0
  92. package/hooks/__pycache__/track-scope-coverage.cpython-314.pyc +0 -0
  93. package/hooks/__pycache__/track-token-usage.cpython-314.pyc +0 -0
  94. package/hooks/__pycache__/track-tool-use.cpython-314.pyc +0 -0
  95. package/hooks/__pycache__/update-adr-decision.cpython-314.pyc +0 -0
  96. package/hooks/__pycache__/update-api-showcase.cpython-314.pyc +0 -0
  97. package/hooks/__pycache__/update-registry.cpython-314.pyc +0 -0
  98. package/hooks/__pycache__/update-ui-showcase.cpython-314.pyc +0 -0
  99. package/hooks/__pycache__/verify-after-green.cpython-314.pyc +0 -0
  100. package/hooks/__pycache__/verify-implementation.cpython-314.pyc +0 -0
  101. package/hooks/api-workflow-check.py +34 -0
  102. package/hooks/auto-answer.py +305 -0
  103. package/hooks/check-update.py +132 -0
  104. package/hooks/completion-promise-detector.py +293 -0
  105. package/hooks/context-capacity-warning.py +171 -0
  106. package/hooks/docs-update-check.py +120 -0
  107. package/hooks/enforce-dry-run.py +134 -0
  108. package/hooks/enforce-external-research.py +25 -0
  109. package/hooks/enforce-interview.py +20 -0
  110. package/hooks/generate-adr-options.py +282 -0
  111. package/hooks/hook_utils.py +609 -0
  112. package/hooks/lib/__pycache__/__init__.cpython-314.pyc +0 -0
  113. package/hooks/lib/__pycache__/greptile.cpython-314.pyc +0 -0
  114. package/hooks/lib/__pycache__/ntfy.cpython-314.pyc +0 -0
  115. package/hooks/ntfy-on-question.py +240 -0
  116. package/hooks/orchestrator-completion.py +313 -0
  117. package/hooks/orchestrator-handoff.py +267 -0
  118. package/hooks/orchestrator-session-startup.py +146 -0
  119. package/hooks/parallel-orchestrator.py +451 -0
  120. package/hooks/periodic-reground.py +270 -67
  121. package/hooks/project-document-prompt.py +302 -0
  122. package/hooks/remote-question-proxy.py +284 -0
  123. package/hooks/remote-question-server.py +1224 -0
  124. package/hooks/run-code-review.py +176 -29
  125. package/hooks/run-visual-qa.py +338 -0
  126. package/hooks/session-logger.py +27 -1
  127. package/hooks/session-startup.py +113 -0
  128. package/hooks/update-adr-decision.py +236 -0
  129. package/hooks/update-api-showcase.py +13 -1
  130. package/hooks/update-testing-checklist.py +195 -0
  131. package/hooks/update-ui-showcase.py +13 -1
  132. package/package.json +7 -3
  133. package/scripts/extract-schema-docs.cjs +322 -0
  134. package/templates/.skills/hustle-interview/SKILL.md +174 -0
  135. package/templates/CLAUDE-SECTION.md +89 -64
  136. package/templates/adr-viewer/_components/ADRViewer.tsx +326 -0
  137. package/templates/api-dev-state.json +33 -1
  138. package/templates/api-showcase/_components/APIModal.tsx +100 -8
  139. package/templates/api-showcase/_components/APIShowcase.tsx +36 -4
  140. package/templates/api-showcase/_components/APITester.tsx +367 -58
  141. package/templates/brand-page/page.tsx +645 -0
  142. package/templates/component/Component.visual.spec.ts +30 -24
  143. package/templates/docs/page.tsx +230 -0
  144. package/templates/eslint-plugin-zod-schema/index.js +446 -0
  145. package/templates/eslint-plugin-zod-schema/package.json +26 -0
  146. package/templates/github-workflows/security.yml +274 -0
  147. package/templates/hustle-build-defaults.json +136 -0
  148. package/templates/hustle-dev-dashboard/page.tsx +365 -0
  149. package/templates/page/page.e2e.test.ts +30 -26
  150. package/templates/performance-budgets.json +63 -5
  151. package/templates/playwright-report/page.tsx +258 -0
  152. package/templates/registry.json +279 -3
  153. package/templates/review-dashboard/page.tsx +510 -0
  154. package/templates/settings.json +155 -7
  155. package/templates/test-results/page.tsx +237 -0
  156. package/templates/typedoc.json +19 -0
  157. package/templates/ui-showcase/_components/UIShowcase.tsx +48 -1
  158. package/templates/ui-showcase/_components/VisualTestingDashboard.tsx +579 -0
  159. package/templates/ui-showcase/page.tsx +1 -1
@@ -0,0 +1,322 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Extract Schema Documentation
4
+ *
5
+ * Parses Zod schema files and extracts parameter documentation
6
+ * for use in the API Showcase registry.
7
+ *
8
+ * Usage:
9
+ * node scripts/extract-schema-docs.cjs <schema-file-path>
10
+ *
11
+ * Output: JSON with parameter documentation
12
+ *
13
+ * Example:
14
+ * node scripts/extract-schema-docs.cjs src/lib/schemas/unsplash.ts
15
+ *
16
+ * Created with Hustle API Dev Tools (v3.12.10)
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+
22
+ /**
23
+ * Parse Zod schema file and extract documentation
24
+ * Uses regex-based parsing since we can't import TypeScript directly
25
+ */
26
+ function parseZodSchema(filePath) {
27
+ const content = fs.readFileSync(filePath, 'utf-8');
28
+
29
+ const result = {
30
+ file: filePath,
31
+ actions: [],
32
+ schemas: {},
33
+ enums: {},
34
+ constants: {}
35
+ };
36
+
37
+ // Extract action enum values
38
+ const actionMatch = content.match(/ActionSchema\s*=\s*z\.enum\(\[([^\]]+)\]\)/);
39
+ if (actionMatch) {
40
+ result.actions = actionMatch[1]
41
+ .split(',')
42
+ .map(s => s.trim().replace(/['"]/g, ''))
43
+ .filter(Boolean);
44
+ }
45
+
46
+ // Extract all enums
47
+ const enumRegex = /export const (\w+Schema)\s*=\s*z\.enum\(\[([^\]]+)\]\)/g;
48
+ let enumMatch;
49
+ while ((enumMatch = enumRegex.exec(content)) !== null) {
50
+ const name = enumMatch[1].replace('Schema', '');
51
+ const values = enumMatch[2]
52
+ .split(',')
53
+ .map(s => s.trim().replace(/['"]/g, ''))
54
+ .filter(Boolean);
55
+ result.enums[name] = values;
56
+ }
57
+
58
+ // Extract request schemas (z.object definitions)
59
+ const schemaRegex = /export const (\w+RequestSchema)\s*=\s*z\s*\.?\s*object\(\{([^}]+(?:\{[^}]*\}[^}]*)*)\}\)/gs;
60
+ let schemaMatch;
61
+
62
+ while ((schemaMatch = schemaRegex.exec(content)) !== null) {
63
+ const schemaName = schemaMatch[1];
64
+ const schemaBody = schemaMatch[2];
65
+ const params = parseSchemaParams(schemaBody, result.enums);
66
+
67
+ // Get action name from schema name (e.g., SearchRequestSchema -> search)
68
+ const actionName = schemaName
69
+ .replace('RequestSchema', '')
70
+ .replace(/([A-Z])/g, (m, p1, offset) => offset > 0 ? '_' + p1.toLowerCase() : p1.toLowerCase())
71
+ .replace(/^_/, '');
72
+
73
+ result.schemas[actionName] = {
74
+ name: schemaName,
75
+ params: params
76
+ };
77
+ }
78
+
79
+ return result;
80
+ }
81
+
82
+ /**
83
+ * Parse individual parameters from schema body
84
+ */
85
+ function parseSchemaParams(schemaBody, enums) {
86
+ const params = [];
87
+
88
+ // Split by lines and process each field
89
+ const lines = schemaBody.split('\n');
90
+
91
+ for (const line of lines) {
92
+ // Match field definitions like: fieldName: z.string().min(1)...
93
+ const fieldMatch = line.match(/^\s*(\w+)\s*:\s*z\.(.+)/);
94
+ if (!fieldMatch) continue;
95
+
96
+ const [, name, definition] = fieldMatch;
97
+ const param = {
98
+ name,
99
+ type: 'string',
100
+ required: true,
101
+ description: '',
102
+ default: null,
103
+ enum: null
104
+ };
105
+
106
+ // Determine type
107
+ if (definition.includes('string()') || definition.includes('literal(')) {
108
+ param.type = 'string';
109
+ } else if (definition.includes('number()') || definition.includes('coerce.number()')) {
110
+ param.type = 'number';
111
+ } else if (definition.includes('boolean()') || definition.includes('coerce.boolean()')) {
112
+ param.type = 'boolean';
113
+ } else if (definition.includes('array(')) {
114
+ param.type = 'array';
115
+ }
116
+
117
+ // Check if it references an enum
118
+ for (const [enumName, enumValues] of Object.entries(enums)) {
119
+ if (definition.includes(enumName + 'Schema')) {
120
+ param.type = 'enum';
121
+ param.enum = enumValues;
122
+ break;
123
+ }
124
+ }
125
+
126
+ // Check if optional
127
+ if (definition.includes('.optional()')) {
128
+ param.required = false;
129
+ }
130
+
131
+ // Extract default value
132
+ const defaultMatch = definition.match(/\.default\(([^)]+)\)/);
133
+ if (defaultMatch) {
134
+ let defaultVal = defaultMatch[1].trim();
135
+ // Parse the default value
136
+ if (defaultVal === 'true') param.default = true;
137
+ else if (defaultVal === 'false') param.default = false;
138
+ else if (/^\d+$/.test(defaultVal)) param.default = parseInt(defaultVal);
139
+ else param.default = defaultVal.replace(/['"]/g, '');
140
+ param.required = false;
141
+ }
142
+
143
+ // Extract description from validation messages
144
+ const descMatch = definition.match(/['"]([^'"]+is required|[^'"]+too long|[^'"]+invalid)['"]/i);
145
+ if (descMatch) {
146
+ param.description = descMatch[1];
147
+ }
148
+
149
+ // Extract min/max for numbers
150
+ const minMatch = definition.match(/\.min\((\d+)/);
151
+ const maxMatch = definition.match(/\.max\((\d+)/);
152
+ if (minMatch) param.min = parseInt(minMatch[1]);
153
+ if (maxMatch) param.max = parseInt(maxMatch[1]);
154
+
155
+ params.push(param);
156
+ }
157
+
158
+ return params;
159
+ }
160
+
161
+ /**
162
+ * Generate example value for a parameter
163
+ */
164
+ function generateExample(param) {
165
+ if (param.default !== null && param.default !== undefined) {
166
+ return param.default;
167
+ }
168
+
169
+ if (param.enum && param.enum.length > 0) {
170
+ return param.enum[0];
171
+ }
172
+
173
+ // Generate based on param name and type
174
+ const name = param.name.toLowerCase();
175
+
176
+ if (param.type === 'number') {
177
+ if (param.min !== undefined) return param.min;
178
+ if (name.includes('page')) return 1;
179
+ if (name.includes('per_page') || name.includes('count')) return 10;
180
+ return 10;
181
+ }
182
+
183
+ if (param.type === 'boolean') {
184
+ return true;
185
+ }
186
+
187
+ // String examples based on common param names
188
+ if (name === 'query' || name === 'q' || name === 'search') return 'nature sunset';
189
+ if (name.includes('id')) return 'abc123';
190
+ if (name.includes('url')) return 'https://example.com';
191
+ if (name.includes('color')) return 'blue';
192
+ if (name.includes('orientation')) return 'landscape';
193
+ if (name.includes('size')) return 'regular';
194
+
195
+ return 'example';
196
+ }
197
+
198
+ /**
199
+ * Generate working examples for an endpoint
200
+ */
201
+ function generateExamples(action, params, apiId) {
202
+ const examples = {};
203
+ const baseUrl = `http://localhost:3000/api/v2/${apiId}`;
204
+
205
+ // Build query parts from required params
206
+ const buildQuery = (includeOptional = false) => {
207
+ const parts = [`action=${action}`];
208
+ for (const param of params) {
209
+ if (param.name === 'action') continue;
210
+ if (param.required || includeOptional) {
211
+ const val = generateExample(param);
212
+ if (val !== null && val !== undefined) {
213
+ parts.push(`${param.name}=${encodeURIComponent(String(val))}`);
214
+ }
215
+ }
216
+ }
217
+ return parts.join('&');
218
+ };
219
+
220
+ // Basic example with required params only
221
+ const basicQuery = buildQuery(false);
222
+ examples.basic = {
223
+ description: `Basic ${action} request`,
224
+ query: basicQuery,
225
+ curl: `curl -X GET '${baseUrl}?${basicQuery}'`
226
+ };
227
+
228
+ // Full example with all params
229
+ const fullQuery = buildQuery(true);
230
+ if (fullQuery !== basicQuery) {
231
+ examples.full = {
232
+ description: `${action} with all parameters`,
233
+ query: fullQuery,
234
+ curl: `curl -X GET '${baseUrl}?${fullQuery}'`
235
+ };
236
+ }
237
+
238
+ // If there are enums, generate examples for each enum value
239
+ for (const param of params) {
240
+ if (param.enum && param.enum.length > 1) {
241
+ for (const enumVal of param.enum.slice(0, 3)) { // First 3 enum values
242
+ const enumQuery = basicQuery.replace(
243
+ `${param.name}=${encodeURIComponent(String(generateExample(param)))}`,
244
+ `${param.name}=${encodeURIComponent(enumVal)}`
245
+ );
246
+ // Only add if different from basic
247
+ if (enumQuery !== basicQuery) {
248
+ examples[`${param.name}_${enumVal}`] = {
249
+ description: `${action} with ${param.name}=${enumVal}`,
250
+ query: enumQuery,
251
+ curl: `curl -X GET '${baseUrl}?${enumQuery}'`
252
+ };
253
+ }
254
+ }
255
+ }
256
+ }
257
+
258
+ return examples;
259
+ }
260
+
261
+ /**
262
+ * Format output for registry.json
263
+ */
264
+ function formatForRegistry(parsed, apiId = 'api') {
265
+ const endpoints = {};
266
+
267
+ for (const [action, schema] of Object.entries(parsed.schemas)) {
268
+ const params = schema.params.map(p => ({
269
+ name: p.name,
270
+ type: p.type,
271
+ required: p.required,
272
+ description: p.description || `The ${p.name} parameter`,
273
+ default: p.default,
274
+ enum: p.enum,
275
+ min: p.min,
276
+ max: p.max,
277
+ example: String(generateExample(p))
278
+ })).filter(p => p.name !== 'action'); // Filter out the action param itself
279
+
280
+ endpoints[action] = {
281
+ method: 'GET', // Default, could be enhanced
282
+ description: `${action.charAt(0).toUpperCase() + action.slice(1).replace(/_/g, ' ')} action`,
283
+ params: params,
284
+ examples: generateExamples(action, params, apiId)
285
+ };
286
+ }
287
+
288
+ return {
289
+ actions: parsed.actions,
290
+ endpoints: endpoints
291
+ };
292
+ }
293
+
294
+ // Main execution
295
+ if (require.main === module) {
296
+ const args = process.argv.slice(2);
297
+
298
+ if (args.length === 0) {
299
+ console.error('Usage: node extract-schema-docs.cjs <schema-file-path> [api-id]');
300
+ console.error(' api-id: Optional API identifier for generating curl examples (e.g., "unsplash")');
301
+ process.exit(1);
302
+ }
303
+
304
+ const schemaPath = args[0];
305
+ const apiId = args[1] || path.basename(schemaPath, '.ts');
306
+
307
+ if (!fs.existsSync(schemaPath)) {
308
+ console.error(`File not found: ${schemaPath}`);
309
+ process.exit(1);
310
+ }
311
+
312
+ try {
313
+ const parsed = parseZodSchema(schemaPath);
314
+ const formatted = formatForRegistry(parsed, apiId);
315
+ console.log(JSON.stringify(formatted, null, 2));
316
+ } catch (error) {
317
+ console.error('Error parsing schema:', error.message);
318
+ process.exit(1);
319
+ }
320
+ }
321
+
322
+ module.exports = { parseZodSchema, formatForRegistry, generateExample, generateExamples };
@@ -0,0 +1,174 @@
1
+ ---
2
+ name: hustle-interview
3
+ description: Comprehensive project interview for /hustle-build orchestrator - establishes project-wide defaults stored in registry
4
+ version: 1.0.0
5
+ ---
6
+
7
+ # Hustle Interview - Project Configuration
8
+
9
+ This skill conducts a comprehensive interview to understand the project scope and establish defaults that persist in the registry for all future workflows.
10
+
11
+ ## When to Use
12
+
13
+ - First time using /hustle-build on a project
14
+ - When orchestrator_defaults in registry.json are empty/null
15
+ - When user wants to reconfigure project defaults
16
+
17
+ ## Interview Sections
18
+
19
+ ### Section 1: Project Overview
20
+
21
+ Ask about:
22
+ 1. **Project Type**: What kind of application? (SaaS, E-commerce, Dashboard, Marketing, Blog, Custom)
23
+ 2. **Target Audience**: Who uses this? (Developers, Business users, General public, Internal team)
24
+ 3. **Scale**: Expected users/traffic? (MVP/Prototype, Small <1k, Medium <10k, Large >10k)
25
+
26
+ ### Section 2: Technical Stack Confirmation
27
+
28
+ Verify and record:
29
+ 1. **Framework**: Next.js version (App Router or Pages Router?)
30
+ 2. **Styling**: Tailwind CSS, CSS Modules, Styled Components, Emotion, Vanilla CSS?
31
+ 3. **State Management**: React Context, Zustand, Redux, Jotai, None?
32
+ 4. **Database**: Supabase, Firebase, Prisma+PostgreSQL, MongoDB, None?
33
+
34
+ ### Section 3: Error Handling Philosophy
35
+
36
+ Ask ONE question with options:
37
+ - **try-catch-rethrow**: Traditional try/catch with custom error classes
38
+ - **error-boundary**: React error boundaries with fallback UI
39
+ - **result-type**: Rust-style Result<T, E> pattern (neverthrow library)
40
+ - **error-codes**: Return error codes with lookup table
41
+
42
+ Store choice in: `registry.orchestrator_defaults.error_handling.style`
43
+
44
+ ### Section 4: Authentication Pattern
45
+
46
+ Ask ONE question with options:
47
+ - **jwt**: JSON Web Tokens (stateless, API-friendly)
48
+ - **session**: Server-side sessions (traditional, secure)
49
+ - **api-key**: API key authentication (for services/integrations)
50
+ - **oauth**: OAuth 2.0 / OpenID Connect (social login)
51
+ - **none**: No authentication needed
52
+
53
+ Store choice in: `registry.orchestrator_defaults.authentication.method`
54
+
55
+ ### Section 5: Logging & Observability
56
+
57
+ Ask ONE question with options:
58
+ - **verbose**: Log everything (development mode)
59
+ - **standard**: Log errors + warnings + key events
60
+ - **minimal**: Log errors only
61
+ - **none**: No logging (not recommended)
62
+
63
+ Store choice in: `registry.orchestrator_defaults.logging.level`
64
+
65
+ ### Section 6: API Versioning Strategy
66
+
67
+ Ask ONE question with options:
68
+ - **url-prefix**: `/api/v1/`, `/api/v2/` (most common)
69
+ - **header**: `Accept: application/vnd.api.v1+json`
70
+ - **query-param**: `?version=1`
71
+ - **none**: No versioning (single version)
72
+
73
+ Store choice in: `registry.orchestrator_defaults.api_versioning.strategy`
74
+
75
+ ### Section 7: Testing Preferences
76
+
77
+ Ask about:
78
+ 1. **Coverage threshold**: What % code coverage is required? (50%, 70%, 80%, 90%)
79
+ 2. **Visual viewports**: Which devices matter most? (Confirm all 7 or subset)
80
+ 3. **E2E browsers**: Chrome only, or Chrome + Firefox + Safari?
81
+
82
+ Store in: `registry.orchestrator_defaults.testing`
83
+
84
+ ### Section 8: Platform Targets
85
+
86
+ Ask ONE question:
87
+ - **web-only**: Just web browser (PWA optional)
88
+ - **web-plus-desktop**: Web + Tauri for Windows/Mac/Linux
89
+ - **web-plus-mobile**: Web + Capacitor for iOS/Android
90
+ - **full-cross-platform**: Web + Desktop + Mobile
91
+
92
+ Store choice in: `registry.orchestrator_defaults.platform_targets`
93
+
94
+ Note: If desktop/mobile selected, prompt for setup during first build.
95
+
96
+ ### Section 9: Styling Approach
97
+
98
+ Already captured in Section 2, but confirm:
99
+ - **tailwind**: Utility-first CSS (default for Hustle)
100
+ - **css-modules**: Scoped CSS with .module.css
101
+ - **styled-components**: CSS-in-JS with tagged templates
102
+ - **emotion**: CSS-in-JS with object styles
103
+ - **vanilla**: Plain CSS files
104
+
105
+ Store in: `registry.orchestrator_defaults.styling.approach`
106
+
107
+ ## Output
108
+
109
+ After interview completes:
110
+
111
+ 1. Update `.claude/registry.json` with all choices
112
+ 2. Display summary of all decisions
113
+ 3. Confirm with user before saving
114
+ 4. Note that these can be changed anytime with `/hustle-interview`
115
+
116
+ ## Example Summary Output
117
+
118
+ ```
119
+ ╔═══════════════════════════════════════════════════════════════╗
120
+ ║ PROJECT CONFIGURATION ║
121
+ ╠═══════════════════════════════════════════════════════════════╣
122
+ ║ Project Type: SaaS Dashboard ║
123
+ ║ Framework: Next.js 15 (App Router) ║
124
+ ║ Styling: Tailwind CSS ║
125
+ ║ State: Zustand ║
126
+ ║ Database: Supabase ║
127
+ ╠───────────────────────────────────────────────────────────────╣
128
+ ║ Error Handling: Result Type (neverthrow) ║
129
+ ║ Authentication: JWT ║
130
+ ║ Logging: Standard ║
131
+ ║ API Versioning: URL Prefix (/api/v1/) ║
132
+ ╠───────────────────────────────────────────────────────────────╣
133
+ ║ Coverage Target: 80% ║
134
+ ║ Visual Viewports: All 7 ║
135
+ ║ E2E Browsers: Chrome + Firefox + WebKit ║
136
+ ║ Platform Target: Web Only ║
137
+ ╚═══════════════════════════════════════════════════════════════╝
138
+
139
+ These settings will be used for ALL future /hustle-build workflows.
140
+ Run /hustle-interview anytime to change them.
141
+ ```
142
+
143
+ ## Registry Integration
144
+
145
+ The skill MUST update the registry file after user confirmation:
146
+
147
+ ```json
148
+ {
149
+ "orchestrator_defaults": {
150
+ "project_type": "saas",
151
+ "error_handling": { "style": "result-type" },
152
+ "authentication": { "method": "jwt" },
153
+ "logging": { "level": "standard" },
154
+ "api_versioning": { "strategy": "url-prefix" },
155
+ "testing": {
156
+ "coverage_threshold": 80,
157
+ "visual_viewports": ["all"],
158
+ "e2e_browsers": ["chromium", "firefox", "webkit"]
159
+ },
160
+ "platform_targets": "web-only",
161
+ "styling": { "approach": "tailwind" },
162
+ "configured_at": "2025-12-29T12:00:00Z",
163
+ "configured_by": "hustle-interview"
164
+ }
165
+ }
166
+ ```
167
+
168
+ ## Re-running the Interview
169
+
170
+ Users can run `/hustle-interview` anytime to:
171
+ - View current settings
172
+ - Modify specific settings
173
+ - Reset all settings
174
+ - Export settings (for team sharing)