Detect drift between API route files and endpoint docs#26
Detect drift between API route files and endpoint docs#26ogero wants to merge 1 commit intodeepnoodle-ai:mainfrom
Conversation
📝 WalkthroughWalkthroughA new hook system is introduced to detect drift between API route definitions and documentation, automatically triggered when API routes or API docs are modified, using a Bash script that compares documented endpoints against implemented routes. Changes
Sequence DiagramsequenceDiagram
participant Claude as Claude (Tool Usage)
participant Hook as Hook System
participant Script as api-doc-sync.sh
participant Files as File System
participant Output as JSON Message
Claude->>Hook: Executes Edit/Write tool
Hook->>Script: Triggers PostToolUse hook
Script->>Files: Read API route files<br/>(app/routes/api.*.ts)
Script->>Files: Read API documentation<br/>(docs/api/*.md)
Script->>Script: Extract endpoints from routes<br/>(loader → GET, action → POST)
Script->>Script: Extract endpoints from docs<br/>(### METHOD /path headings)
Script->>Script: Normalize paths & compare
Script->>Output: Generate drift report<br/>(undocumented routes &<br/>stale documentation)
Output-->>Hook: Return JSON message
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| { | ||
| "permissions": { | ||
| "allow": [ | ||
| "Bash(chmod:*)", | ||
| "Bash(bash:*)", | ||
| "Bash(git checkout:*)", | ||
| "Bash(echo:*)" | ||
| ] | ||
| } | ||
| } |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @.claude/hooks/api-doc-sync.sh:
- Around line 40-48: The current method-detection in the hook uses a grep that
only finds negation guards and only takes the first match, so positive guards
like "request.method === 'PUT'" are missed and multi-method actions get
truncated; update the grep/regex logic in the block that sets ROUTE_ENDPOINTS
(the code that assigns to ROUTE_ENDPOINTS["$method $path"]=1 and the local
variable method) to: 1) match both equality and inequality comparisons (===, ==,
!==, !=) against request.method and capture the quoted method name(s), 2)
collect all matches instead of using head -1, and 3) when a negation operator is
detected (!= or !==) invert the logic to treat the captured method as the
accepted one, then iterate over all captured methods and add
ROUTE_ENDPOINTS["<METHOD> $path"]=1 for each.
🧹 Nitpick comments (2)
.claude/hooks/api-doc-sync.sh (1)
42-42:grep -P(PCRE) is not portable — unavailable on macOS default grep.If contributors run this on macOS,
grep -oPwill fail. Consider usinggrep -oEwith a revised regex, or usingsed/awkinstead..claude/settings.local.json (1)
3-8: Overly broad permissions —Bash(bash:*)allows arbitrary script execution.
bash:*permits running any script, not just the hook. Consider scoping it to the specific hook command. Also,git checkout:*doesn't seem required by the hook script — is it needed for another purpose?Suggested tightening
"permissions": { "allow": [ "Bash(chmod:*)", - "Bash(bash:*)", - "Bash(git checkout:*)", + "Bash(bash:.claude/hooks/*)", "Bash(echo:*)" ] }
| if grep -q 'export async function action' "$route_file"; then | ||
| # Explicit method guard: request.method !== 'METHOD' → accepts METHOD | ||
| method=$(grep -oP "request\.method\s*!==?\s*'\K\w+" "$route_file" | head -1 || true) | ||
| if [[ -n "$method" ]]; then | ||
| ROUTE_ENDPOINTS["$method $path"]=1 | ||
| else | ||
| # No guard → default action method is POST | ||
| ROUTE_ENDPOINTS["POST $path"]=1 | ||
| fi |
There was a problem hiding this comment.
Method-detection heuristic is inverted for === guards and ignores multi-method actions.
The regex on line 42 matches only !== / != (negation guards). This works for the "reject if not X" pattern (if (request.method !== 'DELETE') throw … → accepts DELETE), but it will miss positive-match branches like if (request.method === 'PUT') { … }, silently defaulting those to POST. It also only captures the first method via head -1, so an action handling both PUT and DELETE would only register one.
Consider also matching === patterns and collecting all matched methods:
Proposed improvement
# action export → determine HTTP method
if grep -q 'export async function action' "$route_file"; then
- # Explicit method guard: request.method !== 'METHOD' → accepts METHOD
- method=$(grep -oP "request\.method\s*!==?\s*'\K\w+" "$route_file" | head -1 || true)
- if [[ -n "$method" ]]; then
- ROUTE_ENDPOINTS["$method $path"]=1
- else
- # No guard → default action method is POST
- ROUTE_ENDPOINTS["POST $path"]=1
- fi
+ # Collect methods from both === and !== guards
+ methods=$(grep -oP "request\.method\s*[!=]==?\s*'\K\w+" "$route_file" || true)
+ if [[ -n "$methods" ]]; then
+ while IFS= read -r method; do
+ ROUTE_ENDPOINTS["$method $path"]=1
+ done <<< "$methods"
+ else
+ # No guard → default action method is POST
+ ROUTE_ENDPOINTS["POST $path"]=1
+ fi
fi🤖 Prompt for AI Agents
In @.claude/hooks/api-doc-sync.sh around lines 40 - 48, The current
method-detection in the hook uses a grep that only finds negation guards and
only takes the first match, so positive guards like "request.method === 'PUT'"
are missed and multi-method actions get truncated; update the grep/regex logic
in the block that sets ROUTE_ENDPOINTS (the code that assigns to
ROUTE_ENDPOINTS["$method $path"]=1 and the local variable method) to: 1) match
both equality and inequality comparisons (===, ==, !==, !=) against
request.method and capture the quoted method name(s), 2) collect all matches
instead of using head -1, and 3) when a negation operator is detected (!= or
!==) invert the logic to treat the captured method as the accepted one, then
iterate over all captured methods and add ROUTE_ENDPOINTS["<METHOD> $path"]=1
for each.
|
Ah, nice one, thank you @ogero Let me give this a try! |
Summary by CodeRabbit