@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.
- package/.claude/adr-requests/.gitkeep +10 -0
- package/.claude/agents/adr-researcher.md +109 -0
- package/.claude/agents/visual-analyzer.md +183 -0
- package/.claude/api-dev-state.json +7 -463
- package/.claude/documentation-audit.json +114 -0
- package/.claude/registry.json +289 -0
- package/.claude/settings.json +45 -1
- package/.claude/workflow-logs/None.json +49 -0
- package/.claude/workflow-logs/session-20251230-143727.json +106 -0
- package/.skills/adr-deep-research/SKILL.md +351 -0
- package/.skills/api-create/SKILL.md +116 -17
- package/.skills/api-research/SKILL.md +130 -0
- package/.skills/docs-sync/SKILL.md +260 -0
- package/.skills/docs-update/SKILL.md +205 -0
- package/.skills/hustle-brand/SKILL.md +368 -0
- package/.skills/hustle-build/SKILL.md +786 -0
- package/.skills/hustle-build-review/SKILL.md +518 -0
- package/.skills/parallel-spawn/SKILL.md +212 -0
- package/.skills/ralph-continue/SKILL.md +151 -0
- package/.skills/ralph-loop/SKILL.md +341 -0
- package/.skills/ralph-status/SKILL.md +87 -0
- package/.skills/refactor/SKILL.md +59 -0
- package/.skills/shadcn/SKILL.md +522 -0
- package/.skills/test-all/SKILL.md +210 -0
- package/.skills/test-builds/SKILL.md +208 -0
- package/.skills/test-debug/SKILL.md +212 -0
- package/.skills/test-e2e/SKILL.md +168 -0
- package/.skills/test-review/SKILL.md +707 -0
- package/.skills/test-unit/SKILL.md +143 -0
- package/.skills/test-visual/SKILL.md +301 -0
- package/.skills/token-report/SKILL.md +132 -0
- package/CHANGELOG.md +575 -0
- package/README.md +426 -56
- package/bin/cli.js +1538 -88
- package/commands/hustle-api-create.md +22 -0
- package/commands/hustle-build.md +259 -0
- package/commands/hustle-combine.md +81 -2
- package/commands/hustle-ui-create-page.md +84 -2
- package/commands/hustle-ui-create.md +82 -2
- package/hooks/__pycache__/api-workflow-check.cpython-314.pyc +0 -0
- package/hooks/__pycache__/auto-answer.cpython-314.pyc +0 -0
- package/hooks/__pycache__/cache-research.cpython-314.pyc +0 -0
- package/hooks/__pycache__/check-api-routes.cpython-314.pyc +0 -0
- package/hooks/__pycache__/check-playwright-setup.cpython-314.pyc +0 -0
- package/hooks/__pycache__/check-storybook-setup.cpython-314.pyc +0 -0
- package/hooks/__pycache__/check-update.cpython-314.pyc +0 -0
- package/hooks/__pycache__/completion-promise-detector.cpython-314.pyc +0 -0
- package/hooks/__pycache__/context-capacity-warning.cpython-314.pyc +0 -0
- package/hooks/__pycache__/detect-interruption.cpython-314.pyc +0 -0
- package/hooks/__pycache__/docs-update-check.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-a11y-audit.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-brand-guide.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-component-type-confirm.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-deep-research.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-disambiguation.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-documentation.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-dry-run.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-environment.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-external-research.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-freshness.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-interview.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-page-components.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-page-data-schema.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-questions-sourced.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-refactor.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-research.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-schema-from-interview.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-schema.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-scope.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-tdd-red.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-ui-disambiguation.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-ui-interview.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-verify.cpython-314.pyc +0 -0
- package/hooks/__pycache__/generate-adr-options.cpython-314.pyc +0 -0
- package/hooks/__pycache__/generate-manifest-entry.cpython-314.pyc +0 -0
- package/hooks/__pycache__/hook_utils.cpython-314.pyc +0 -0
- package/hooks/__pycache__/notify-input-needed.cpython-314.pyc +0 -0
- package/hooks/__pycache__/notify-phase-complete.cpython-314.pyc +0 -0
- package/hooks/__pycache__/ntfy-on-question.cpython-314.pyc +0 -0
- package/hooks/__pycache__/orchestrator-completion.cpython-314.pyc +0 -0
- package/hooks/__pycache__/orchestrator-handoff.cpython-314.pyc +0 -0
- package/hooks/__pycache__/orchestrator-session-startup.cpython-314.pyc +0 -0
- package/hooks/__pycache__/parallel-orchestrator.cpython-314.pyc +0 -0
- package/hooks/__pycache__/periodic-reground.cpython-314.pyc +0 -0
- package/hooks/__pycache__/project-document-prompt.cpython-314.pyc +0 -0
- package/hooks/__pycache__/remote-question-proxy.cpython-314.pyc +0 -0
- package/hooks/__pycache__/remote-question-server.cpython-314.pyc +0 -0
- package/hooks/__pycache__/run-code-review.cpython-314.pyc +0 -0
- package/hooks/__pycache__/run-visual-qa.cpython-314.pyc +0 -0
- package/hooks/__pycache__/session-logger.cpython-314.pyc +0 -0
- package/hooks/__pycache__/session-startup.cpython-314.pyc +0 -0
- package/hooks/__pycache__/track-scope-coverage.cpython-314.pyc +0 -0
- package/hooks/__pycache__/track-token-usage.cpython-314.pyc +0 -0
- package/hooks/__pycache__/track-tool-use.cpython-314.pyc +0 -0
- package/hooks/__pycache__/update-adr-decision.cpython-314.pyc +0 -0
- package/hooks/__pycache__/update-api-showcase.cpython-314.pyc +0 -0
- package/hooks/__pycache__/update-registry.cpython-314.pyc +0 -0
- package/hooks/__pycache__/update-ui-showcase.cpython-314.pyc +0 -0
- package/hooks/__pycache__/verify-after-green.cpython-314.pyc +0 -0
- package/hooks/__pycache__/verify-implementation.cpython-314.pyc +0 -0
- package/hooks/api-workflow-check.py +34 -0
- package/hooks/auto-answer.py +305 -0
- package/hooks/check-update.py +132 -0
- package/hooks/completion-promise-detector.py +293 -0
- package/hooks/context-capacity-warning.py +171 -0
- package/hooks/docs-update-check.py +120 -0
- package/hooks/enforce-dry-run.py +134 -0
- package/hooks/enforce-external-research.py +25 -0
- package/hooks/enforce-interview.py +20 -0
- package/hooks/generate-adr-options.py +282 -0
- package/hooks/hook_utils.py +609 -0
- package/hooks/lib/__pycache__/__init__.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/greptile.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/ntfy.cpython-314.pyc +0 -0
- package/hooks/ntfy-on-question.py +240 -0
- package/hooks/orchestrator-completion.py +313 -0
- package/hooks/orchestrator-handoff.py +267 -0
- package/hooks/orchestrator-session-startup.py +146 -0
- package/hooks/parallel-orchestrator.py +451 -0
- package/hooks/periodic-reground.py +270 -67
- package/hooks/project-document-prompt.py +302 -0
- package/hooks/remote-question-proxy.py +284 -0
- package/hooks/remote-question-server.py +1224 -0
- package/hooks/run-code-review.py +176 -29
- package/hooks/run-visual-qa.py +338 -0
- package/hooks/session-logger.py +27 -1
- package/hooks/session-startup.py +113 -0
- package/hooks/update-adr-decision.py +236 -0
- package/hooks/update-api-showcase.py +13 -1
- package/hooks/update-testing-checklist.py +195 -0
- package/hooks/update-ui-showcase.py +13 -1
- package/package.json +7 -3
- package/scripts/extract-schema-docs.cjs +322 -0
- package/templates/.skills/hustle-interview/SKILL.md +174 -0
- package/templates/CLAUDE-SECTION.md +89 -64
- package/templates/adr-viewer/_components/ADRViewer.tsx +326 -0
- package/templates/api-dev-state.json +33 -1
- package/templates/api-showcase/_components/APIModal.tsx +100 -8
- package/templates/api-showcase/_components/APIShowcase.tsx +36 -4
- package/templates/api-showcase/_components/APITester.tsx +367 -58
- package/templates/brand-page/page.tsx +645 -0
- package/templates/component/Component.visual.spec.ts +30 -24
- package/templates/docs/page.tsx +230 -0
- package/templates/eslint-plugin-zod-schema/index.js +446 -0
- package/templates/eslint-plugin-zod-schema/package.json +26 -0
- package/templates/github-workflows/security.yml +274 -0
- package/templates/hustle-build-defaults.json +136 -0
- package/templates/hustle-dev-dashboard/page.tsx +365 -0
- package/templates/page/page.e2e.test.ts +30 -26
- package/templates/performance-budgets.json +63 -5
- package/templates/playwright-report/page.tsx +258 -0
- package/templates/registry.json +279 -3
- package/templates/review-dashboard/page.tsx +510 -0
- package/templates/settings.json +155 -7
- package/templates/test-results/page.tsx +237 -0
- package/templates/typedoc.json +19 -0
- package/templates/ui-showcase/_components/UIShowcase.tsx +48 -1
- package/templates/ui-showcase/_components/VisualTestingDashboard.tsx +579 -0
- 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)
|