@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.
- package/.claude/adr-requests/.gitkeep +10 -0
- package/.claude/agents/adr-researcher.md +109 -0
- package/.claude/agents/visual-analyzer.md +183 -0
- package/.claude/api-dev-state.json +10 -0
- package/.claude/documentation-audit.json +114 -0
- package/.claude/registry.json +289 -0
- package/.claude/settings.json +45 -1
- package/.claude/settings.local.json +1 -7
- package/.claude/workflow-logs/None.json +49 -0
- package/.claude/workflow-logs/session-20251230-143727.json +106 -0
- package/.skills/adr-deep-research/SKILL.md +351 -0
- package/.skills/api-create/SKILL.md +34 -20
- package/.skills/api-research/SKILL.md +130 -0
- package/.skills/docs-update/SKILL.md +205 -0
- package/.skills/hustle-brand/SKILL.md +368 -0
- package/.skills/hustle-build/SKILL.md +365 -38
- package/.skills/parallel-spawn/SKILL.md +212 -0
- package/.skills/ralph-continue/SKILL.md +151 -0
- package/.skills/ralph-loop/SKILL.md +341 -0
- package/.skills/ralph-status/SKILL.md +87 -0
- package/.skills/refactor/SKILL.md +59 -0
- package/.skills/shadcn/SKILL.md +522 -0
- package/.skills/test-all/SKILL.md +210 -0
- package/.skills/test-builds/SKILL.md +208 -0
- package/.skills/test-debug/SKILL.md +212 -0
- package/.skills/test-e2e/SKILL.md +168 -0
- package/.skills/test-review/SKILL.md +707 -0
- package/.skills/test-unit/SKILL.md +143 -0
- package/.skills/test-visual/SKILL.md +301 -0
- package/.skills/token-report/SKILL.md +132 -0
- package/CHANGELOG.md +488 -0
- package/README.md +346 -53
- package/bin/cli.js +359 -123
- package/hooks/__pycache__/api-workflow-check.cpython-314.pyc +0 -0
- package/hooks/__pycache__/auto-answer.cpython-314.pyc +0 -0
- package/hooks/__pycache__/cache-research.cpython-314.pyc +0 -0
- package/hooks/__pycache__/check-api-routes.cpython-314.pyc +0 -0
- package/hooks/__pycache__/check-playwright-setup.cpython-314.pyc +0 -0
- package/hooks/__pycache__/check-storybook-setup.cpython-314.pyc +0 -0
- package/hooks/__pycache__/check-update.cpython-314.pyc +0 -0
- package/hooks/__pycache__/completion-promise-detector.cpython-314.pyc +0 -0
- package/hooks/__pycache__/context-capacity-warning.cpython-314.pyc +0 -0
- package/hooks/__pycache__/detect-interruption.cpython-314.pyc +0 -0
- package/hooks/__pycache__/docs-update-check.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-a11y-audit.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-brand-guide.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-component-type-confirm.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-deep-research.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-disambiguation.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-documentation.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-dry-run.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-environment.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-external-research.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-freshness.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-interview.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-page-components.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-page-data-schema.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-questions-sourced.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-refactor.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-research.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-schema-from-interview.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-schema.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-scope.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-tdd-red.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-ui-disambiguation.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-ui-interview.cpython-314.pyc +0 -0
- package/hooks/__pycache__/enforce-verify.cpython-314.pyc +0 -0
- package/hooks/__pycache__/generate-adr-options.cpython-314.pyc +0 -0
- package/hooks/__pycache__/generate-manifest-entry.cpython-314.pyc +0 -0
- package/hooks/__pycache__/hook_utils.cpython-314.pyc +0 -0
- package/hooks/__pycache__/notify-input-needed.cpython-314.pyc +0 -0
- package/hooks/__pycache__/notify-phase-complete.cpython-314.pyc +0 -0
- package/hooks/__pycache__/ntfy-on-question.cpython-314.pyc +0 -0
- package/hooks/__pycache__/orchestrator-completion.cpython-314.pyc +0 -0
- package/hooks/__pycache__/orchestrator-handoff.cpython-314.pyc +0 -0
- package/hooks/__pycache__/orchestrator-session-startup.cpython-314.pyc +0 -0
- package/hooks/__pycache__/parallel-orchestrator.cpython-314.pyc +0 -0
- package/hooks/__pycache__/periodic-reground.cpython-314.pyc +0 -0
- package/hooks/__pycache__/project-document-prompt.cpython-314.pyc +0 -0
- package/hooks/__pycache__/remote-question-proxy.cpython-314.pyc +0 -0
- package/hooks/__pycache__/remote-question-server.cpython-314.pyc +0 -0
- package/hooks/__pycache__/run-code-review.cpython-314.pyc +0 -0
- package/hooks/__pycache__/run-visual-qa.cpython-314.pyc +0 -0
- package/hooks/__pycache__/session-logger.cpython-314.pyc +0 -0
- package/hooks/__pycache__/session-startup.cpython-314.pyc +0 -0
- package/hooks/__pycache__/track-scope-coverage.cpython-314.pyc +0 -0
- package/hooks/__pycache__/track-token-usage.cpython-314.pyc +0 -0
- package/hooks/__pycache__/track-tool-use.cpython-314.pyc +0 -0
- package/hooks/__pycache__/update-adr-decision.cpython-314.pyc +0 -0
- package/hooks/__pycache__/update-api-showcase.cpython-314.pyc +0 -0
- package/hooks/__pycache__/update-registry.cpython-314.pyc +0 -0
- package/hooks/__pycache__/update-ui-showcase.cpython-314.pyc +0 -0
- package/hooks/__pycache__/verify-after-green.cpython-314.pyc +0 -0
- package/hooks/__pycache__/verify-implementation.cpython-314.pyc +0 -0
- package/hooks/api-workflow-check.py +34 -0
- package/hooks/auto-answer.py +97 -20
- package/{.claude/hooks → hooks}/completion-promise-detector.py +0 -0
- package/{.claude/hooks → hooks}/context-capacity-warning.py +0 -0
- package/{.claude/hooks → hooks}/docs-update-check.py +0 -0
- package/{.claude/hooks → hooks}/enforce-dry-run.py +0 -0
- package/hooks/enforce-external-research.py +25 -0
- package/hooks/enforce-interview.py +20 -0
- package/{.claude/hooks → hooks}/generate-adr-options.py +0 -0
- package/{.claude/hooks → hooks}/hook_utils.py +0 -0
- package/hooks/ntfy-on-question.py +15 -2
- package/hooks/orchestrator-handoff.py +81 -3
- package/{.claude/hooks → hooks}/parallel-orchestrator.py +0 -0
- package/hooks/periodic-reground.py +40 -0
- package/{.claude/hooks → hooks}/remote-question-server.py +0 -0
- package/hooks/run-code-review.py +176 -29
- package/{.claude/hooks → hooks}/run-visual-qa.py +0 -0
- package/hooks/session-logger.py +27 -1
- package/hooks/session-startup.py +113 -0
- package/{.claude/hooks → hooks}/update-adr-decision.py +0 -0
- package/package.json +1 -1
- package/templates/.skills/hustle-interview/SKILL.md +174 -0
- package/templates/adr-viewer/_components/ADRViewer.tsx +326 -0
- package/templates/api-dev-state.json +33 -1
- package/templates/brand-page/page.tsx +645 -0
- package/templates/component/Component.visual.spec.ts +30 -24
- package/templates/eslint-plugin-zod-schema/index.js +446 -0
- package/templates/eslint-plugin-zod-schema/package.json +26 -0
- package/templates/github-workflows/security.yml +274 -0
- package/templates/hustle-build-defaults.json +53 -1
- package/templates/page/page.e2e.test.ts +30 -26
- package/templates/performance-budgets.json +63 -5
- package/templates/registry.json +279 -3
- package/templates/review-dashboard/page.tsx +510 -0
- package/templates/settings.json +74 -7
- package/templates/ui-showcase/_components/UIShowcase.tsx +47 -0
- package/templates/ui-showcase/_components/VisualTestingDashboard.tsx +579 -0
- package/.claude/commands/hustle-combine.md +0 -1089
- package/.claude/commands/hustle-ui-create-page.md +0 -1078
- package/.claude/commands/hustle-ui-create.md +0 -1058
- package/.claude/hooks/auto-answer.py +0 -305
- package/.claude/hooks/cache-research.py +0 -337
- package/.claude/hooks/check-api-routes.py +0 -168
- package/.claude/hooks/check-playwright-setup.py +0 -103
- package/.claude/hooks/check-storybook-setup.py +0 -81
- package/.claude/hooks/check-update.py +0 -132
- package/.claude/hooks/detect-interruption.py +0 -165
- package/.claude/hooks/enforce-a11y-audit.py +0 -202
- package/.claude/hooks/enforce-brand-guide.py +0 -241
- package/.claude/hooks/enforce-component-type-confirm.py +0 -97
- package/.claude/hooks/enforce-freshness.py +0 -184
- package/.claude/hooks/enforce-page-components.py +0 -186
- package/.claude/hooks/enforce-page-data-schema.py +0 -155
- package/.claude/hooks/enforce-questions-sourced.py +0 -146
- package/.claude/hooks/enforce-schema-from-interview.py +0 -248
- package/.claude/hooks/enforce-ui-disambiguation.py +0 -108
- package/.claude/hooks/enforce-ui-interview.py +0 -130
- package/.claude/hooks/generate-manifest-entry.py +0 -1161
- package/.claude/hooks/lib/__init__.py +0 -1
- package/.claude/hooks/lib/greptile.py +0 -355
- package/.claude/hooks/lib/ntfy.py +0 -209
- package/.claude/hooks/notify-input-needed.py +0 -73
- package/.claude/hooks/notify-phase-complete.py +0 -90
- package/.claude/hooks/ntfy-on-question.py +0 -240
- package/.claude/hooks/orchestrator-completion.py +0 -313
- package/.claude/hooks/orchestrator-handoff.py +0 -267
- package/.claude/hooks/orchestrator-session-startup.py +0 -146
- package/.claude/hooks/run-code-review.py +0 -393
- package/.claude/hooks/session-logger.py +0 -323
- package/.claude/hooks/test-orchestrator-reground.py +0 -248
- package/.claude/hooks/track-scope-coverage.py +0 -220
- package/.claude/hooks/track-token-usage.py +0 -121
- package/.claude/hooks/update-api-showcase.py +0 -161
- package/.claude/hooks/update-registry.py +0 -352
- package/.claude/hooks/update-ui-showcase.py +0 -224
- package/.claude/test-auto-answer-bot.py +0 -183
- package/.claude/test-completion-detector.py +0 -263
- package/.claude/test-orchestrator-state.json +0 -20
- package/.claude/test-orchestrator.sh +0 -271
- /package/{.claude/commands → commands}/hustle-build.md +0 -0
- /package/{.claude/hooks → hooks}/lib/__pycache__/__init__.cpython-314.pyc +0 -0
- /package/{.claude/hooks → hooks}/lib/__pycache__/greptile.cpython-314.pyc +0 -0
- /package/{.claude/hooks → hooks}/lib/__pycache__/ntfy.cpython-314.pyc +0 -0
- /package/{.claude/hooks → hooks}/project-document-prompt.py +0 -0
- /package/{.claude/hooks → hooks}/remote-question-proxy.py +0 -0
- /package/{.claude/hooks → hooks}/update-testing-checklist.py +0 -0
package/hooks/run-code-review.py
CHANGED
|
@@ -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
|
|
6
|
-
This ensures
|
|
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
|
|
8
|
+
Hook Type: PostToolUse (triggers after tests pass)
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
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:
|
|
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
|
|
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
|
|
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 =
|
|
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":
|
|
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)
|
|
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 -
|
|
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
|
|
327
|
+
# Parse results
|
|
227
328
|
summary = get_review_summary(result)
|
|
228
|
-
|
|
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
|
-
#
|
|
234
|
-
|
|
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":
|
|
238
|
-
"message":
|
|
382
|
+
"continue": False, # Block until fixed
|
|
383
|
+
"message": output,
|
|
239
384
|
"review_score": summary.get("score", 0),
|
|
240
|
-
"issues_count":
|
|
241
|
-
"
|
|
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
|
package/hooks/session-logger.py
CHANGED
|
@@ -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
|
|
package/hooks/session-startup.py
CHANGED
|
@@ -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
|
+
"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)
|