vibe-and-thrive 1.6.6 → 1.6.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-and-thrive",
3
- "version": "1.6.6",
3
+ "version": "1.6.7",
4
4
  "description": "Ship quality code faster with AI - RALPH autonomous loop, Claude Code hooks, and pre-commit checks",
5
5
  "author": "Allie Jones <allie@allthrive.ai>",
6
6
  "license": "MIT",
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Check that FastAPI endpoints have Pydantic response models defined.
4
+ Ensures Swagger/OpenAPI docs show proper response schemas.
5
+
6
+ Usage: python check-fastapi-responses.py [directory]
7
+
8
+ Exit codes:
9
+ 0 - All endpoints have response models
10
+ 1 - Some endpoints missing response models
11
+ """
12
+
13
+ import ast
14
+ import sys
15
+ from pathlib import Path
16
+ from typing import NamedTuple
17
+
18
+
19
+ class EndpointIssue(NamedTuple):
20
+ file: str
21
+ line: int
22
+ method: str
23
+ path: str
24
+ issue: str
25
+
26
+
27
+ def check_file(filepath: Path) -> list[EndpointIssue]:
28
+ """Check a single Python file for FastAPI endpoints without response models."""
29
+ issues = []
30
+
31
+ try:
32
+ content = filepath.read_text()
33
+ tree = ast.parse(content)
34
+ except (SyntaxError, UnicodeDecodeError):
35
+ return issues
36
+
37
+ # HTTP methods that should have response models
38
+ http_methods = {'get', 'post', 'put', 'patch', 'delete'}
39
+
40
+ for node in ast.walk(tree):
41
+ if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
42
+ continue
43
+
44
+ # Check decorators for FastAPI route decorators
45
+ for decorator in node.decorator_list:
46
+ # Handle @router.get("/path") or @app.get("/path")
47
+ if isinstance(decorator, ast.Call):
48
+ if isinstance(decorator.func, ast.Attribute):
49
+ method = decorator.func.attr
50
+ if method not in http_methods:
51
+ continue
52
+
53
+ # Get the path from first argument
54
+ path = "unknown"
55
+ if decorator.args and isinstance(decorator.args[0], ast.Constant):
56
+ path = decorator.args[0].value
57
+
58
+ # Check for response_model in keyword arguments
59
+ has_response_model = any(
60
+ kw.arg == 'response_model'
61
+ for kw in decorator.keywords
62
+ )
63
+
64
+ # Check for return type annotation
65
+ has_return_annotation = node.returns is not None
66
+
67
+ # Check if return annotation is None or missing
68
+ is_none_return = (
69
+ isinstance(node.returns, ast.Constant) and
70
+ node.returns.value is None
71
+ )
72
+
73
+ if not has_response_model and (not has_return_annotation or is_none_return):
74
+ # Skip certain common patterns that don't need response models
75
+ if method == 'delete' and path.endswith('}'):
76
+ continue # DELETE /items/{id} often returns nothing
77
+
78
+ issues.append(EndpointIssue(
79
+ file=str(filepath),
80
+ line=node.lineno,
81
+ method=method.upper(),
82
+ path=path,
83
+ issue="Missing response_model or return type annotation"
84
+ ))
85
+
86
+ return issues
87
+
88
+
89
+ def check_directory(directory: Path) -> list[EndpointIssue]:
90
+ """Check all Python files in directory for FastAPI response model issues."""
91
+ all_issues = []
92
+
93
+ # Common patterns for API files
94
+ patterns = [
95
+ '**/router*.py',
96
+ '**/routes*.py',
97
+ '**/api*.py',
98
+ '**/endpoints*.py',
99
+ '**/views*.py',
100
+ ]
101
+
102
+ checked_files = set()
103
+
104
+ for pattern in patterns:
105
+ for filepath in directory.glob(pattern):
106
+ if filepath in checked_files:
107
+ continue
108
+ checked_files.add(filepath)
109
+ all_issues.extend(check_file(filepath))
110
+
111
+ # Also check any file with FastAPI imports
112
+ for filepath in directory.rglob('*.py'):
113
+ if filepath in checked_files:
114
+ continue
115
+ try:
116
+ content = filepath.read_text()
117
+ if 'from fastapi' in content or 'import fastapi' in content:
118
+ if 'APIRouter' in content or '@app.' in content or '@router.' in content:
119
+ all_issues.extend(check_file(filepath))
120
+ checked_files.add(filepath)
121
+ except (UnicodeDecodeError, PermissionError):
122
+ continue
123
+
124
+ return all_issues
125
+
126
+
127
+ def main():
128
+ directory = Path(sys.argv[1]) if len(sys.argv) > 1 else Path('.')
129
+
130
+ if not directory.exists():
131
+ print(f"Error: Directory not found: {directory}")
132
+ sys.exit(1)
133
+
134
+ issues = check_directory(directory)
135
+
136
+ if not issues:
137
+ print("✓ All FastAPI endpoints have response models defined")
138
+ sys.exit(0)
139
+
140
+ print(f"Found {len(issues)} endpoint(s) without response models:\n")
141
+
142
+ for issue in sorted(issues, key=lambda x: (x.file, x.line)):
143
+ print(f" {issue.file}:{issue.line}")
144
+ print(f" {issue.method} {issue.path}")
145
+ print(f" → {issue.issue}\n")
146
+
147
+ print("Fix: Add response_model parameter or return type annotation:")
148
+ print(' @router.get("/items", response_model=list[ItemSchema])')
149
+ print(" async def get_items() -> list[ItemSchema]:")
150
+
151
+ sys.exit(1)
152
+
153
+
154
+ if __name__ == '__main__':
155
+ main()
package/ralph/verify.sh CHANGED
@@ -411,6 +411,45 @@ verify_lint() {
411
411
  return $failed
412
412
  }
413
413
 
414
+ # Check FastAPI endpoints have Pydantic response models (for Swagger docs)
415
+ run_fastapi_response_check() {
416
+ local check_script="$SCRIPT_DIR/checks/check-fastapi-responses.py"
417
+
418
+ # Skip if check script doesn't exist
419
+ [[ ! -f "$check_script" ]] && return 0
420
+
421
+ # Find FastAPI directories
422
+ local fastapi_dirs=()
423
+ for dir in "." "apps/api" "api" "backend" "server"; do
424
+ if [[ -d "$dir" ]]; then
425
+ # Check for FastAPI imports
426
+ if grep -rq "from fastapi" "$dir"/*.py 2>/dev/null || \
427
+ grep -rq "from fastapi" "$dir"/**/*.py 2>/dev/null; then
428
+ fastapi_dirs+=("$dir")
429
+ fi
430
+ fi
431
+ done
432
+
433
+ [[ ${#fastapi_dirs[@]} -eq 0 ]] && return 0
434
+
435
+ for dir in "${fastapi_dirs[@]}"; do
436
+ echo -n " FastAPI response models ($dir)... "
437
+
438
+ local output
439
+ if output=$(python3 "$check_script" "$dir" 2>&1); then
440
+ print_success "passed"
441
+ else
442
+ print_warning "issues found"
443
+ echo ""
444
+ echo "$output" | head -30 | sed 's/^/ /'
445
+ # Don't fail verification - just warn for now
446
+ # return 1
447
+ fi
448
+ done
449
+
450
+ return 0
451
+ }
452
+
414
453
  # Run all checks defined in config.json
415
454
  run_configured_checks() {
416
455
  local config="$RALPH_DIR/config.json"
@@ -423,6 +462,9 @@ run_configured_checks() {
423
462
  return 1
424
463
  fi
425
464
 
465
+ # Auto-detect and run FastAPI response model check
466
+ run_fastapi_response_check
467
+
426
468
  # Run pre-commit hooks if available (catches errors before commit attempt)
427
469
  if command -v pre-commit &>/dev/null && [[ -f ".pre-commit-config.yaml" ]]; then
428
470
  echo -n " pre-commit hooks... "