@hustle-together/api-dev-tools 3.3.0 → 3.6.0
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/README.md +712 -377
- package/commands/api-create.md +68 -23
- package/demo/hustle-together/blog/gemini-vs-claude-widgets.html +1 -1
- package/demo/hustle-together/blog/interview-driven-api-development.html +1 -1
- package/demo/hustle-together/blog/tdd-for-ai.html +1 -1
- package/demo/hustle-together/index.html +2 -2
- package/demo/workflow-demo-v3.5-backup.html +5008 -0
- package/demo/workflow-demo.html +5137 -3805
- package/hooks/enforce-deep-research.py +6 -1
- package/hooks/enforce-disambiguation.py +7 -1
- package/hooks/enforce-documentation.py +6 -1
- package/hooks/enforce-environment.py +5 -1
- package/hooks/enforce-interview.py +5 -1
- package/hooks/enforce-refactor.py +3 -1
- package/hooks/enforce-schema.py +0 -0
- package/hooks/enforce-scope.py +5 -1
- package/hooks/enforce-tdd-red.py +5 -1
- package/hooks/enforce-verify.py +0 -0
- package/hooks/track-tool-use.py +167 -0
- package/hooks/verify-implementation.py +0 -0
- package/package.json +1 -1
- package/templates/api-dev-state.json +24 -0
- package/demo/audio/audio-sync.js +0 -295
- package/demo/audio/generate-all-narrations.js +0 -581
- package/demo/audio/generate-narration.js +0 -486
- package/demo/audio/generate-voice-previews.js +0 -140
- package/demo/audio/narration-adam-timing.json +0 -4675
- package/demo/audio/narration-adam.mp3 +0 -0
- package/demo/audio/narration-creature-timing.json +0 -4675
- package/demo/audio/narration-creature.mp3 +0 -0
- package/demo/audio/narration-gaming-timing.json +0 -4675
- package/demo/audio/narration-gaming.mp3 +0 -0
- package/demo/audio/narration-hope-timing.json +0 -4675
- package/demo/audio/narration-hope.mp3 +0 -0
- package/demo/audio/narration-mark-timing.json +0 -4675
- package/demo/audio/narration-mark.mp3 +0 -0
- package/demo/audio/narration-timing.json +0 -3614
- package/demo/audio/narration-timing.sample.json +0 -48
- package/demo/audio/narration.mp3 +0 -0
- package/demo/audio/previews/manifest.json +0 -30
- package/demo/audio/previews/preview-creature.mp3 +0 -0
- package/demo/audio/previews/preview-gaming.mp3 +0 -0
- package/demo/audio/previews/preview-hope.mp3 +0 -0
- package/demo/audio/previews/preview-mark.mp3 +0 -0
- package/demo/audio/voices-manifest.json +0 -50
|
@@ -72,7 +72,9 @@ def main():
|
|
|
72
72
|
print(json.dumps({"permissionDecision": "allow"}))
|
|
73
73
|
sys.exit(0)
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
phase_exit_confirmed = research_deep.get("phase_exit_confirmed", False)
|
|
76
|
+
|
|
77
|
+
if status != "complete" or not phase_exit_confirmed:
|
|
76
78
|
user_question_asked = research_deep.get("user_question_asked", False)
|
|
77
79
|
user_approved = research_deep.get("user_approved", False)
|
|
78
80
|
proposals_shown = research_deep.get("proposals_shown", False)
|
|
@@ -92,6 +94,8 @@ def main():
|
|
|
92
94
|
missing.append("User hasn't approved the search list")
|
|
93
95
|
if pending:
|
|
94
96
|
missing.append(f"Approved searches not executed ({len(pending)} pending)")
|
|
97
|
+
if not phase_exit_confirmed:
|
|
98
|
+
missing.append("Phase exit confirmation (user must explicitly approve to proceed)")
|
|
95
99
|
|
|
96
100
|
print(json.dumps({
|
|
97
101
|
"permissionDecision": "deny",
|
|
@@ -106,6 +110,7 @@ Approved: {len(approved_searches)}
|
|
|
106
110
|
Executed: {len(executed_searches)}
|
|
107
111
|
Skipped: {len(skipped_searches)}
|
|
108
112
|
Pending: {len(pending)}
|
|
113
|
+
Phase exit confirmed: {phase_exit_confirmed}
|
|
109
114
|
|
|
110
115
|
MISSING:
|
|
111
116
|
{chr(10).join(f" • {m}" for m in missing)}
|
|
@@ -74,7 +74,10 @@ Phase 0 (Disambiguation) is required before any implementation."""
|
|
|
74
74
|
disambiguation = phases.get("disambiguation", {})
|
|
75
75
|
status = disambiguation.get("status", "not_started")
|
|
76
76
|
|
|
77
|
-
if status
|
|
77
|
+
# Also check phase_exit_confirmed even if status is "complete"
|
|
78
|
+
phase_exit_confirmed = disambiguation.get("phase_exit_confirmed", False)
|
|
79
|
+
|
|
80
|
+
if status != "complete" or not phase_exit_confirmed:
|
|
78
81
|
search_variations = disambiguation.get("search_variations", [])
|
|
79
82
|
user_question_asked = disambiguation.get("user_question_asked", False)
|
|
80
83
|
user_selected = disambiguation.get("user_selected", None)
|
|
@@ -87,6 +90,8 @@ Phase 0 (Disambiguation) is required before any implementation."""
|
|
|
87
90
|
missing.append("User question (AskUserQuestion not used)")
|
|
88
91
|
if not user_selected:
|
|
89
92
|
missing.append("User selection (no choice recorded)")
|
|
93
|
+
if not phase_exit_confirmed:
|
|
94
|
+
missing.append("Phase exit confirmation (user must explicitly confirm to proceed)")
|
|
90
95
|
|
|
91
96
|
print(json.dumps({
|
|
92
97
|
"permissionDecision": "deny",
|
|
@@ -96,6 +101,7 @@ Status: {status}
|
|
|
96
101
|
Search variations: {len(search_variations)}
|
|
97
102
|
User question asked: {user_question_asked}
|
|
98
103
|
User selection: {user_selected or "None"}
|
|
104
|
+
Phase exit confirmed: {phase_exit_confirmed}
|
|
99
105
|
|
|
100
106
|
MISSING:
|
|
101
107
|
{chr(10).join(f" • {m}" for m in missing)}
|
|
@@ -67,7 +67,9 @@ def main():
|
|
|
67
67
|
|
|
68
68
|
status = documentation.get("status", "not_started")
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
phase_exit_confirmed = documentation.get("phase_exit_confirmed", False)
|
|
71
|
+
|
|
72
|
+
if status != "complete" or not phase_exit_confirmed:
|
|
71
73
|
user_question_asked = documentation.get("user_question_asked", False)
|
|
72
74
|
user_confirmed = documentation.get("user_confirmed", False)
|
|
73
75
|
checklist_shown = documentation.get("checklist_shown", False)
|
|
@@ -86,6 +88,8 @@ def main():
|
|
|
86
88
|
missing.append("User confirmation question (AskUserQuestion not used)")
|
|
87
89
|
if not user_confirmed:
|
|
88
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)")
|
|
89
93
|
|
|
90
94
|
print(json.dumps({
|
|
91
95
|
"permissionDecision": "deny",
|
|
@@ -98,6 +102,7 @@ OpenAPI updated: {openapi_updated}
|
|
|
98
102
|
Checklist shown: {checklist_shown}
|
|
99
103
|
User question asked: {user_question_asked}
|
|
100
104
|
User confirmed: {user_confirmed}
|
|
105
|
+
Phase exit confirmed: {phase_exit_confirmed}
|
|
101
106
|
|
|
102
107
|
MISSING:
|
|
103
108
|
{chr(10).join(f" • {m}" for m in missing)}
|
|
@@ -131,9 +131,10 @@ def main():
|
|
|
131
131
|
user_question_asked = environment_check.get("user_question_asked", False)
|
|
132
132
|
user_ready = environment_check.get("user_ready", False)
|
|
133
133
|
env_shown = environment_check.get("env_shown", False)
|
|
134
|
+
phase_exit_confirmed = environment_check.get("phase_exit_confirmed", False)
|
|
134
135
|
|
|
135
136
|
# Check if environment check is complete
|
|
136
|
-
if env_status != "complete":
|
|
137
|
+
if env_status != "complete" or not phase_exit_confirmed:
|
|
137
138
|
# Infer required keys if not already set
|
|
138
139
|
if not keys_required:
|
|
139
140
|
interview = phases.get("interview", {})
|
|
@@ -157,6 +158,8 @@ def main():
|
|
|
157
158
|
missing_steps.append("User readiness question (AskUserQuestion not used)")
|
|
158
159
|
if not user_ready:
|
|
159
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)")
|
|
160
163
|
|
|
161
164
|
print(json.dumps({
|
|
162
165
|
"permissionDecision": "deny",
|
|
@@ -169,6 +172,7 @@ Missing: {len(missing)}
|
|
|
169
172
|
User shown env: {env_shown}
|
|
170
173
|
User question asked: {user_question_asked}
|
|
171
174
|
User ready: {user_ready}
|
|
175
|
+
Phase exit confirmed: {phase_exit_confirmed}
|
|
172
176
|
|
|
173
177
|
MISSING:
|
|
174
178
|
{chr(10).join(f" • {m}" for m in missing_steps)}
|
|
@@ -262,15 +262,18 @@ Reset the interview and ask with options based on research."""
|
|
|
262
262
|
# Check 6: FINAL USER CONFIRMATION - must confirm interview is complete
|
|
263
263
|
user_question_asked_final = interview.get("user_question_asked", False)
|
|
264
264
|
user_completed = interview.get("user_completed", False)
|
|
265
|
+
phase_exit_confirmed = interview.get("phase_exit_confirmed", False)
|
|
265
266
|
decisions = interview.get("decisions", {})
|
|
266
267
|
|
|
267
|
-
if not user_completed or not user_question_asked_final:
|
|
268
|
+
if not user_completed or not user_question_asked_final or not phase_exit_confirmed:
|
|
268
269
|
decision_summary = _build_decision_summary(decisions)
|
|
269
270
|
missing = []
|
|
270
271
|
if not user_question_asked_final:
|
|
271
272
|
missing.append("Final confirmation question (AskUserQuestion not used)")
|
|
272
273
|
if not user_completed:
|
|
273
274
|
missing.append("User hasn't confirmed interview complete")
|
|
275
|
+
if not phase_exit_confirmed:
|
|
276
|
+
missing.append("Phase exit confirmation (user must explicitly approve to proceed)")
|
|
274
277
|
|
|
275
278
|
print(json.dumps({
|
|
276
279
|
"permissionDecision": "deny",
|
|
@@ -279,6 +282,7 @@ Reset the interview and ask with options based on research."""
|
|
|
279
282
|
Questions asked: {len(questions)}
|
|
280
283
|
Structured questions: {actual_structured}
|
|
281
284
|
User final confirmation: {user_completed}
|
|
285
|
+
Phase exit confirmed: {phase_exit_confirmed}
|
|
282
286
|
|
|
283
287
|
MISSING:
|
|
284
288
|
{chr(10).join(f" • {m}" for m in missing)}
|
|
@@ -119,8 +119,9 @@ def main():
|
|
|
119
119
|
gaps_found = verify.get("gaps_found", 0)
|
|
120
120
|
gaps_fixed = verify.get("gaps_fixed", 0)
|
|
121
121
|
intentional_omissions = verify.get("intentional_omissions", [])
|
|
122
|
+
phase_exit_confirmed = verify.get("phase_exit_confirmed", False)
|
|
122
123
|
|
|
123
|
-
if verify_status != "complete":
|
|
124
|
+
if verify_status != "complete" or not phase_exit_confirmed:
|
|
124
125
|
print(json.dumps({
|
|
125
126
|
"permissionDecision": "deny",
|
|
126
127
|
"reason": f"""❌ BLOCKED: Verify phase (Phase 9) not complete.
|
|
@@ -129,6 +130,7 @@ Current status: {verify_status}
|
|
|
129
130
|
Gaps found: {gaps_found}
|
|
130
131
|
Gaps fixed: {gaps_fixed}
|
|
131
132
|
Intentional omissions: {len(intentional_omissions)}
|
|
133
|
+
Phase exit confirmed: {phase_exit_confirmed}
|
|
132
134
|
|
|
133
135
|
═══════════════════════════════════════════════════════════
|
|
134
136
|
⚠️ VERIFY BEFORE REFACTORING
|
package/hooks/enforce-schema.py
CHANGED
|
File without changes
|
package/hooks/enforce-scope.py
CHANGED
|
@@ -71,8 +71,9 @@ def main():
|
|
|
71
71
|
status = scope.get("status", "not_started")
|
|
72
72
|
user_confirmed = scope.get("user_confirmed", False)
|
|
73
73
|
user_question_asked = scope.get("user_question_asked", False)
|
|
74
|
+
phase_exit_confirmed = scope.get("phase_exit_confirmed", False)
|
|
74
75
|
|
|
75
|
-
if status != "complete" or not user_confirmed:
|
|
76
|
+
if status != "complete" or not user_confirmed or not phase_exit_confirmed:
|
|
76
77
|
endpoint_path = scope.get("endpoint_path", f"/api/v2/{endpoint}")
|
|
77
78
|
modifications = scope.get("modifications", [])
|
|
78
79
|
|
|
@@ -81,6 +82,8 @@ def main():
|
|
|
81
82
|
missing.append("User question (AskUserQuestion not used)")
|
|
82
83
|
if not user_confirmed:
|
|
83
84
|
missing.append("User confirmation (user hasn't said 'yes')")
|
|
85
|
+
if not phase_exit_confirmed:
|
|
86
|
+
missing.append("Phase exit confirmation (user must explicitly approve to proceed)")
|
|
84
87
|
|
|
85
88
|
print(json.dumps({
|
|
86
89
|
"permissionDecision": "deny",
|
|
@@ -89,6 +92,7 @@ def main():
|
|
|
89
92
|
Status: {status}
|
|
90
93
|
User question asked: {user_question_asked}
|
|
91
94
|
User confirmed: {user_confirmed}
|
|
95
|
+
Phase exit confirmed: {phase_exit_confirmed}
|
|
92
96
|
Proposed path: {endpoint_path}
|
|
93
97
|
Modifications: {len(modifications)}
|
|
94
98
|
|
package/hooks/enforce-tdd-red.py
CHANGED
|
@@ -123,9 +123,10 @@ Example test structure:
|
|
|
123
123
|
user_approved = tdd_red.get("user_approved", False)
|
|
124
124
|
matrix_shown = tdd_red.get("matrix_shown", False)
|
|
125
125
|
test_scenarios = tdd_red.get("test_scenarios", [])
|
|
126
|
+
phase_exit_confirmed = tdd_red.get("phase_exit_confirmed", False)
|
|
126
127
|
|
|
127
128
|
# Check if TDD Red phase is complete
|
|
128
|
-
if tdd_red_status != "complete":
|
|
129
|
+
if tdd_red_status != "complete" or not phase_exit_confirmed:
|
|
129
130
|
test_exists, expected_path = find_test_file(file_path)
|
|
130
131
|
|
|
131
132
|
# Check what's missing for user checkpoint
|
|
@@ -138,6 +139,8 @@ Example test structure:
|
|
|
138
139
|
missing.append("User approval question (AskUserQuestion not used)")
|
|
139
140
|
if not user_approved:
|
|
140
141
|
missing.append("User hasn't approved the test plan")
|
|
142
|
+
if not phase_exit_confirmed:
|
|
143
|
+
missing.append("Phase exit confirmation (user must explicitly approve to proceed)")
|
|
141
144
|
|
|
142
145
|
print(json.dumps({
|
|
143
146
|
"permissionDecision": "deny",
|
|
@@ -149,6 +152,7 @@ Test file exists: {test_exists}
|
|
|
149
152
|
Matrix shown: {matrix_shown}
|
|
150
153
|
User question asked: {user_question_asked}
|
|
151
154
|
User approved: {user_approved}
|
|
155
|
+
Phase exit confirmed: {phase_exit_confirmed}
|
|
152
156
|
Scenarios: {len(test_scenarios)}
|
|
153
157
|
|
|
154
158
|
MISSING:
|
package/hooks/enforce-verify.py
CHANGED
|
File without changes
|
package/hooks/track-tool-use.py
CHANGED
|
@@ -156,6 +156,36 @@ def main():
|
|
|
156
156
|
|
|
157
157
|
interview["last_activity"] = datetime.now().isoformat()
|
|
158
158
|
|
|
159
|
+
# ========================================
|
|
160
|
+
# CRITICAL: Set user_question_asked flags
|
|
161
|
+
# This is what the enforcement hooks check!
|
|
162
|
+
# ========================================
|
|
163
|
+
interview["user_question_asked"] = True
|
|
164
|
+
|
|
165
|
+
# Also update the CURRENT phase based on workflow state
|
|
166
|
+
# Determine which phase we're in and set its user_question_asked flag
|
|
167
|
+
current_phase = _determine_current_phase(phases)
|
|
168
|
+
if current_phase and current_phase in phases:
|
|
169
|
+
phases[current_phase]["user_question_asked"] = True
|
|
170
|
+
# If user responded, also track that
|
|
171
|
+
if user_response:
|
|
172
|
+
phases[current_phase]["last_user_response"] = user_response[:200]
|
|
173
|
+
phases[current_phase]["last_question_timestamp"] = datetime.now().isoformat()
|
|
174
|
+
|
|
175
|
+
# ========================================
|
|
176
|
+
# CRITICAL: Detect phase exit confirmations
|
|
177
|
+
# This prevents Claude from self-answering
|
|
178
|
+
# ========================================
|
|
179
|
+
question_text = tool_input.get("question", "").lower()
|
|
180
|
+
question_type = _detect_question_type(question_text, options)
|
|
181
|
+
phases[current_phase]["last_question_type"] = question_type
|
|
182
|
+
|
|
183
|
+
# If this is an exit confirmation question AND user responded affirmatively
|
|
184
|
+
if question_type == "exit_confirmation":
|
|
185
|
+
# Check if user's response indicates approval/confirmation
|
|
186
|
+
if user_response and _is_affirmative_response(user_response, options):
|
|
187
|
+
phases[current_phase]["phase_exit_confirmed"] = True
|
|
188
|
+
|
|
159
189
|
# Log for visibility
|
|
160
190
|
if has_options:
|
|
161
191
|
interview["last_structured_question"] = {
|
|
@@ -294,6 +324,143 @@ def main():
|
|
|
294
324
|
sys.exit(0)
|
|
295
325
|
|
|
296
326
|
|
|
327
|
+
def _detect_question_type(question_text: str, options: list) -> str:
|
|
328
|
+
"""
|
|
329
|
+
Detect the type of question being asked.
|
|
330
|
+
Returns: 'exit_confirmation', 'data_collection', 'clarification', or 'unknown'
|
|
331
|
+
"""
|
|
332
|
+
question_lower = question_text.lower()
|
|
333
|
+
|
|
334
|
+
# Exit confirmation patterns - questions asking to proceed/continue/move to next phase
|
|
335
|
+
exit_patterns = [
|
|
336
|
+
"proceed",
|
|
337
|
+
"continue",
|
|
338
|
+
"ready to",
|
|
339
|
+
"move to",
|
|
340
|
+
"is this correct",
|
|
341
|
+
"all correct",
|
|
342
|
+
"looks correct",
|
|
343
|
+
"approve",
|
|
344
|
+
"approved",
|
|
345
|
+
"confirm",
|
|
346
|
+
"complete",
|
|
347
|
+
"shall i",
|
|
348
|
+
"should i proceed",
|
|
349
|
+
"does this match",
|
|
350
|
+
"ready for",
|
|
351
|
+
"start tdd",
|
|
352
|
+
"start tests",
|
|
353
|
+
"begin",
|
|
354
|
+
"next phase",
|
|
355
|
+
"move on",
|
|
356
|
+
"go ahead"
|
|
357
|
+
]
|
|
358
|
+
|
|
359
|
+
# Check options for exit-like labels
|
|
360
|
+
option_labels = [opt.get("label", "").lower() for opt in options] if options else []
|
|
361
|
+
exit_option_patterns = [
|
|
362
|
+
"yes", "proceed", "continue", "approve", "confirm",
|
|
363
|
+
"ready", "looks good", "correct", "done", "complete"
|
|
364
|
+
]
|
|
365
|
+
|
|
366
|
+
# If question matches exit patterns
|
|
367
|
+
for pattern in exit_patterns:
|
|
368
|
+
if pattern in question_lower:
|
|
369
|
+
return "exit_confirmation"
|
|
370
|
+
|
|
371
|
+
# If options suggest it's an exit confirmation
|
|
372
|
+
for opt_label in option_labels:
|
|
373
|
+
for pattern in exit_option_patterns:
|
|
374
|
+
if pattern in opt_label:
|
|
375
|
+
return "exit_confirmation"
|
|
376
|
+
|
|
377
|
+
# Data collection - asking for choices about implementation
|
|
378
|
+
data_patterns = [
|
|
379
|
+
"which", "what", "how should", "prefer", "want",
|
|
380
|
+
"format", "handling", "strategy", "method"
|
|
381
|
+
]
|
|
382
|
+
for pattern in data_patterns:
|
|
383
|
+
if pattern in question_lower:
|
|
384
|
+
return "data_collection"
|
|
385
|
+
|
|
386
|
+
# Clarification - asking for more info
|
|
387
|
+
clarify_patterns = [
|
|
388
|
+
"clarify", "explain", "more detail", "what do you mean"
|
|
389
|
+
]
|
|
390
|
+
for pattern in clarify_patterns:
|
|
391
|
+
if pattern in question_lower:
|
|
392
|
+
return "clarification"
|
|
393
|
+
|
|
394
|
+
return "unknown"
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def _is_affirmative_response(response: str, options: list) -> bool:
|
|
398
|
+
"""
|
|
399
|
+
Check if the user's response indicates approval/confirmation.
|
|
400
|
+
"""
|
|
401
|
+
response_lower = response.lower().strip()
|
|
402
|
+
|
|
403
|
+
# Direct affirmative words
|
|
404
|
+
affirmative_words = [
|
|
405
|
+
"yes", "y", "proceed", "continue", "approve", "confirm",
|
|
406
|
+
"correct", "ready", "go", "ok", "okay", "looks good",
|
|
407
|
+
"sounds good", "perfect", "great", "fine", "done",
|
|
408
|
+
"all good", "looks correct", "is correct", "all correct"
|
|
409
|
+
]
|
|
410
|
+
|
|
411
|
+
for word in affirmative_words:
|
|
412
|
+
if word in response_lower:
|
|
413
|
+
return True
|
|
414
|
+
|
|
415
|
+
# Check if response matches an affirmative option
|
|
416
|
+
if options:
|
|
417
|
+
for opt in options:
|
|
418
|
+
opt_label = opt.get("label", "").lower()
|
|
419
|
+
opt_value = opt.get("value", "").lower()
|
|
420
|
+
|
|
421
|
+
# If response matches an option that sounds affirmative
|
|
422
|
+
if opt_label in response_lower or response_lower in opt_label:
|
|
423
|
+
for aff in affirmative_words:
|
|
424
|
+
if aff in opt_label:
|
|
425
|
+
return True
|
|
426
|
+
|
|
427
|
+
# Check for negative responses (to avoid false positives)
|
|
428
|
+
negative_words = ["no", "change", "modify", "add more", "not yet", "wait"]
|
|
429
|
+
for word in negative_words:
|
|
430
|
+
if word in response_lower:
|
|
431
|
+
return False
|
|
432
|
+
|
|
433
|
+
return False
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def _determine_current_phase(phases: dict) -> str:
|
|
437
|
+
"""Determine which phase is currently active based on status."""
|
|
438
|
+
# Phase order - return first incomplete phase
|
|
439
|
+
phase_order = [
|
|
440
|
+
"disambiguation",
|
|
441
|
+
"scope",
|
|
442
|
+
"research_initial",
|
|
443
|
+
"interview",
|
|
444
|
+
"research_deep",
|
|
445
|
+
"schema_creation",
|
|
446
|
+
"environment_check",
|
|
447
|
+
"tdd_red",
|
|
448
|
+
"tdd_green",
|
|
449
|
+
"verify",
|
|
450
|
+
"tdd_refactor",
|
|
451
|
+
"documentation"
|
|
452
|
+
]
|
|
453
|
+
|
|
454
|
+
for phase_name in phase_order:
|
|
455
|
+
phase = phases.get(phase_name, {})
|
|
456
|
+
status = phase.get("status", "not_started")
|
|
457
|
+
if status != "complete":
|
|
458
|
+
return phase_name
|
|
459
|
+
|
|
460
|
+
# All complete, return documentation
|
|
461
|
+
return "documentation"
|
|
462
|
+
|
|
463
|
+
|
|
297
464
|
def create_initial_state():
|
|
298
465
|
"""Create initial state structure (v3.0.0)"""
|
|
299
466
|
return {
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
"search_variations": [],
|
|
16
16
|
"user_question_asked": false,
|
|
17
17
|
"user_selected": null,
|
|
18
|
+
"phase_exit_confirmed": false,
|
|
19
|
+
"last_question_type": null,
|
|
18
20
|
"description": "Pre-research disambiguation to clarify ambiguous requests"
|
|
19
21
|
},
|
|
20
22
|
"scope": {
|
|
@@ -22,6 +24,8 @@
|
|
|
22
24
|
"confirmed": false,
|
|
23
25
|
"user_question_asked": false,
|
|
24
26
|
"user_confirmed": false,
|
|
27
|
+
"phase_exit_confirmed": false,
|
|
28
|
+
"last_question_type": null,
|
|
25
29
|
"description": "Initial scope understanding and confirmation"
|
|
26
30
|
},
|
|
27
31
|
"research_initial": {
|
|
@@ -30,6 +34,8 @@
|
|
|
30
34
|
"summary_shown": false,
|
|
31
35
|
"user_question_asked": false,
|
|
32
36
|
"user_approved": false,
|
|
37
|
+
"phase_exit_confirmed": false,
|
|
38
|
+
"last_question_type": null,
|
|
33
39
|
"description": "Context7/WebSearch research for live documentation"
|
|
34
40
|
},
|
|
35
41
|
"interview": {
|
|
@@ -40,6 +46,8 @@
|
|
|
40
46
|
"decisions": {},
|
|
41
47
|
"user_question_asked": false,
|
|
42
48
|
"user_completed": false,
|
|
49
|
+
"phase_exit_confirmed": false,
|
|
50
|
+
"last_question_type": null,
|
|
43
51
|
"description": "Structured interview about requirements (generated FROM research)"
|
|
44
52
|
},
|
|
45
53
|
"research_deep": {
|
|
@@ -52,6 +60,8 @@
|
|
|
52
60
|
"proposals_shown": false,
|
|
53
61
|
"user_question_asked": false,
|
|
54
62
|
"user_approved": false,
|
|
63
|
+
"phase_exit_confirmed": false,
|
|
64
|
+
"last_question_type": null,
|
|
55
65
|
"description": "Deep dive based on interview answers (adaptive, not shotgun)"
|
|
56
66
|
},
|
|
57
67
|
"schema_creation": {
|
|
@@ -61,6 +71,8 @@
|
|
|
61
71
|
"schema_shown": false,
|
|
62
72
|
"user_question_asked": false,
|
|
63
73
|
"user_confirmed": false,
|
|
74
|
+
"phase_exit_confirmed": false,
|
|
75
|
+
"last_question_type": null,
|
|
64
76
|
"description": "Zod schema creation from research"
|
|
65
77
|
},
|
|
66
78
|
"environment_check": {
|
|
@@ -71,6 +83,8 @@
|
|
|
71
83
|
"env_shown": false,
|
|
72
84
|
"user_question_asked": false,
|
|
73
85
|
"user_ready": false,
|
|
86
|
+
"phase_exit_confirmed": false,
|
|
87
|
+
"last_question_type": null,
|
|
74
88
|
"description": "API key and environment verification"
|
|
75
89
|
},
|
|
76
90
|
"tdd_red": {
|
|
@@ -81,12 +95,16 @@
|
|
|
81
95
|
"matrix_shown": false,
|
|
82
96
|
"user_question_asked": false,
|
|
83
97
|
"user_approved": false,
|
|
98
|
+
"phase_exit_confirmed": false,
|
|
99
|
+
"last_question_type": null,
|
|
84
100
|
"description": "Write failing tests first"
|
|
85
101
|
},
|
|
86
102
|
"tdd_green": {
|
|
87
103
|
"status": "not_started",
|
|
88
104
|
"implementation_file": null,
|
|
89
105
|
"all_tests_passing": false,
|
|
106
|
+
"phase_exit_confirmed": false,
|
|
107
|
+
"last_question_type": null,
|
|
90
108
|
"description": "Minimal implementation to pass tests"
|
|
91
109
|
},
|
|
92
110
|
"verify": {
|
|
@@ -100,10 +118,14 @@
|
|
|
100
118
|
"user_question_asked": false,
|
|
101
119
|
"user_decided": false,
|
|
102
120
|
"user_decision": null,
|
|
121
|
+
"phase_exit_confirmed": false,
|
|
122
|
+
"last_question_type": null,
|
|
103
123
|
"description": "Re-research after Green to verify implementation matches docs"
|
|
104
124
|
},
|
|
105
125
|
"tdd_refactor": {
|
|
106
126
|
"status": "not_started",
|
|
127
|
+
"phase_exit_confirmed": false,
|
|
128
|
+
"last_question_type": null,
|
|
107
129
|
"description": "Code cleanup while keeping tests green"
|
|
108
130
|
},
|
|
109
131
|
"documentation": {
|
|
@@ -115,6 +137,8 @@
|
|
|
115
137
|
"checklist_shown": false,
|
|
116
138
|
"user_question_asked": false,
|
|
117
139
|
"user_confirmed": false,
|
|
140
|
+
"phase_exit_confirmed": false,
|
|
141
|
+
"last_question_type": null,
|
|
118
142
|
"description": "Update manifests, OpenAPI, cache research"
|
|
119
143
|
}
|
|
120
144
|
},
|