feat(routes): FastAPI prefixes, ASP.NET endpoint groups, wrapped server actions on the Screens tab

- python.ts postExtract: APIRouter(prefix=) and literal include_router(prefix=) composed down the include tree (module import, alias, local); a computed prefix leaves that mount alone; full-stack-fastapi-template 23 routes named by path
- csharp.ts: handler-first MapPost(Handler[, "path"]) under the endpoint-group class, the app's $"/api/{groupName}" head read in postExtract, RoutePrefix honoured; detection covers Endpoints/ files; CleanArchitecture 10 routes
- tier-synthesizer: a type argument between a client call and its parentheses (useSWR<T>(…), ky.get<T>(…)); recorded callee without it
- screens.ts: a file-scope navigation attributed to the value spanning it; a value nothing calls attributed to the functions mentioning it in importing files (request-time source read, bounded); steps.ts lends navigates edges to a value root
- tests: servers fixture (FastAPI prefixed routers, ASP.NET endpoint group end to end), frameworks.test.ts (group form, RoutePrefix), cross-tier (generic useSWR)
- docs: CHANGELOG, plan, playbook rows

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REFyW9hmNrxhwN5wxRoAkC
This commit is contained in:
Colby McHenry
2026-08-28 14:52:40 -05:00
co-authored by Claude Fable 5
parent 9c6bc23b21
commit bc45e9071d
11 changed files with 487 additions and 14 deletions
+36
View File
@@ -1424,6 +1424,42 @@ public IActionResult ListUsers()
expect(nodes[0].name).toBe('GET /users');
expect(references[0].referenceName).toBe('ListUsers');
});
it('extracts the handler-first endpoint-group form under the class, with the optional path', () => {
const src = `
public class TodoItems : IEndpointGroup
{
public static void Map(RouteGroupBuilder groupBuilder)
{
groupBuilder.RequireAuthorization();
groupBuilder.MapPost(CreateTodoItem);
groupBuilder.MapPut(UpdateTodoItem, "{id}");
groupBuilder.MapDelete(DeleteTodoItem, "{id}");
}
public static async Task<Created<int>> CreateTodoItem(ISender sender, CreateTodoItemCommand command) { }
}
`;
const { nodes, references } = aspnetResolver.extract!('Web/Endpoints/TodoItems.cs', src);
expect(nodes.map((n) => n.name)).toEqual(['POST /TodoItems', 'PUT /TodoItems/{id}', 'DELETE /TodoItems/{id}']);
expect(nodes.map((n) => n.startLine)).toEqual([7, 8, 9]);
expect(references.map((r) => r.referenceName)).toEqual(['CreateTodoItem', 'UpdateTodoItem', 'DeleteTodoItem']);
expect(nodes[0]!.qualifiedName).toBe('Web/Endpoints/TodoItems.cs::group:TodoItems:POST:');
});
it('a class with its own RoutePrefix literal names its routes under it', () => {
const src = `
public class TodoLists : IEndpointGroup
{
public static string RoutePrefix => "/api/todo-lists";
public static void Map(RouteGroupBuilder group)
{
group.MapGet(GetTodoLists);
}
}
`;
const { nodes } = aspnetResolver.extract!('TodoLists.cs', src);
expect(nodes.map((n) => n.name)).toEqual(['GET /api/todo-lists']);
});
});
import { vaporResolver } from '../src/resolution/frameworks/swift';