@hustle-together/api-dev-tools 3.12.16 → 4.5.3

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 (180) 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 +10 -0
  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/settings.local.json +1 -7
  9. package/.claude/workflow-logs/None.json +49 -0
  10. package/.claude/workflow-logs/session-20251230-143727.json +106 -0
  11. package/.skills/adr-deep-research/SKILL.md +351 -0
  12. package/.skills/api-create/SKILL.md +34 -20
  13. package/.skills/api-research/SKILL.md +130 -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 +365 -38
  17. package/.skills/parallel-spawn/SKILL.md +212 -0
  18. package/.skills/ralph-continue/SKILL.md +151 -0
  19. package/.skills/ralph-loop/SKILL.md +341 -0
  20. package/.skills/ralph-status/SKILL.md +87 -0
  21. package/.skills/refactor/SKILL.md +59 -0
  22. package/.skills/shadcn/SKILL.md +522 -0
  23. package/.skills/test-all/SKILL.md +210 -0
  24. package/.skills/test-builds/SKILL.md +208 -0
  25. package/.skills/test-debug/SKILL.md +212 -0
  26. package/.skills/test-e2e/SKILL.md +168 -0
  27. package/.skills/test-review/SKILL.md +707 -0
  28. package/.skills/test-unit/SKILL.md +143 -0
  29. package/.skills/test-visual/SKILL.md +301 -0
  30. package/.skills/token-report/SKILL.md +132 -0
  31. package/CHANGELOG.md +488 -0
  32. package/README.md +346 -53
  33. package/bin/cli.js +359 -123
  34. package/hooks/__pycache__/api-workflow-check.cpython-314.pyc +0 -0
  35. package/hooks/__pycache__/auto-answer.cpython-314.pyc +0 -0
  36. package/hooks/__pycache__/cache-research.cpython-314.pyc +0 -0
  37. package/hooks/__pycache__/check-api-routes.cpython-314.pyc +0 -0
  38. package/hooks/__pycache__/check-playwright-setup.cpython-314.pyc +0 -0
  39. package/hooks/__pycache__/check-storybook-setup.cpython-314.pyc +0 -0
  40. package/hooks/__pycache__/check-update.cpython-314.pyc +0 -0
  41. package/hooks/__pycache__/completion-promise-detector.cpython-314.pyc +0 -0
  42. package/hooks/__pycache__/context-capacity-warning.cpython-314.pyc +0 -0
  43. package/hooks/__pycache__/detect-interruption.cpython-314.pyc +0 -0
  44. package/hooks/__pycache__/docs-update-check.cpython-314.pyc +0 -0
  45. package/hooks/__pycache__/enforce-a11y-audit.cpython-314.pyc +0 -0
  46. package/hooks/__pycache__/enforce-brand-guide.cpython-314.pyc +0 -0
  47. package/hooks/__pycache__/enforce-component-type-confirm.cpython-314.pyc +0 -0
  48. package/hooks/__pycache__/enforce-deep-research.cpython-314.pyc +0 -0
  49. package/hooks/__pycache__/enforce-disambiguation.cpython-314.pyc +0 -0
  50. package/hooks/__pycache__/enforce-documentation.cpython-314.pyc +0 -0
  51. package/hooks/__pycache__/enforce-dry-run.cpython-314.pyc +0 -0
  52. package/hooks/__pycache__/enforce-environment.cpython-314.pyc +0 -0
  53. package/hooks/__pycache__/enforce-external-research.cpython-314.pyc +0 -0
  54. package/hooks/__pycache__/enforce-freshness.cpython-314.pyc +0 -0
  55. package/hooks/__pycache__/enforce-interview.cpython-314.pyc +0 -0
  56. package/hooks/__pycache__/enforce-page-components.cpython-314.pyc +0 -0
  57. package/hooks/__pycache__/enforce-page-data-schema.cpython-314.pyc +0 -0
  58. package/hooks/__pycache__/enforce-questions-sourced.cpython-314.pyc +0 -0
  59. package/hooks/__pycache__/enforce-refactor.cpython-314.pyc +0 -0
  60. package/hooks/__pycache__/enforce-research.cpython-314.pyc +0 -0
  61. package/hooks/__pycache__/enforce-schema-from-interview.cpython-314.pyc +0 -0
  62. package/hooks/__pycache__/enforce-schema.cpython-314.pyc +0 -0
  63. package/hooks/__pycache__/enforce-scope.cpython-314.pyc +0 -0
  64. package/hooks/__pycache__/enforce-tdd-red.cpython-314.pyc +0 -0
  65. package/hooks/__pycache__/enforce-ui-disambiguation.cpython-314.pyc +0 -0
  66. package/hooks/__pycache__/enforce-ui-interview.cpython-314.pyc +0 -0
  67. package/hooks/__pycache__/enforce-verify.cpython-314.pyc +0 -0
  68. package/hooks/__pycache__/generate-adr-options.cpython-314.pyc +0 -0
  69. package/hooks/__pycache__/generate-manifest-entry.cpython-314.pyc +0 -0
  70. package/hooks/__pycache__/hook_utils.cpython-314.pyc +0 -0
  71. package/hooks/__pycache__/notify-input-needed.cpython-314.pyc +0 -0
  72. package/hooks/__pycache__/notify-phase-complete.cpython-314.pyc +0 -0
  73. package/hooks/__pycache__/ntfy-on-question.cpython-314.pyc +0 -0
  74. package/hooks/__pycache__/orchestrator-completion.cpython-314.pyc +0 -0
  75. package/hooks/__pycache__/orchestrator-handoff.cpython-314.pyc +0 -0
  76. package/hooks/__pycache__/orchestrator-session-startup.cpython-314.pyc +0 -0
  77. package/hooks/__pycache__/parallel-orchestrator.cpython-314.pyc +0 -0
  78. package/hooks/__pycache__/periodic-reground.cpython-314.pyc +0 -0
  79. package/hooks/__pycache__/project-document-prompt.cpython-314.pyc +0 -0
  80. package/hooks/__pycache__/remote-question-proxy.cpython-314.pyc +0 -0
  81. package/hooks/__pycache__/remote-question-server.cpython-314.pyc +0 -0
  82. package/hooks/__pycache__/run-code-review.cpython-314.pyc +0 -0
  83. package/hooks/__pycache__/run-visual-qa.cpython-314.pyc +0 -0
  84. package/hooks/__pycache__/session-logger.cpython-314.pyc +0 -0
  85. package/hooks/__pycache__/session-startup.cpython-314.pyc +0 -0
  86. package/hooks/__pycache__/track-scope-coverage.cpython-314.pyc +0 -0
  87. package/hooks/__pycache__/track-token-usage.cpython-314.pyc +0 -0
  88. package/hooks/__pycache__/track-tool-use.cpython-314.pyc +0 -0
  89. package/hooks/__pycache__/update-adr-decision.cpython-314.pyc +0 -0
  90. package/hooks/__pycache__/update-api-showcase.cpython-314.pyc +0 -0
  91. package/hooks/__pycache__/update-registry.cpython-314.pyc +0 -0
  92. package/hooks/__pycache__/update-ui-showcase.cpython-314.pyc +0 -0
  93. package/hooks/__pycache__/verify-after-green.cpython-314.pyc +0 -0
  94. package/hooks/__pycache__/verify-implementation.cpython-314.pyc +0 -0
  95. package/hooks/api-workflow-check.py +34 -0
  96. package/hooks/auto-answer.py +97 -20
  97. package/{.claude/hooks → hooks}/completion-promise-detector.py +0 -0
  98. package/{.claude/hooks → hooks}/context-capacity-warning.py +0 -0
  99. package/{.claude/hooks → hooks}/docs-update-check.py +0 -0
  100. package/{.claude/hooks → hooks}/enforce-dry-run.py +0 -0
  101. package/hooks/enforce-external-research.py +25 -0
  102. package/hooks/enforce-interview.py +20 -0
  103. package/{.claude/hooks → hooks}/generate-adr-options.py +0 -0
  104. package/{.claude/hooks → hooks}/hook_utils.py +0 -0
  105. package/hooks/ntfy-on-question.py +15 -2
  106. package/hooks/orchestrator-handoff.py +81 -3
  107. package/{.claude/hooks → hooks}/parallel-orchestrator.py +0 -0
  108. package/hooks/periodic-reground.py +40 -0
  109. package/{.claude/hooks → hooks}/remote-question-server.py +0 -0
  110. package/hooks/run-code-review.py +176 -29
  111. package/{.claude/hooks → hooks}/run-visual-qa.py +0 -0
  112. package/hooks/session-logger.py +27 -1
  113. package/hooks/session-startup.py +113 -0
  114. package/{.claude/hooks → hooks}/update-adr-decision.py +0 -0
  115. package/package.json +1 -1
  116. package/templates/.skills/hustle-interview/SKILL.md +174 -0
  117. package/templates/adr-viewer/_components/ADRViewer.tsx +326 -0
  118. package/templates/api-dev-state.json +33 -1
  119. package/templates/brand-page/page.tsx +645 -0
  120. package/templates/component/Component.visual.spec.ts +30 -24
  121. package/templates/eslint-plugin-zod-schema/index.js +446 -0
  122. package/templates/eslint-plugin-zod-schema/package.json +26 -0
  123. package/templates/github-workflows/security.yml +274 -0
  124. package/templates/hustle-build-defaults.json +53 -1
  125. package/templates/page/page.e2e.test.ts +30 -26
  126. package/templates/performance-budgets.json +63 -5
  127. package/templates/registry.json +279 -3
  128. package/templates/review-dashboard/page.tsx +510 -0
  129. package/templates/settings.json +74 -7
  130. package/templates/ui-showcase/_components/UIShowcase.tsx +47 -0
  131. package/templates/ui-showcase/_components/VisualTestingDashboard.tsx +579 -0
  132. package/.claude/commands/hustle-combine.md +0 -1089
  133. package/.claude/commands/hustle-ui-create-page.md +0 -1078
  134. package/.claude/commands/hustle-ui-create.md +0 -1058
  135. package/.claude/hooks/auto-answer.py +0 -305
  136. package/.claude/hooks/cache-research.py +0 -337
  137. package/.claude/hooks/check-api-routes.py +0 -168
  138. package/.claude/hooks/check-playwright-setup.py +0 -103
  139. package/.claude/hooks/check-storybook-setup.py +0 -81
  140. package/.claude/hooks/check-update.py +0 -132
  141. package/.claude/hooks/detect-interruption.py +0 -165
  142. package/.claude/hooks/enforce-a11y-audit.py +0 -202
  143. package/.claude/hooks/enforce-brand-guide.py +0 -241
  144. package/.claude/hooks/enforce-component-type-confirm.py +0 -97
  145. package/.claude/hooks/enforce-freshness.py +0 -184
  146. package/.claude/hooks/enforce-page-components.py +0 -186
  147. package/.claude/hooks/enforce-page-data-schema.py +0 -155
  148. package/.claude/hooks/enforce-questions-sourced.py +0 -146
  149. package/.claude/hooks/enforce-schema-from-interview.py +0 -248
  150. package/.claude/hooks/enforce-ui-disambiguation.py +0 -108
  151. package/.claude/hooks/enforce-ui-interview.py +0 -130
  152. package/.claude/hooks/generate-manifest-entry.py +0 -1161
  153. package/.claude/hooks/lib/__init__.py +0 -1
  154. package/.claude/hooks/lib/greptile.py +0 -355
  155. package/.claude/hooks/lib/ntfy.py +0 -209
  156. package/.claude/hooks/notify-input-needed.py +0 -73
  157. package/.claude/hooks/notify-phase-complete.py +0 -90
  158. package/.claude/hooks/ntfy-on-question.py +0 -240
  159. package/.claude/hooks/orchestrator-completion.py +0 -313
  160. package/.claude/hooks/orchestrator-handoff.py +0 -267
  161. package/.claude/hooks/orchestrator-session-startup.py +0 -146
  162. package/.claude/hooks/run-code-review.py +0 -393
  163. package/.claude/hooks/session-logger.py +0 -323
  164. package/.claude/hooks/test-orchestrator-reground.py +0 -248
  165. package/.claude/hooks/track-scope-coverage.py +0 -220
  166. package/.claude/hooks/track-token-usage.py +0 -121
  167. package/.claude/hooks/update-api-showcase.py +0 -161
  168. package/.claude/hooks/update-registry.py +0 -352
  169. package/.claude/hooks/update-ui-showcase.py +0 -224
  170. package/.claude/test-auto-answer-bot.py +0 -183
  171. package/.claude/test-completion-detector.py +0 -263
  172. package/.claude/test-orchestrator-state.json +0 -20
  173. package/.claude/test-orchestrator.sh +0 -271
  174. /package/{.claude/commands → commands}/hustle-build.md +0 -0
  175. /package/{.claude/hooks → hooks}/lib/__pycache__/__init__.cpython-314.pyc +0 -0
  176. /package/{.claude/hooks → hooks}/lib/__pycache__/greptile.cpython-314.pyc +0 -0
  177. /package/{.claude/hooks → hooks}/lib/__pycache__/ntfy.cpython-314.pyc +0 -0
  178. /package/{.claude/hooks → hooks}/project-document-prompt.py +0 -0
  179. /package/{.claude/hooks → hooks}/remote-question-proxy.py +0 -0
  180. /package/{.claude/hooks → hooks}/update-testing-checklist.py +0 -0
