@hustle-together/api-dev-tools 3.6.5 → 3.9.2
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 +5307 -258
- package/bin/cli.js +348 -20
- package/commands/README.md +459 -71
- package/commands/hustle-api-continue.md +158 -0
- package/commands/{api-create.md → hustle-api-create.md} +22 -2
- package/commands/{api-env.md → hustle-api-env.md} +4 -4
- package/commands/{api-interview.md → hustle-api-interview.md} +1 -1
- package/commands/{api-research.md → hustle-api-research.md} +3 -3
- package/commands/hustle-api-sessions.md +149 -0
- package/commands/{api-status.md → hustle-api-status.md} +16 -16
- package/commands/{api-verify.md → hustle-api-verify.md} +2 -2
- package/commands/hustle-combine.md +763 -0
- package/commands/hustle-ui-create.md +825 -0
- package/hooks/api-workflow-check.py +385 -19
- package/hooks/cache-research.py +337 -0
- package/hooks/check-playwright-setup.py +103 -0
- package/hooks/check-storybook-setup.py +81 -0
- package/hooks/detect-interruption.py +165 -0
- package/hooks/enforce-brand-guide.py +131 -0
- package/hooks/enforce-documentation.py +60 -8
- package/hooks/enforce-freshness.py +184 -0
- package/hooks/enforce-questions-sourced.py +146 -0
- package/hooks/enforce-schema-from-interview.py +248 -0
- package/hooks/enforce-ui-disambiguation.py +108 -0
- package/hooks/enforce-ui-interview.py +130 -0
- package/hooks/generate-manifest-entry.py +981 -0
- package/hooks/session-logger.py +297 -0
- package/hooks/session-startup.py +65 -10
- package/hooks/track-scope-coverage.py +220 -0
- package/hooks/track-tool-use.py +81 -1
- package/hooks/update-api-showcase.py +149 -0
- package/hooks/update-registry.py +352 -0
- package/hooks/update-ui-showcase.py +148 -0
- package/package.json +8 -2
- package/templates/BRAND_GUIDE.md +299 -0
- package/templates/CLAUDE-SECTION.md +56 -24
- package/templates/SPEC.json +640 -0
- package/templates/api-dev-state.json +179 -161
- package/templates/api-showcase/APICard.tsx +153 -0
- package/templates/api-showcase/APIModal.tsx +375 -0
- package/templates/api-showcase/APIShowcase.tsx +231 -0
- package/templates/api-showcase/APITester.tsx +522 -0
- package/templates/api-showcase/page.tsx +41 -0
- package/templates/component/Component.stories.tsx +172 -0
- package/templates/component/Component.test.tsx +237 -0
- package/templates/component/Component.tsx +86 -0
- package/templates/component/Component.types.ts +55 -0
- package/templates/component/index.ts +15 -0
- package/templates/dev-tools/_components/DevToolsLanding.tsx +320 -0
- package/templates/dev-tools/page.tsx +10 -0
- package/templates/page/page.e2e.test.ts +218 -0
- package/templates/page/page.tsx +42 -0
- package/templates/performance-budgets.json +58 -0
- package/templates/registry.json +13 -0
- package/templates/settings.json +74 -0
- package/templates/shared/HeroHeader.tsx +261 -0
- package/templates/shared/index.ts +1 -0
- package/templates/ui-showcase/PreviewCard.tsx +315 -0
- package/templates/ui-showcase/PreviewModal.tsx +676 -0
- package/templates/ui-showcase/UIShowcase.tsx +262 -0
- package/templates/ui-showcase/page.tsx +26 -0
|
@@ -11,6 +11,12 @@ Gap Fixes Applied:
|
|
|
11
11
|
- Gap 3: Warns if there are verification_warnings that weren't addressed
|
|
12
12
|
- Gap 4: Requires explicit verification that implementation matches interview
|
|
13
13
|
|
|
14
|
+
v3.6.7 Enhancement:
|
|
15
|
+
- Phase 13 completion output with curl examples, test commands, parameter tables
|
|
16
|
+
- Scope coverage report (discovered vs implemented vs deferred)
|
|
17
|
+
- Research cache location
|
|
18
|
+
- Summary statistics
|
|
19
|
+
|
|
14
20
|
Returns:
|
|
15
21
|
- {"decision": "approve"} - Allow stopping
|
|
16
22
|
- {"decision": "block", "reason": "..."} - Prevent stopping with explanation
|
|
@@ -18,10 +24,13 @@ Returns:
|
|
|
18
24
|
import json
|
|
19
25
|
import sys
|
|
20
26
|
import subprocess
|
|
27
|
+
import re
|
|
28
|
+
from datetime import datetime
|
|
21
29
|
from pathlib import Path
|
|
22
30
|
|
|
23
31
|
# State file is in .claude/ directory (sibling to hooks/)
|
|
24
32
|
STATE_FILE = Path(__file__).parent.parent / "api-dev-state.json"
|
|
33
|
+
RESEARCH_DIR = Path(__file__).parent.parent / "research"
|
|
25
34
|
|
|
26
35
|
# Phases that MUST be complete before stopping
|
|
27
36
|
REQUIRED_PHASES = [
|
|
@@ -41,6 +50,22 @@ RECOMMENDED_PHASES = [
|
|
|
41
50
|
]
|
|
42
51
|
|
|
43
52
|
|
|
53
|
+
def get_active_endpoint(state):
|
|
54
|
+
"""Get active endpoint - supports both old and new state formats."""
|
|
55
|
+
if "endpoints" in state and "active_endpoint" in state:
|
|
56
|
+
active = state.get("active_endpoint")
|
|
57
|
+
if active and active in state["endpoints"]:
|
|
58
|
+
return active, state["endpoints"][active]
|
|
59
|
+
return None, None
|
|
60
|
+
|
|
61
|
+
# Old format: single endpoint
|
|
62
|
+
endpoint = state.get("endpoint")
|
|
63
|
+
if endpoint:
|
|
64
|
+
return endpoint, state
|
|
65
|
+
|
|
66
|
+
return None, None
|
|
67
|
+
|
|
68
|
+
|
|
44
69
|
def get_git_modified_files() -> list[str]:
|
|
45
70
|
"""Get list of modified files from git.
|
|
46
71
|
|
|
@@ -76,21 +101,23 @@ def check_verification_warnings(state: dict) -> list[str]:
|
|
|
76
101
|
return []
|
|
77
102
|
|
|
78
103
|
|
|
79
|
-
def check_interview_implementation_match(state: dict) -> list[str]:
|
|
104
|
+
def check_interview_implementation_match(state: dict, endpoint_data: dict = None) -> list[str]:
|
|
80
105
|
"""Verify implementation matches interview requirements.
|
|
81
106
|
|
|
82
107
|
Gap 4 Fix: Define specific "done" criteria based on interview.
|
|
83
108
|
"""
|
|
84
109
|
issues = []
|
|
85
110
|
|
|
86
|
-
|
|
111
|
+
# Use endpoint_data if provided (multi-API), otherwise use state directly
|
|
112
|
+
data = endpoint_data if endpoint_data else state
|
|
113
|
+
interview = data.get("phases", {}).get("interview", {})
|
|
87
114
|
questions = interview.get("questions", [])
|
|
88
115
|
|
|
89
116
|
# Extract key requirements from interview
|
|
90
117
|
all_text = " ".join(str(q) for q in questions)
|
|
91
118
|
|
|
92
119
|
# Check files_created includes expected patterns
|
|
93
|
-
files_created = state.get("files_created", [])
|
|
120
|
+
files_created = data.get("files_created", []) or state.get("files_created", [])
|
|
94
121
|
|
|
95
122
|
# Look for route files if interview mentioned endpoints
|
|
96
123
|
if "endpoint" in all_text.lower() or "/api/" in all_text.lower():
|
|
@@ -106,6 +133,324 @@ def check_interview_implementation_match(state: dict) -> list[str]:
|
|
|
106
133
|
return issues
|
|
107
134
|
|
|
108
135
|
|
|
136
|
+
def extract_schema_params(endpoint: str, endpoint_data: dict) -> list[dict]:
|
|
137
|
+
"""Extract parameters from schema file for the parameter table."""
|
|
138
|
+
schema_file = endpoint_data.get("phases", {}).get("schema_creation", {}).get("schema_file")
|
|
139
|
+
if not schema_file:
|
|
140
|
+
return []
|
|
141
|
+
|
|
142
|
+
# Try to read the schema file
|
|
143
|
+
try:
|
|
144
|
+
schema_path = STATE_FILE.parent.parent / schema_file
|
|
145
|
+
if not schema_path.exists():
|
|
146
|
+
return []
|
|
147
|
+
|
|
148
|
+
content = schema_path.read_text()
|
|
149
|
+
|
|
150
|
+
# Simple regex to extract Zod field definitions
|
|
151
|
+
# Matches patterns like: fieldName: z.string(), fieldName: z.number().optional()
|
|
152
|
+
params = []
|
|
153
|
+
field_pattern = r'(\w+):\s*z\.(\w+)\(([^)]*)\)(\.[^,\n}]+)?'
|
|
154
|
+
|
|
155
|
+
for match in re.finditer(field_pattern, content):
|
|
156
|
+
name = match.group(1)
|
|
157
|
+
zod_type = match.group(2)
|
|
158
|
+
chain = match.group(4) or ""
|
|
159
|
+
|
|
160
|
+
# Map Zod types to simple types
|
|
161
|
+
type_map = {
|
|
162
|
+
"string": "string",
|
|
163
|
+
"number": "number",
|
|
164
|
+
"boolean": "boolean",
|
|
165
|
+
"array": "array",
|
|
166
|
+
"object": "object",
|
|
167
|
+
"enum": "enum",
|
|
168
|
+
"literal": "literal",
|
|
169
|
+
"union": "union",
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
param_type = type_map.get(zod_type, zod_type)
|
|
173
|
+
required = ".optional()" not in chain
|
|
174
|
+
description = ""
|
|
175
|
+
|
|
176
|
+
# Try to extract description from .describe()
|
|
177
|
+
desc_match = re.search(r'\.describe\(["\']([^"\']+)["\']', chain)
|
|
178
|
+
if desc_match:
|
|
179
|
+
description = desc_match.group(1)
|
|
180
|
+
|
|
181
|
+
params.append({
|
|
182
|
+
"name": name,
|
|
183
|
+
"type": param_type,
|
|
184
|
+
"required": required,
|
|
185
|
+
"description": description
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
return params
|
|
189
|
+
except Exception:
|
|
190
|
+
return []
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def generate_curl_examples(endpoint: str, endpoint_data: dict, params: list) -> list[str]:
|
|
194
|
+
"""Generate curl command examples for the endpoint."""
|
|
195
|
+
lines = []
|
|
196
|
+
|
|
197
|
+
# Determine HTTP method from route file
|
|
198
|
+
method = "POST" # Default
|
|
199
|
+
files_created = endpoint_data.get("files_created", [])
|
|
200
|
+
for f in files_created:
|
|
201
|
+
if "route.ts" in f:
|
|
202
|
+
try:
|
|
203
|
+
route_path = STATE_FILE.parent.parent / f
|
|
204
|
+
if route_path.exists():
|
|
205
|
+
route_content = route_path.read_text()
|
|
206
|
+
if "export async function GET" in route_content:
|
|
207
|
+
method = "GET"
|
|
208
|
+
elif "export async function DELETE" in route_content:
|
|
209
|
+
method = "DELETE"
|
|
210
|
+
elif "export async function PUT" in route_content:
|
|
211
|
+
method = "PUT"
|
|
212
|
+
elif "export async function PATCH" in route_content:
|
|
213
|
+
method = "PATCH"
|
|
214
|
+
except Exception:
|
|
215
|
+
pass
|
|
216
|
+
break
|
|
217
|
+
|
|
218
|
+
lines.append("## API Usage (curl)")
|
|
219
|
+
lines.append("")
|
|
220
|
+
lines.append("```bash")
|
|
221
|
+
lines.append("# Basic request")
|
|
222
|
+
|
|
223
|
+
# Build example request body from params
|
|
224
|
+
if method in ["POST", "PUT", "PATCH"] and params:
|
|
225
|
+
example_body = {}
|
|
226
|
+
for p in params[:5]: # First 5 params
|
|
227
|
+
if p["type"] == "string":
|
|
228
|
+
example_body[p["name"]] = f"example-{p['name']}"
|
|
229
|
+
elif p["type"] == "number":
|
|
230
|
+
example_body[p["name"]] = 42
|
|
231
|
+
elif p["type"] == "boolean":
|
|
232
|
+
example_body[p["name"]] = True
|
|
233
|
+
elif p["type"] == "array":
|
|
234
|
+
example_body[p["name"]] = []
|
|
235
|
+
|
|
236
|
+
body_json = json.dumps(example_body, indent=2)
|
|
237
|
+
lines.append(f"curl -X {method} http://localhost:3001/api/v2/{endpoint} \\")
|
|
238
|
+
lines.append(" -H \"Content-Type: application/json\" \\")
|
|
239
|
+
lines.append(f" -d '{body_json}'")
|
|
240
|
+
else:
|
|
241
|
+
lines.append(f"curl http://localhost:3001/api/v2/{endpoint}")
|
|
242
|
+
|
|
243
|
+
lines.append("")
|
|
244
|
+
|
|
245
|
+
# With authentication example
|
|
246
|
+
lines.append("# With API key (if required)")
|
|
247
|
+
if method in ["POST", "PUT", "PATCH"]:
|
|
248
|
+
lines.append(f"curl -X {method} http://localhost:3001/api/v2/{endpoint} \\")
|
|
249
|
+
lines.append(" -H \"Content-Type: application/json\" \\")
|
|
250
|
+
lines.append(" -H \"X-API-Key: your-api-key\" \\")
|
|
251
|
+
lines.append(" -d '{\"param\": \"value\"}'")
|
|
252
|
+
else:
|
|
253
|
+
lines.append(f"curl http://localhost:3001/api/v2/{endpoint} \\")
|
|
254
|
+
lines.append(" -H \"X-API-Key: your-api-key\"")
|
|
255
|
+
|
|
256
|
+
lines.append("```")
|
|
257
|
+
|
|
258
|
+
return lines
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def generate_test_commands(endpoint: str, endpoint_data: dict) -> list[str]:
|
|
262
|
+
"""Generate test commands for running endpoint tests."""
|
|
263
|
+
lines = []
|
|
264
|
+
|
|
265
|
+
lines.append("## Test Commands")
|
|
266
|
+
lines.append("")
|
|
267
|
+
lines.append("```bash")
|
|
268
|
+
lines.append("# Run endpoint tests")
|
|
269
|
+
lines.append(f"pnpm test -- {endpoint}")
|
|
270
|
+
lines.append("")
|
|
271
|
+
lines.append("# Run with coverage")
|
|
272
|
+
lines.append(f"pnpm test:coverage -- {endpoint}")
|
|
273
|
+
lines.append("")
|
|
274
|
+
lines.append("# Run specific test file")
|
|
275
|
+
|
|
276
|
+
# Find test file
|
|
277
|
+
files_created = endpoint_data.get("files_created", [])
|
|
278
|
+
test_file = None
|
|
279
|
+
for f in files_created:
|
|
280
|
+
if ".test." in f or "__tests__" in f:
|
|
281
|
+
test_file = f
|
|
282
|
+
break
|
|
283
|
+
|
|
284
|
+
if test_file:
|
|
285
|
+
lines.append(f"pnpm test:run {test_file}")
|
|
286
|
+
else:
|
|
287
|
+
lines.append(f"pnpm test:run src/app/api/v2/{endpoint}/__tests__/{endpoint}.api.test.ts")
|
|
288
|
+
|
|
289
|
+
lines.append("")
|
|
290
|
+
lines.append("# Full test suite")
|
|
291
|
+
lines.append("pnpm test:run")
|
|
292
|
+
lines.append("```")
|
|
293
|
+
|
|
294
|
+
return lines
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def generate_parameter_table(params: list) -> list[str]:
|
|
298
|
+
"""Generate markdown parameter table."""
|
|
299
|
+
if not params:
|
|
300
|
+
return []
|
|
301
|
+
|
|
302
|
+
lines = []
|
|
303
|
+
lines.append("## Parameters Discovered")
|
|
304
|
+
lines.append("")
|
|
305
|
+
lines.append("| Name | Type | Required | Description |")
|
|
306
|
+
lines.append("|------|------|----------|-------------|")
|
|
307
|
+
|
|
308
|
+
for p in params:
|
|
309
|
+
req = "✓" if p.get("required") else "-"
|
|
310
|
+
desc = p.get("description", "")[:50] # Truncate long descriptions
|
|
311
|
+
lines.append(f"| {p['name']} | {p['type']} | {req} | {desc} |")
|
|
312
|
+
|
|
313
|
+
return lines
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def generate_scope_coverage(endpoint_data: dict) -> list[str]:
|
|
317
|
+
"""Generate scope coverage report."""
|
|
318
|
+
scope = endpoint_data.get("scope", {})
|
|
319
|
+
if not scope:
|
|
320
|
+
return []
|
|
321
|
+
|
|
322
|
+
discovered = scope.get("discovered_features", [])
|
|
323
|
+
implemented = scope.get("implemented_features", [])
|
|
324
|
+
deferred = scope.get("deferred_features", [])
|
|
325
|
+
coverage = scope.get("coverage_percent", 0)
|
|
326
|
+
|
|
327
|
+
if not discovered and not implemented and not deferred:
|
|
328
|
+
return []
|
|
329
|
+
|
|
330
|
+
lines = []
|
|
331
|
+
lines.append("## Implementation Scope")
|
|
332
|
+
lines.append("")
|
|
333
|
+
|
|
334
|
+
if implemented:
|
|
335
|
+
lines.append(f"### Implemented ({len(implemented)}/{len(discovered)} features)")
|
|
336
|
+
lines.append("")
|
|
337
|
+
lines.append("| Feature | Status |")
|
|
338
|
+
lines.append("|---------|--------|")
|
|
339
|
+
for feat in implemented:
|
|
340
|
+
if isinstance(feat, dict):
|
|
341
|
+
lines.append(f"| {feat.get('name', feat)} | ✅ |")
|
|
342
|
+
else:
|
|
343
|
+
lines.append(f"| {feat} | ✅ |")
|
|
344
|
+
lines.append("")
|
|
345
|
+
|
|
346
|
+
if deferred:
|
|
347
|
+
lines.append(f"### Deferred ({len(deferred)} features)")
|
|
348
|
+
lines.append("")
|
|
349
|
+
lines.append("| Feature | Reason |")
|
|
350
|
+
lines.append("|---------|--------|")
|
|
351
|
+
for feat in deferred:
|
|
352
|
+
if isinstance(feat, dict):
|
|
353
|
+
reason = feat.get("reason", "User choice")
|
|
354
|
+
lines.append(f"| {feat.get('name', feat)} | {reason} |")
|
|
355
|
+
else:
|
|
356
|
+
lines.append(f"| {feat} | User choice |")
|
|
357
|
+
lines.append("")
|
|
358
|
+
|
|
359
|
+
if discovered:
|
|
360
|
+
total = len(discovered)
|
|
361
|
+
impl_count = len(implemented)
|
|
362
|
+
lines.append(f"**Coverage:** {impl_count}/{total} features ({coverage}%)")
|
|
363
|
+
|
|
364
|
+
return lines
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def generate_completion_output(endpoint: str, endpoint_data: dict, state: dict) -> str:
|
|
368
|
+
"""Generate comprehensive Phase 13 completion output."""
|
|
369
|
+
lines = []
|
|
370
|
+
|
|
371
|
+
# Header
|
|
372
|
+
lines.append("")
|
|
373
|
+
lines.append("=" * 60)
|
|
374
|
+
lines.append(f"# ✅ API Implementation Complete: {endpoint}")
|
|
375
|
+
lines.append("=" * 60)
|
|
376
|
+
lines.append("")
|
|
377
|
+
|
|
378
|
+
# Summary
|
|
379
|
+
phases = endpoint_data.get("phases", {})
|
|
380
|
+
phases_complete = sum(1 for p in phases.values() if isinstance(p, dict) and p.get("status") == "complete")
|
|
381
|
+
total_phases = len([p for p in phases.values() if isinstance(p, dict)])
|
|
382
|
+
|
|
383
|
+
started_at = endpoint_data.get("started_at", "Unknown")
|
|
384
|
+
files_created = endpoint_data.get("files_created", []) or state.get("files_created", [])
|
|
385
|
+
|
|
386
|
+
# Calculate test count from state
|
|
387
|
+
tdd_red = phases.get("tdd_red", {})
|
|
388
|
+
test_count = tdd_red.get("test_count", 0)
|
|
389
|
+
|
|
390
|
+
lines.append("## Summary")
|
|
391
|
+
lines.append("")
|
|
392
|
+
lines.append(f"- **Status:** PRODUCTION READY")
|
|
393
|
+
lines.append(f"- **Phases:** {phases_complete}/{total_phases} Complete")
|
|
394
|
+
lines.append(f"- **Tests:** {test_count} test scenarios")
|
|
395
|
+
lines.append(f"- **Started:** {started_at}")
|
|
396
|
+
lines.append(f"- **Completed:** {datetime.now().isoformat()}")
|
|
397
|
+
lines.append("")
|
|
398
|
+
|
|
399
|
+
# Files Created
|
|
400
|
+
if files_created:
|
|
401
|
+
lines.append("## Files Created")
|
|
402
|
+
lines.append("")
|
|
403
|
+
for f in files_created:
|
|
404
|
+
lines.append(f"- {f}")
|
|
405
|
+
lines.append("")
|
|
406
|
+
|
|
407
|
+
# Extract schema params
|
|
408
|
+
params = extract_schema_params(endpoint, endpoint_data)
|
|
409
|
+
|
|
410
|
+
# Test Commands
|
|
411
|
+
lines.extend(generate_test_commands(endpoint, endpoint_data))
|
|
412
|
+
lines.append("")
|
|
413
|
+
|
|
414
|
+
# Curl Examples
|
|
415
|
+
lines.extend(generate_curl_examples(endpoint, endpoint_data, params))
|
|
416
|
+
lines.append("")
|
|
417
|
+
|
|
418
|
+
# Parameter Table
|
|
419
|
+
param_lines = generate_parameter_table(params)
|
|
420
|
+
if param_lines:
|
|
421
|
+
lines.extend(param_lines)
|
|
422
|
+
lines.append("")
|
|
423
|
+
|
|
424
|
+
# Scope Coverage
|
|
425
|
+
scope_lines = generate_scope_coverage(endpoint_data)
|
|
426
|
+
if scope_lines:
|
|
427
|
+
lines.extend(scope_lines)
|
|
428
|
+
lines.append("")
|
|
429
|
+
|
|
430
|
+
# Research Cache Location
|
|
431
|
+
research_cache = RESEARCH_DIR / endpoint
|
|
432
|
+
if research_cache.exists():
|
|
433
|
+
lines.append("## Research Cache")
|
|
434
|
+
lines.append("")
|
|
435
|
+
lines.append(f"- `.claude/research/{endpoint}/CURRENT.md`")
|
|
436
|
+
lines.append(f"- `.claude/research/{endpoint}/sources.json`")
|
|
437
|
+
lines.append(f"- `.claude/research/{endpoint}/interview.json`")
|
|
438
|
+
lines.append("")
|
|
439
|
+
|
|
440
|
+
# Next Steps
|
|
441
|
+
lines.append("## Next Steps")
|
|
442
|
+
lines.append("")
|
|
443
|
+
lines.append(f"1. Review tests: `pnpm test -- {endpoint}`")
|
|
444
|
+
lines.append("2. Test manually with curl examples above")
|
|
445
|
+
lines.append("3. Deploy to staging")
|
|
446
|
+
lines.append("4. Update OpenAPI spec if needed")
|
|
447
|
+
lines.append("")
|
|
448
|
+
|
|
449
|
+
lines.append("=" * 60)
|
|
450
|
+
|
|
451
|
+
return "\n".join(lines)
|
|
452
|
+
|
|
453
|
+
|
|
109
454
|
def main():
|
|
110
455
|
# If no state file, we're not in an API workflow - allow stop
|
|
111
456
|
if not STATE_FILE.exists():
|
|
@@ -120,7 +465,14 @@ def main():
|
|
|
120
465
|
print(json.dumps({"decision": "approve"}))
|
|
121
466
|
sys.exit(0)
|
|
122
467
|
|
|
123
|
-
|
|
468
|
+
# Get active endpoint (multi-API support)
|
|
469
|
+
endpoint, endpoint_data = get_active_endpoint(state)
|
|
470
|
+
|
|
471
|
+
# If no active endpoint, check if using old format
|
|
472
|
+
if not endpoint_data:
|
|
473
|
+
phases = state.get("phases", {})
|
|
474
|
+
else:
|
|
475
|
+
phases = endpoint_data.get("phases", {})
|
|
124
476
|
|
|
125
477
|
# Check if workflow was even started
|
|
126
478
|
research = phases.get("research_initial", {})
|
|
@@ -154,7 +506,8 @@ def main():
|
|
|
154
506
|
|
|
155
507
|
# Gap 2: Check git diff vs tracked files
|
|
156
508
|
git_files = get_git_modified_files()
|
|
157
|
-
|
|
509
|
+
data_for_files = endpoint_data if endpoint_data else state
|
|
510
|
+
tracked_files = (data_for_files.get("files_created", []) or []) + (data_for_files.get("files_modified", []) or [])
|
|
158
511
|
|
|
159
512
|
if git_files and tracked_files:
|
|
160
513
|
# Find files in git but not tracked
|
|
@@ -169,12 +522,12 @@ def main():
|
|
|
169
522
|
all_issues.extend([f" - {f}" for f in untracked_changes[:5]])
|
|
170
523
|
|
|
171
524
|
# Gap 3: Check for unaddressed warnings
|
|
172
|
-
warning_issues = check_verification_warnings(state)
|
|
525
|
+
warning_issues = check_verification_warnings(endpoint_data if endpoint_data else state)
|
|
173
526
|
if warning_issues:
|
|
174
527
|
all_issues.append("\n" + "\n".join(warning_issues))
|
|
175
528
|
|
|
176
529
|
# Gap 4: Check interview-implementation match
|
|
177
|
-
match_issues = check_interview_implementation_match(state)
|
|
530
|
+
match_issues = check_interview_implementation_match(state, endpoint_data)
|
|
178
531
|
if match_issues:
|
|
179
532
|
all_issues.append("\n⚠️ Gap 4: Implementation verification:")
|
|
180
533
|
all_issues.extend([f" {i}" for i in match_issues])
|
|
@@ -192,22 +545,35 @@ def main():
|
|
|
192
545
|
}))
|
|
193
546
|
sys.exit(0)
|
|
194
547
|
|
|
195
|
-
#
|
|
196
|
-
|
|
197
|
-
|
|
548
|
+
# ================================================================
|
|
549
|
+
# Phase 13: Generate comprehensive completion output (v3.6.7)
|
|
550
|
+
# ================================================================
|
|
551
|
+
|
|
552
|
+
# Build completion message with full output
|
|
553
|
+
message_parts = []
|
|
554
|
+
|
|
555
|
+
# Generate comprehensive output if we have endpoint data
|
|
556
|
+
if endpoint and endpoint_data:
|
|
557
|
+
completion_output = generate_completion_output(endpoint, endpoint_data, state)
|
|
558
|
+
message_parts.append(completion_output)
|
|
559
|
+
else:
|
|
560
|
+
# Fallback for old format
|
|
561
|
+
message_parts.append("✅ API workflow completing")
|
|
562
|
+
|
|
563
|
+
# Show summary of tracked files
|
|
564
|
+
files_created = state.get("files_created", [])
|
|
565
|
+
if files_created:
|
|
566
|
+
message_parts.append(f"\n📁 Files created: {len(files_created)}")
|
|
567
|
+
for f in files_created[:5]:
|
|
568
|
+
message_parts.append(f" - {f}")
|
|
569
|
+
if len(files_created) > 5:
|
|
570
|
+
message_parts.append(f" ... and {len(files_created) - 5} more")
|
|
571
|
+
|
|
572
|
+
# Add warnings if any optional phases were skipped
|
|
198
573
|
if incomplete_recommended:
|
|
199
574
|
message_parts.append("\n⚠️ Optional phases skipped:")
|
|
200
575
|
message_parts.extend(incomplete_recommended)
|
|
201
576
|
|
|
202
|
-
# Show summary of tracked files
|
|
203
|
-
files_created = state.get("files_created", [])
|
|
204
|
-
if files_created:
|
|
205
|
-
message_parts.append(f"\n📁 Files created: {len(files_created)}")
|
|
206
|
-
for f in files_created[:5]:
|
|
207
|
-
message_parts.append(f" - {f}")
|
|
208
|
-
if len(files_created) > 5:
|
|
209
|
-
message_parts.append(f" ... and {len(files_created) - 5} more")
|
|
210
|
-
|
|
211
577
|
# Show any remaining warnings
|
|
212
578
|
if warning_issues or match_issues:
|
|
213
579
|
message_parts.append("\n⚠️ Review suggested:")
|