@hustle-together/api-dev-tools 3.10.1 → 3.12.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.
Files changed (178) hide show
  1. package/.claude/agents/code-reviewer.md +170 -0
  2. package/.claude/agents/docs-generator.md +80 -0
  3. package/.claude/agents/implementation-reviewer.md +119 -0
  4. package/.claude/agents/parallel-researcher.md +52 -0
  5. package/.claude/agents/research-validator.md +116 -0
  6. package/.claude/agents/schema-generator.md +70 -0
  7. package/.claude/agents/test-writer.md +104 -0
  8. package/.claude/api-dev-state.json +331 -0
  9. package/.claude/commands/README.md +196 -0
  10. package/.claude/commands/add-command.md +212 -0
  11. package/.claude/commands/api-create.md +510 -0
  12. package/.claude/commands/api-env.md +51 -0
  13. package/.claude/commands/api-interview.md +344 -0
  14. package/.claude/commands/api-research.md +357 -0
  15. package/.claude/commands/api-status.md +279 -0
  16. package/.claude/commands/api-verify.md +232 -0
  17. package/.claude/commands/beepboop.md +96 -0
  18. package/.claude/commands/busycommit.md +111 -0
  19. package/.claude/commands/commit.md +82 -0
  20. package/.claude/commands/cycle.md +137 -0
  21. package/.claude/commands/gap.md +85 -0
  22. package/.claude/commands/green.md +137 -0
  23. package/.claude/commands/issue.md +187 -0
  24. package/.claude/commands/ntfy-setup.md +91 -0
  25. package/.claude/commands/ntfy-test.md +74 -0
  26. package/.claude/commands/plan.md +167 -0
  27. package/.claude/commands/pr.md +121 -0
  28. package/.claude/commands/publish.md +40 -0
  29. package/.claude/commands/red.md +137 -0
  30. package/.claude/commands/refactor.md +137 -0
  31. package/.claude/commands/spike.md +137 -0
  32. package/.claude/commands/summarize.md +93 -0
  33. package/.claude/commands/tdd.md +139 -0
  34. package/.claude/commands/worktree-add.md +307 -0
  35. package/.claude/commands/worktree-cleanup.md +275 -0
  36. package/.claude/hooks/api-workflow-check.py +227 -0
  37. package/.claude/hooks/enforce-deep-research.py +185 -0
  38. package/.claude/hooks/enforce-disambiguation.py +155 -0
  39. package/.claude/hooks/enforce-documentation.py +192 -0
  40. package/.claude/hooks/enforce-environment.py +253 -0
  41. package/.claude/hooks/enforce-external-research.py +328 -0
  42. package/.claude/hooks/enforce-interview.py +421 -0
  43. package/.claude/hooks/enforce-refactor.py +189 -0
  44. package/.claude/hooks/enforce-research.py +159 -0
  45. package/.claude/hooks/enforce-schema.py +186 -0
  46. package/.claude/hooks/enforce-scope.py +160 -0
  47. package/.claude/hooks/enforce-tdd-red.py +250 -0
  48. package/.claude/hooks/enforce-verify.py +186 -0
  49. package/.claude/hooks/periodic-reground.py +154 -0
  50. package/.claude/hooks/session-startup.py +151 -0
  51. package/.claude/hooks/track-tool-use.py +626 -0
  52. package/.claude/hooks/verify-after-green.py +282 -0
  53. package/.claude/hooks/verify-implementation.py +225 -0
  54. package/.claude/research/index.json +6 -0
  55. package/.claude/settings.json +144 -0
  56. package/.claude/settings.local.json +12 -0
  57. package/.claude-plugin/marketplace.json +103 -0
  58. package/.skills/README.md +293 -0
  59. package/.skills/_shared/convert-commands.py +192 -0
  60. package/.skills/_shared/hooks/api-workflow-check.py +227 -0
  61. package/.skills/_shared/hooks/enforce-deep-research.py +185 -0
  62. package/.skills/_shared/hooks/enforce-disambiguation.py +155 -0
  63. package/.skills/_shared/hooks/enforce-documentation.py +192 -0
  64. package/.skills/_shared/hooks/enforce-environment.py +253 -0
  65. package/.skills/_shared/hooks/enforce-external-research.py +328 -0
  66. package/.skills/_shared/hooks/enforce-interview.py +421 -0
  67. package/.skills/_shared/hooks/enforce-refactor.py +189 -0
  68. package/.skills/_shared/hooks/enforce-research.py +159 -0
  69. package/.skills/_shared/hooks/enforce-schema.py +186 -0
  70. package/.skills/_shared/hooks/enforce-scope.py +160 -0
  71. package/.skills/_shared/hooks/enforce-tdd-red.py +250 -0
  72. package/.skills/_shared/hooks/enforce-verify.py +186 -0
  73. package/.skills/_shared/hooks/periodic-reground.py +154 -0
  74. package/.skills/_shared/hooks/session-startup.py +151 -0
  75. package/.skills/_shared/hooks/track-tool-use.py +626 -0
  76. package/.skills/_shared/hooks/verify-after-green.py +282 -0
  77. package/.skills/_shared/hooks/verify-implementation.py +225 -0
  78. package/.skills/_shared/install.sh +114 -0
  79. package/.skills/_shared/settings.json +93 -0
  80. package/.skills/add-command/SKILL.md +227 -0
  81. package/.skills/api-create/SKILL.md +623 -0
  82. package/.skills/api-env/SKILL.md +64 -0
  83. package/.skills/api-interview/SKILL.md +357 -0
  84. package/.skills/api-research/SKILL.md +370 -0
  85. package/.skills/api-status/SKILL.md +292 -0
  86. package/.skills/api-verify/SKILL.md +245 -0
  87. package/.skills/beepboop/SKILL.md +111 -0
  88. package/.skills/busycommit/SKILL.md +126 -0
  89. package/.skills/commit/SKILL.md +97 -0
  90. package/.skills/cycle/SKILL.md +152 -0
  91. package/.skills/gap/SKILL.md +100 -0
  92. package/.skills/green/SKILL.md +152 -0
  93. package/.skills/issue/SKILL.md +202 -0
  94. package/.skills/plan/SKILL.md +182 -0
  95. package/.skills/pr/SKILL.md +136 -0
  96. package/.skills/publish/SKILL.md +160 -0
  97. package/.skills/red/SKILL.md +152 -0
  98. package/.skills/refactor/SKILL.md +152 -0
  99. package/.skills/spike/SKILL.md +152 -0
  100. package/.skills/summarize/SKILL.md +108 -0
  101. package/.skills/tdd/SKILL.md +154 -0
  102. package/.skills/update-todos/SKILL.md +250 -0
  103. package/.skills/worktree-add/SKILL.md +322 -0
  104. package/.skills/worktree-cleanup/SKILL.md +290 -0
  105. package/CHANGELOG.md +115 -0
  106. package/README.md +161 -7101
  107. package/bin/cli.js +448 -805
  108. package/commands/README.md +66 -31
  109. package/commands/add-command.md +8 -5
  110. package/commands/beepboop.md +4 -5
  111. package/commands/busycommit.md +2 -3
  112. package/commands/commit.md +2 -3
  113. package/commands/cycle.md +2 -7
  114. package/commands/gap.md +2 -3
  115. package/commands/green.md +2 -7
  116. package/commands/hustle-api-continue.md +8 -5
  117. package/commands/hustle-api-create.md +70 -29
  118. package/commands/hustle-api-env.md +1 -0
  119. package/commands/hustle-api-interview.md +32 -19
  120. package/commands/hustle-api-research.md +47 -21
  121. package/commands/hustle-api-sessions.md +8 -7
  122. package/commands/hustle-api-status.md +21 -1
  123. package/commands/hustle-api-verify.md +14 -13
  124. package/commands/hustle-combine.md +488 -241
  125. package/commands/hustle-ui-create-page.md +113 -50
  126. package/commands/hustle-ui-create.md +179 -26
  127. package/commands/issue.md +3 -8
  128. package/commands/plan.md +2 -3
  129. package/commands/pr.md +2 -3
  130. package/commands/red.md +2 -7
  131. package/commands/refactor.md +2 -7
  132. package/commands/spike.md +2 -7
  133. package/commands/summarize.md +2 -3
  134. package/commands/tdd.md +2 -7
  135. package/commands/worktree-add.md +208 -216
  136. package/commands/worktree-cleanup.md +172 -178
  137. package/hooks/api-workflow-check.py +5 -3
  138. package/hooks/enforce-component-type-confirm.py +97 -0
  139. package/hooks/lib/__init__.py +1 -0
  140. package/hooks/lib/greptile.py +355 -0
  141. package/hooks/lib/ntfy.py +209 -0
  142. package/hooks/notify-input-needed.py +73 -0
  143. package/hooks/notify-phase-complete.py +90 -0
  144. package/hooks/run-code-review.py +246 -0
  145. package/hooks/track-token-usage.py +121 -0
  146. package/package.json +33 -12
  147. package/scripts/collect-test-results.ts +102 -77
  148. package/scripts/extract-parameters.ts +112 -70
  149. package/scripts/generate-test-manifest.ts +118 -77
  150. package/templates/.env.example +57 -0
  151. package/templates/BRAND_GUIDE.md +92 -52
  152. package/templates/CLAUDE-SECTION.md +40 -37
  153. package/templates/SPEC.json +186 -38
  154. package/templates/api-dev-state.json +33 -4
  155. package/templates/api-showcase/_components/APICard.tsx +22 -18
  156. package/templates/api-showcase/_components/APIModal.tsx +110 -64
  157. package/templates/api-showcase/_components/APIShowcase.tsx +53 -35
  158. package/templates/api-showcase/_components/APITester.tsx +128 -67
  159. package/templates/api-showcase/page.tsx +4 -4
  160. package/templates/api-test/page.tsx +51 -30
  161. package/templates/api-test/test-structure/route.ts +43 -34
  162. package/templates/component/Component.stories.tsx +41 -39
  163. package/templates/component/Component.test.tsx +96 -78
  164. package/templates/component/Component.tsx +63 -52
  165. package/templates/component/Component.types.ts +10 -6
  166. package/templates/component/Component.visual.spec.ts +170 -0
  167. package/templates/component/index.ts +2 -2
  168. package/templates/dev-tools/_components/DevToolsLanding.tsx +8 -8
  169. package/templates/dev-tools/page.tsx +4 -3
  170. package/templates/mcp-servers.json +30 -2
  171. package/templates/page/page.e2e.test.ts +56 -48
  172. package/templates/page/page.tsx +3 -3
  173. package/templates/shared/HeroHeader.tsx +16 -15
  174. package/templates/shared/index.ts +1 -1
  175. package/templates/ui-showcase/_components/PreviewCard.tsx +20 -20
  176. package/templates/ui-showcase/_components/PreviewModal.tsx +149 -108
  177. package/templates/ui-showcase/_components/UIShowcase.tsx +43 -35
  178. package/templates/ui-showcase/page.tsx +4 -4
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Hook: PreToolUse for Write/Edit (and Stop)
4
+ Purpose: Block completion until documentation confirmed WITH USER REVIEW
5
+
6
+ Phase 21 (Documentation) requires:
7
+ 1. Update api-tests-manifest.json
8
+ 2. Cache research to .claude/research/
9
+ 3. Update OpenAPI spec if applicable
10
+ 4. SHOW documentation checklist to user
11
+ 5. USE AskUserQuestion: "Documentation complete? [Y/n]"
12
+ 6. Only allow completion when user confirms
13
+
14
+ Returns:
15
+ - {"permissionDecision": "allow"} - Let the tool run
16
+ - {"permissionDecision": "deny", "reason": "..."} - Block with explanation
17
+ """
18
+ import json
19
+ import sys
20
+ from pathlib import Path
21
+
22
+ STATE_FILE = Path(__file__).parent.parent / "api-dev-state.json"
23
+
24
+
25
+ def main():
26
+ try:
27
+ input_data = json.load(sys.stdin)
28
+ except json.JSONDecodeError:
29
+ print(json.dumps({"permissionDecision": "allow"}))
30
+ sys.exit(0)
31
+
32
+ tool_input = input_data.get("tool_input", {})
33
+ file_path = tool_input.get("file_path", "")
34
+
35
+ # Only enforce for documentation files or when trying to mark workflow complete
36
+ is_manifest = "api-tests-manifest.json" in file_path
37
+ is_openapi = "openapi" in file_path.lower() and file_path.endswith((".json", ".yaml", ".yml"))
38
+ is_readme = file_path.endswith("README.md") and "/api/" in file_path
39
+
40
+ # Also check for state file updates (marking complete)
41
+ is_state_update = "api-dev-state.json" in file_path
42
+
43
+ if not is_manifest and not is_openapi and not is_readme and not is_state_update:
44
+ print(json.dumps({"permissionDecision": "allow"}))
45
+ sys.exit(0)
46
+
47
+ if not STATE_FILE.exists():
48
+ print(json.dumps({"permissionDecision": "allow"}))
49
+ sys.exit(0)
50
+
51
+ try:
52
+ state = json.loads(STATE_FILE.read_text())
53
+ except json.JSONDecodeError:
54
+ print(json.dumps({"permissionDecision": "allow"}))
55
+ sys.exit(0)
56
+
57
+ endpoint = state.get("endpoint", "unknown")
58
+ phases = state.get("phases", {})
59
+ tdd_refactor = phases.get("tdd_refactor", {})
60
+ documentation = phases.get("documentation", {})
61
+
62
+ # Only enforce after refactor is complete
63
+ if tdd_refactor.get("status") != "complete":
64
+ # Allow documentation updates during development
65
+ print(json.dumps({"permissionDecision": "allow"}))
66
+ sys.exit(0)
67
+
68
+ status = documentation.get("status", "not_started")
69
+
70
+ phase_exit_confirmed = documentation.get("phase_exit_confirmed", False)
71
+
72
+ if status != "complete" or not phase_exit_confirmed:
73
+ user_question_asked = documentation.get("user_question_asked", False)
74
+ user_confirmed = documentation.get("user_confirmed", False)
75
+ checklist_shown = documentation.get("checklist_shown", False)
76
+ manifest_updated = documentation.get("manifest_updated", False)
77
+ research_cached = documentation.get("research_cached", False)
78
+ openapi_updated = documentation.get("openapi_updated", False)
79
+
80
+ missing = []
81
+ if not manifest_updated:
82
+ missing.append("api-tests-manifest.json not updated")
83
+ if not research_cached:
84
+ missing.append("Research not cached to .claude/research/")
85
+ if not checklist_shown:
86
+ missing.append("Documentation checklist not shown to user")
87
+ if not user_question_asked:
88
+ missing.append("User confirmation question (AskUserQuestion not used)")
89
+ if not user_confirmed:
90
+ missing.append("User hasn't confirmed documentation complete")
91
+ if not phase_exit_confirmed:
92
+ missing.append("Phase exit confirmation (user must explicitly approve to complete)")
93
+
94
+ print(json.dumps({
95
+ "permissionDecision": "deny",
96
+ "reason": f"""❌ BLOCKED: Documentation (Phase 21) not complete.
97
+
98
+ Status: {status}
99
+ Manifest updated: {manifest_updated}
100
+ Research cached: {research_cached}
101
+ OpenAPI updated: {openapi_updated}
102
+ Checklist shown: {checklist_shown}
103
+ User question asked: {user_question_asked}
104
+ User confirmed: {user_confirmed}
105
+ Phase exit confirmed: {phase_exit_confirmed}
106
+
107
+ MISSING:
108
+ {chr(10).join(f" • {m}" for m in missing)}
109
+
110
+ ═══════════════════════════════════════════════════════════
111
+ ⚠️ GET USER CONFIRMATION FOR DOCUMENTATION
112
+ ═══════════════════════════════════════════════════════════
113
+
114
+ REQUIRED STEPS:
115
+
116
+ 1. Update api-tests-manifest.json with:
117
+ • Endpoint path, method, description
118
+ • Request/response schemas
119
+ • Test coverage info
120
+ • Code examples
121
+ • Testing notes
122
+
123
+ 2. Cache research to .claude/research/{endpoint}/:
124
+ • sources.json - URLs and summaries
125
+ • interview.json - User decisions
126
+ • schema.json - Final Zod schemas
127
+
128
+ 3. Update OpenAPI spec (if applicable):
129
+ • Add endpoint definition
130
+ • Document parameters
131
+ • Document responses
132
+
133
+ 4. SHOW documentation checklist to user:
134
+ ┌───────────────────────────────────────────────────────┐
135
+ │ DOCUMENTATION CHECKLIST │
136
+ │ │
137
+ │ ✓ api-tests-manifest.json │
138
+ │ • Added {endpoint} entry │
139
+ │ • 8 test scenarios documented │
140
+ │ • Code examples included │
141
+ │ │
142
+ │ ✓ Research Cache │
143
+ │ • .claude/research/{endpoint}/sources.json │
144
+ │ • .claude/research/{endpoint}/interview.json │
145
+ │ │
146
+ │ {'✓' if openapi_updated else '⏭'} OpenAPI Spec │
147
+ │ • {'Updated' if openapi_updated else 'Skipped (internal API)'} │
148
+ │ │
149
+ │ All documentation complete? [Y] │
150
+ │ Need to add something? [n] ____ │
151
+ └───────────────────────────────────────────────────────┘
152
+
153
+ 5. USE AskUserQuestion:
154
+ question: "Documentation checklist complete?"
155
+ options: [
156
+ {{"value": "confirm", "label": "Yes, all documentation is done"}},
157
+ {{"value": "add", "label": "No, I need to add [what]"}},
158
+ {{"value": "skip", "label": "Skip docs for now (not recommended)"}}
159
+ ]
160
+
161
+ 6. If user says "add":
162
+ • Ask what documentation is missing
163
+ • Update the relevant files
164
+ • LOOP BACK and show updated checklist
165
+
166
+ 7. If user says "confirm":
167
+ • Set documentation.user_confirmed = true
168
+ • Set documentation.user_question_asked = true
169
+ • Set documentation.checklist_shown = true
170
+ • Set documentation.manifest_updated = true
171
+ • Set documentation.research_cached = true
172
+ • Set documentation.status = "complete"
173
+ • Mark entire workflow as complete!
174
+
175
+ WHY: Documentation ensures next developer (or future Claude) has context."""
176
+ }))
177
+ sys.exit(0)
178
+
179
+ # Documentation complete
180
+ print(json.dumps({
181
+ "permissionDecision": "allow",
182
+ "message": f"""✅ Documentation complete for {endpoint}.
183
+ Manifest updated: {manifest_updated}
184
+ Research cached: {research_cached}
185
+ OpenAPI updated: {openapi_updated}
186
+ User confirmed documentation is complete."""
187
+ }))
188
+ sys.exit(0)
189
+
190
+
191
+ if __name__ == "__main__":
192
+ main()
@@ -0,0 +1,253 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Hook: PreToolUse for Write/Edit
4
+ Purpose: Block writing if environment not verified WITH USER READINESS CONFIRMATION
5
+
6
+ Phase 7 requires:
7
+ 1. Check required API keys based on endpoint/interview
8
+ 2. Report found/missing keys to user
9
+ 3. USE AskUserQuestion: "Ready for testing? [Y/n]"
10
+ 4. Only proceed to TDD when user confirms readiness
11
+
12
+ Returns:
13
+ - {"permissionDecision": "allow"} - Let the tool run
14
+ - {"permissionDecision": "deny", "reason": "..."} - Block with explanation
15
+ """
16
+ import json
17
+ import os
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ # State file is in .claude/ directory (sibling to hooks/)
22
+ STATE_FILE = Path(__file__).parent.parent / "api-dev-state.json"
23
+
24
+ # Common API key patterns to check
25
+ COMMON_KEY_PATTERNS = {
26
+ "openai": ["OPENAI_API_KEY", "NEXT_PUBLIC_OPENAI_API_KEY"],
27
+ "anthropic": ["ANTHROPIC_API_KEY", "NEXT_PUBLIC_ANTHROPIC_API_KEY"],
28
+ "google": ["GOOGLE_API_KEY", "GOOGLE_GENERATIVE_AI_API_KEY"],
29
+ "brandfetch": ["BRANDFETCH_API_KEY"],
30
+ "firecrawl": ["FIRECRAWL_API_KEY"],
31
+ "brave": ["BRAVE_SEARCH_API_KEY"],
32
+ "perplexity": ["PERPLEXITY_API_KEY"],
33
+ "exa": ["EXA_API_KEY"],
34
+ "cartesia": ["CARTESIA_API_KEY"],
35
+ "elevenlabs": ["ELEVENLABS_API_KEY"],
36
+ "unsplash": ["UNSPLASH_ACCESS_KEY"],
37
+ "pexels": ["PEXELS_API_KEY"],
38
+ "supabase": ["SUPABASE_URL", "SUPABASE_ANON_KEY", "SUPABASE_SERVICE_ROLE_KEY"],
39
+ }
40
+
41
+
42
+ def check_env_keys(required_keys: list) -> tuple[list, list]:
43
+ """Check which keys exist and which are missing."""
44
+ found = []
45
+ missing = []
46
+
47
+ for key in required_keys:
48
+ if os.environ.get(key):
49
+ found.append(key)
50
+ else:
51
+ missing.append(key)
52
+
53
+ return found, missing
54
+
55
+
56
+ def infer_required_keys(endpoint: str, external_services: list) -> list:
57
+ """Infer required API keys from endpoint name and external services."""
58
+ required = []
59
+
60
+ # Check endpoint name against common patterns
61
+ endpoint_lower = endpoint.lower()
62
+ for service, keys in COMMON_KEY_PATTERNS.items():
63
+ if service in endpoint_lower:
64
+ required.extend(keys)
65
+
66
+ # Check external services list
67
+ for service in external_services:
68
+ service_lower = service.lower()
69
+ for pattern, keys in COMMON_KEY_PATTERNS.items():
70
+ if pattern in service_lower:
71
+ required.extend(keys)
72
+
73
+ return list(set(required)) # Deduplicate
74
+
75
+
76
+ def main():
77
+ # Read hook input from stdin
78
+ try:
79
+ input_data = json.load(sys.stdin)
80
+ except json.JSONDecodeError:
81
+ print(json.dumps({"permissionDecision": "allow"}))
82
+ sys.exit(0)
83
+
84
+ tool_input = input_data.get("tool_input", {})
85
+ file_path = tool_input.get("file_path", "")
86
+
87
+ # Only enforce for API route files (not tests - tests should fail if keys missing)
88
+ is_api_file = "/api/" in file_path and file_path.endswith(".ts")
89
+ is_route_file = file_path.endswith("route.ts")
90
+
91
+ if not is_api_file or not is_route_file:
92
+ print(json.dumps({"permissionDecision": "allow"}))
93
+ sys.exit(0)
94
+
95
+ # Skip test files
96
+ if ".test." in file_path or "/__tests__/" in file_path or ".spec." in file_path:
97
+ print(json.dumps({"permissionDecision": "allow"}))
98
+ sys.exit(0)
99
+
100
+ # Check if state file exists
101
+ if not STATE_FILE.exists():
102
+ print(json.dumps({"permissionDecision": "allow"}))
103
+ sys.exit(0)
104
+
105
+ # Load state
106
+ try:
107
+ state = json.loads(STATE_FILE.read_text())
108
+ except json.JSONDecodeError:
109
+ print(json.dumps({"permissionDecision": "allow"}))
110
+ sys.exit(0)
111
+
112
+ endpoint = state.get("endpoint")
113
+ if not endpoint:
114
+ print(json.dumps({"permissionDecision": "allow"}))
115
+ sys.exit(0)
116
+
117
+ phases = state.get("phases", {})
118
+ schema_creation = phases.get("schema_creation", {})
119
+ environment_check = phases.get("environment_check", {})
120
+
121
+ # Only enforce after schema creation
122
+ if schema_creation.get("status") != "complete":
123
+ # Let earlier hooks handle this
124
+ print(json.dumps({"permissionDecision": "allow"}))
125
+ sys.exit(0)
126
+
127
+ env_status = environment_check.get("status", "not_started")
128
+ keys_required = environment_check.get("keys_required", [])
129
+ keys_found = environment_check.get("keys_found", [])
130
+ keys_missing = environment_check.get("keys_missing", [])
131
+ user_question_asked = environment_check.get("user_question_asked", False)
132
+ user_ready = environment_check.get("user_ready", False)
133
+ env_shown = environment_check.get("env_shown", False)
134
+ phase_exit_confirmed = environment_check.get("phase_exit_confirmed", False)
135
+
136
+ # Check if environment check is complete
137
+ if env_status != "complete" or not phase_exit_confirmed:
138
+ # Infer required keys if not already set
139
+ if not keys_required:
140
+ interview = phases.get("interview", {})
141
+ decisions = interview.get("decisions", {})
142
+ external_services = decisions.get("external_services", {}).get("value", [])
143
+ if isinstance(external_services, str):
144
+ external_services = [external_services]
145
+ keys_required = infer_required_keys(endpoint, external_services)
146
+
147
+ # Check current environment
148
+ if keys_required:
149
+ found, missing = check_env_keys(keys_required)
150
+ else:
151
+ found, missing = [], []
152
+
153
+ # Check what's missing for user checkpoint
154
+ missing_steps = []
155
+ if not env_shown:
156
+ missing_steps.append("Environment status not shown to user")
157
+ if not user_question_asked:
158
+ missing_steps.append("User readiness question (AskUserQuestion not used)")
159
+ if not user_ready:
160
+ missing_steps.append("User hasn't confirmed readiness for TDD")
161
+ if not phase_exit_confirmed:
162
+ missing_steps.append("Phase exit confirmation (user must explicitly approve to proceed)")
163
+
164
+ print(json.dumps({
165
+ "permissionDecision": "deny",
166
+ "reason": f"""❌ BLOCKED: Environment check (Phase 7) not complete.
167
+
168
+ Current status: {env_status}
169
+ Required keys: {len(keys_required)}
170
+ Found: {len(found)}
171
+ Missing: {len(missing)}
172
+ User shown env: {env_shown}
173
+ User question asked: {user_question_asked}
174
+ User ready: {user_ready}
175
+ Phase exit confirmed: {phase_exit_confirmed}
176
+
177
+ MISSING:
178
+ {chr(10).join(f" • {m}" for m in missing_steps)}
179
+
180
+ ═══════════════════════════════════════════════════════════
181
+ ⚠️ GET USER READINESS CONFIRMATION
182
+ ═══════════════════════════════════════════════════════════
183
+
184
+ REQUIRED STEPS:
185
+
186
+ 1. Check API keys and SHOW status to user:
187
+ ┌───────────────────────────────────────────────────────┐
188
+ │ ENVIRONMENT CHECK │
189
+ │ │
190
+ │ Required for {endpoint}: │
191
+ │ │
192
+ │ API Keys: │
193
+ {chr(10).join(f" │ {'✓' if k in found else '❌'} {k:<40} │" for k in keys_required) if keys_required else " │ No API keys required │"}
194
+ │ │
195
+ │ Testing Setup: │
196
+ │ • Schema file ready │
197
+ │ • Test patterns defined │
198
+ │ • Mock data prepared (if needed) │
199
+ │ │
200
+ │ Ready to begin TDD? [Y] │
201
+ │ Need to fix something? [n] │
202
+ └───────────────────────────────────────────────────────┘
203
+
204
+ 2. USE AskUserQuestion:
205
+ question: "Environment looks ready. Start TDD?"
206
+ options: [
207
+ {{"value": "ready", "label": "Yes, ready to write tests"}},
208
+ {{"value": "fix_keys", "label": "No, need to set up API keys first"}},
209
+ {{"value": "fix_other", "label": "No, need to fix something else"}}
210
+ ]
211
+
212
+ 3. If user says "fix_keys" or "fix_other":
213
+ • Help them resolve the issue
214
+ • Re-check environment
215
+ • LOOP BACK and show updated status
216
+
217
+ 4. If user says "ready":
218
+ • Set environment_check.user_ready = true
219
+ • Set environment_check.user_question_asked = true
220
+ • Set environment_check.env_shown = true
221
+ • Set environment_check.keys_found = [list]
222
+ • Set environment_check.keys_missing = [list]
223
+ • Set environment_check.status = "complete"
224
+
225
+ {'API KEY ISSUES:' if missing else ''}
226
+ {chr(10).join(f" ❌ {k}" for k in missing) if missing else ''}
227
+
228
+ WHY: Verify environment before writing tests that depend on it."""
229
+ }))
230
+ sys.exit(0)
231
+
232
+ # Environment check complete
233
+ if keys_missing and not user_ready:
234
+ print(json.dumps({
235
+ "permissionDecision": "deny",
236
+ "reason": f"""❌ Missing keys noted but user hasn't confirmed readiness.
237
+ Use AskUserQuestion to confirm user is ready to proceed with missing keys:
238
+ {chr(10).join(f" ⚠️ {k}" for k in keys_missing[:3])}"""
239
+ }))
240
+ sys.exit(0)
241
+
242
+ print(json.dumps({
243
+ "permissionDecision": "allow",
244
+ "message": f"""✅ Environment check complete.
245
+ User confirmed ready for TDD.
246
+ Keys found: {len(keys_found)}
247
+ Keys missing (acknowledged): {len(keys_missing)}"""
248
+ }))
249
+ sys.exit(0)
250
+
251
+
252
+ if __name__ == "__main__":
253
+ main()