@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.
Files changed (96) hide show
  1. package/.claude/commands/hustle-build.md +259 -0
  2. package/.claude/commands/hustle-combine.md +1089 -0
  3. package/.claude/commands/hustle-ui-create-page.md +1078 -0
  4. package/.claude/commands/hustle-ui-create.md +1058 -0
  5. package/.claude/hooks/auto-answer.py +305 -0
  6. package/.claude/hooks/cache-research.py +337 -0
  7. package/.claude/hooks/check-api-routes.py +168 -0
  8. package/.claude/hooks/check-playwright-setup.py +103 -0
  9. package/.claude/hooks/check-storybook-setup.py +81 -0
  10. package/.claude/hooks/check-update.py +132 -0
  11. package/.claude/hooks/completion-promise-detector.py +293 -0
  12. package/.claude/hooks/context-capacity-warning.py +171 -0
  13. package/.claude/hooks/detect-interruption.py +165 -0
  14. package/.claude/hooks/docs-update-check.py +120 -0
  15. package/.claude/hooks/enforce-a11y-audit.py +202 -0
  16. package/.claude/hooks/enforce-brand-guide.py +241 -0
  17. package/.claude/hooks/enforce-component-type-confirm.py +97 -0
  18. package/.claude/hooks/enforce-dry-run.py +134 -0
  19. package/.claude/hooks/enforce-freshness.py +184 -0
  20. package/.claude/hooks/enforce-page-components.py +186 -0
  21. package/.claude/hooks/enforce-page-data-schema.py +155 -0
  22. package/.claude/hooks/enforce-questions-sourced.py +146 -0
  23. package/.claude/hooks/enforce-schema-from-interview.py +248 -0
  24. package/.claude/hooks/enforce-ui-disambiguation.py +108 -0
  25. package/.claude/hooks/enforce-ui-interview.py +130 -0
  26. package/.claude/hooks/generate-adr-options.py +282 -0
  27. package/.claude/hooks/generate-manifest-entry.py +1161 -0
  28. package/.claude/hooks/hook_utils.py +609 -0
  29. package/.claude/hooks/lib/__init__.py +1 -0
  30. package/.claude/hooks/lib/__pycache__/__init__.cpython-314.pyc +0 -0
  31. package/.claude/hooks/lib/__pycache__/greptile.cpython-314.pyc +0 -0
  32. package/.claude/hooks/lib/__pycache__/ntfy.cpython-314.pyc +0 -0
  33. package/.claude/hooks/lib/greptile.py +355 -0
  34. package/.claude/hooks/lib/ntfy.py +209 -0
  35. package/.claude/hooks/notify-input-needed.py +73 -0
  36. package/.claude/hooks/notify-phase-complete.py +90 -0
  37. package/.claude/hooks/ntfy-on-question.py +240 -0
  38. package/.claude/hooks/orchestrator-completion.py +313 -0
  39. package/.claude/hooks/orchestrator-handoff.py +267 -0
  40. package/.claude/hooks/orchestrator-session-startup.py +146 -0
  41. package/.claude/hooks/parallel-orchestrator.py +451 -0
  42. package/.claude/hooks/project-document-prompt.py +302 -0
  43. package/.claude/hooks/remote-question-proxy.py +284 -0
  44. package/.claude/hooks/remote-question-server.py +1224 -0
  45. package/.claude/hooks/run-code-review.py +393 -0
  46. package/.claude/hooks/run-visual-qa.py +338 -0
  47. package/.claude/hooks/session-logger.py +323 -0
  48. package/.claude/hooks/test-orchestrator-reground.py +248 -0
  49. package/.claude/hooks/track-scope-coverage.py +220 -0
  50. package/.claude/hooks/track-token-usage.py +121 -0
  51. package/.claude/hooks/update-adr-decision.py +236 -0
  52. package/.claude/hooks/update-api-showcase.py +161 -0
  53. package/.claude/hooks/update-registry.py +352 -0
  54. package/.claude/hooks/update-testing-checklist.py +195 -0
  55. package/.claude/hooks/update-ui-showcase.py +224 -0
  56. package/.claude/settings.local.json +7 -1
  57. package/.claude/test-auto-answer-bot.py +183 -0
  58. package/.claude/test-completion-detector.py +263 -0
  59. package/.claude/test-orchestrator-state.json +20 -0
  60. package/.claude/test-orchestrator.sh +271 -0
  61. package/.skills/api-create/SKILL.md +88 -3
  62. package/.skills/docs-sync/SKILL.md +260 -0
  63. package/.skills/hustle-build/SKILL.md +459 -0
  64. package/.skills/hustle-build-review/SKILL.md +518 -0
  65. package/CHANGELOG.md +87 -0
  66. package/README.md +86 -9
  67. package/bin/cli.js +1302 -88
  68. package/commands/hustle-api-create.md +22 -0
  69. package/commands/hustle-combine.md +81 -2
  70. package/commands/hustle-ui-create-page.md +84 -2
  71. package/commands/hustle-ui-create.md +82 -2
  72. package/hooks/auto-answer.py +228 -0
  73. package/hooks/check-update.py +132 -0
  74. package/hooks/ntfy-on-question.py +227 -0
  75. package/hooks/orchestrator-completion.py +313 -0
  76. package/hooks/orchestrator-handoff.py +189 -0
  77. package/hooks/orchestrator-session-startup.py +146 -0
  78. package/hooks/periodic-reground.py +230 -67
  79. package/hooks/update-api-showcase.py +13 -1
  80. package/hooks/update-ui-showcase.py +13 -1
  81. package/package.json +7 -3
  82. package/scripts/extract-schema-docs.cjs +322 -0
  83. package/templates/CLAUDE-SECTION.md +89 -64
  84. package/templates/api-showcase/_components/APIModal.tsx +100 -8
  85. package/templates/api-showcase/_components/APIShowcase.tsx +36 -4
  86. package/templates/api-showcase/_components/APITester.tsx +367 -58
  87. package/templates/docs/page.tsx +230 -0
  88. package/templates/hustle-build-defaults.json +84 -0
  89. package/templates/hustle-dev-dashboard/page.tsx +365 -0
  90. package/templates/playwright-report/page.tsx +258 -0
  91. package/templates/settings.json +88 -7
  92. package/templates/test-results/page.tsx +237 -0
  93. package/templates/typedoc.json +19 -0
  94. package/templates/ui-showcase/_components/UIShowcase.tsx +1 -1
  95. package/templates/ui-showcase/page.tsx +1 -1
  96. 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()