@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
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Hook: check-api-routes.py
|
|
4
|
-
Trigger: PreToolUse (Write|Edit)
|
|
5
|
-
Purpose: Verify required API routes exist before page implementation
|
|
6
|
-
|
|
7
|
-
For ui-create-page workflow, ensures Phase 7 (ENVIRONMENT) has verified
|
|
8
|
-
that required API routes are available.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import json
|
|
12
|
-
import sys
|
|
13
|
-
import os
|
|
14
|
-
import glob
|
|
15
|
-
|
|
16
|
-
def load_state():
|
|
17
|
-
"""Load the api-dev-state.json file"""
|
|
18
|
-
state_paths = [
|
|
19
|
-
".claude/api-dev-state.json",
|
|
20
|
-
os.path.join(os.environ.get("CLAUDE_PROJECT_DIR", ""), ".claude/api-dev-state.json")
|
|
21
|
-
]
|
|
22
|
-
|
|
23
|
-
for path in state_paths:
|
|
24
|
-
if os.path.exists(path):
|
|
25
|
-
with open(path, 'r') as f:
|
|
26
|
-
return json.load(f)
|
|
27
|
-
return None
|
|
28
|
-
|
|
29
|
-
def is_page_workflow(state):
|
|
30
|
-
"""Check if current workflow is ui-create-page"""
|
|
31
|
-
workflow = state.get("workflow", "")
|
|
32
|
-
return workflow == "ui-create-page"
|
|
33
|
-
|
|
34
|
-
def get_active_element(state):
|
|
35
|
-
"""Get the active element being worked on"""
|
|
36
|
-
active = state.get("active_element", "")
|
|
37
|
-
if not active:
|
|
38
|
-
active = state.get("endpoint", "")
|
|
39
|
-
return active
|
|
40
|
-
|
|
41
|
-
def is_page_implementation(file_path, element_name):
|
|
42
|
-
"""Check if the file is a page implementation file"""
|
|
43
|
-
if not file_path or not element_name:
|
|
44
|
-
return False
|
|
45
|
-
|
|
46
|
-
patterns = [
|
|
47
|
-
f"src/app/{element_name}/page.tsx",
|
|
48
|
-
f"app/{element_name}/page.tsx",
|
|
49
|
-
]
|
|
50
|
-
|
|
51
|
-
return any(pattern in file_path for pattern in patterns)
|
|
52
|
-
|
|
53
|
-
def check_environment_phase(state, element_name):
|
|
54
|
-
"""Check if environment phase is complete"""
|
|
55
|
-
elements = state.get("elements", {})
|
|
56
|
-
element = elements.get(element_name, {})
|
|
57
|
-
phases = element.get("phases", {})
|
|
58
|
-
|
|
59
|
-
environment = phases.get("environment_check", {})
|
|
60
|
-
return environment.get("status") == "complete"
|
|
61
|
-
|
|
62
|
-
def get_required_api_routes(state, element_name):
|
|
63
|
-
"""Get list of required API routes from interview decisions"""
|
|
64
|
-
elements = state.get("elements", {})
|
|
65
|
-
element = elements.get(element_name, {})
|
|
66
|
-
ui_config = element.get("ui_config", {})
|
|
67
|
-
|
|
68
|
-
# Check if data sources were defined
|
|
69
|
-
data_sources = ui_config.get("data_sources", [])
|
|
70
|
-
return data_sources
|
|
71
|
-
|
|
72
|
-
def find_existing_api_routes():
|
|
73
|
-
"""Find all existing API routes in the project"""
|
|
74
|
-
routes = []
|
|
75
|
-
|
|
76
|
-
# Check src/app/api paths
|
|
77
|
-
api_patterns = [
|
|
78
|
-
"src/app/api/**/*.ts",
|
|
79
|
-
"src/app/api/**/*.tsx",
|
|
80
|
-
"app/api/**/*.ts",
|
|
81
|
-
"app/api/**/*.tsx",
|
|
82
|
-
]
|
|
83
|
-
|
|
84
|
-
for pattern in api_patterns:
|
|
85
|
-
for file_path in glob.glob(pattern, recursive=True):
|
|
86
|
-
if "route.ts" in file_path or "route.tsx" in file_path:
|
|
87
|
-
# Extract route name from path
|
|
88
|
-
route = file_path.replace("src/app/api/", "/api/")
|
|
89
|
-
route = route.replace("app/api/", "/api/")
|
|
90
|
-
route = route.replace("/route.ts", "")
|
|
91
|
-
route = route.replace("/route.tsx", "")
|
|
92
|
-
routes.append(route)
|
|
93
|
-
|
|
94
|
-
return routes
|
|
95
|
-
|
|
96
|
-
def main():
|
|
97
|
-
try:
|
|
98
|
-
# Read tool input from stdin
|
|
99
|
-
input_data = json.loads(sys.stdin.read())
|
|
100
|
-
tool_name = input_data.get("tool_name", "")
|
|
101
|
-
tool_input = input_data.get("tool_input", {})
|
|
102
|
-
|
|
103
|
-
# Only check Write tool
|
|
104
|
-
if tool_name != "Write":
|
|
105
|
-
print(json.dumps({"decision": "allow"}))
|
|
106
|
-
return
|
|
107
|
-
|
|
108
|
-
file_path = tool_input.get("file_path", "")
|
|
109
|
-
|
|
110
|
-
# Load state
|
|
111
|
-
state = load_state()
|
|
112
|
-
if not state:
|
|
113
|
-
print(json.dumps({"decision": "allow"}))
|
|
114
|
-
return
|
|
115
|
-
|
|
116
|
-
# Only apply to ui-create-page workflow
|
|
117
|
-
if not is_page_workflow(state):
|
|
118
|
-
print(json.dumps({"decision": "allow"}))
|
|
119
|
-
return
|
|
120
|
-
|
|
121
|
-
element_name = get_active_element(state)
|
|
122
|
-
if not element_name:
|
|
123
|
-
print(json.dumps({"decision": "allow"}))
|
|
124
|
-
return
|
|
125
|
-
|
|
126
|
-
# Check if writing main page file
|
|
127
|
-
if is_page_implementation(file_path, element_name):
|
|
128
|
-
# Verify environment phase is complete
|
|
129
|
-
if not check_environment_phase(state, element_name):
|
|
130
|
-
# Find existing API routes for reference
|
|
131
|
-
existing_routes = find_existing_api_routes()
|
|
132
|
-
routes_list = "\n".join([f" - {r}" for r in existing_routes[:15]])
|
|
133
|
-
if len(existing_routes) > 15:
|
|
134
|
-
routes_list += f"\n ... and {len(existing_routes) - 15} more"
|
|
135
|
-
|
|
136
|
-
print(json.dumps({
|
|
137
|
-
"decision": "block",
|
|
138
|
-
"reason": f"""
|
|
139
|
-
ENVIRONMENT CHECK REQUIRED (Phase 7)
|
|
140
|
-
|
|
141
|
-
You are implementing the main page, but the Environment phase is not complete.
|
|
142
|
-
|
|
143
|
-
Before implementing page.tsx:
|
|
144
|
-
1. Verify required API routes exist
|
|
145
|
-
2. Check authentication configuration
|
|
146
|
-
3. Verify required packages are installed
|
|
147
|
-
4. Update state: phases.environment_check.status = "complete"
|
|
148
|
-
|
|
149
|
-
Existing API Routes Found:
|
|
150
|
-
{routes_list if existing_routes else " (No API routes found)"}
|
|
151
|
-
|
|
152
|
-
If you need new API routes, use /api-create to create them first.
|
|
153
|
-
"""
|
|
154
|
-
}))
|
|
155
|
-
return
|
|
156
|
-
|
|
157
|
-
# Allow everything else
|
|
158
|
-
print(json.dumps({"decision": "allow"}))
|
|
159
|
-
|
|
160
|
-
except Exception as e:
|
|
161
|
-
# On error, allow to avoid blocking workflow
|
|
162
|
-
print(json.dumps({
|
|
163
|
-
"decision": "allow",
|
|
164
|
-
"error": str(e)
|
|
165
|
-
}))
|
|
166
|
-
|
|
167
|
-
if __name__ == "__main__":
|
|
168
|
-
main()
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Hook: PreToolUse for Write
|
|
4
|
-
Purpose: Verify Playwright is configured before writing E2E test files
|
|
5
|
-
|
|
6
|
-
This hook runs before writing E2E test files. It checks that:
|
|
7
|
-
- playwright.config.ts or playwright.config.js exists
|
|
8
|
-
- @playwright/test is in package.json dependencies
|
|
9
|
-
|
|
10
|
-
If Playwright is not configured, it blocks and suggests installation.
|
|
11
|
-
|
|
12
|
-
Version: 3.9.0
|
|
13
|
-
|
|
14
|
-
Returns:
|
|
15
|
-
- {"continue": true} - If Playwright is configured or not an E2E test file
|
|
16
|
-
- {"continue": false, "reason": "..."} - If Playwright is not configured
|
|
17
|
-
"""
|
|
18
|
-
import json
|
|
19
|
-
import sys
|
|
20
|
-
from pathlib import Path
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def main():
|
|
24
|
-
# Read hook input from stdin
|
|
25
|
-
try:
|
|
26
|
-
input_data = json.load(sys.stdin)
|
|
27
|
-
except json.JSONDecodeError:
|
|
28
|
-
print(json.dumps({"continue": True}))
|
|
29
|
-
sys.exit(0)
|
|
30
|
-
|
|
31
|
-
tool_name = input_data.get("tool_name", "")
|
|
32
|
-
tool_input = input_data.get("tool_input", {})
|
|
33
|
-
|
|
34
|
-
# Only check Write operations
|
|
35
|
-
if tool_name != "Write":
|
|
36
|
-
print(json.dumps({"continue": True}))
|
|
37
|
-
sys.exit(0)
|
|
38
|
-
|
|
39
|
-
# Check if writing an E2E test file
|
|
40
|
-
file_path = tool_input.get("file_path", "")
|
|
41
|
-
|
|
42
|
-
# Common E2E test patterns
|
|
43
|
-
is_e2e_test = (
|
|
44
|
-
file_path.endswith(".spec.ts") or
|
|
45
|
-
file_path.endswith(".spec.tsx") or
|
|
46
|
-
file_path.endswith(".e2e.ts") or
|
|
47
|
-
file_path.endswith(".e2e.tsx") or
|
|
48
|
-
"/e2e/" in file_path or
|
|
49
|
-
"/tests/e2e/" in file_path
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
if not is_e2e_test:
|
|
53
|
-
print(json.dumps({"continue": True}))
|
|
54
|
-
sys.exit(0)
|
|
55
|
-
|
|
56
|
-
# Look for playwright config in common locations
|
|
57
|
-
cwd = Path.cwd()
|
|
58
|
-
config_files = [
|
|
59
|
-
cwd / "playwright.config.ts",
|
|
60
|
-
cwd / "playwright.config.js",
|
|
61
|
-
cwd.parent / "playwright.config.ts",
|
|
62
|
-
cwd.parent / "playwright.config.js",
|
|
63
|
-
]
|
|
64
|
-
|
|
65
|
-
playwright_found = False
|
|
66
|
-
for config_file in config_files:
|
|
67
|
-
if config_file.exists():
|
|
68
|
-
playwright_found = True
|
|
69
|
-
break
|
|
70
|
-
|
|
71
|
-
# Also check package.json for @playwright/test
|
|
72
|
-
if not playwright_found:
|
|
73
|
-
package_json = cwd / "package.json"
|
|
74
|
-
if package_json.exists():
|
|
75
|
-
try:
|
|
76
|
-
pkg = json.loads(package_json.read_text())
|
|
77
|
-
deps = pkg.get("devDependencies", {})
|
|
78
|
-
deps.update(pkg.get("dependencies", {}))
|
|
79
|
-
if "@playwright/test" in deps:
|
|
80
|
-
playwright_found = True
|
|
81
|
-
except (json.JSONDecodeError, IOError):
|
|
82
|
-
pass
|
|
83
|
-
|
|
84
|
-
if not playwright_found:
|
|
85
|
-
print(json.dumps({
|
|
86
|
-
"continue": False,
|
|
87
|
-
"reason": (
|
|
88
|
-
"Playwright is not configured in this project.\n\n"
|
|
89
|
-
"Before writing E2E test files, please install Playwright:\n\n"
|
|
90
|
-
" npm init playwright@latest\n\n"
|
|
91
|
-
"This will create playwright.config.ts and install browsers.\n"
|
|
92
|
-
"After installation, run 'npx playwright test' to run tests."
|
|
93
|
-
)
|
|
94
|
-
}))
|
|
95
|
-
sys.exit(0)
|
|
96
|
-
|
|
97
|
-
# Playwright is configured
|
|
98
|
-
print(json.dumps({"continue": True}))
|
|
99
|
-
sys.exit(0)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if __name__ == "__main__":
|
|
103
|
-
main()
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Hook: PreToolUse for Write
|
|
4
|
-
Purpose: Verify Storybook is configured before writing story files
|
|
5
|
-
|
|
6
|
-
This hook runs before writing .stories.tsx files. It checks that:
|
|
7
|
-
- .storybook/ directory exists
|
|
8
|
-
- main.ts or main.js config exists
|
|
9
|
-
|
|
10
|
-
If Storybook is not configured, it blocks and suggests installation.
|
|
11
|
-
|
|
12
|
-
Version: 3.9.0
|
|
13
|
-
|
|
14
|
-
Returns:
|
|
15
|
-
- {"continue": true} - If Storybook is configured or not a story file
|
|
16
|
-
- {"continue": false, "reason": "..."} - If Storybook is not configured
|
|
17
|
-
"""
|
|
18
|
-
import json
|
|
19
|
-
import sys
|
|
20
|
-
from pathlib import Path
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def main():
|
|
24
|
-
# Read hook input from stdin
|
|
25
|
-
try:
|
|
26
|
-
input_data = json.load(sys.stdin)
|
|
27
|
-
except json.JSONDecodeError:
|
|
28
|
-
print(json.dumps({"continue": True}))
|
|
29
|
-
sys.exit(0)
|
|
30
|
-
|
|
31
|
-
tool_name = input_data.get("tool_name", "")
|
|
32
|
-
tool_input = input_data.get("tool_input", {})
|
|
33
|
-
|
|
34
|
-
# Only check Write operations
|
|
35
|
-
if tool_name != "Write":
|
|
36
|
-
print(json.dumps({"continue": True}))
|
|
37
|
-
sys.exit(0)
|
|
38
|
-
|
|
39
|
-
# Check if writing a story file
|
|
40
|
-
file_path = tool_input.get("file_path", "")
|
|
41
|
-
if not file_path.endswith(".stories.tsx") and not file_path.endswith(".stories.ts"):
|
|
42
|
-
print(json.dumps({"continue": True}))
|
|
43
|
-
sys.exit(0)
|
|
44
|
-
|
|
45
|
-
# Look for .storybook directory in common locations
|
|
46
|
-
cwd = Path.cwd()
|
|
47
|
-
storybook_dirs = [
|
|
48
|
-
cwd / ".storybook",
|
|
49
|
-
cwd.parent / ".storybook", # In case running from subdirectory
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
storybook_found = False
|
|
53
|
-
for storybook_dir in storybook_dirs:
|
|
54
|
-
if storybook_dir.exists():
|
|
55
|
-
# Check for main config file
|
|
56
|
-
main_ts = storybook_dir / "main.ts"
|
|
57
|
-
main_js = storybook_dir / "main.js"
|
|
58
|
-
if main_ts.exists() or main_js.exists():
|
|
59
|
-
storybook_found = True
|
|
60
|
-
break
|
|
61
|
-
|
|
62
|
-
if not storybook_found:
|
|
63
|
-
print(json.dumps({
|
|
64
|
-
"continue": False,
|
|
65
|
-
"reason": (
|
|
66
|
-
"Storybook is not configured in this project.\n\n"
|
|
67
|
-
"Before writing story files, please install Storybook:\n\n"
|
|
68
|
-
" npx storybook@latest init\n\n"
|
|
69
|
-
"This will create the .storybook/ directory and configuration.\n"
|
|
70
|
-
"After installation, run 'pnpm storybook' to start the dev server."
|
|
71
|
-
)
|
|
72
|
-
}))
|
|
73
|
-
sys.exit(0)
|
|
74
|
-
|
|
75
|
-
# Storybook is configured
|
|
76
|
-
print(json.dumps({"continue": True}))
|
|
77
|
-
sys.exit(0)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if __name__ == "__main__":
|
|
81
|
-
main()
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Check for api-dev-tools updates at session start.
|
|
4
|
-
|
|
5
|
-
This hook runs at SessionStart and checks npm registry for newer versions.
|
|
6
|
-
Non-blocking - only injects a message if update available.
|
|
7
|
-
|
|
8
|
-
Hook Type: SessionStart
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import json
|
|
12
|
-
import os
|
|
13
|
-
import sys
|
|
14
|
-
import subprocess
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def get_installed_version():
|
|
19
|
-
"""Get currently installed version from package.json"""
|
|
20
|
-
project_dir = os.environ.get("CLAUDE_PROJECT_DIR", ".")
|
|
21
|
-
package_json = Path(project_dir) / "package.json"
|
|
22
|
-
|
|
23
|
-
if package_json.exists():
|
|
24
|
-
try:
|
|
25
|
-
data = json.loads(package_json.read_text())
|
|
26
|
-
# Check if this project uses api-dev-tools
|
|
27
|
-
deps = data.get("devDependencies", {})
|
|
28
|
-
deps.update(data.get("dependencies", {}))
|
|
29
|
-
|
|
30
|
-
if "@hustle-together/api-dev-tools" in deps:
|
|
31
|
-
version = deps["@hustle-together/api-dev-tools"]
|
|
32
|
-
# Remove ^ or ~ prefix
|
|
33
|
-
return version.lstrip("^~")
|
|
34
|
-
except Exception:
|
|
35
|
-
pass
|
|
36
|
-
|
|
37
|
-
# Check for version in state file
|
|
38
|
-
state_file = Path(project_dir) / ".claude" / "api-dev-state.json"
|
|
39
|
-
if state_file.exists():
|
|
40
|
-
try:
|
|
41
|
-
state = json.loads(state_file.read_text())
|
|
42
|
-
return state.get("version", "0.0.0")
|
|
43
|
-
except Exception:
|
|
44
|
-
pass
|
|
45
|
-
|
|
46
|
-
return None
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def get_latest_version():
|
|
50
|
-
"""Check npm registry for latest version"""
|
|
51
|
-
try:
|
|
52
|
-
result = subprocess.run(
|
|
53
|
-
["npm", "view", "@hustle-together/api-dev-tools", "version"],
|
|
54
|
-
capture_output=True,
|
|
55
|
-
text=True,
|
|
56
|
-
timeout=5
|
|
57
|
-
)
|
|
58
|
-
if result.returncode == 0:
|
|
59
|
-
return result.stdout.strip()
|
|
60
|
-
except Exception:
|
|
61
|
-
pass
|
|
62
|
-
return None
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def version_tuple(v):
|
|
66
|
-
"""Convert version string to tuple for comparison"""
|
|
67
|
-
try:
|
|
68
|
-
return tuple(map(int, v.split(".")))
|
|
69
|
-
except Exception:
|
|
70
|
-
return (0, 0, 0)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def main():
|
|
74
|
-
# Check if we should skip (already checked recently)
|
|
75
|
-
project_dir = os.environ.get("CLAUDE_PROJECT_DIR", ".")
|
|
76
|
-
state_file = Path(project_dir) / ".claude" / "api-dev-state.json"
|
|
77
|
-
|
|
78
|
-
try:
|
|
79
|
-
if state_file.exists():
|
|
80
|
-
state = json.loads(state_file.read_text())
|
|
81
|
-
last_check = state.get("last_update_check")
|
|
82
|
-
|
|
83
|
-
if last_check:
|
|
84
|
-
from datetime import datetime, timedelta
|
|
85
|
-
last_check_dt = datetime.fromisoformat(last_check)
|
|
86
|
-
if datetime.now() - last_check_dt < timedelta(hours=24):
|
|
87
|
-
# Already checked today, skip
|
|
88
|
-
print(json.dumps({"continue": True}))
|
|
89
|
-
return
|
|
90
|
-
except Exception:
|
|
91
|
-
pass
|
|
92
|
-
|
|
93
|
-
installed = get_installed_version()
|
|
94
|
-
latest = get_latest_version()
|
|
95
|
-
|
|
96
|
-
result = {"continue": True}
|
|
97
|
-
|
|
98
|
-
if installed and latest:
|
|
99
|
-
if version_tuple(latest) > version_tuple(installed):
|
|
100
|
-
result["additionalContext"] = f"""
|
|
101
|
-
## Update Available
|
|
102
|
-
|
|
103
|
-
A new version of api-dev-tools is available:
|
|
104
|
-
- **Current**: {installed}
|
|
105
|
-
- **Latest**: {latest}
|
|
106
|
-
|
|
107
|
-
To update, run:
|
|
108
|
-
```bash
|
|
109
|
-
npx @hustle-together/api-dev-tools@latest
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
This update may include new features, bug fixes, and improved workflows.
|
|
113
|
-
"""
|
|
114
|
-
# Update state with last check time
|
|
115
|
-
try:
|
|
116
|
-
if state_file.exists():
|
|
117
|
-
state = json.loads(state_file.read_text())
|
|
118
|
-
else:
|
|
119
|
-
state = {}
|
|
120
|
-
|
|
121
|
-
from datetime import datetime
|
|
122
|
-
state["last_update_check"] = datetime.now().isoformat()
|
|
123
|
-
state["available_update"] = latest
|
|
124
|
-
state_file.write_text(json.dumps(state, indent=2))
|
|
125
|
-
except Exception:
|
|
126
|
-
pass
|
|
127
|
-
|
|
128
|
-
print(json.dumps(result))
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if __name__ == "__main__":
|
|
132
|
-
main()
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Hook: SessionStart
|
|
4
|
-
Purpose: Detect and prompt for interrupted workflows
|
|
5
|
-
|
|
6
|
-
This hook runs at session start and checks if there are any
|
|
7
|
-
in-progress workflows that were interrupted. If found, it injects
|
|
8
|
-
a prompt asking the user if they want to resume.
|
|
9
|
-
|
|
10
|
-
Added in v3.6.7 for session continuation support.
|
|
11
|
-
|
|
12
|
-
Returns:
|
|
13
|
-
- JSON with additionalContext about interrupted workflows
|
|
14
|
-
"""
|
|
15
|
-
import json
|
|
16
|
-
import sys
|
|
17
|
-
import os
|
|
18
|
-
from datetime import datetime
|
|
19
|
-
from pathlib import Path
|
|
20
|
-
|
|
21
|
-
STATE_FILE = Path(__file__).parent.parent / "api-dev-state.json"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def get_interrupted_workflows(state):
|
|
25
|
-
"""Find all workflows that are in_progress but not active."""
|
|
26
|
-
interrupted = []
|
|
27
|
-
|
|
28
|
-
# New format (v3.6.7+): check endpoints object
|
|
29
|
-
if "endpoints" in state:
|
|
30
|
-
active = state.get("active_endpoint")
|
|
31
|
-
for endpoint_name, endpoint_data in state["endpoints"].items():
|
|
32
|
-
status = endpoint_data.get("status", "not_started")
|
|
33
|
-
if status == "in_progress" and endpoint_name != active:
|
|
34
|
-
# Find the current phase
|
|
35
|
-
phases = endpoint_data.get("phases", {})
|
|
36
|
-
current_phase = None
|
|
37
|
-
for phase_name, phase_data in phases.items():
|
|
38
|
-
if phase_data.get("status") == "in_progress":
|
|
39
|
-
current_phase = phase_name
|
|
40
|
-
break
|
|
41
|
-
|
|
42
|
-
interrupted.append({
|
|
43
|
-
"endpoint": endpoint_name,
|
|
44
|
-
"status": status,
|
|
45
|
-
"current_phase": current_phase,
|
|
46
|
-
"started_at": endpoint_data.get("started_at"),
|
|
47
|
-
"interrupted_at": endpoint_data.get("session", {}).get("interrupted_at"),
|
|
48
|
-
"interrupted_phase": endpoint_data.get("session", {}).get("interrupted_phase")
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
# Also check if active endpoint is not fully started
|
|
52
|
-
if active and active in state["endpoints"]:
|
|
53
|
-
active_data = state["endpoints"][active]
|
|
54
|
-
session = active_data.get("session", {})
|
|
55
|
-
if session.get("interrupted_at"):
|
|
56
|
-
# Active endpoint was previously interrupted
|
|
57
|
-
interrupted.insert(0, {
|
|
58
|
-
"endpoint": active,
|
|
59
|
-
"status": active_data.get("status"),
|
|
60
|
-
"current_phase": session.get("interrupted_phase"),
|
|
61
|
-
"started_at": active_data.get("started_at"),
|
|
62
|
-
"interrupted_at": session.get("interrupted_at"),
|
|
63
|
-
"is_active": True
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
# Old format: single endpoint
|
|
67
|
-
elif state.get("endpoint"):
|
|
68
|
-
endpoint = state.get("endpoint")
|
|
69
|
-
phases = state.get("phases", {})
|
|
70
|
-
|
|
71
|
-
# Check if any phase is in_progress
|
|
72
|
-
for phase_name, phase_data in phases.items():
|
|
73
|
-
if phase_data.get("status") == "in_progress":
|
|
74
|
-
interrupted.append({
|
|
75
|
-
"endpoint": endpoint,
|
|
76
|
-
"status": "in_progress",
|
|
77
|
-
"current_phase": phase_name,
|
|
78
|
-
"started_at": state.get("created_at"),
|
|
79
|
-
"is_legacy": True
|
|
80
|
-
})
|
|
81
|
-
break
|
|
82
|
-
|
|
83
|
-
return interrupted
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def format_interrupted_message(interrupted):
|
|
87
|
-
"""Format a user-friendly message about interrupted workflows."""
|
|
88
|
-
if not interrupted:
|
|
89
|
-
return None
|
|
90
|
-
|
|
91
|
-
lines = [
|
|
92
|
-
"",
|
|
93
|
-
"=" * 60,
|
|
94
|
-
" INTERRUPTED WORKFLOW DETECTED",
|
|
95
|
-
"=" * 60,
|
|
96
|
-
""
|
|
97
|
-
]
|
|
98
|
-
|
|
99
|
-
for i, workflow in enumerate(interrupted, 1):
|
|
100
|
-
endpoint = workflow["endpoint"]
|
|
101
|
-
phase = workflow.get("current_phase", "unknown")
|
|
102
|
-
started = workflow.get("started_at", "unknown")
|
|
103
|
-
interrupted_at = workflow.get("interrupted_at", "")
|
|
104
|
-
|
|
105
|
-
lines.append(f"{i}. **{endpoint}**")
|
|
106
|
-
lines.append(f" - Phase: {phase}")
|
|
107
|
-
lines.append(f" - Started: {started}")
|
|
108
|
-
if interrupted_at:
|
|
109
|
-
lines.append(f" - Interrupted: {interrupted_at}")
|
|
110
|
-
lines.append("")
|
|
111
|
-
|
|
112
|
-
lines.extend([
|
|
113
|
-
"To resume an interrupted workflow, use:",
|
|
114
|
-
" /api-continue [endpoint-name]",
|
|
115
|
-
"",
|
|
116
|
-
"Or start a new workflow with:",
|
|
117
|
-
" /api-create [new-endpoint-name]",
|
|
118
|
-
"",
|
|
119
|
-
"=" * 60
|
|
120
|
-
])
|
|
121
|
-
|
|
122
|
-
return "\n".join(lines)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def main():
|
|
126
|
-
try:
|
|
127
|
-
input_data = json.load(sys.stdin)
|
|
128
|
-
except json.JSONDecodeError:
|
|
129
|
-
input_data = {}
|
|
130
|
-
|
|
131
|
-
# Check if state file exists
|
|
132
|
-
if not STATE_FILE.exists():
|
|
133
|
-
print(json.dumps({"continue": True}))
|
|
134
|
-
sys.exit(0)
|
|
135
|
-
|
|
136
|
-
try:
|
|
137
|
-
state = json.loads(STATE_FILE.read_text())
|
|
138
|
-
except json.JSONDecodeError:
|
|
139
|
-
print(json.dumps({"continue": True}))
|
|
140
|
-
sys.exit(0)
|
|
141
|
-
|
|
142
|
-
# Find interrupted workflows
|
|
143
|
-
interrupted = get_interrupted_workflows(state)
|
|
144
|
-
|
|
145
|
-
if not interrupted:
|
|
146
|
-
print(json.dumps({"continue": True}))
|
|
147
|
-
sys.exit(0)
|
|
148
|
-
|
|
149
|
-
# Format message
|
|
150
|
-
message = format_interrupted_message(interrupted)
|
|
151
|
-
|
|
152
|
-
output = {
|
|
153
|
-
"hookSpecificOutput": {
|
|
154
|
-
"hookEventName": "SessionStart",
|
|
155
|
-
"additionalContext": message,
|
|
156
|
-
"interruptedWorkflows": interrupted
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
print(json.dumps(output))
|
|
161
|
-
sys.exit(0)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if __name__ == "__main__":
|
|
165
|
-
main()
|