@hustle-together/api-dev-tools 3.12.3 → 4.5.1
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 +7 -463
- package/.claude/documentation-audit.json +114 -0
- package/.claude/registry.json +289 -0
- package/.claude/settings.json +45 -1
- 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 +116 -17
- package/.skills/api-research/SKILL.md +130 -0
- package/.skills/docs-sync/SKILL.md +260 -0
- package/.skills/docs-update/SKILL.md +205 -0
- package/.skills/hustle-brand/SKILL.md +368 -0
- package/.skills/hustle-build/SKILL.md +786 -0
- package/.skills/hustle-build-review/SKILL.md +518 -0
- 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 +575 -0
- package/README.md +426 -56
- package/bin/cli.js +1538 -88
- package/commands/hustle-api-create.md +22 -0
- package/commands/hustle-build.md +259 -0
- package/commands/hustle-combine.md +81 -2
- package/commands/hustle-ui-create-page.md +84 -2
- package/commands/hustle-ui-create.md +82 -2
- 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 +305 -0
- package/hooks/check-update.py +132 -0
- package/hooks/completion-promise-detector.py +293 -0
- package/hooks/context-capacity-warning.py +171 -0
- package/hooks/docs-update-check.py +120 -0
- package/hooks/enforce-dry-run.py +134 -0
- package/hooks/enforce-external-research.py +25 -0
- package/hooks/enforce-interview.py +20 -0
- package/hooks/generate-adr-options.py +282 -0
- package/hooks/hook_utils.py +609 -0
- package/hooks/lib/__pycache__/__init__.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/greptile.cpython-314.pyc +0 -0
- package/hooks/lib/__pycache__/ntfy.cpython-314.pyc +0 -0
- package/hooks/ntfy-on-question.py +240 -0
- package/hooks/orchestrator-completion.py +313 -0
- package/hooks/orchestrator-handoff.py +267 -0
- package/hooks/orchestrator-session-startup.py +146 -0
- package/hooks/parallel-orchestrator.py +451 -0
- package/hooks/periodic-reground.py +270 -67
- package/hooks/project-document-prompt.py +302 -0
- package/hooks/remote-question-proxy.py +284 -0
- package/hooks/remote-question-server.py +1224 -0
- package/hooks/run-code-review.py +176 -29
- package/hooks/run-visual-qa.py +338 -0
- package/hooks/session-logger.py +27 -1
- package/hooks/session-startup.py +113 -0
- package/hooks/update-adr-decision.py +236 -0
- package/hooks/update-api-showcase.py +13 -1
- package/hooks/update-testing-checklist.py +195 -0
- package/hooks/update-ui-showcase.py +13 -1
- package/package.json +7 -3
- package/scripts/extract-schema-docs.cjs +322 -0
- package/templates/.skills/hustle-interview/SKILL.md +174 -0
- package/templates/CLAUDE-SECTION.md +89 -64
- package/templates/adr-viewer/_components/ADRViewer.tsx +326 -0
- package/templates/api-dev-state.json +33 -1
- package/templates/api-showcase/_components/APIModal.tsx +100 -8
- package/templates/api-showcase/_components/APIShowcase.tsx +36 -4
- package/templates/api-showcase/_components/APITester.tsx +367 -58
- package/templates/brand-page/page.tsx +645 -0
- package/templates/component/Component.visual.spec.ts +30 -24
- package/templates/docs/page.tsx +230 -0
- 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 +136 -0
- package/templates/hustle-dev-dashboard/page.tsx +365 -0
- package/templates/page/page.e2e.test.ts +30 -26
- package/templates/performance-budgets.json +63 -5
- package/templates/playwright-report/page.tsx +258 -0
- package/templates/registry.json +279 -3
- package/templates/review-dashboard/page.tsx +510 -0
- package/templates/settings.json +155 -7
- package/templates/test-results/page.tsx +237 -0
- package/templates/typedoc.json +19 -0
- package/templates/ui-showcase/_components/UIShowcase.tsx +48 -1
- package/templates/ui-showcase/_components/VisualTestingDashboard.tsx +579 -0
- package/templates/ui-showcase/page.tsx +1 -1
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -17,6 +17,10 @@ v3.6.7 Enhancement:
|
|
|
17
17
|
- Research cache location
|
|
18
18
|
- Summary statistics
|
|
19
19
|
|
|
20
|
+
v3.12.13 Fix:
|
|
21
|
+
- Skip enforcement when running in source repository (developing the package)
|
|
22
|
+
- Detect via package.json name = @hustle-together/api-dev-tools
|
|
23
|
+
|
|
20
24
|
Returns:
|
|
21
25
|
- {"decision": "approve"} - Allow stopping
|
|
22
26
|
- {"decision": "block", "reason": "..."} - Prevent stopping with explanation
|
|
@@ -28,6 +32,36 @@ import re
|
|
|
28
32
|
from datetime import datetime
|
|
29
33
|
from pathlib import Path
|
|
30
34
|
|
|
35
|
+
|
|
36
|
+
def is_source_repository() -> bool:
|
|
37
|
+
"""
|
|
38
|
+
Check if we're running in the api-dev-tools source repository.
|
|
39
|
+
If so, hooks should NOT enforce workflow - we're developing, not using.
|
|
40
|
+
"""
|
|
41
|
+
try:
|
|
42
|
+
# Use parent of hooks dir (project root), not cwd which may be hooks/
|
|
43
|
+
project_root = Path(__file__).parent.parent
|
|
44
|
+
package_json = project_root / "package.json"
|
|
45
|
+
if package_json.exists():
|
|
46
|
+
data = json.loads(package_json.read_text())
|
|
47
|
+
# If this is the source repo, skip enforcement
|
|
48
|
+
if data.get("name") == "@hustle-together/api-dev-tools":
|
|
49
|
+
return True
|
|
50
|
+
|
|
51
|
+
# Also check for templates/ folder (only exists in source repo)
|
|
52
|
+
if (project_root / "templates").is_dir():
|
|
53
|
+
return True
|
|
54
|
+
|
|
55
|
+
except Exception:
|
|
56
|
+
pass
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Skip enforcement in source repository
|
|
61
|
+
if is_source_repository():
|
|
62
|
+
print(json.dumps({"decision": "approve"}))
|
|
63
|
+
sys.exit(0)
|
|
64
|
+
|
|
31
65
|
# State file is in .claude/ directory (sibling to hooks/)
|
|
32
66
|
STATE_FILE = Path(__file__).parent.parent / "api-dev-state.json"
|
|
33
67
|
RESEARCH_DIR = Path(__file__).parent.parent / "research"
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Auto-answer hook for --auto mode.
|
|
4
|
+
|
|
5
|
+
This hook intercepts AskUserQuestion calls when running in auto-mode
|
|
6
|
+
and either:
|
|
7
|
+
1. Uses pre-configured defaults from hustle-build-defaults.json
|
|
8
|
+
2. Spawns a Haiku sub-agent to pick the most comprehensive option
|
|
9
|
+
|
|
10
|
+
Hook Type: PreToolUse (matcher: AskUserQuestion)
|
|
11
|
+
|
|
12
|
+
Updated in v4.5.0:
|
|
13
|
+
- Use shared hook_utils for logging
|
|
14
|
+
- Log all auto-answered questions to workflow logs
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
# Import shared utilities
|
|
23
|
+
try:
|
|
24
|
+
from hook_utils import log_workflow_event
|
|
25
|
+
UTILS_AVAILABLE = True
|
|
26
|
+
except ImportError:
|
|
27
|
+
UTILS_AVAILABLE = False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def load_state():
|
|
31
|
+
"""Load workflow state to check if in auto mode"""
|
|
32
|
+
project_dir = os.environ.get("CLAUDE_PROJECT_DIR", ".")
|
|
33
|
+
|
|
34
|
+
# Check hustle-build state first
|
|
35
|
+
build_state = Path(project_dir) / ".claude" / "hustle-build-state.json"
|
|
36
|
+
if build_state.exists():
|
|
37
|
+
try:
|
|
38
|
+
state = json.loads(build_state.read_text())
|
|
39
|
+
if state.get("mode") == "auto":
|
|
40
|
+
return state, "build"
|
|
41
|
+
except Exception:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
# Check api-dev state
|
|
45
|
+
api_state = Path(project_dir) / ".claude" / "api-dev-state.json"
|
|
46
|
+
if api_state.exists():
|
|
47
|
+
try:
|
|
48
|
+
state = json.loads(api_state.read_text())
|
|
49
|
+
if state.get("mode") == "auto":
|
|
50
|
+
return state, "workflow"
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
return None, None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def load_defaults():
|
|
58
|
+
"""Load pre-configured default answers"""
|
|
59
|
+
project_dir = os.environ.get("CLAUDE_PROJECT_DIR", ".")
|
|
60
|
+
|
|
61
|
+
# Check project-specific defaults first
|
|
62
|
+
defaults_file = Path(project_dir) / ".claude" / "hustle-build-defaults.json"
|
|
63
|
+
if defaults_file.exists():
|
|
64
|
+
try:
|
|
65
|
+
return json.loads(defaults_file.read_text())
|
|
66
|
+
except Exception:
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
# Fall back to template defaults
|
|
70
|
+
template_defaults = Path(project_dir) / "templates" / "hustle-build-defaults.json"
|
|
71
|
+
if template_defaults.exists():
|
|
72
|
+
try:
|
|
73
|
+
return json.loads(template_defaults.read_text())
|
|
74
|
+
except Exception:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
return {}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def is_autonomous_enabled():
|
|
81
|
+
"""Check if autonomous mode is enabled by default in settings"""
|
|
82
|
+
defaults = load_defaults()
|
|
83
|
+
autonomous = defaults.get("autonomous", {})
|
|
84
|
+
return autonomous.get("enabled", False) and autonomous.get("skip_interviews", False)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def find_comprehensive_option(options):
|
|
88
|
+
"""
|
|
89
|
+
Find the most comprehensive option based on keywords.
|
|
90
|
+
|
|
91
|
+
Comprehensive options typically include words like:
|
|
92
|
+
- "all", "full", "complete", "comprehensive"
|
|
93
|
+
- Higher numbers (e.g., "100%" vs "50%")
|
|
94
|
+
- More features listed
|
|
95
|
+
|
|
96
|
+
Also prioritizes affirmative options for phase exits:
|
|
97
|
+
- "yes", "proceed", "continue", "approve", "confirm"
|
|
98
|
+
"""
|
|
99
|
+
if not options:
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
comprehensive_keywords = [
|
|
103
|
+
"all", "full", "complete", "comprehensive", "everything",
|
|
104
|
+
"maximum", "extensive", "detailed", "thorough", "wcag-aa"
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
# Affirmative keywords for phase exit questions
|
|
108
|
+
affirmative_keywords = [
|
|
109
|
+
"yes", "proceed", "continue", "approve", "confirm",
|
|
110
|
+
"accept", "ready", "go ahead", "move forward",
|
|
111
|
+
"auto", "defaults", "use auto", "use defaults"
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
# Negative keywords to avoid
|
|
115
|
+
negative_keywords = [
|
|
116
|
+
"no", "skip", "cancel", "stop", "more research", "not ready"
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
# Score each option
|
|
120
|
+
scored = []
|
|
121
|
+
for i, opt in enumerate(options):
|
|
122
|
+
label = opt.get("label", "").lower()
|
|
123
|
+
description = opt.get("description", "").lower()
|
|
124
|
+
text = f"{label} {description}"
|
|
125
|
+
|
|
126
|
+
score = 0
|
|
127
|
+
|
|
128
|
+
# Check for negative keywords first (penalize heavily)
|
|
129
|
+
for keyword in negative_keywords:
|
|
130
|
+
if keyword in text:
|
|
131
|
+
score -= 50
|
|
132
|
+
|
|
133
|
+
# Check for comprehensive keywords
|
|
134
|
+
for keyword in comprehensive_keywords:
|
|
135
|
+
if keyword in text:
|
|
136
|
+
score += 10
|
|
137
|
+
|
|
138
|
+
# Check for affirmative keywords (high priority for phase exits)
|
|
139
|
+
for keyword in affirmative_keywords:
|
|
140
|
+
if keyword in text:
|
|
141
|
+
score += 25
|
|
142
|
+
|
|
143
|
+
# Check for "(Recommended)" suffix
|
|
144
|
+
if "recommended" in label.lower():
|
|
145
|
+
score += 20
|
|
146
|
+
|
|
147
|
+
# Prefer options with more content (longer descriptions = more features)
|
|
148
|
+
score += len(description) / 50
|
|
149
|
+
|
|
150
|
+
scored.append((i, score, opt))
|
|
151
|
+
|
|
152
|
+
# Sort by score descending
|
|
153
|
+
scored.sort(key=lambda x: x[1], reverse=True)
|
|
154
|
+
|
|
155
|
+
# Return the index of the best option (0-based)
|
|
156
|
+
if scored:
|
|
157
|
+
return scored[0][0]
|
|
158
|
+
|
|
159
|
+
return 0 # Default to first option
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def get_question_key(questions):
|
|
163
|
+
"""Extract a key from the question for lookup in defaults"""
|
|
164
|
+
if not questions or len(questions) == 0:
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
q = questions[0]
|
|
168
|
+
header = q.get("header", "").lower().replace(" ", "_")
|
|
169
|
+
return header
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def main():
|
|
173
|
+
# Get tool input from environment
|
|
174
|
+
tool_input = os.environ.get("CLAUDE_TOOL_INPUT", "{}")
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
input_data = json.loads(tool_input)
|
|
178
|
+
except Exception:
|
|
179
|
+
print(json.dumps({"continue": True}))
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
# Check if in auto mode (explicit flag OR defaults enabled)
|
|
183
|
+
state, state_type = load_state()
|
|
184
|
+
autonomous_by_default = is_autonomous_enabled()
|
|
185
|
+
|
|
186
|
+
if not state and not autonomous_by_default:
|
|
187
|
+
# Not in auto mode and autonomous not enabled, continue normally
|
|
188
|
+
print(json.dumps({"continue": True}))
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
# If no state but autonomous is enabled, create a minimal state
|
|
192
|
+
if not state and autonomous_by_default:
|
|
193
|
+
state = {"mode": "auto", "source": "defaults"}
|
|
194
|
+
|
|
195
|
+
# Load defaults
|
|
196
|
+
defaults = load_defaults()
|
|
197
|
+
|
|
198
|
+
questions = input_data.get("questions", [])
|
|
199
|
+
if not questions:
|
|
200
|
+
print(json.dumps({"continue": True}))
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
# Try to find pre-configured answer
|
|
204
|
+
question_key = get_question_key(questions)
|
|
205
|
+
answers = {}
|
|
206
|
+
|
|
207
|
+
for q in questions:
|
|
208
|
+
header = q.get("header", "")
|
|
209
|
+
options = q.get("options", [])
|
|
210
|
+
question_text = q.get("question", "")
|
|
211
|
+
|
|
212
|
+
# Check defaults first
|
|
213
|
+
default_answer = None
|
|
214
|
+
if question_key and question_key in defaults:
|
|
215
|
+
default_answer = defaults[question_key]
|
|
216
|
+
elif header.lower().replace(" ", "_") in defaults:
|
|
217
|
+
default_answer = defaults[header.lower().replace(" ", "_")]
|
|
218
|
+
|
|
219
|
+
if default_answer is not None:
|
|
220
|
+
# Use pre-configured default
|
|
221
|
+
answers[question_text] = default_answer
|
|
222
|
+
else:
|
|
223
|
+
# Auto-select comprehensive option
|
|
224
|
+
best_idx = find_comprehensive_option(options)
|
|
225
|
+
if best_idx is not None and options:
|
|
226
|
+
answers[question_text] = options[best_idx].get("label", "")
|
|
227
|
+
|
|
228
|
+
if answers:
|
|
229
|
+
# Log the auto-answer
|
|
230
|
+
log_auto_answer(state, questions, answers)
|
|
231
|
+
|
|
232
|
+
# Get the first question and answer for display
|
|
233
|
+
first_question = questions[0] if questions else {}
|
|
234
|
+
header = first_question.get("header", "Question")
|
|
235
|
+
question_text = first_question.get("question", "")
|
|
236
|
+
answer = list(answers.values())[0] if answers else "Unknown"
|
|
237
|
+
|
|
238
|
+
# BLOCK the tool and provide the answer in the reason
|
|
239
|
+
# This prevents the question UI from showing and tells the AI to use this answer
|
|
240
|
+
result = {
|
|
241
|
+
"continue": False,
|
|
242
|
+
"reason": f"""## 🤖 Auto-Selected
|
|
243
|
+
|
|
244
|
+
**{header}:** {answer}
|
|
245
|
+
|
|
246
|
+
_Question: {question_text}_
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
Autonomous mode is active. The workflow will proceed with this answer.
|
|
251
|
+
|
|
252
|
+
To review auto-selected answers: `.claude/workflow-logs/`
|
|
253
|
+
To disable: Set `autonomous.enabled: false` in `.claude/hustle-build-defaults.json`
|
|
254
|
+
"""
|
|
255
|
+
}
|
|
256
|
+
print(json.dumps(result))
|
|
257
|
+
else:
|
|
258
|
+
print(json.dumps({"continue": True}))
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def log_auto_answer(state, questions, answers):
|
|
262
|
+
"""Log auto-answered questions to workflow log using shared utility (v4.5.0)"""
|
|
263
|
+
# Use shared utility if available
|
|
264
|
+
if UTILS_AVAILABLE:
|
|
265
|
+
try:
|
|
266
|
+
log_workflow_event("auto_answer", {
|
|
267
|
+
"questions": [q.get("question") for q in questions],
|
|
268
|
+
"headers": [q.get("header") for q in questions],
|
|
269
|
+
"answers": answers,
|
|
270
|
+
"reason": "auto-comprehensive",
|
|
271
|
+
"mode": state.get("mode", "auto") if state else "auto"
|
|
272
|
+
})
|
|
273
|
+
return
|
|
274
|
+
except Exception:
|
|
275
|
+
pass
|
|
276
|
+
|
|
277
|
+
# Fallback to legacy logging
|
|
278
|
+
project_dir = os.environ.get("CLAUDE_PROJECT_DIR", ".")
|
|
279
|
+
logs_dir = Path(project_dir) / ".claude" / "workflow-logs"
|
|
280
|
+
logs_dir.mkdir(parents=True, exist_ok=True)
|
|
281
|
+
|
|
282
|
+
build_id = state.get("build_id", state.get("workflow_id", "unknown")) if state else "unknown"
|
|
283
|
+
log_file = logs_dir / f"{build_id}.json"
|
|
284
|
+
|
|
285
|
+
try:
|
|
286
|
+
if log_file.exists():
|
|
287
|
+
log = json.loads(log_file.read_text())
|
|
288
|
+
else:
|
|
289
|
+
log = {"auto_answers": [], "events": []}
|
|
290
|
+
|
|
291
|
+
from datetime import datetime
|
|
292
|
+
log["auto_answers"].append({
|
|
293
|
+
"timestamp": datetime.now().isoformat(),
|
|
294
|
+
"questions": [q.get("question") for q in questions],
|
|
295
|
+
"answers": answers,
|
|
296
|
+
"reason": "auto-comprehensive"
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
log_file.write_text(json.dumps(log, indent=2))
|
|
300
|
+
except Exception:
|
|
301
|
+
pass
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
if __name__ == "__main__":
|
|
305
|
+
main()
|
|
@@ -0,0 +1,132 @@
|
|
|
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()
|