@@ -1,31 +1,35 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- Phase 11: AI Code Review Hook
3
+ Phase 11: AI Code Review Hook (Ralph Wiggum Loop Pattern)
4
4
 
5
- Triggers Greptile AI code review after Phase 10 (Verify) and BEFORE Phase 12 (Refactor).
6
- This ensures issues are caught early and can be fixed during the refactor phase,
7
- rather than after PR creation when it's too late.
5
+ Triggers Greptile AI code review and LOOPS until all issues are fixed.
6
+ This ensures code quality before proceeding to refactor phase.
8
7
 
9
- Hook Type: PostToolUse (triggers after tests pass in Phase 9/10)
8
+ Hook Type: PostToolUse (triggers after tests pass)
10
9
 
11
- Greptile API:
12
- POST https://api.greptile.com/v2/query
13
- - Analyzes code changes against entire codebase context
14
- - Returns issues with file:line references
15
- - Provides actionable fix suggestions
10
+ Ralph Wiggum Pattern:
11
+ 1. Run Greptile review
12
+ 2. If issues found inject context for agent to fix
13
+ 3. Agent fixes issues
14
+ 4. Tests re-run hook triggers again
15
+ 5. Re-review with Greptile
16
+ 6. Loop until clean OR max iterations
17
+ 7. Emit <promise>REVIEW_CLEAN</promise>
16
18
 
