@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
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
ADR Options Generator Hook (v2.0 - Deep Research)
|
|
4
|
+
|
|
5
|
+
Automatically creates ADR research REQUESTS when research discovers
|
|
6
|
+
multiple options for significant decisions (database, auth, caching, etc.).
|
|
7
|
+
|
|
8
|
+
Hook Type: PostToolUse (matcher: WebSearch, WebFetch, mcp__context7)
|
|
9
|
+
|
|
10
|
+
Flow:
|
|
11
|
+
1. Research phase discovers options (e.g., "Supabase vs Firebase vs Postgres")
|
|
12
|
+
2. Hook detects multiple options for significant decision category
|
|
13
|
+
3. Creates RESEARCH REQUEST file (not placeholder ADR)
|
|
14
|
+
4. Injects context telling AI to run /adr-deep-research
|
|
15
|
+
5. Deep research skill spawns parallel agents to research each option
|
|
16
|
+
6. Real ADR with substantive pros/cons is created
|
|
17
|
+
7. Interview phase presents these informed options to user
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
from datetime import datetime
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def load_config():
|
|
28
|
+
"""Load ADR configuration from hustle-build-defaults.json"""
|
|
29
|
+
project_dir = os.environ.get("CLAUDE_PROJECT_DIR", ".")
|
|
30
|
+
|
|
31
|
+
# Check project-specific config
|
|
32
|
+
config_file = Path(project_dir) / ".claude" / "hustle-build-defaults.json"
|
|
33
|
+
if config_file.exists():
|
|
34
|
+
try:
|
|
35
|
+
config = json.loads(config_file.read_text())
|
|
36
|
+
return config.get("adr", {})
|
|
37
|
+
except Exception:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
# Fall back to template
|
|
41
|
+
template_file = Path(project_dir) / "templates" / "hustle-build-defaults.json"
|
|
42
|
+
if template_file.exists():
|
|
43
|
+
try:
|
|
44
|
+
config = json.loads(template_file.read_text())
|
|
45
|
+
return config.get("adr", {})
|
|
46
|
+
except Exception:
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
# Default config
|
|
50
|
+
return {
|
|
51
|
+
"enabled": True,
|
|
52
|
+
"significant_decisions": {
|
|
53
|
+
"database": ["supabase", "firebase", "postgres", "mysql", "mongodb", "sqlite", "planetscale", "neon"],
|
|
54
|
+
"auth": ["api key", "oauth", "jwt", "session", "cookie", "basic auth", "api-key", "bearer"],
|
|
55
|
+
"cache": ["redis", "memcached", "in-memory", "cdn", "edge", "vercel kv"],
|
|
56
|
+
"hosting": ["vercel", "netlify", "aws", "cloudflare", "railway", "render", "fly.io"],
|
|
57
|
+
"state": ["redux", "zustand", "jotai", "context", "mobx", "recoil", "valtio"],
|
|
58
|
+
"styling": ["tailwind", "css modules", "styled-components", "emotion", "vanilla-extract"],
|
|
59
|
+
},
|
|
60
|
+
"min_options_for_adr": 2
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def load_state():
|
|
65
|
+
"""Load current workflow state"""
|
|
66
|
+
project_dir = os.environ.get("CLAUDE_PROJECT_DIR", ".")
|
|
67
|
+
state_file = Path(project_dir) / ".claude" / "api-dev-state.json"
|
|
68
|
+
|
|
69
|
+
if state_file.exists():
|
|
70
|
+
try:
|
|
71
|
+
return json.loads(state_file.read_text())
|
|
72
|
+
except Exception:
|
|
73
|
+
pass
|
|
74
|
+
return {}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_next_adr_number():
|
|
78
|
+
"""Get the next ADR number from existing ADRs"""
|
|
79
|
+
project_dir = os.environ.get("CLAUDE_PROJECT_DIR", ".")
|
|
80
|
+
adrs_dir = Path(project_dir) / ".claude" / "adrs"
|
|
81
|
+
|
|
82
|
+
if not adrs_dir.exists():
|
|
83
|
+
return 1
|
|
84
|
+
|
|
85
|
+
existing = list(adrs_dir.glob("*.md"))
|
|
86
|
+
if not existing:
|
|
87
|
+
return 1
|
|
88
|
+
|
|
89
|
+
numbers = []
|
|
90
|
+
for f in existing:
|
|
91
|
+
match = re.match(r"(\d+)-", f.name)
|
|
92
|
+
if match:
|
|
93
|
+
numbers.append(int(match.group(1)))
|
|
94
|
+
|
|
95
|
+
return max(numbers, default=0) + 1
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def detect_decision_points(content, config):
|
|
99
|
+
"""
|
|
100
|
+
Check if research content contains multiple options for significant decisions.
|
|
101
|
+
Returns list of (category, matched_options) tuples.
|
|
102
|
+
"""
|
|
103
|
+
if not config.get("enabled", True):
|
|
104
|
+
return []
|
|
105
|
+
|
|
106
|
+
significant = config.get("significant_decisions", {})
|
|
107
|
+
min_options = config.get("min_options_for_adr", 2)
|
|
108
|
+
|
|
109
|
+
content_lower = content.lower()
|
|
110
|
+
detected = []
|
|
111
|
+
|
|
112
|
+
for category, keywords in significant.items():
|
|
113
|
+
matches = [k for k in keywords if k.lower() in content_lower]
|
|
114
|
+
if len(matches) >= min_options:
|
|
115
|
+
detected.append((category, matches))
|
|
116
|
+
|
|
117
|
+
return detected
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def create_adr_research_request(category, options, context, endpoint):
|
|
121
|
+
"""Create a research REQUEST file for deep ADR research.
|
|
122
|
+
|
|
123
|
+
Instead of creating a placeholder ADR with empty pros/cons,
|
|
124
|
+
we create a request file that triggers /adr-deep-research skill.
|
|
125
|
+
"""
|
|
126
|
+
project_dir = os.environ.get("CLAUDE_PROJECT_DIR", ".")
|
|
127
|
+
requests_dir = Path(project_dir) / ".claude" / "adr-requests"
|
|
128
|
+
requests_dir.mkdir(parents=True, exist_ok=True)
|
|
129
|
+
|
|
130
|
+
# Check if request already exists for this category
|
|
131
|
+
pending_file = requests_dir / f"pending-{category}.json"
|
|
132
|
+
if pending_file.exists():
|
|
133
|
+
return None # Already pending
|
|
134
|
+
|
|
135
|
+
# Check if ADR already exists for this category
|
|
136
|
+
adrs_dir = Path(project_dir) / ".claude" / "adrs"
|
|
137
|
+
if adrs_dir.exists():
|
|
138
|
+
existing = list(adrs_dir.glob(f"*-{category}-choice.md"))
|
|
139
|
+
if existing:
|
|
140
|
+
return None # ADR already created
|
|
141
|
+
|
|
142
|
+
# Create research request
|
|
143
|
+
request = {
|
|
144
|
+
"category": category,
|
|
145
|
+
"options": options,
|
|
146
|
+
"context": context[:1000] if context else "",
|
|
147
|
+
"endpoint": endpoint,
|
|
148
|
+
"status": "pending",
|
|
149
|
+
"created_at": datetime.now().isoformat(),
|
|
150
|
+
"adr_number": get_next_adr_number()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
pending_file.write_text(json.dumps(request, indent=2))
|
|
154
|
+
|
|
155
|
+
return request
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def update_registry(adr_number, category, options, endpoint, filename):
|
|
159
|
+
"""Add ADR to registry"""
|
|
160
|
+
project_dir = os.environ.get("CLAUDE_PROJECT_DIR", ".")
|
|
161
|
+
registry_file = Path(project_dir) / ".claude" / "registry.json"
|
|
162
|
+
|
|
163
|
+
if registry_file.exists():
|
|
164
|
+
try:
|
|
165
|
+
registry = json.loads(registry_file.read_text())
|
|
166
|
+
except Exception:
|
|
167
|
+
registry = {}
|
|
168
|
+
else:
|
|
169
|
+
registry = {}
|
|
170
|
+
|
|
171
|
+
if "adrs" not in registry:
|
|
172
|
+
registry["adrs"] = {}
|
|
173
|
+
|
|
174
|
+
adr_key = f"{adr_number:04d}-{category}-choice"
|
|
175
|
+
registry["adrs"][adr_key] = {
|
|
176
|
+
"number": adr_number,
|
|
177
|
+
"title": f"{category.title()} Choice",
|
|
178
|
+
"status": "proposed",
|
|
179
|
+
"date": datetime.now().strftime("%Y-%m-%d"),
|
|
180
|
+
"phase": "initial_research",
|
|
181
|
+
"endpoint": endpoint,
|
|
182
|
+
"category": category,
|
|
183
|
+
"decision": None,
|
|
184
|
+
"options_considered": options,
|
|
185
|
+
"file": f".claude/adrs/{filename}"
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
registry_file.parent.mkdir(parents=True, exist_ok=True)
|
|
189
|
+
registry_file.write_text(json.dumps(registry, indent=2))
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def main():
|
|
193
|
+
# Get tool output from environment
|
|
194
|
+
tool_output = os.environ.get("CLAUDE_TOOL_OUTPUT", "")
|
|
195
|
+
tool_name = os.environ.get("CLAUDE_TOOL_NAME", "")
|
|
196
|
+
|
|
197
|
+
# Only process research tools
|
|
198
|
+
if tool_name not in ["WebSearch", "WebFetch", "mcp__context7__get-library-docs"]:
|
|
199
|
+
print(json.dumps({"continue": True}))
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
# Load config
|
|
203
|
+
config = load_config()
|
|
204
|
+
if not config.get("enabled", True):
|
|
205
|
+
print(json.dumps({"continue": True}))
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
# Get current workflow context
|
|
209
|
+
state = load_state()
|
|
210
|
+
current_phase = state.get("current_phase", "")
|
|
211
|
+
endpoint = state.get("current_endpoint", state.get("workflow_id", "unknown"))
|
|
212
|
+
|
|
213
|
+
# Only generate ADRs during research phases
|
|
214
|
+
if current_phase not in ["initial_research", "deep_research", ""]:
|
|
215
|
+
print(json.dumps({"continue": True}))
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
# Detect decision points in research content
|
|
219
|
+
decision_points = detect_decision_points(tool_output, config)
|
|
220
|
+
|
|
221
|
+
if not decision_points:
|
|
222
|
+
print(json.dumps({"continue": True}))
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
# Create research requests for each decision point
|
|
226
|
+
created_requests = []
|
|
227
|
+
for category, options in decision_points:
|
|
228
|
+
request = create_adr_research_request(
|
|
229
|
+
category=category,
|
|
230
|
+
options=options,
|
|
231
|
+
context=tool_output[:1000],
|
|
232
|
+
endpoint=endpoint
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if request:
|
|
236
|
+
created_requests.append({
|
|
237
|
+
"category": category,
|
|
238
|
+
"options": options,
|
|
239
|
+
"adr_number": request["adr_number"]
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
if created_requests:
|
|
243
|
+
# Build list of research commands to run
|
|
244
|
+
research_commands = "\n".join([
|
|
245
|
+
f"- `/adr-deep-research {r['category']}` - Research {', '.join(r['options'])}"
|
|
246
|
+
for r in created_requests
|
|
247
|
+
])
|
|
248
|
+
|
|
249
|
+
# Build summary of what was detected
|
|
250
|
+
detection_summary = "\n".join([
|
|
251
|
+
f"- **{r['category'].title()}**: {', '.join(r['options'])}"
|
|
252
|
+
for r in created_requests
|
|
253
|
+
])
|
|
254
|
+
|
|
255
|
+
result = {
|
|
256
|
+
"continue": True,
|
|
257
|
+
"additionalContext": f"""## ADR Research Needed
|
|
258
|
+
|
|
259
|
+
Research discovered significant decision points that require deeper investigation:
|
|
260
|
+
|
|
261
|
+
{detection_summary}
|
|
262
|
+
|
|
263
|
+
**Next Step:** Run deep research to get real pros/cons before the interview:
|
|
264
|
+
|
|
265
|
+
{research_commands}
|
|
266
|
+
|
|
267
|
+
This will:
|
|
268
|
+
1. Spawn parallel research agents (one per option)
|
|
269
|
+
2. Fetch official documentation for each technology
|
|
270
|
+
3. Extract real pros, cons, pricing, and best-use cases
|
|
271
|
+
4. Create a substantive ADR with informed recommendations
|
|
272
|
+
|
|
273
|
+
The ADR will then be referenced during the interview phase.
|
|
274
|
+
"""
|
|
275
|
+
}
|
|
276
|
+
print(json.dumps(result))
|
|
277
|
+
else:
|
|
278
|
+
print(json.dumps({"continue": True}))
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
if __name__ == "__main__":
|
|
282
|
+
main()
|