@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,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()