@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,323 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Hook: Stop
4
- Purpose: Save session to .claude/api-sessions/ for later review
5
-
6
- This hook runs when a Claude Code session ends (Stop event).
7
- It saves the session data for the completed workflow including:
8
- - State snapshot at completion
9
- - Files created during the workflow
10
- - Summary of phases completed
11
- - Research sources used
12
- - Interview decisions made
13
-
14
- Added in v3.6.7 for session logging support.
15
-
16
- Returns:
17
- - JSON with session save info
18
- """
19
- import json
20
- import sys
21
- import os
22
- from datetime import datetime
23
- from pathlib import Path
24
- import shutil
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
-
33
- STATE_FILE = Path(__file__).parent.parent / "api-dev-state.json"
34
- SESSIONS_DIR = Path(__file__).parent.parent / "api-sessions"
35
- RESEARCH_DIR = Path(__file__).parent.parent / "research"
36
-
37
-
38
- def get_active_endpoint(state):
39
- """Get active endpoint - supports both old and new state formats."""
40
- # New format (v3.6.7+): endpoints object with active_endpoint pointer
41
- if "endpoints" in state and "active_endpoint" in state:
42
- active = state.get("active_endpoint")
43
- if active and active in state["endpoints"]:
44
- return active, state["endpoints"][active]
45
- return None, None
46
-
47
- # Old format: single endpoint field
48
- endpoint = state.get("endpoint")
49
- if endpoint:
50
- return endpoint, state
51
-
52
- return None, None
53
-
54
-
55
- def get_completed_phases(endpoint_data):
56
- """Get list of completed phases."""
57
- completed = []
58
- phases = endpoint_data.get("phases", {})
59
-
60
- phase_order = [
61
- "disambiguation", "scope", "research_initial", "interview",
62
- "research_deep", "schema_creation", "environment_check",
63
- "tdd_red", "tdd_green", "verify", "tdd_refactor", "documentation", "completion"
64
- ]
65
-
66
- for phase_name in phase_order:
67
- phase = phases.get(phase_name, {})
68
- if phase.get("status") == "complete":
69
- completed.append(phase_name)
70
-
71
- return completed
72
-
73
-
74
- def get_files_created(endpoint_data):
75
- """Get list of files created during this workflow."""
76
- files = []
77
-
78
- # From completion phase
79
- completion = endpoint_data.get("phases", {}).get("completion", {})
80
- files.extend(completion.get("files_created", []))
81
-
82
- # From schema phase
83
- schema = endpoint_data.get("phases", {}).get("schema_creation", {})
84
- if schema.get("schema_file"):
85
- files.append(schema.get("schema_file"))
86
-
87
- # From TDD phases
88
- tdd_red = endpoint_data.get("phases", {}).get("tdd_red", {})
89
- if tdd_red.get("test_file"):
90
- files.append(tdd_red.get("test_file"))
91
-
92
- tdd_green = endpoint_data.get("phases", {}).get("tdd_green", {})
93
- if tdd_green.get("implementation_file"):
94
- files.append(tdd_green.get("implementation_file"))
95
-
96
- return list(set(files)) # Deduplicate
97
-
98
-
99
- def generate_summary(endpoint, endpoint_data, state):
100
- """Generate a markdown summary of the session."""
101
- completed = get_completed_phases(endpoint_data)
102
- files = get_files_created(endpoint_data)
103
- decisions = endpoint_data.get("phases", {}).get("interview", {}).get("decisions", {})
104
-
105
- lines = [
106
- f"# Session Summary: {endpoint}",
107
- "",
108
- f"*Generated: {datetime.now().isoformat()}*",
109
- "",
110
- "## Overview",
111
- "",
112
- f"- **Endpoint:** {endpoint}",
113
- f"- **Library:** {endpoint_data.get('library', 'N/A')}",
114
- f"- **Started:** {endpoint_data.get('started_at', 'N/A')}",
115
- f"- **Completed Phases:** {len(completed)}/13",
116
- f"- **Status:** {endpoint_data.get('status', 'unknown')}",
117
- "",
118
- "## Phases Completed",
119
- ""
120
- ]
121
-
122
- for i, phase in enumerate(completed, 1):
123
- lines.append(f"{i}. {phase.replace('_', ' ').title()}")
124
-
125
- lines.extend([
126
- "",
127
- "## Files Created",
128
- ""
129
- ])
130
-
131
- for f in files:
132
- lines.append(f"- `{f}`")
133
-
134
- if decisions:
135
- lines.extend([
136
- "",
137
- "## Interview Decisions",
138
- ""
139
- ])
140
- for key, value in decisions.items():
141
- response = value.get("response", value.get("value", "N/A"))
142
- lines.append(f"- **{key}:** {response}")
143
-
144
- lines.extend([
145
- "",
146
- "## Research Sources",
147
- ""
148
- ])
149
-
150
- # Check for research cache
151
- research_path = RESEARCH_DIR / endpoint / "sources.json"
152
- if research_path.exists():
153
- try:
154
- sources = json.loads(research_path.read_text())
155
- for src in sources.get("sources", [])[:10]: # Limit to 10
156
- url = src.get("url", src.get("query", ""))
157
- if url:
158
- lines.append(f"- {url}")
159
- except (json.JSONDecodeError, IOError):
160
- lines.append("- (sources.json not readable)")
161
- else:
162
- lines.append("- (no sources.json found)")
163
-
164
- lines.extend([
165
- "",
166
- "---",
167
- "",
168
- f"*Session saved to: .claude/api-sessions/{endpoint}_{{timestamp}}/*"
169
- ])
170
-
171
- return "\n".join(lines)
172
-
173
-
174
- def save_session(endpoint, endpoint_data, state):
175
- """Save session to .claude/api-sessions/."""
176
- # Create timestamp
177
- timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
178
- session_dir = SESSIONS_DIR / f"{endpoint}_{timestamp}"
179
- session_dir.mkdir(parents=True, exist_ok=True)
180
-
181
- # 1. Save state snapshot
182
- state_snapshot = {
183
- "saved_at": datetime.now().isoformat(),
184
- "endpoint": endpoint,
185
- "endpoint_data": endpoint_data,
186
- "turn_count": state.get("turn_count", 0),
187
- "research_queries": state.get("research_queries", [])
188
- }
189
- (session_dir / "state-snapshot.json").write_text(json.dumps(state_snapshot, indent=2))
190
-
191
- # 2. Save files list
192
- files = get_files_created(endpoint_data)
193
- (session_dir / "files-created.txt").write_text("\n".join(files))
194
-
195
- # 3. Generate and save summary
196
- summary = generate_summary(endpoint, endpoint_data, state)
197
- (session_dir / "summary.md").write_text(summary)
198
-
199
- # 4. Copy research cache if exists
200
- research_src = RESEARCH_DIR / endpoint
201
- if research_src.exists():
202
- research_dst = session_dir / "research-cache"
203
- research_dst.mkdir(exist_ok=True)
204
- for f in research_src.iterdir():
205
- if f.is_file():
206
- shutil.copy2(f, research_dst / f.name)
207
-
208
- # 5. Update sessions index
209
- update_sessions_index(endpoint, timestamp, endpoint_data)
210
-
211
- return session_dir
212
-
213
-
214
- def update_sessions_index(endpoint, timestamp, endpoint_data):
215
- """Update the sessions index file."""
216
- index_file = SESSIONS_DIR / "index.json"
217
-
218
- if index_file.exists():
219
- try:
220
- index = json.loads(index_file.read_text())
221
- except json.JSONDecodeError:
222
- index = {"version": "3.6.7", "sessions": []}
223
- else:
224
- index = {"version": "3.6.7", "sessions": []}
225
-
226
- # Add this session
227
- completed = get_completed_phases(endpoint_data)
228
- index["sessions"].append({
229
- "endpoint": endpoint,
230
- "timestamp": timestamp,
231
- "folder": f"{endpoint}_{timestamp}",
232
- "status": endpoint_data.get("status", "unknown"),
233
- "phases_completed": len(completed),
234
- "created_at": datetime.now().isoformat()
235
- })
236
-
237
- index_file.write_text(json.dumps(index, indent=2))
238
-
239
-
240
- def main():
241
- try:
242
- input_data = json.load(sys.stdin)
243
- except json.JSONDecodeError:
244
- print(json.dumps({"continue": True}))
245
- sys.exit(0)
246
-
247
- # Check if state file exists
248
- if not STATE_FILE.exists():
249
- print(json.dumps({"continue": True}))
250
- sys.exit(0)
251
-
252
- try:
253
- state = json.loads(STATE_FILE.read_text())
254
- except json.JSONDecodeError:
255
- print(json.dumps({"continue": True}))
256
- sys.exit(0)
257
-
258
- # Get active endpoint
259
- endpoint, endpoint_data = get_active_endpoint(state)
260
- if not endpoint or not endpoint_data:
261
- print(json.dumps({"continue": True}))
262
- sys.exit(0)
263
-
264
- # Only save if there's meaningful progress
265
- completed = get_completed_phases(endpoint_data)
266
- if len(completed) < 2:
267
- # Not enough progress to save
268
- print(json.dumps({
269
- "hookSpecificOutput": {
270
- "sessionSaved": False,
271
- "reason": "Not enough progress to save (need at least 2 completed phases)"
272
- }
273
- }))
274
- sys.exit(0)
275
-
276
- # Save the session
277
- try:
278
- session_dir = save_session(endpoint, endpoint_data, state)
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
-
298
- output = {
299
- "hookSpecificOutput": {
300
- "sessionSaved": True,
301
- "endpoint": endpoint,
302
- "sessionDir": str(session_dir),
303
- "phasesCompleted": len(completed),
304
- "notificationSent": HAS_NTFY
305
- }
306
- }
307
-
308
- print(json.dumps(output))
309
- sys.exit(0)
310
-
311
- except Exception as e:
312
- output = {
313
- "hookSpecificOutput": {
314
- "sessionSaved": False,
315
- "error": str(e)
316
- }
317
- }
318
- print(json.dumps(output))
319
- sys.exit(0)
320
-
321
-
322
- if __name__ == "__main__":
323
- main()
@@ -1,248 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Test Orchestrator Re-grounding Hook
4
-
5
- Runs every 5 turns during test orchestration to:
6
- 1. Re-inject testing goals and current progress
7
- 2. Send NTFY notification with status update
8
- 3. Prevent context dilution during long test sessions
9
-
10
- Hook Type: PostToolUse
11
- Trigger: Every 5 turns
12
- NTFY Topic: test_api_devtools_alerts
13
-
14
- Version: 1.0.0
15
- """
16
-
17
- import json
18
- import os
19
- import sys
20
- import subprocess
21
- from datetime import datetime
22
- from pathlib import Path
23
-
24
- # Configuration
25
- REGROUND_INTERVAL = 5 # Re-ground every 5 turns
26
- NTFY_TOPIC = "test_api_devtools_alerts"
27
- STATE_FILE = Path(__file__).parent.parent / "test-orchestrator-state.json"
28
-
29
-
30
- def send_ntfy(message, title="Test Orchestrator", priority=3, tags=None):
31
- """Send NTFY notification."""
32
- try:
33
- headers = [
34
- f"Title: {title}",
35
- f"Priority: {priority}",
36
- ]
37
- if tags:
38
- headers.append(f"Tags: {','.join(tags)}")
39
-
40
- header_args = []
41
- for h in headers:
42
- header_args.extend(["-H", h])
43
-
44
- subprocess.run(
45
- ["curl", "-s"] + header_args + ["-d", message, f"https://ntfy.sh/{NTFY_TOPIC}"],
46
- capture_output=True,
47
- timeout=10
48
- )
49
- except Exception as e:
50
- # Don't fail if notification fails
51
- print(f"NTFY failed: {e}", file=sys.stderr)
52
-
53
-
54
- def load_test_state():
55
- """Load test orchestrator state."""
56
- if not STATE_FILE.exists():
57
- return {
58
- "turn_count": 0,
59
- "started_at": datetime.now().isoformat(),
60
- "commands_tested": {},
61
- "current_command": None,
62
- "current_phase": None,
63
- "total_retries": 0,
64
- "reground_history": []
65
- }
66
-
67
- try:
68
- return json.loads(STATE_FILE.read_text())
69
- except Exception:
70
- return {
71
- "turn_count": 0,
72
- "started_at": datetime.now().isoformat(),
73
- "commands_tested": {},
74
- "current_command": None,
75
- "current_phase": None,
76
- "total_retries": 0,
77
- "reground_history": []
78
- }
79
-
80
-
81
- def save_test_state(state):
82
- """Save test orchestrator state."""
83
- STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
84
- STATE_FILE.write_text(json.dumps(state, indent=2))
85
-
86
-
87
- def format_progress_summary(state):
88
- """Format a progress summary for re-grounding."""
89
- commands = state.get("commands_tested", {})
90
-
91
- summary_lines = []
92
- summary_lines.append("## Test Orchestrator Progress")
93
- summary_lines.append("")
94
-
95
- # Overall stats
96
- turn = state.get("turn_count", 0)
97
- started = state.get("started_at", "unknown")
98
- summary_lines.append(f"**Turn:** {turn}")
99
- summary_lines.append(f"**Started:** {started}")
100
- summary_lines.append(f"**Total Retries:** {state.get('total_retries', 0)}")
101
- summary_lines.append("")
102
-
103
- # Command progress
104
- summary_lines.append("**Command Progress:**")
105
- all_commands = [
106
- "/api-create",
107
- "/hustle-ui-create",
108
- "/hustle-ui-create-page",
109
- "/hustle-combine",
110
- "/hustle-build"
111
- ]
112
-
113
- for cmd in all_commands:
114
- cmd_state = commands.get(cmd, {})
115
- status = cmd_state.get("status", "NOT STARTED")
116
-
117
- icon = {
118
- "PASSED": "✅",
119
- "FAILED": "❌",
120
- "IN PROGRESS": "🔄",
121
- "NOT STARTED": "⏳"
122
- }.get(status, "❓")
123
-
124
- phases = cmd_state.get("phases_complete", 0)
125
- retries = cmd_state.get("retries", 0)
126
-
127
- if status == "PASSED":
128
- summary_lines.append(f"- {icon} {cmd}: PASSED ({phases}/14 phases)")
129
- elif status == "FAILED":
130
- summary_lines.append(f"- {icon} {cmd}: FAILED at phase {phases}/14 (retry {retries}/∞)")
131
- elif status == "IN PROGRESS":
132
- summary_lines.append(f"- {icon} {cmd}: IN PROGRESS (phase {phases}/14)")
133
- else:
134
- summary_lines.append(f"- {icon} {cmd}: NOT STARTED")
135
-
136
- summary_lines.append("")
137
-
138
- # Current task
139
- current_cmd = state.get("current_command")
140
- current_phase = state.get("current_phase")
141
- if current_cmd:
142
- summary_lines.append(f"**Current Task:** {current_cmd} - Phase {current_phase}")
143
- else:
144
- summary_lines.append("**Current Task:** Initializing test harness")
145
-
146
- summary_lines.append("")
147
- summary_lines.append("## Primary Goal")
148
- summary_lines.append("")
149
- summary_lines.append("Test ALL 5 commands until they work perfectly:")
150
- summary_lines.append("1. Run each command in isolated test directory")
151
- summary_lines.append("2. Auto-answer questions via pending-answer.json")
152
- summary_lines.append("3. Verify ALL 14 phases complete")
153
- summary_lines.append("4. Verify ALL hooks fire correctly")
154
- summary_lines.append("5. If tests fail: research, fix code, rebuild, retry")
155
- summary_lines.append("6. NEVER STOP until all 5 commands pass")
156
- summary_lines.append("")
157
- summary_lines.append("## Key Resources")
158
- summary_lines.append("")
159
- summary_lines.append("- Test directory: ~/test-api-dev-tools-auto/")
160
- summary_lines.append("- .env file: Copy from /Users/alfonso/Documents/GitHub/api-dev-tools/.env.example")
161
- summary_lines.append("- WORKFLOW_CHECKLIST.md: Track results")
162
- summary_lines.append("- NTFY topic: test_api_devtools_alerts")
163
- summary_lines.append("")
164
- summary_lines.append("## Failure Strategy")
165
- summary_lines.append("")
166
- summary_lines.append("If stuck after 5 retries:")
167
- summary_lines.append("1. Use WebSearch to research the error")
168
- summary_lines.append("2. Find similar issues and solutions")
169
- summary_lines.append("3. Try new approaches")
170
- summary_lines.append("4. Use git commits as savepoints")
171
- summary_lines.append("5. NEVER give up - keep iterating")
172
-
173
- return "\n".join(summary_lines)
174
-
175
-
176
- def main():
177
- # Load state
178
- state = load_test_state()
179
-
180
- # Increment turn count
181
- turn_count = state.get("turn_count", 0) + 1
182
- state["turn_count"] = turn_count
183
- state["last_turn_timestamp"] = datetime.now().isoformat()
184
-
185
- # Check if we should re-ground
186
- should_reground = turn_count % REGROUND_INTERVAL == 0
187
-
188
- if should_reground:
189
- # Generate progress summary
190
- summary = format_progress_summary(state)
191
-
192
- # Send NTFY notification
193
- commands = state.get("commands_tested", {})
194
- passed = sum(1 for c in commands.values() if c.get("status") == "PASSED")
195
- in_progress = sum(1 for c in commands.values() if c.get("status") == "IN PROGRESS")
196
- failed = sum(1 for c in commands.values() if c.get("status") == "FAILED")
197
-
198
- ntfy_msg = f"""Turn {turn_count} Update:
199
- ✅ Passed: {passed}/5
200
- 🔄 In Progress: {in_progress}/5
201
- ❌ Failed: {failed}/5
202
-
203
- Current: {state.get('current_command', 'Initializing')}
204
- Retries: {state.get('total_retries', 0)}
205
- """
206
-
207
- send_ntfy(
208
- ntfy_msg,
209
- title=f"🔄 Turn {turn_count} - Test Orchestrator",
210
- priority=3,
211
- tags=["robot", "test"]
212
- )
213
-
214
- # Add to reground history
215
- reground_history = state.setdefault("reground_history", [])
216
- reground_history.append({
217
- "turn": turn_count,
218
- "timestamp": datetime.now().isoformat(),
219
- "current_command": state.get("current_command"),
220
- "current_phase": state.get("current_phase"),
221
- "passed": passed,
222
- "failed": failed
223
- })
224
- # Keep only last 20 reground events
225
- state["reground_history"] = reground_history[-20:]
226
-
227
- # Save state
228
- save_test_state(state)
229
-
230
- # Output with context injection
231
- output = {
232
- "continue": True,
233
- "hookSpecificOutput": {
234
- "hookEventName": "PostToolUse",
235
- "additionalContext": summary
236
- }
237
- }
238
- print(json.dumps(output))
239
- else:
240
- # Just update turn count
241
- save_test_state(state)
242
- print(json.dumps({"continue": True}))
243
-
244
- sys.exit(0)
245
-
246
-
247
- if __name__ == "__main__":
248
- main()