@hustle-together/api-dev-tools 3.12.3 → 3.12.16
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/commands/hustle-build.md +259 -0
- package/.claude/commands/hustle-combine.md +1089 -0
- package/.claude/commands/hustle-ui-create-page.md +1078 -0
- package/.claude/commands/hustle-ui-create.md +1058 -0
- package/.claude/hooks/auto-answer.py +305 -0
- package/.claude/hooks/cache-research.py +337 -0
- package/.claude/hooks/check-api-routes.py +168 -0
- package/.claude/hooks/check-playwright-setup.py +103 -0
- package/.claude/hooks/check-storybook-setup.py +81 -0
- package/.claude/hooks/check-update.py +132 -0
- package/.claude/hooks/completion-promise-detector.py +293 -0
- package/.claude/hooks/context-capacity-warning.py +171 -0
- package/.claude/hooks/detect-interruption.py +165 -0
- package/.claude/hooks/docs-update-check.py +120 -0
- package/.claude/hooks/enforce-a11y-audit.py +202 -0
- package/.claude/hooks/enforce-brand-guide.py +241 -0
- package/.claude/hooks/enforce-component-type-confirm.py +97 -0
- package/.claude/hooks/enforce-dry-run.py +134 -0
- package/.claude/hooks/enforce-freshness.py +184 -0
- package/.claude/hooks/enforce-page-components.py +186 -0
- package/.claude/hooks/enforce-page-data-schema.py +155 -0
- package/.claude/hooks/enforce-questions-sourced.py +146 -0
- package/.claude/hooks/enforce-schema-from-interview.py +248 -0
- package/.claude/hooks/enforce-ui-disambiguation.py +108 -0
- package/.claude/hooks/enforce-ui-interview.py +130 -0
- package/.claude/hooks/generate-adr-options.py +282 -0
- package/.claude/hooks/generate-manifest-entry.py +1161 -0
- package/.claude/hooks/hook_utils.py +609 -0
- package/.claude/hooks/lib/__init__.py +1 -0
- package/.claude/hooks/lib/__pycache__/__init__.cpython-314.pyc +0 -0
- package/.claude/hooks/lib/__pycache__/greptile.cpython-314.pyc +0 -0
- package/.claude/hooks/lib/__pycache__/ntfy.cpython-314.pyc +0 -0
- package/.claude/hooks/lib/greptile.py +355 -0
- package/.claude/hooks/lib/ntfy.py +209 -0
- package/.claude/hooks/notify-input-needed.py +73 -0
- package/.claude/hooks/notify-phase-complete.py +90 -0
- package/.claude/hooks/ntfy-on-question.py +240 -0
- package/.claude/hooks/orchestrator-completion.py +313 -0
- package/.claude/hooks/orchestrator-handoff.py +267 -0
- package/.claude/hooks/orchestrator-session-startup.py +146 -0
- package/.claude/hooks/parallel-orchestrator.py +451 -0
- package/.claude/hooks/project-document-prompt.py +302 -0
- package/.claude/hooks/remote-question-proxy.py +284 -0
- package/.claude/hooks/remote-question-server.py +1224 -0
- package/.claude/hooks/run-code-review.py +393 -0
- package/.claude/hooks/run-visual-qa.py +338 -0
- package/.claude/hooks/session-logger.py +323 -0
- package/.claude/hooks/test-orchestrator-reground.py +248 -0
- package/.claude/hooks/track-scope-coverage.py +220 -0
- package/.claude/hooks/track-token-usage.py +121 -0
- package/.claude/hooks/update-adr-decision.py +236 -0
- package/.claude/hooks/update-api-showcase.py +161 -0
- package/.claude/hooks/update-registry.py +352 -0
- package/.claude/hooks/update-testing-checklist.py +195 -0
- package/.claude/hooks/update-ui-showcase.py +224 -0
- package/.claude/settings.local.json +7 -1
- package/.claude/test-auto-answer-bot.py +183 -0
- package/.claude/test-completion-detector.py +263 -0
- package/.claude/test-orchestrator-state.json +20 -0
- package/.claude/test-orchestrator.sh +271 -0
- package/.skills/api-create/SKILL.md +88 -3
- package/.skills/docs-sync/SKILL.md +260 -0
- package/.skills/hustle-build/SKILL.md +459 -0
- package/.skills/hustle-build-review/SKILL.md +518 -0
- package/CHANGELOG.md +87 -0
- package/README.md +86 -9
- package/bin/cli.js +1302 -88
- package/commands/hustle-api-create.md +22 -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/auto-answer.py +228 -0
- package/hooks/check-update.py +132 -0
- package/hooks/ntfy-on-question.py +227 -0
- package/hooks/orchestrator-completion.py +313 -0
- package/hooks/orchestrator-handoff.py +189 -0
- package/hooks/orchestrator-session-startup.py +146 -0
- package/hooks/periodic-reground.py +230 -67
- package/hooks/update-api-showcase.py +13 -1
- package/hooks/update-ui-showcase.py +13 -1
- package/package.json +7 -3
- package/scripts/extract-schema-docs.cjs +322 -0
- package/templates/CLAUDE-SECTION.md +89 -64
- 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/docs/page.tsx +230 -0
- package/templates/hustle-build-defaults.json +84 -0
- package/templates/hustle-dev-dashboard/page.tsx +365 -0
- package/templates/playwright-report/page.tsx +258 -0
- package/templates/settings.json +88 -7
- package/templates/test-results/page.tsx +237 -0
- package/templates/typedoc.json +19 -0
- package/templates/ui-showcase/_components/UIShowcase.tsx +1 -1
- package/templates/ui-showcase/page.tsx +1 -1
- package/.claude/api-dev-state.json +0 -466
|
@@ -27,12 +27,15 @@ def copy_showcase_templates(cwd):
|
|
|
27
27
|
"""Copy API showcase templates to src/app/api-showcase/."""
|
|
28
28
|
# Source templates (installed by CLI)
|
|
29
29
|
templates_dir = Path(__file__).parent.parent / "templates" / "api-showcase"
|
|
30
|
+
shared_templates_dir = Path(__file__).parent.parent / "templates" / "shared"
|
|
30
31
|
|
|
31
32
|
# Destination
|
|
32
33
|
showcase_dir = cwd / "src" / "app" / "api-showcase"
|
|
34
|
+
shared_dir = cwd / "src" / "app" / "shared"
|
|
33
35
|
|
|
34
|
-
# Create
|
|
36
|
+
# Create directories if needed
|
|
35
37
|
showcase_dir.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
shared_dir.mkdir(parents=True, exist_ok=True)
|
|
36
39
|
|
|
37
40
|
# Copy template files
|
|
38
41
|
templates_to_copy = [
|
|
@@ -55,6 +58,15 @@ def copy_showcase_templates(cwd):
|
|
|
55
58
|
shutil.copy2(src_path, dest_path)
|
|
56
59
|
created_files.append(str(dest_path.relative_to(cwd)))
|
|
57
60
|
|
|
61
|
+
# Also copy shared components (HeroHeader, etc.)
|
|
62
|
+
if shared_templates_dir.exists():
|
|
63
|
+
for src_file in shared_templates_dir.iterdir():
|
|
64
|
+
if src_file.is_file():
|
|
65
|
+
dest_path = shared_dir / src_file.name
|
|
66
|
+
if not dest_path.exists():
|
|
67
|
+
shutil.copy2(src_file, dest_path)
|
|
68
|
+
created_files.append(str(dest_path.relative_to(cwd)))
|
|
69
|
+
|
|
58
70
|
return created_files
|
|
59
71
|
|
|
60
72
|
|
|
@@ -84,12 +84,15 @@ def copy_showcase_templates(cwd):
|
|
|
84
84
|
"""Copy UI showcase templates to src/app/ui-showcase/."""
|
|
85
85
|
# Source templates (installed by CLI)
|
|
86
86
|
templates_dir = Path(__file__).parent.parent / "templates" / "ui-showcase"
|
|
87
|
+
shared_templates_dir = Path(__file__).parent.parent / "templates" / "shared"
|
|
87
88
|
|
|
88
89
|
# Destination
|
|
89
90
|
showcase_dir = cwd / "src" / "app" / "ui-showcase"
|
|
91
|
+
shared_dir = cwd / "src" / "app" / "shared"
|
|
90
92
|
|
|
91
|
-
# Create
|
|
93
|
+
# Create directories if needed
|
|
92
94
|
showcase_dir.mkdir(parents=True, exist_ok=True)
|
|
95
|
+
shared_dir.mkdir(parents=True, exist_ok=True)
|
|
93
96
|
|
|
94
97
|
# Copy template files
|
|
95
98
|
templates_to_copy = [
|
|
@@ -111,6 +114,15 @@ def copy_showcase_templates(cwd):
|
|
|
111
114
|
shutil.copy2(src_path, dest_path)
|
|
112
115
|
created_files.append(str(dest_path.relative_to(cwd)))
|
|
113
116
|
|
|
117
|
+
# Also copy shared components (HeroHeader, etc.)
|
|
118
|
+
if shared_templates_dir.exists():
|
|
119
|
+
for src_file in shared_templates_dir.iterdir():
|
|
120
|
+
if src_file.is_file():
|
|
121
|
+
dest_path = shared_dir / src_file.name
|
|
122
|
+
if not dest_path.exists():
|
|
123
|
+
shutil.copy2(src_file, dest_path)
|
|
124
|
+
created_files.append(str(dest_path.relative_to(cwd)))
|
|
125
|
+
|
|
114
126
|
return created_files
|
|
115
127
|
|
|
116
128
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hustle-together/api-dev-tools",
|
|
3
|
-
"version": "3.12.
|
|
3
|
+
"version": "3.12.16",
|
|
4
4
|
"description": "Interview-driven, research-first API development toolkit with 14-phase TDD workflow, enforcement hooks, and 23 Agent Skills for cross-platform AI agents",
|
|
5
5
|
"main": "bin/cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -23,11 +23,15 @@
|
|
|
23
23
|
"test": "node bin/cli.js --scope=project",
|
|
24
24
|
"usage": "ccusage",
|
|
25
25
|
"format": "prettier --write .",
|
|
26
|
-
"lint": "eslint . --fix"
|
|
26
|
+
"lint": "eslint . --fix",
|
|
27
|
+
"typedoc": "typedoc",
|
|
28
|
+
"typedoc:watch": "typedoc --watch"
|
|
27
29
|
},
|
|
28
30
|
"devDependencies": {
|
|
29
31
|
"prettier": "^3.0.0",
|
|
30
|
-
"eslint": "^8.0.0"
|
|
32
|
+
"eslint": "^8.0.0",
|
|
33
|
+
"typedoc": "^0.27.0",
|
|
34
|
+
"typedoc-plugin-markdown": "^4.4.0"
|
|
31
35
|
},
|
|
32
36
|
"optionalDependencies": {
|
|
33
37
|
"ccusage": "^1.0.0"
|
|
@@ -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 };
|
|
@@ -1,21 +1,37 @@
|
|
|
1
|
-
## Hustle API Development Workflow (
|
|
1
|
+
## Hustle API Development Workflow (v4.0.0)
|
|
2
2
|
|
|
3
|
-
This project uses **@hustle-together/api-dev-tools** for interview-driven, research-first
|
|
3
|
+
This project uses **@hustle-together/api-dev-tools** for interview-driven, research-first development.
|
|
4
|
+
|
|
5
|
+
### Project Context
|
|
6
|
+
|
|
7
|
+
<!-- INSTALLER: Replace these with actual project values -->
|
|
8
|
+
**Tech Stack:** [Framework] + [Language] + [Database]
|
|
9
|
+
**UI Library:** [UI framework or component library]
|
|
10
|
+
**Testing:** [Test framework] + [E2E framework]
|
|
11
|
+
|
|
12
|
+
### Existing Elements
|
|
13
|
+
|
|
14
|
+
<!-- AUTO-POPULATED: Updated by registry hooks -->
|
|
15
|
+
**APIs:** (check `.claude/registry.json`)
|
|
16
|
+
**Components:** (check `.claude/registry.json`)
|
|
17
|
+
**Pages:** (check `.claude/registry.json`)
|
|
4
18
|
|
|
5
19
|
### Available Commands
|
|
6
20
|
|
|
7
21
|
| Command | Purpose |
|
|
8
22
|
| ---------------------------------- | ------------------------------------- |
|
|
9
|
-
| `/hustle-
|
|
10
|
-
| `/
|
|
11
|
-
| `/hustle-
|
|
12
|
-
| `/hustle-
|
|
13
|
-
| `/hustle-
|
|
14
|
-
| `/
|
|
15
|
-
| `/
|
|
16
|
-
| `/
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
| `/hustle-build [description]` | Orchestrated multi-workflow build |
|
|
24
|
+
| `/api-create [endpoint]` | Complete 14-phase API workflow |
|
|
25
|
+
| `/hustle-ui-create [component]` | Component with Storybook |
|
|
26
|
+
| `/hustle-ui-create-page [page]` | Page with Playwright E2E |
|
|
27
|
+
| `/hustle-combine [name]` | Combine multiple APIs |
|
|
28
|
+
| `/api-research [library]` | Adaptive propose-approve research |
|
|
29
|
+
| `/api-interview [endpoint]` | Questions FROM research findings |
|
|
30
|
+
| `/api-verify [endpoint]` | Re-research and verify implementation |
|
|
31
|
+
| `/api-env [endpoint]` | Check API keys |
|
|
32
|
+
| `/api-status [endpoint]` | Track progress |
|
|
33
|
+
|
|
34
|
+
### 14-Phase Flow
|
|
19
35
|
|
|
20
36
|
```
|
|
21
37
|
Phase 1: DISAMBIGUATION - Clarify ambiguous terms before research
|
|
@@ -28,18 +44,20 @@ Phase 7: ENVIRONMENT - Verify API keys exist
|
|
|
28
44
|
Phase 8: TDD RED - Write failing tests from schema
|
|
29
45
|
Phase 9: TDD GREEN - Minimal implementation to pass tests
|
|
30
46
|
Phase 10: VERIFY - Re-research docs, compare to implementation
|
|
31
|
-
Phase 11:
|
|
32
|
-
Phase 12:
|
|
33
|
-
Phase 13:
|
|
47
|
+
Phase 11: CODE REVIEW - AI review (bugs, security, performance)
|
|
48
|
+
Phase 12: TDD REFACTOR - Fix review issues + clean up code
|
|
49
|
+
Phase 13: DOCUMENTATION - Update manifests, cache research
|
|
50
|
+
Phase 14: COMPLETION - Final verification, commit
|
|
34
51
|
```
|
|
35
52
|
|
|
36
53
|
### Key Principles
|
|
37
54
|
|
|
38
|
-
1. **
|
|
55
|
+
1. **Research-First** - Never write code from memory, always verify docs
|
|
39
56
|
2. **Questions FROM Research** - Never use generic template questions
|
|
40
|
-
3. **
|
|
57
|
+
3. **Loop Until Green** - Every verification phase loops back if not successful
|
|
41
58
|
4. **7-Turn Re-grounding** - Context injected every 7 turns to prevent dilution
|
|
42
59
|
5. **Verify After Green** - Re-research to catch memory-based implementation errors
|
|
60
|
+
6. **Registry Awareness** - Don't recreate existing elements
|
|
43
61
|
|
|
44
62
|
### State Tracking
|
|
45
63
|
|
|
@@ -49,7 +67,17 @@ All progress is tracked in `.claude/api-dev-state.json`:
|
|
|
49
67
|
- Interview decisions (injected during implementation)
|
|
50
68
|
- Research sources with freshness tracking
|
|
51
69
|
- Turn count for re-grounding
|
|
52
|
-
-
|
|
70
|
+
- Deferred features list
|
|
71
|
+
- Test run history
|
|
72
|
+
|
|
73
|
+
### Registry
|
|
74
|
+
|
|
75
|
+
`.claude/registry.json` tracks all created elements:
|
|
76
|
+
|
|
77
|
+
- APIs with endpoints, schemas, and examples
|
|
78
|
+
- Components with props and variants
|
|
79
|
+
- Pages with routes and data requirements
|
|
80
|
+
- Combined APIs with orchestration patterns
|
|
53
81
|
|
|
54
82
|
### Research Cache
|
|
55
83
|
|
|
@@ -59,61 +87,58 @@ Research is cached in `.claude/research/` with 7-day freshness:
|
|
|
59
87
|
- `[api-name]/CURRENT.md` - Latest research
|
|
60
88
|
- `[api-name]/sources.json` - Research sources
|
|
61
89
|
- `[api-name]/interview.json` - Interview decisions
|
|
62
|
-
- `[api-name]/schema.json` - Schema snapshot
|
|
63
90
|
- Stale research (>7 days) triggers re-research prompt
|
|
64
91
|
|
|
65
|
-
###
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
|
91
|
-
|
|
|
92
|
-
|
|
|
93
|
-
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
When Phase 12 completes, `generate-manifest-entry.py` automatically creates:
|
|
98
|
-
|
|
99
|
-
- **Comprehensive curl examples** (minimal, full, auth, enum variations, boundary values)
|
|
100
|
-
- **Complete test cases** (success, validation, required fields, types, boundaries, arrays)
|
|
101
|
-
- **Parameter documentation** with all possible values
|
|
102
|
-
- **Ready-to-use entries** for `api-tests-manifest.json`
|
|
92
|
+
### Re-grounding System
|
|
93
|
+
|
|
94
|
+
Every 7 turns, the system injects a reminder with:
|
|
95
|
+
|
|
96
|
+
- Current endpoint and phase progress
|
|
97
|
+
- Key interview decisions
|
|
98
|
+
- Existing registry elements (don't recreate)
|
|
99
|
+
- Deferred features (don't re-suggest)
|
|
100
|
+
- Last test status (GREEN/RED)
|
|
101
|
+
- Brand guide status
|
|
102
|
+
- Research freshness warnings
|
|
103
|
+
- Orchestrator progress (if in /hustle-build)
|
|
104
|
+
|
|
105
|
+
See: [docs/REGROUNDING.md](./docs/REGROUNDING.md)
|
|
106
|
+
|
|
107
|
+
### Brand Guide
|
|
108
|
+
|
|
109
|
+
If `.claude/BRAND_GUIDE.md` exists:
|
|
110
|
+
|
|
111
|
+
- All UI components use brand colors/fonts
|
|
112
|
+
- Enforce hook checks before component creation
|
|
113
|
+
- Re-grounding reminds about brand guide
|
|
114
|
+
|
|
115
|
+
### Hooks (45+ Automatic Enforcement)
|
|
116
|
+
|
|
117
|
+
| Category | Hooks | Purpose |
|
|
118
|
+
| -------- | ----- | ------- |
|
|
119
|
+
| SessionStart | 4 | Inject state, detect interruptions, check updates |
|
|
120
|
+
| UserPromptSubmit | 1 | Require research before API questions |
|
|
121
|
+
| PreToolUse | 22 | Phase enforcement, schema validation |
|
|
122
|
+
| PostToolUse | 12 | Tracking, re-grounding, registry updates |
|
|
123
|
+
| Stop | 2 | Workflow completion, session logging |
|
|
103
124
|
|
|
104
125
|
### Usage
|
|
105
126
|
|
|
106
127
|
```bash
|
|
107
|
-
#
|
|
108
|
-
/hustle-
|
|
128
|
+
# Orchestrated build (recommended for features)
|
|
129
|
+
/hustle-build dashboard with user stats and activity charts
|
|
109
130
|
|
|
110
|
-
#
|
|
111
|
-
/
|
|
112
|
-
/hustle-
|
|
113
|
-
/hustle-
|
|
131
|
+
# Individual workflows
|
|
132
|
+
/api-create stripe-checkout
|
|
133
|
+
/hustle-ui-create StatCard
|
|
134
|
+
/hustle-ui-create-page Dashboard
|
|
135
|
+
|
|
136
|
+
# TDD cycle
|
|
114
137
|
/red
|
|
115
138
|
/green
|
|
116
|
-
/hustle-api-verify [endpoint]
|
|
117
139
|
/refactor
|
|
140
|
+
|
|
141
|
+
# Git
|
|
118
142
|
/commit
|
|
143
|
+
/pr
|
|
119
144
|
```
|