17
19
  Environment Variables:
18
20
  GREPTILE_API_KEY: Your Greptile API key (get from https://app.greptile.com)
19
21
  GITHUB_TOKEN: GitHub Personal Access Token with repo access
20
22
  CODE_REVIEW_ENABLED: Set to 'true' to enable (default: true)
23
+ CODE_REVIEW_MAX_ITERATIONS: Max review cycles (default: 5)
21
24
 
22
- Version: 1.1.0
25
+ Version: 2.0.0
23
26
  """
24
27
  import os
25
28
  import sys
26
29
  import json
27
30
  import subprocess
28
31
  from pathlib import Path
32
+ from datetime import datetime
29
33
 
30
34
  # Add lib directory to path for imports
31
35
  HOOK_DIR = Path(__file__).parent
@@ -44,6 +48,10 @@ try:
44
48
  except ImportError:
45
49
  GREPTILE_AVAILABLE = False
46
50
 
51
+ # State file for tracking review loops
52
+ REVIEW_STATE_FILE = ".claude/code-review-state.json"
53
+ MAX_ITERATIONS = int(os.environ.get("CODE_REVIEW_MAX_ITERATIONS", "5"))
54
+
47
55
 
48
56
  def get_git_diff() -> tuple:
49
57
  """Get the current git diff and changed files."""
@@ -98,7 +106,44 @@ def get_repo_info() -> tuple:
98
106
  return None, None
99
107
 
100
108
 
101
- def load_state() -> dict:
109
+ def load_review_state() -> dict:
110
+ """Load code review loop state."""
111
+ state_file = Path.cwd() / REVIEW_STATE_FILE
112
+ if state_file.exists():
113
+ try:
114
+ return json.loads(state_file.read_text())
115
+ except (json.JSONDecodeError, IOError):
116
+ pass
117
+ return {
118
+ "iteration": 0,
119
+ "issues_found": [],
120
+ "status": "pending",
121
+ "started_at": None,
122
+ "last_review_at": None
123
+ }
124
+
125
+
126
+ def save_review_state(state: dict):
127
+ """Save code review loop state."""
128
+ state_file = Path.cwd() / REVIEW_STATE_FILE
129
+ state_file.parent.mkdir(parents=True, exist_ok=True)
130
+ try:
131
+ state_file.write_text(json.dumps(state, indent=2))
132
+ except IOError:
133
+ pass
134
+
135
+
136
+ def clear_review_state():
137
+ """Clear review state after successful completion."""
138
+ state_file = Path.cwd() / REVIEW_STATE_FILE
139
+ if state_file.exists():
140
+ try:
141
+ state_file.unlink()
142
+ except IOError:
143
+ pass
144
+
145
+
146
+ def load_workflow_state() -> dict:
102
147
  """Load current workflow state."""
103
148
  state_file = Path.cwd() / ".claude" / "api-dev-state.json"
104
149
  if state_file.exists():
@@ -109,21 +154,22 @@ def load_state() -> dict:
109
154
  return {}
110
155
 
111
156
 
112
- def update_state_with_review(review_summary: dict):
113
- """Update state file with code review results."""
157
+ def update_workflow_state_with_review(review_summary: dict, iteration: int):
158
+ """Update workflow state file with code review results."""
114
159
  state_file = Path.cwd() / ".claude" / "api-dev-state.json"
115
- state = load_state()
160
+ state = load_workflow_state()
116
161
 
117
162
  # Add or update code_review phase
118
163
  if "phases" not in state:
119
164
  state["phases"] = {}
120
165
 
121
166
  state["phases"]["code_review"] = {
122
- "status": "complete",
167
+ "status": "in_progress" if review_summary.get("issue_count", 0) > 0 else "complete",
168
+ "iteration": iteration,
123
169
  "score": review_summary.get("score", 0),
124
170
  "issues_found": review_summary.get("issue_count", 0),
125
171
  "suggestions": review_summary.get("suggestion_count", 0),
126
- "reviewed_at": __import__("datetime").datetime.now().isoformat()
172
+ "reviewed_at": datetime.now().isoformat()
127
173
  }
128
174
 
129
175
  try:
@@ -140,7 +186,7 @@ def should_run_review(hook_input: dict) -> bool:
140
186
 
141
187
  tool_name = hook_input.get("tool_name", "")
142
188
 
143
- # Run after tests pass (Phase 9/10) - triggers before refactoring
189
+ # Run after tests pass (Phase 9/10)
144
190
  if tool_name == "Bash":
145
191
  tool_input = hook_input.get("tool_input", {})
146
192
  command = tool_input.get("command", "")
@@ -160,8 +206,40 @@ def should_run_review(hook_input: dict) -> bool:
160
206
  return False
161
207
 
162
208
 
209
+ def format_issues_for_context(issues: list) -> str:
210
+ """Format issues as context for the agent to fix."""
211
+ if not issues:
212
+ return ""
213
+
214
+ lines = [
215
+ "",
216
+ "=" * 60,
217
+ "CODE REVIEW ISSUES TO FIX (Ralph Wiggum Loop)",
218
+ "=" * 60,
219
+ "",
220
+ "The following issues were found by Greptile code review.",
221
+ "Please fix ALL issues, then run tests again.",
222
+ "The review will re-run automatically after tests pass.",
223
+ "",
224
+ "ISSUES:",
225
+ ]
226
+
227
+ for i, issue in enumerate(issues, 1):
228
+ lines.append(f" {i}. {issue}")
229
+
230
+ lines.extend([
231
+ "",
232
+ "After fixing all issues, run: pnpm test",
233
+ "Review will loop until all issues are resolved.",
234
+ "=" * 60,
235
+ ""
236
+ ])
237
+
238
+ return "\n".join(lines)
239
+
240
+
163
241
  def main():
164
- """Main hook entry point."""
242
+ """Main hook entry point with Ralph Wiggum loop pattern."""
165
243
  # Read hook input
166
244
  try:
167
245
  hook_input = json.loads(sys.stdin.read())
@@ -190,6 +268,28 @@ def main():
190
268
  }))
191
269
  return
192
270
 
271
+ # Load current review loop state
272
+ review_state = load_review_state()
273
+
274
+ # Increment iteration
275
+ review_state["iteration"] += 1
276
+ iteration = review_state["iteration"]
277
+
278
+ # Check max iterations
279
+ if iteration > MAX_ITERATIONS:
280
+ print(json.dumps({
281
+ "continue": True,
282
+ "message": f"Code review max iterations ({MAX_ITERATIONS}) reached. Proceeding with warnings.\n\n<promise>REVIEW_CLEAN</promise>"
283
+ }))
284
+ clear_review_state()
285
+ return
286
+
287
+ # Track start time on first iteration
288
+ if iteration == 1:
289
+ review_state["started_at"] = datetime.now().isoformat()
290
+
291
+ review_state["last_review_at"] = datetime.now().isoformat()
292
+
193
293
  # Get repository info
194
294
  repo_owner, repo_name = get_repo_info()
195
295
  if not repo_owner or not repo_name:
@@ -204,11 +304,12 @@ def main():
204
304
  if not diff_content:
205
305
  print(json.dumps({
206
306
  "continue": True,
207
- "message": "No changes detected - skipping code review"
307
+ "message": "No changes detected - code review complete.\n\n<promise>REVIEW_CLEAN</promise>"
208
308
  }))
309
+ clear_review_state()
209
310
  return
210
311
 
211
- # Run the review
312
+ # Run the Greptile review
212
313
  result = review_changes(
213
314
  repo_owner=repo_owner,
214
315
  repo_name=repo_name,
@@ -223,22 +324,68 @@ def main():
223
324
  }))
224
325
  return
225
326
 
226
- # Parse and display results
327
+ # Parse results
227
328
  summary = get_review_summary(result)
228
- update_state_with_review(summary)
329
+ update_workflow_state_with_review(summary, iteration)
229
330
 
230
331
  # Format for display
231
332
  display_output = format_review_for_display(summary)
232
333
 
233
- # Determine if we should block based on critical issues
234
- has_critical = summary.get("score", 10) < 5
334
+ # Check if issues found
335
+ issue_count = summary.get("issue_count", 0)
336
+ issues = summary.get("issues", [])
337
+
338
+ if issue_count == 0:
339
+ # All clean! Emit promise and proceed
340
+ review_state["status"] = "complete"
341
+ save_review_state(review_state)
342
+
343
+ output = f"""
344
+ {display_output}
345
+
346
+ ================================================================================
347
+ REVIEW LOOP COMPLETE (Iteration {iteration}/{MAX_ITERATIONS})
348
+ ================================================================================
349
+ All code review checks passed!
350
+ Proceeding to next phase.
351
+
352
+ <promise>REVIEW_CLEAN</promise>
353
+ """
354
+ print(json.dumps({
355
+ "continue": True,
356
+ "message": output
357
+ }))
358
+ clear_review_state()
359
+ return
360
+
361
+ # Issues found - save state and block for fixes
362
+ review_state["status"] = "needs_fixing"
363
+ review_state["issues_found"] = issues
364
+ save_review_state(review_state)
365
+
366
+ # Format issues as context for agent
367
+ issues_context = format_issues_for_context(issues)
368
+
369
+ output = f"""
370
+ {display_output}
371
+
372
+ ================================================================================
373
+ REVIEW LOOP - ITERATION {iteration}/{MAX_ITERATIONS}
374
+ ================================================================================
375
+ {issue_count} issue(s) found. Fix them and run tests again.
376
+ Review will re-run automatically after tests pass.
377
+ {issues_context}
378
+ """
235
379
 
380
+ # Block workflow - agent needs to fix issues
236
381
  print(json.dumps({
237
- "continue": not has_critical,
238
- "message": display_output,
382
+ "continue": False, # Block until fixed
383
+ "message": output,
239
384
  "review_score": summary.get("score", 0),
240
- "issues_count": summary.get("issue_count", 0),
241
- "action_required": has_critical
385
+ "issues_count": issue_count,
386
+ "iteration": iteration,
387
+ "action_required": True,
388
+ "next_action": "Fix the issues above, then run tests again"
242
389
  }))
243
390
 
244
391
 
File without changes
@@ -23,6 +23,13 @@ from datetime import datetime
23
23
  from pathlib import Path
24
24
  import shutil
25
25
 
26
+ # Import shared utilities for NTFY
27
+ try:
28
+ from hook_utils import send_ntfy_notification
29
+ HAS_NTFY = True
30
+ except ImportError:
31
+ HAS_NTFY = False
32
+
26
33
  STATE_FILE = Path(__file__).parent.parent / "api-dev-state.json"
27
34
  SESSIONS_DIR = Path(__file__).parent.parent / "api-sessions"
28
35
  RESEARCH_DIR = Path(__file__).parent.parent / "research"
@@ -270,12 +277,31 @@ def main():
270
277
  try:
271
278
  session_dir = save_session(endpoint, endpoint_data, state)
272
279
 
280
+ # Send NTFY notification on session end
281
+ if HAS_NTFY:
282
+ status = endpoint_data.get("status", "unknown")
283
+ if status == "complete":
284
+ send_ntfy_notification(
285
+ title=f"✅ Session Complete: {endpoint}",
286
+ message=f"Completed {len(completed)}/13 phases. Session saved.",
287
+ priority="default",
288
+ tags=["white_check_mark", "robot"]
289
+ )
290
+ else:
291
+ send_ntfy_notification(
292
+ title=f"📋 Session Ended: {endpoint}",
293
+ message=f"Completed {len(completed)}/13 phases. Status: {status}",
294
+ priority="low",
295
+ tags=["clipboard", "robot"]
296
+ )
297
+
273
298
  output = {
274
299
  "hookSpecificOutput": {
275
300
  "sessionSaved": True,
276
301
  "endpoint": endpoint,
277
302
  "sessionDir": str(session_dir),
278
- "phasesCompleted": len(completed)
303
+ "phasesCompleted": len(completed),
304
+ "notificationSent": HAS_NTFY
279
305
  }
280
306
  }
281
307
 
@@ -18,6 +18,12 @@ Updated in v3.6.7:
18
18
  - Support multi-API state structure (endpoints object)
19
19
  - Read research index from .claude/research/index.json file
20
20
  - Calculate freshness from timestamps
21
+
22
+ Updated in v4.5.0:
23
+ - Ensure .claude/ directories exist on session start
24
+ - Create registry.json from template if not exists
25
+ - Log session_start event to workflow logs
26
+ - Detect --dry-run and --resume flags
21
27
  """
22
28
  import json
23
29
  import sys
@@ -25,6 +31,20 @@ import os
25
31
  from datetime import datetime
26
32
  from pathlib import Path
27
33
 
34
+ # Import shared utilities
35
+ try:
36
+ from hook_utils import (
37
+ ensure_directories,
38
+ ensure_registry,
39
+ log_workflow_event,
40
+ load_state as utils_load_state,
41
+ save_state as utils_save_state,
42
+ get_project_dir
43
+ )
44
+ UTILS_AVAILABLE = True
45
+ except ImportError:
46
+ UTILS_AVAILABLE = False
47
+
28
48
  # State file is in .claude/ directory (sibling to hooks/)
29
49
  STATE_FILE = Path(__file__).parent.parent / "api-dev-state.json"
30
50
  RESEARCH_INDEX = Path(__file__).parent.parent / "research" / "index.json"
@@ -99,6 +119,93 @@ def calculate_days_old(timestamp_str):
99
119
  return 0
100
120
 
101
121
 
122
+ def setup_session():
123
+ """
124
+ Initialize session: create directories, registry, log session start.
125
+ Called at the beginning of every session (v4.5.0).
126
+
127
+ Returns:
128
+ dict: Setup results with directories created, registry status
129
+ """
130
+ results = {
131
+ "directories_created": [],
132
+ "registry_created": False,
133
+ "session_logged": False
134
+ }
135
+
136
+ if not UTILS_AVAILABLE:
137
+ return results
138
+
139
+ # Ensure directories exist
140
+ try:
141
+ results["directories_created"] = ensure_directories()
142
+ except Exception:
143
+ pass
144
+
145
+ # Ensure registry exists
146
+ try:
147
+ success, created = ensure_registry()
148
+ results["registry_created"] = created
149
+ except Exception:
150
+ pass
151
+
152
+ # Log session start event
153
+ try:
154
+ log_workflow_event("session_start", {
155
+ "directories_created": results["directories_created"],
156
+ "registry_created": results["registry_created"]
157
+ })
158
+ results["session_logged"] = True
159
+ except Exception:
160
+ pass
161
+
162
+ return results
163
+
164
+
165
+ def detect_flags(input_data):
166
+ """
167
+ Detect --dry-run, --resume, and other flags from conversation context.
168
+ Sets flags in state for other hooks to use (v4.5.0).
169
+
170
+ Args:
171
+ input_data: Hook input from stdin
172
+
173
+ Returns:
174
+ dict: Detected flags
175
+ """
176
+ flags = {
177
+ "dry_run": False,
178
+ "resume": None,
179
+ "parallel": False
180
+ }
181
+
182
+ if not UTILS_AVAILABLE:
183
+ return flags
184
+
185
+ # The conversation context might contain flags
186
+ # Note: In SessionStart, we have limited context - flags are typically
187
+ # detected by skills reading the user's initial message
188
+ # This function prepares the state structure for flag detection
189
+
190
+ try:
191
+ state = utils_load_state()
192
+
193
+ # Initialize flags structure if not present
194
+ if "flags" not in state:
195
+ state["flags"] = {
196
+ "dry_run": False,
197
+ "resume": None,
198
+ "parallel": False
199
+ }
200
+ utils_save_state(state)
201
+
202
+ flags = state.get("flags", flags)
203
+ except Exception:
204
+ pass
205
+
206
+ return flags
207
+
208
+
102
209
  def main():
103
210
  # Read hook input from stdin
104
211
  try:
@@ -108,6 +215,12 @@ def main():
108
215
 
109
216
  cwd = input_data.get("cwd", os.getcwd())
110
217
 
218
+ # v4.5.0: Initialize session (directories, registry, logging)
219
+ setup_results = setup_session()
220
+
221
+ # v4.5.0: Detect and store flags
222
+ flags = detect_flags(input_data)
223
+
111
224
  # Check if state file exists
112
225
  if not STATE_FILE.exists():
113
226
  # No active workflow - just continue without injection
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hustle-together/api-dev-tools",
3
- "version": "3.12.16",
3
+ "version": "4.5.3",
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": {
@@ -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)