@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,183 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Auto-Answer Bot for Test Orchestration
|
|
4
|
-
|
|
5
|
-
Monitors .claude/current-question.json in test directory and automatically
|
|
6
|
-
answers questions by writing to .claude/pending-answer.json.
|
|
7
|
-
|
|
8
|
-
This enables autonomous testing by auto-selecting "comprehensive" options
|
|
9
|
-
without manual intervention.
|
|
10
|
-
|
|
11
|
-
Usage:
|
|
12
|
-
python test-auto-answer-bot.py <test_directory>
|
|
13
|
-
|
|
14
|
-
Example:
|
|
15
|
-
python test-auto-answer-bot.py ~/test-api-dev-tools-auto
|
|
16
|
-
|
|
17
|
-
Version: 1.0.0
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
import json
|
|
21
|
-
import sys
|
|
22
|
-
import time
|
|
23
|
-
from pathlib import Path
|
|
24
|
-
from datetime import datetime
|
|
25
|
-
|
|
26
|
-
# Configuration
|
|
27
|
-
POLL_INTERVAL = 2 # seconds
|
|
28
|
-
MAX_WAIT_TIME = 3600 # 1 hour max
|
|
29
|
-
AFFIRMATIVE_KEYWORDS = [
|
|
30
|
-
"comprehensive", "all", "yes", "proceed", "continue",
|
|
31
|
-
"recommended", "auto", "defaults", "use auto", "use defaults"
|
|
32
|
-
]
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def find_best_answer(question_data):
|
|
36
|
-
"""
|
|
37
|
-
Find the best answer for a question.
|
|
38
|
-
|
|
39
|
-
Strategy:
|
|
40
|
-
1. Look for options with affirmative keywords (comprehensive, all, yes, etc.)
|
|
41
|
-
2. If multiple match, prefer first one
|
|
42
|
-
3. If none match, select first option
|
|
43
|
-
|
|
44
|
-
Args:
|
|
45
|
-
question_data: Parsed question data from current-question.json
|
|
46
|
-
|
|
47
|
-
Returns:
|
|
48
|
-
dict: Answer data with selected option
|
|
49
|
-
"""
|
|
50
|
-
questions = question_data.get("questions", [])
|
|
51
|
-
|
|
52
|
-
if not questions:
|
|
53
|
-
return None
|
|
54
|
-
|
|
55
|
-
# For now, handle first question (can extend to handle multiple)
|
|
56
|
-
first_q = questions[0]
|
|
57
|
-
options = first_q.get("options", [])
|
|
58
|
-
|
|
59
|
-
if not options:
|
|
60
|
-
return None
|
|
61
|
-
|
|
62
|
-
# Find option with affirmative keyword
|
|
63
|
-
selected_index = 0
|
|
64
|
-
selected_label = None
|
|
65
|
-
|
|
66
|
-
for i, opt in enumerate(options):
|
|
67
|
-
label = opt.get("label", "") if isinstance(opt, dict) else str(opt)
|
|
68
|
-
label_lower = label.lower()
|
|
69
|
-
|
|
70
|
-
# Check for affirmative keywords
|
|
71
|
-
for keyword in AFFIRMATIVE_KEYWORDS:
|
|
72
|
-
if keyword in label_lower:
|
|
73
|
-
selected_index = i
|
|
74
|
-
selected_label = label
|
|
75
|
-
break
|
|
76
|
-
|
|
77
|
-
if selected_label:
|
|
78
|
-
break
|
|
79
|
-
|
|
80
|
-
# If no affirmative found, use first option
|
|
81
|
-
if not selected_label:
|
|
82
|
-
first_opt = options[0]
|
|
83
|
-
selected_label = first_opt.get("label", "") if isinstance(first_opt, dict) else str(first_opt)
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
"question_id": first_q.get("id", "question"),
|
|
87
|
-
"question": first_q.get("question", ""),
|
|
88
|
-
"header": first_q.get("header", "Question"),
|
|
89
|
-
"answer": selected_label,
|
|
90
|
-
"option_index": selected_index,
|
|
91
|
-
"phase": question_data.get("phase", "unknown"),
|
|
92
|
-
"status": "submitted",
|
|
93
|
-
"submitted_at": datetime.now().isoformat(),
|
|
94
|
-
"auto_answered": True,
|
|
95
|
-
"answers": {
|
|
96
|
-
first_q.get("header", "Question"): selected_label
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def watch_and_answer(test_dir):
|
|
102
|
-
"""
|
|
103
|
-
Watch for questions and auto-answer them.
|
|
104
|
-
|
|
105
|
-
Args:
|
|
106
|
-
test_dir: Path to test directory
|
|
107
|
-
"""
|
|
108
|
-
test_path = Path(test_dir).expanduser()
|
|
109
|
-
question_file = test_path / ".claude" / "current-question.json"
|
|
110
|
-
answer_file = test_path / ".claude" / "pending-answer.json"
|
|
111
|
-
|
|
112
|
-
print(f"Auto-Answer Bot started")
|
|
113
|
-
print(f"Monitoring: {question_file}")
|
|
114
|
-
print(f"Writing answers to: {answer_file}")
|
|
115
|
-
print(f"Poll interval: {POLL_INTERVAL}s")
|
|
116
|
-
print()
|
|
117
|
-
|
|
118
|
-
start_time = time.time()
|
|
119
|
-
answered_count = 0
|
|
120
|
-
|
|
121
|
-
while True:
|
|
122
|
-
# Check timeout
|
|
123
|
-
if time.time() - start_time > MAX_WAIT_TIME:
|
|
124
|
-
print(f"Max wait time ({MAX_WAIT_TIME}s) exceeded. Exiting.")
|
|
125
|
-
break
|
|
126
|
-
|
|
127
|
-
# Check for question
|
|
128
|
-
if question_file.exists():
|
|
129
|
-
try:
|
|
130
|
-
# Read question
|
|
131
|
-
question_data = json.loads(question_file.read_text())
|
|
132
|
-
|
|
133
|
-
# Skip if already answered recently
|
|
134
|
-
if answer_file.exists():
|
|
135
|
-
answer_data = json.loads(answer_file.read_text())
|
|
136
|
-
answer_time = answer_data.get("submitted_at", "")
|
|
137
|
-
question_time = question_data.get("created_at", "")
|
|
138
|
-
|
|
139
|
-
# If answer is newer than question, skip
|
|
140
|
-
if answer_time > question_time:
|
|
141
|
-
time.sleep(POLL_INTERVAL)
|
|
142
|
-
continue
|
|
143
|
-
|
|
144
|
-
# Find best answer
|
|
145
|
-
answer_data = find_best_answer(question_data)
|
|
146
|
-
|
|
147
|
-
if answer_data:
|
|
148
|
-
# Write answer
|
|
149
|
-
answer_file.parent.mkdir(parents=True, exist_ok=True)
|
|
150
|
-
answer_file.write_text(json.dumps(answer_data, indent=2))
|
|
151
|
-
|
|
152
|
-
answered_count += 1
|
|
153
|
-
print(f"[{datetime.now().strftime('%H:%M:%S')}] Answered question #{answered_count}")
|
|
154
|
-
print(f" Question: {answer_data.get('question', '')[:80]}...")
|
|
155
|
-
print(f" Answer: {answer_data.get('answer', '')}")
|
|
156
|
-
print(f" Phase: {answer_data.get('phase', 'unknown')}")
|
|
157
|
-
print()
|
|
158
|
-
|
|
159
|
-
# Clear the question file
|
|
160
|
-
question_file.unlink()
|
|
161
|
-
|
|
162
|
-
except Exception as e:
|
|
163
|
-
print(f"Error processing question: {e}")
|
|
164
|
-
|
|
165
|
-
time.sleep(POLL_INTERVAL)
|
|
166
|
-
|
|
167
|
-
print(f"Bot finished. Answered {answered_count} questions.")
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def main():
|
|
171
|
-
if len(sys.argv) < 2:
|
|
172
|
-
print("Usage: python test-auto-answer-bot.py <test_directory>")
|
|
173
|
-
print()
|
|
174
|
-
print("Example:")
|
|
175
|
-
print(" python test-auto-answer-bot.py ~/test-api-dev-tools-auto")
|
|
176
|
-
sys.exit(1)
|
|
177
|
-
|
|
178
|
-
test_dir = sys.argv[1]
|
|
179
|
-
watch_and_answer(test_dir)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if __name__ == "__main__":
|
|
183
|
-
main()
|
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Test Completion Detector
|
|
4
|
-
|
|
5
|
-
Programmatically verifies that a workflow completed successfully by checking:
|
|
6
|
-
1. All 14 phases marked as "complete" in api-dev-state.json
|
|
7
|
-
2. All expected artifacts exist (route.ts, schema.ts, tests, README)
|
|
8
|
-
3. All hooks logged in workflow-logs/
|
|
9
|
-
4. Tests pass (if test files exist)
|
|
10
|
-
|
|
11
|
-
Usage:
|
|
12
|
-
python test-completion-detector.py <test_directory> <command_type>
|
|
13
|
-
|
|
14
|
-
Example:
|
|
15
|
-
python test-completion-detector.py ~/test-api-dev-tools-auto api-create
|
|
16
|
-
|
|
17
|
-
Returns:
|
|
18
|
-
Exit code 0 if complete, 1 if incomplete, 2 if error
|
|
19
|
-
Prints JSON with detailed status
|
|
20
|
-
|
|
21
|
-
Version: 1.0.0
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
import json
|
|
25
|
-
import sys
|
|
26
|
-
from pathlib import Path
|
|
27
|
-
from datetime import datetime
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def load_state(test_dir):
|
|
31
|
-
"""Load api-dev-state.json."""
|
|
32
|
-
state_file = Path(test_dir) / ".claude" / "api-dev-state.json"
|
|
33
|
-
|
|
34
|
-
if not state_file.exists():
|
|
35
|
-
return None
|
|
36
|
-
|
|
37
|
-
try:
|
|
38
|
-
return json.loads(state_file.read_text())
|
|
39
|
-
except Exception as e:
|
|
40
|
-
print(f"Error loading state: {e}", file=sys.stderr)
|
|
41
|
-
return None
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def check_phases_complete(state):
|
|
45
|
-
"""Check if all 14 phases are marked complete."""
|
|
46
|
-
required_phases = [
|
|
47
|
-
"disambiguation",
|
|
48
|
-
"scope",
|
|
49
|
-
"research_initial",
|
|
50
|
-
"interview",
|
|
51
|
-
"research_deep",
|
|
52
|
-
"schema_creation",
|
|
53
|
-
"environment_check",
|
|
54
|
-
"tdd_red",
|
|
55
|
-
"tdd_green",
|
|
56
|
-
"verify",
|
|
57
|
-
"code_review",
|
|
58
|
-
"tdd_refactor",
|
|
59
|
-
"documentation",
|
|
60
|
-
"completion"
|
|
61
|
-
]
|
|
62
|
-
|
|
63
|
-
phases = state.get("phases", {})
|
|
64
|
-
incomplete_phases = []
|
|
65
|
-
|
|
66
|
-
for phase_name in required_phases:
|
|
67
|
-
phase_data = phases.get(phase_name, {})
|
|
68
|
-
status = phase_data.get("status", "not_started")
|
|
69
|
-
|
|
70
|
-
if status != "complete":
|
|
71
|
-
# Special case: research_deep can be skipped if comprehensive docs found
|
|
72
|
-
if phase_name == "research_deep" and status == "skipped":
|
|
73
|
-
continue
|
|
74
|
-
|
|
75
|
-
# Special case: code_review can be partial if no API key
|
|
76
|
-
if phase_name == "code_review" and status == "partial":
|
|
77
|
-
continue
|
|
78
|
-
|
|
79
|
-
incomplete_phases.append({
|
|
80
|
-
"phase": phase_name,
|
|
81
|
-
"status": status,
|
|
82
|
-
"reason": phase_data.get("incomplete_reason", "Unknown")
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
"complete": len(incomplete_phases) == 0,
|
|
87
|
-
"total_phases": len(required_phases),
|
|
88
|
-
"complete_phases": len(required_phases) - len(incomplete_phases),
|
|
89
|
-
"incomplete": incomplete_phases
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def check_artifacts_exist(test_dir, command_type, state):
|
|
94
|
-
"""Check if all expected artifacts exist."""
|
|
95
|
-
endpoint = state.get("endpoint", "unknown")
|
|
96
|
-
missing_artifacts = []
|
|
97
|
-
found_artifacts = []
|
|
98
|
-
|
|
99
|
-
# Define expected artifacts by command type
|
|
100
|
-
if command_type == "api-create":
|
|
101
|
-
expected = {
|
|
102
|
-
f"src/app/api/v2/{endpoint}/route.ts": "API route handler",
|
|
103
|
-
f"src/app/api/v2/{endpoint}/schema.ts": "Zod schema definitions",
|
|
104
|
-
f"src/app/api/v2/{endpoint}/__tests__/{endpoint}.api.test.ts": "API tests",
|
|
105
|
-
f"src/app/api/v2/{endpoint}/README.md": "API documentation"
|
|
106
|
-
}
|
|
107
|
-
elif command_type == "hustle-ui-create":
|
|
108
|
-
component_name = state.get("component_name", endpoint)
|
|
109
|
-
expected = {
|
|
110
|
-
f"src/components/{component_name}/{component_name}.tsx": "Component file",
|
|
111
|
-
f"src/components/{component_name}/{component_name}.test.tsx": "Component tests",
|
|
112
|
-
f"src/components/{component_name}/{component_name}.stories.tsx": "Storybook story"
|
|
113
|
-
}
|
|
114
|
-
elif command_type == "hustle-ui-create-page":
|
|
115
|
-
page_route = state.get("page_route", endpoint)
|
|
116
|
-
expected = {
|
|
117
|
-
f"src/app/{page_route}/page.tsx": "Page file",
|
|
118
|
-
f"e2e/{page_route}.spec.ts": "E2E tests"
|
|
119
|
-
}
|
|
120
|
-
elif command_type == "hustle-combine":
|
|
121
|
-
expected = {
|
|
122
|
-
f"src/app/api/v2/{endpoint}/route.ts": "Combined API route",
|
|
123
|
-
f"src/app/api/v2/{endpoint}/schema.ts": "Combined schema",
|
|
124
|
-
f"src/app/api/v2/{endpoint}/__tests__/{endpoint}.api.test.ts": "Integration tests"
|
|
125
|
-
}
|
|
126
|
-
elif command_type == "hustle-build":
|
|
127
|
-
# Build creates multiple artifacts - check decomposition
|
|
128
|
-
expected = {} # Will be checked differently
|
|
129
|
-
else:
|
|
130
|
-
return {
|
|
131
|
-
"complete": False,
|
|
132
|
-
"error": f"Unknown command type: {command_type}"
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
# Check each artifact
|
|
136
|
-
test_path = Path(test_dir)
|
|
137
|
-
for artifact_path, description in expected.items():
|
|
138
|
-
full_path = test_path / artifact_path
|
|
139
|
-
if full_path.exists():
|
|
140
|
-
found_artifacts.append({"path": artifact_path, "description": description})
|
|
141
|
-
else:
|
|
142
|
-
missing_artifacts.append({"path": artifact_path, "description": description})
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
"complete": len(missing_artifacts) == 0,
|
|
146
|
-
"found": found_artifacts,
|
|
147
|
-
"missing": missing_artifacts
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def check_registry_updated(test_dir, command_type):
|
|
152
|
-
"""Check if registry.json was updated."""
|
|
153
|
-
registry_file = Path(test_dir) / ".claude" / "registry.json"
|
|
154
|
-
|
|
155
|
-
if not registry_file.exists():
|
|
156
|
-
return {"complete": False, "reason": "Registry file missing"}
|
|
157
|
-
|
|
158
|
-
try:
|
|
159
|
-
registry = json.loads(registry_file.read_text())
|
|
160
|
-
|
|
161
|
-
# Check if there are entries
|
|
162
|
-
if command_type == "api-create":
|
|
163
|
-
apis = registry.get("apis", [])
|
|
164
|
-
if len(apis) == 0:
|
|
165
|
-
return {"complete": False, "reason": "No APIs in registry"}
|
|
166
|
-
elif command_type == "hustle-ui-create":
|
|
167
|
-
components = registry.get("components", [])
|
|
168
|
-
if len(components) == 0:
|
|
169
|
-
return {"complete": False, "reason": "No components in registry"}
|
|
170
|
-
elif command_type == "hustle-ui-create-page":
|
|
171
|
-
pages = registry.get("pages", [])
|
|
172
|
-
if len(pages) == 0:
|
|
173
|
-
return {"complete": False, "reason": "No pages in registry"}
|
|
174
|
-
|
|
175
|
-
return {"complete": True}
|
|
176
|
-
|
|
177
|
-
except Exception as e:
|
|
178
|
-
return {"complete": False, "reason": f"Error reading registry: {e}"}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def check_workflow_logs(test_dir):
|
|
182
|
-
"""Check if workflow events were logged."""
|
|
183
|
-
logs_dir = Path(test_dir) / ".claude" / "workflow-logs"
|
|
184
|
-
|
|
185
|
-
if not logs_dir.exists():
|
|
186
|
-
return {"complete": False, "reason": "Workflow logs directory missing"}
|
|
187
|
-
|
|
188
|
-
log_files = list(logs_dir.glob("*.json"))
|
|
189
|
-
|
|
190
|
-
if len(log_files) == 0:
|
|
191
|
-
return {"complete": False, "reason": "No workflow log files found"}
|
|
192
|
-
|
|
193
|
-
return {
|
|
194
|
-
"complete": True,
|
|
195
|
-
"log_files": [str(f.name) for f in log_files]
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
def main():
|
|
200
|
-
if len(sys.argv) < 3:
|
|
201
|
-
print(json.dumps({
|
|
202
|
-
"error": "Usage: python test-completion-detector.py <test_directory> <command_type>"
|
|
203
|
-
}))
|
|
204
|
-
sys.exit(2)
|
|
205
|
-
|
|
206
|
-
test_dir = Path(sys.argv[1]).expanduser()
|
|
207
|
-
command_type = sys.argv[2]
|
|
208
|
-
|
|
209
|
-
if not test_dir.exists():
|
|
210
|
-
print(json.dumps({
|
|
211
|
-
"error": f"Test directory does not exist: {test_dir}"
|
|
212
|
-
}))
|
|
213
|
-
sys.exit(2)
|
|
214
|
-
|
|
215
|
-
# Load state
|
|
216
|
-
state = load_state(test_dir)
|
|
217
|
-
|
|
218
|
-
if not state:
|
|
219
|
-
print(json.dumps({
|
|
220
|
-
"complete": False,
|
|
221
|
-
"error": "Could not load api-dev-state.json"
|
|
222
|
-
}))
|
|
223
|
-
sys.exit(1)
|
|
224
|
-
|
|
225
|
-
# Run all checks
|
|
226
|
-
phases_check = check_phases_complete(state)
|
|
227
|
-
artifacts_check = check_artifacts_exist(test_dir, command_type, state)
|
|
228
|
-
registry_check = check_registry_updated(test_dir, command_type)
|
|
229
|
-
logs_check = check_workflow_logs(test_dir)
|
|
230
|
-
|
|
231
|
-
# Overall result
|
|
232
|
-
all_complete = (
|
|
233
|
-
phases_check["complete"] and
|
|
234
|
-
artifacts_check["complete"] and
|
|
235
|
-
registry_check["complete"] and
|
|
236
|
-
logs_check["complete"]
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
result = {
|
|
240
|
-
"complete": all_complete,
|
|
241
|
-
"timestamp": datetime.now().isoformat(),
|
|
242
|
-
"test_directory": str(test_dir),
|
|
243
|
-
"command_type": command_type,
|
|
244
|
-
"endpoint": state.get("endpoint", "unknown"),
|
|
245
|
-
"checks": {
|
|
246
|
-
"phases": phases_check,
|
|
247
|
-
"artifacts": artifacts_check,
|
|
248
|
-
"registry": registry_check,
|
|
249
|
-
"logs": logs_check
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
print(json.dumps(result, indent=2))
|
|
254
|
-
|
|
255
|
-
# Exit code based on result
|
|
256
|
-
if all_complete:
|
|
257
|
-
sys.exit(0)
|
|
258
|
-
else:
|
|
259
|
-
sys.exit(1)
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if __name__ == "__main__":
|
|
263
|
-
main()
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"turn_count": 0,
|
|
3
|
-
"started_at": "2025-12-31T19:42:00.000Z",
|
|
4
|
-
"test_directory": "/Users/alfonso/test-api-dev-tools-auto",
|
|
5
|
-
"current_command": "/api-create",
|
|
6
|
-
"current_endpoint": "weather-api",
|
|
7
|
-
"current_phase": "initialization",
|
|
8
|
-
"commands_tested": {
|
|
9
|
-
"/api-create": {
|
|
10
|
-
"status": "IN PROGRESS",
|
|
11
|
-
"endpoint": "weather-api",
|
|
12
|
-
"started_at": "2025-12-31T19:42:00.000Z",
|
|
13
|
-
"phases_complete": 0,
|
|
14
|
-
"retries": 0
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
"total_retries": 0,
|
|
18
|
-
"reground_history": [],
|
|
19
|
-
"auto_answer_bot_pid": null
|
|
20
|
-
}
|